Ver Fonte

Implementing https://github.com/cashubtc/nuts/pull/250

Cesar Rodas há 1 mês atrás
pai
commit
c45300ad1f

+ 87 - 69
crates/cdk-signatory/proto/signatory.proto

@@ -2,57 +2,85 @@ syntax = "proto3";
 
 package signatory;
 
+/// TODO: research about the stream, to notify when RotateKeyset is triggered.
 service Signatory {
-    rpc BlindSign(BlindedMessage) returns (BlindSignature);
+  rpc BlindSign (BlindedMessages) returns (BlindSignResponse);
 
-    rpc VerifyProof(Proof) returns (Empty);
+  rpc VerifyProofs (Proofs) returns (BooleanResponse);
 
-    rpc Keysets(Empty) returns (VecSignatoryKeySet);
+  rpc Keysets (EmptyRequest) returns (KeysResponse);
 
-    rpc RotateKeyset(RotateKeyArguments) returns (MintKeySetInfo);
+  rpc RotateKeyset (RotationRequest) returns (KeyRotationResponse);
 }
 
-message Empty {}
+message BlindSignResponse {
+    oneof result {
+        BlindSignatures sigs  = 1;
+        Error error = 2;
+    }
+}
 
-message VecSignatoryKeySet {
-    repeated SignatoryKeySet keysets = 1;
+message BlindedMessages {
+    repeated BlindedMessage blinded_messages = 1;
+}
 
-    optional bool is_none = 2;
+// Represents a blinded message
+message BlindedMessage {
+    uint64 amount = 1;
+    string keyset_id = 2;
+    bytes blinded_secret = 3;
 }
 
-message SignatoryKeySet {
-    KeySet key = 1;
-    MintKeySetInfo info = 2;
+message BooleanResponse {
+    oneof result {
+        bool success  = 1;
+        Error error = 2;
+    }
 }
 
-message KeySet {
-    Id id = 1;
-    CurrencyUnit unit = 2;
-    Keys keys = 3;
+message KeyRotationResponse {
+    oneof result {
+        KeySet keyset  = 1;
+        Error error = 2;
+    }
 }
 
-message Keys {
-    map<uint64, bytes> keys = 1;
+message KeysResponse {
+    oneof result {
+        SignatoryKeysets keysets = 1;
+        Error error = 2;
+    }
 }
 
+message SignatoryKeysets {
+    bytes pubkey = 1;
+    repeated KeySet keysets = 2;
+}
 
-message RotateKeyArguments {
-    CurrencyUnit unit = 1;
-    optional uint32 derivation_path_index = 2;
-    uint32 max_order = 3;
+message KeySet {
+    string id = 1;
+    CurrencyUnit unit = 2;
+    bool active = 3;
     uint64 input_fee_ppk = 4;
+    Keys keys = 5;
 }
 
-message CustomDerivationPath {
-    CurrencyUnit unit = 1;
-    repeated DerivationPath derivation_path = 2;
+message Keys {
+    map<uint64, bytes> keys = 1;
+}
+
+message RotationRequest {
+    CurrencyUnit unit  = 1;
+    uint64 input_fee_ppk  = 2;
+    uint64 max_order = 3;
 }
 
 enum CurrencyUnitType {
-  SAT = 0;
-  MSAT = 1;
-  USD = 2;
-  EUR = 3;
+    SAT = 0;
+    MSAT = 1;
+    USD = 2;
+    EUR = 3;
+    AUTH = 4;
 }
 
 message CurrencyUnit {
@@ -62,13 +90,15 @@ message CurrencyUnit {
     }
 }
 
+message Proofs {
+    repeated Proof proof = 1;
+}
+
 message Proof {
     uint64 amount = 1;
     string keyset_id = 2;
-    string secret = 3;
+    bytes secret = 3;
     bytes C = 4;
-    optional Witness witness = 5;
-    optional ProofDLEQ dleq = 6;
 }
 
 message ProofDLEQ {
@@ -77,6 +107,16 @@ message ProofDLEQ {
     bytes r = 3;
 }
 
+message SigningResponse {
+    oneof result {
+        BlindSignatures blind_signatures = 1;
+        Error error = 2;
+    }
+}
+message BlindSignatures {
+    repeated BlindSignature blind_signatures = 1;
+}
+
 message BlindSignature {
     uint64 amount = 1;
     string keyset_id = 2;
@@ -89,13 +129,6 @@ message BlindSignatureDLEQ {
     bytes s = 2;
 }
 
-message KeySetInfo {
-    Id id = 1;
-    CurrencyUnit unit = 2;
-    bool active = 3;
-    uint64 input_fee_ppk = 4;
-}
-
 // Witness type
 message Witness {
     oneof witness_type {
@@ -114,42 +147,27 @@ message P2PKWitness {
 message HTLCWitness {
     // Preimage
     string preimage = 1;
-
     // List of signatures
     repeated string signatures = 2;
 }
 
-message BlindedMessage {
-    uint64 amount = 1;
-    string keyset_id = 2;
-    bytes blinded_secret = 3;
-    optional Witness witness = 4; // This field is optional by default in proto3
-}
-
-message KeysResponse {
-    repeated KeySet keysets = 1;
-}
-
-
-message Id {
-    bytes inner = 1;
+enum ErrorCode {
+    UNKNOWN = 0;
+    AMOUNT_OUTSIDE_LIMIT = 1;
+    DUPLICATE_INPUTS_PROVIDED = 2;
+    DUPLICATE_OUTPUTS_PROVIDED = 3;
+    KEYSET_NOT_KNOWN = 4;
+    KEYSET_INACTIVE = 5;
+    MINTING_DISABLED = 6;
+    COULD_NOT_ROTATE_KEYSET = 7;
+    INVALID_PROOF = 8;
+    INVALID_BLIND_MESSAGE = 9;
+    UNIT_NOT_SUPPORTED = 10;
 }
 
-message DerivationPath {
-    oneof child_number {
-        uint32 normal = 1;
-        uint32 hardened = 2;
-    }
+message Error {
+  ErrorCode code = 1;
+  string detail = 2;
 }
 
-message MintKeySetInfo {
-    Id id = 1;
-    CurrencyUnit unit = 2;
-    bool active = 3;
-    uint64 valid_from = 4;
-    optional uint64 valid_to = 5;
-    repeated DerivationPath derivation_path = 6;
-    optional uint32 derivation_path_index = 7;
-    uint32 max_order = 8;
-    uint64 input_fee_ppk = 9;
-}
+message EmptyRequest {}

+ 41 - 27
crates/cdk-signatory/src/db_signatory.rs

@@ -175,41 +175,55 @@ impl DbSignatory {
 
 #[async_trait::async_trait]
 impl Signatory for DbSignatory {
-    async fn blind_sign(&self, blinded_message: BlindedMessage) -> Result<BlindSignature, Error> {
-        let BlindedMessage {
-            amount,
-            blinded_secret,
-            keyset_id,
-            ..
-        } = blinded_message;
-
+    async fn blind_sign(
+        &self,
+        blinded_messages: Vec<BlindedMessage>,
+    ) -> Result<Vec<BlindSignature>, Error> {
         let keysets = self.keysets.read().await;
-        let (info, key) = keysets.get(&keyset_id).ok_or(Error::UnknownKeySet)?;
-        if !info.active {
-            return Err(Error::InactiveKeyset);
-        }
 
-        let key_pair = key.keys.get(&amount).ok_or(Error::UnknownKeySet)?;
-        let c = sign_message(&key_pair.secret_key, &blinded_secret)?;
+        blinded_messages
+            .into_iter()
+            .map(|blinded_message| {
+                let BlindedMessage {
+                    amount,
+                    blinded_secret,
+                    keyset_id,
+                    ..
+                } = blinded_message;
+
+                let (info, key) = keysets.get(&keyset_id).ok_or(Error::UnknownKeySet)?;
+                if !info.active {
+                    return Err(Error::InactiveKeyset);
+                }
 
-        let blinded_signature = BlindSignature::new(
-            amount,
-            c,
-            keyset_id,
-            &blinded_message.blinded_secret,
-            key_pair.secret_key.clone(),
-        )?;
+                let key_pair = key.keys.get(&amount).ok_or(Error::UnknownKeySet)?;
+                let c = sign_message(&key_pair.secret_key, &blinded_secret)?;
 
-        Ok(blinded_signature)
+                let blinded_signature = BlindSignature::new(
+                    amount,
+                    c,
+                    keyset_id,
+                    &blinded_message.blinded_secret,
+                    key_pair.secret_key.clone(),
+                )?;
+
+                Ok(blinded_signature)
+            })
+            .collect::<Result<Vec<_>, _>>()
     }
 
-    async fn verify_proof(&self, proof: Proof) -> Result<(), Error> {
+    async fn verify_proofs(&self, proofs: Vec<Proof>) -> Result<(), Error> {
         let keysets = self.keysets.read().await;
-        let (_, key) = keysets.get(&proof.keyset_id).ok_or(Error::UnknownKeySet)?;
-        let key_pair = key.keys.get(&proof.amount).ok_or(Error::UnknownKeySet)?;
-        verify_message(&key_pair.secret_key, proof.c, proof.secret.as_bytes())?;
 
-        Ok(())
+        proofs
+            .into_iter()
+            .map(|proof| {
+                let (_, key) = keysets.get(&proof.keyset_id).ok_or(Error::UnknownKeySet)?;
+                let key_pair = key.keys.get(&proof.amount).ok_or(Error::UnknownKeySet)?;
+                verify_message(&key_pair.secret_key, proof.c, proof.secret.as_bytes())?;
+                Ok(())
+            })
+            .collect()
     }
 
     async fn keysets(&self) -> Result<Vec<SignatoryKeySet>, Error> {

+ 11 - 8
crates/cdk-signatory/src/embedded.rs

@@ -13,11 +13,11 @@ use crate::signatory::{RotateKeyArguments, Signatory, SignatoryKeySet};
 enum Request {
     BlindSign(
         (
-            BlindedMessage,
-            oneshot::Sender<Result<BlindSignature, Error>>,
+            Vec<BlindedMessage>,
+            oneshot::Sender<Result<Vec<BlindSignature>, Error>>,
         ),
     ),
-    VerifyProof((Proof, oneshot::Sender<Result<(), Error>>)),
+    VerifyProof((Vec<Proof>, oneshot::Sender<Result<(), Error>>)),
     Keysets(oneshot::Sender<Result<Vec<SignatoryKeySet>, Error>>),
     RotateKeyset(
         (
@@ -69,7 +69,7 @@ impl Service {
                     }
                 }
                 Request::VerifyProof((proof, response)) => {
-                    let output = handler.verify_proof(proof).await;
+                    let output = handler.verify_proofs(proof).await;
                     if let Err(err) = response.send(output) {
                         tracing::error!("Error sending response: {:?}", err);
                     }
@@ -93,20 +93,23 @@ impl Service {
 
 #[async_trait::async_trait]
 impl Signatory for Service {
-    async fn blind_sign(&self, blinded_message: BlindedMessage) -> Result<BlindSignature, Error> {
+    async fn blind_sign(
+        &self,
+        blinded_messages: Vec<BlindedMessage>,
+    ) -> Result<Vec<BlindSignature>, Error> {
         let (tx, rx) = oneshot::channel();
         self.pipeline
-            .send(Request::BlindSign((blinded_message, tx)))
+            .send(Request::BlindSign((blinded_messages, tx)))
             .await
             .map_err(|e| Error::SendError(e.to_string()))?;
 
         rx.await.map_err(|e| Error::RecvError(e.to_string()))?
     }
 
-    async fn verify_proof(&self, proof: Proof) -> Result<(), Error> {
+    async fn verify_proofs(&self, proofs: Vec<Proof>) -> Result<(), Error> {
         let (tx, rx) = oneshot::channel();
         self.pipeline
-            .send(Request::VerifyProof((proof, tx)))
+            .send(Request::VerifyProof((proofs, tx)))
             .await
             .map_err(|e| Error::SendError(e.to_string()))?;
 

+ 5 - 5
crates/cdk-signatory/src/proto/client.rs

@@ -21,8 +21,8 @@ impl SignatoryRpcClient {
 
 #[async_trait::async_trait]
 impl Signatory for SignatoryRpcClient {
-    async fn blind_sign(&self, request: BlindedMessage) -> Result<BlindSignature, Error> {
-        let req: super::BlindedMessage = request.into();
+    async fn blind_sign(&self, request: Vec<BlindedMessage>) -> Result<Vec<BlindSignature>, Error> {
+        let req: super::BlindedMessages = request.into();
         self.client
             .clone()
             .blind_sign(req)
@@ -31,11 +31,11 @@ impl Signatory for SignatoryRpcClient {
             .map_err(|e| Error::Custom(e.to_string()))?
     }
 
-    async fn verify_proof(&self, proof: Proof) -> Result<(), Error> {
-        let req: super::Proof = proof.into();
+    async fn verify_proofs(&self, proofs: Vec<Proof>) -> Result<(), Error> {
+        let req: super::Proofs = proofs.into();
         self.client
             .clone()
-            .verify_proof(req)
+            .verify_proofs(req)
             .await
             .map(|response| response.into_inner().try_into())
             .map_err(|e| Error::Custom(e.to_string()))?

+ 43 - 55
crates/cdk-signatory/src/proto/convert.rs

@@ -1,29 +1,30 @@
 //! Type conversions between Rust types and the generated protobuf types.
 use std::collections::BTreeMap;
-use std::str::FromStr;
 
-use cashu::secret::Secret;
+use cashu::{secret::Secret, util::hex};
 use cdk_common::{HTLCWitness, P2PKWitness};
 use tonic::Status;
 
 use super::*;
 
-impl From<cashu::Id> for Id {
-    fn from(value: cashu::Id) -> Self {
-        Id {
-            inner: value.to_bytes().to_vec(),
+impl From<cdk_common::Error> for Error {
+    fn from(err: cdk_common::Error) -> Self {
+        let code = match err {
+            cdk_common::Error::AmountError(_) => ErrorCode::AmountOutsideLimit,
+            cdk_common::Error::DuplicateInputs => ErrorCode::DuplicateInputsProvided,
+            cdk_common::Error::DuplicateOutputs => ErrorCode::DuplicateInputsProvided,
+            cdk_common::Error::UnknownKeySet => ErrorCode::KeysetNotKnown,
+            cdk_common::Error::InactiveKeyset => ErrorCode::KeysetInactive,
+            _ => ErrorCode::Unknown,
+        };
+
+        Error {
+            code: code.into(),
+            detail: err.to_string(),
         }
     }
 }
 
-impl TryInto<cashu::Id> for Id {
-    type Error = cdk_common::error::Error;
-
-    fn try_into(self) -> Result<cashu::Id, Self::Error> {
-        Ok(cashu::Id::from_bytes(&self.inner)?)
-    }
-}
-
 impl From<cdk_common::BlindSignatureDleq> for BlindSignatureDleq {
     fn from(value: cdk_common::BlindSignatureDleq) -> Self {
         BlindSignatureDleq {
@@ -43,33 +44,9 @@ impl TryInto<cdk_common::BlindSignatureDleq> for BlindSignatureDleq {
     }
 }
 
-impl From<crate::signatory::SignatoryKeySet> for SignatoryKeySet {
-    fn from(value: crate::signatory::SignatoryKeySet) -> Self {
-        SignatoryKeySet {
-            key: Some(value.key.into()),
-            info: Some(value.info.into()),
-        }
-    }
-}
-
-impl TryInto<crate::signatory::SignatoryKeySet> for SignatoryKeySet {
-    type Error = cdk_common::error::Error;
-
-    fn try_into(self) -> Result<crate::signatory::SignatoryKeySet, Self::Error> {
-        Ok(crate::signatory::SignatoryKeySet {
-            key: self
-                .key
-                .ok_or(cdk_common::Error::RecvError(
-                    "Missing property key".to_owned(),
-                ))?
-                .try_into()?,
-            info: self
-                .info
-                .ok_or(cdk_common::Error::RecvError(
-                    "Missing property info".to_owned(),
-                ))?
-                .try_into()?,
-        })
+impl From<Vec<crate::signatory::SignatoryKeySet>> for SignatoryKeysets {
+    fn from(_value: Vec<crate::signatory::SignatoryKeySet>) -> Self {
+        todo!()
     }
 }
 
@@ -84,15 +61,21 @@ impl From<cdk_common::BlindSignature> for BlindSignature {
     }
 }
 
+impl From<Vec<cdk_common::Proof>> for Proofs {
+    fn from(value: Vec<cdk_common::Proof>) -> Self {
+        Proofs {
+            proof: value.into_iter().map(|x| x.into()).collect(),
+        }
+    }
+}
+
 impl From<cdk_common::Proof> for Proof {
     fn from(value: cdk_common::Proof) -> Self {
         Proof {
             amount: value.amount.into(),
             keyset_id: value.keyset_id.to_string(),
-            secret: value.secret.to_string(),
+            secret: value.secret.to_bytes(),
             c: value.c.to_bytes().to_vec(),
-            witness: value.witness.map(|w| w.into()),
-            dleq: value.dleq.map(|dleq| dleq.into()),
         }
     }
 }
@@ -100,17 +83,23 @@ impl From<cdk_common::Proof> for Proof {
 impl TryInto<cdk_common::Proof> for Proof {
     type Error = Status;
     fn try_into(self) -> Result<cdk_common::Proof, Self::Error> {
+        let secret = if let Ok(str) = String::from_utf8(self.secret.clone()) {
+            str
+        } else {
+            hex::encode(&self.secret)
+        };
+
         Ok(cdk_common::Proof {
             amount: self.amount.into(),
             keyset_id: self
                 .keyset_id
                 .parse()
                 .map_err(|e| Status::from_error(Box::new(e)))?,
-            secret: Secret::from_str(&self.secret).map_err(|e| Status::from_error(Box::new(e)))?,
+            secret: Secret::new(secret),
             c: cdk_common::PublicKey::from_slice(&self.c)
                 .map_err(|e| Status::from_error(Box::new(e)))?,
-            witness: self.witness.map(|w| w.try_into()).transpose()?,
-            dleq: self.dleq.map(|x| x.try_into()).transpose()?,
+            witness: None,
+            dleq: None,
         })
     }
 }
@@ -159,7 +148,6 @@ impl From<cdk_common::BlindedMessage> for BlindedMessage {
             amount: value.amount.into(),
             keyset_id: value.keyset_id.to_string(),
             blinded_secret: value.blinded_secret.to_bytes().to_vec(),
-            witness: value.witness.map(|x| x.into()),
         }
     }
 }
@@ -175,7 +163,7 @@ impl TryInto<cdk_common::BlindedMessage> for BlindedMessage {
                 .map_err(|e| Status::from_error(Box::new(e)))?,
             blinded_secret: cdk_common::PublicKey::from_slice(&self.blinded_secret)
                 .map_err(|e| Status::from_error(Box::new(e)))?,
-            witness: self.witness.map(|x| x.try_into()).transpose()?,
+            witness: None,
         })
     }
 }
@@ -222,13 +210,13 @@ impl TryInto<cdk_common::Witness> for Witness {
     }
 }
 
-impl From<()> for Empty {
+impl From<()> for EmptyRequest {
     fn from(_: ()) -> Self {
-        Empty {}
+        EmptyRequest {}
     }
 }
 
-impl TryInto<()> for Empty {
+impl TryInto<()> for EmptyRequest {
     type Error = cdk_common::error::Error;
 
     fn try_into(self) -> Result<(), Self::Error> {
@@ -382,7 +370,7 @@ impl TryInto<cdk_common::mint::MintKeySetInfo> for MintKeySetInfo {
 impl From<cashu::KeySet> for KeySet {
     fn from(value: cashu::KeySet) -> Self {
         Self {
-            id: Some(value.id.into()),
+            id: value.id.to_string(),
             unit: Some(value.unit.into()),
             keys: Some(Keys {
                 keys: value
@@ -401,8 +389,8 @@ impl TryInto<cashu::KeySet> for KeySet {
         Ok(cashu::KeySet {
             id: self
                 .id
-                .ok_or(cdk_common::error::Error::Custom("id not set".to_owned()))?
-                .try_into()?,
+                .parse()
+                .map_err(|e| cdk_common::error::Error::Custom("id not set".to_owned()))?,
             unit: self
                 .unit
                 .ok_or(cdk_common::error::Error::Custom("unit not set".to_owned()))?

+ 44 - 20
crates/cdk-signatory/src/proto/server.rs

@@ -6,6 +6,8 @@ use tonic::{Request, Response, Status};
 use crate::proto::{self, signatory_server};
 use crate::signatory::Signatory;
 
+use super::{boolean_response, keys_response, BooleanResponse};
+
 pub struct CdkSignatoryServer<T>
 where
     T: Signatory + Send + Sync + 'static,
@@ -20,39 +22,61 @@ where
 {
     async fn blind_sign(
         &self,
-        request: Request<proto::BlindedMessage>,
-    ) -> Result<Response<proto::BlindSignature>, Status> {
+        request: Request<proto::BlindedMessages>,
+    ) -> Result<Response<proto::BlindSignResponse>, Status> {
         let blind_signature = self
             .inner
-            .blind_sign(request.into_inner().try_into()?)
+            .blind_sign(
+                request
+                    .into_inner()
+                    .blinded_messages
+                    .into_iter()
+                    .map(|blind_message| blind_message.try_into())
+                    .collect::<Result<Vec<_>, _>>()?,
+            )
             .await
             .map_err(|e| Status::from_error(Box::new(e)))?;
         Ok(Response::new(blind_signature.into()))
     }
 
-    async fn verify_proof(
+    async fn verify_proofs(
         &self,
-        request: Request<proto::Proof>,
-    ) -> Result<Response<proto::Empty>, Status> {
-        self.inner
-            .verify_proof(request.into_inner().try_into()?)
+        request: Request<proto::Proofs>,
+    ) -> Result<Response<proto::BooleanResponse>, Status> {
+        let result = match self
+            .inner
+            .verify_proofs(
+                request
+                    .into_inner()
+                    .proof
+                    .into_iter()
+                    .map(|x| x.try_into())
+                    .collect::<Result<Vec<_>, _>>()?,
+            )
             .await
-            .map_err(|e| Status::from_error(Box::new(e)))?;
-        Ok(Response::new(proto::Empty {}))
+        {
+            Ok(()) => boolean_response::Result::Success(true),
+
+            Err(cdk_common::Error::DHKE(_)) => boolean_response::Result::Success(false),
+            Err(err) => boolean_response::Result::Error(err.into()),
+        };
+
+        Ok(Response::new(BooleanResponse {
+            result: Some(result),
+        }))
     }
 
     async fn keysets(
         &self,
-        _request: Request<proto::Empty>,
-    ) -> Result<Response<proto::VecSignatoryKeySet>, Status> {
-        let keys_response = self
-            .inner
-            .keysets()
-            .await
-            .map_err(|e| Status::from_error(Box::new(e)))?;
-        Ok(Response::new(proto::VecSignatoryKeySet {
-            keysets: keys_response.into_iter().map(|k| k.into()).collect(),
-            is_none: Some(false),
+        _request: Request<proto::EmptyRequest>,
+    ) -> Result<Response<proto::KeysResponse>, Status> {
+        let result = match self.inner.keysets().await {
+            Ok(result) => keys_response::Result::Keysets(result.into()),
+            Err(err) => keys_response::Result::Error(err.into()),
+        };
+
+        Ok(Response::new(proto::KeysResponse {
+            result: Some(result),
         }))
     }
 

+ 5 - 2
crates/cdk-signatory/src/signatory.rs

@@ -69,10 +69,13 @@ pub trait Signatory {
     /// Blind sign a message.
     ///
     /// The message can be for a coin or an auth token.
-    async fn blind_sign(&self, blinded_message: BlindedMessage) -> Result<BlindSignature, Error>;
+    async fn blind_sign(
+        &self,
+        blinded_messages: Vec<BlindedMessage>,
+    ) -> Result<Vec<BlindSignature>, Error>;
 
     /// Verify [`Proof`] meets conditions and is signed
-    async fn verify_proof(&self, proofs: Proof) -> Result<(), Error>;
+    async fn verify_proofs(&self, proofs: Vec<Proof>) -> Result<(), Error>;
 
     /// Retrieve the list of all mint keysets
     async fn keysets(&self) -> Result<Vec<SignatoryKeySet>, Error>;

+ 1 - 1
crates/cdk/src/mint/auth/mod.rs

@@ -35,7 +35,7 @@ impl Mint {
     #[instrument(skip(self, token))]
     pub async fn verify_blind_auth(&self, token: &BlindAuthToken) -> Result<(), Error> {
         self.signatory
-            .verify_proof(token.auth_proof.clone().into())
+            .verify_proofs(vec![token.auth_proof.clone().into()])
             .await
     }
 

+ 27 - 20
crates/cdk/src/mint/mod.rs

@@ -354,27 +354,34 @@ impl Mint {
 
     /// Verify [`Proof`] meets conditions and is signed
     #[instrument(skip_all)]
-    pub async fn verify_proof(&self, proof: &Proof) -> Result<(), Error> {
-        // Check if secret is a nut10 secret with conditions
-        if let Ok(secret) =
-            <&secret::Secret as TryInto<nuts::nut10::Secret>>::try_into(&proof.secret)
-        {
-            // Checks and verifies known secret kinds.
-            // If it is an unknown secret kind it will be treated as a normal secret.
-            // Spending conditions will **not** be check. It is up to the wallet to ensure
-            // only supported secret kinds are used as there is no way for the mint to
-            // enforce only signing supported secrets as they are blinded at
-            // that point.
-            match secret.kind {
-                Kind::P2PK => {
-                    proof.verify_p2pk()?;
-                }
-                Kind::HTLC => {
-                    proof.verify_htlc()?;
+    pub async fn verify_proofs(&self, proofs: &[Proof]) -> Result<(), Error> {
+        proofs
+            .into_iter()
+            .map(|proof| {
+                // Check if secret is a nut10 secret with conditions
+                if let Ok(secret) =
+                    <&secret::Secret as TryInto<nuts::nut10::Secret>>::try_into(&proof.secret)
+                {
+                    // Checks and verifies known secret kinds.
+                    // If it is an unknown secret kind it will be treated as a normal secret.
+                    // Spending conditions will **not** be check. It is up to the wallet to ensure
+                    // only supported secret kinds are used as there is no way for the mint to
+                    // enforce only signing supported secrets as they are blinded at
+                    // that point.
+                    match secret.kind {
+                        Kind::P2PK => {
+                            proof.verify_p2pk()?;
+                        }
+                        Kind::HTLC => {
+                            proof.verify_htlc()?;
+                        }
+                    }
                 }
-            }
-        }
-        self.signatory.verify_proof(proof.to_owned()).await
+                Ok(())
+            })
+            .collect()?;
+
+        self.signatory.verify_proofs(proofs.to_owned()).await
     }
 
     /// Verify melt request is valid

+ 1 - 3
crates/cdk/src/mint/verification.rs

@@ -203,9 +203,7 @@ impl Mint {
         let unit = self.verify_inputs_keyset(inputs).await?;
         let amount = inputs.total_amount()?;
 
-        for proof in inputs {
-            self.verify_proof(proof).await?;
-        }
+        self.verify_proofs(inputs).await?;
 
         Ok(Verification {
             amount,