Explorar el Código

Merge pull request #731 from thesimplekid/fix_p2pk_multi_sig

Fix p2pk multi sig
thesimplekid hace 2 días
padre
commit
db14c1aecc
Se han modificado 2 ficheros con 41 adiciones y 11 borrados
  1. 39 9
      crates/cashu/src/nuts/nut11/mod.rs
  2. 2 2
      crates/cashu/src/nuts/nut14/mod.rs

+ 39 - 9
crates/cashu/src/nuts/nut11/mod.rs

@@ -60,6 +60,9 @@ pub enum Error {
     /// Witness Signatures not provided
     #[error("Witness signatures not provided")]
     SignaturesNotProvided,
+    /// Duplicate signature from same pubkey
+    #[error("Duplicate signature from the same pubkey detected")]
+    DuplicateSignature,
     /// Parse Url Error
     #[error(transparent)]
     UrlParseError(#[from] url::ParseError),
@@ -128,7 +131,7 @@ impl Proof {
             secret.secret_data.tags.unwrap_or_default().try_into()?;
         let msg: &[u8] = self.secret.as_bytes();
 
-        let mut valid_sigs = 0;
+        let mut verified_pubkeys = HashSet::new();
 
         let witness_signatures = match &self.witness {
             Some(witness) => witness.signatures(),
@@ -148,7 +151,10 @@ impl Proof {
                 let sig = Signature::from_str(signature)?;
 
                 if v.verify(msg, &sig).is_ok() {
-                    valid_sigs += 1;
+                    // If the pubkey is already verified, return a duplicate signature error
+                    if !verified_pubkeys.insert(*v) {
+                        return Err(Error::DuplicateSignature);
+                    }
                 } else {
                     tracing::debug!(
                         "Could not verify signature: {sig} on message: {}",
@@ -158,6 +164,8 @@ impl Proof {
             }
         }
 
+        let valid_sigs = verified_pubkeys.len() as u64;
+
         if valid_sigs >= spending_conditions.num_sigs.unwrap_or(1) {
             return Ok(());
         }
@@ -185,19 +193,27 @@ impl Proof {
     }
 }
 
-/// Returns count of valid signatures
-pub fn valid_signatures(msg: &[u8], pubkeys: &[PublicKey], signatures: &[Signature]) -> u64 {
-    let mut count = 0;
+/// Returns count of valid signatures (each public key is only counted once)
+/// Returns error if the same pubkey has multiple valid signatures
+pub fn valid_signatures(
+    msg: &[u8],
+    pubkeys: &[PublicKey],
+    signatures: &[Signature],
+) -> Result<u64, Error> {
+    let mut verified_pubkeys = HashSet::new();
 
     for pubkey in pubkeys {
         for signature in signatures {
             if pubkey.verify(msg, signature).is_ok() {
-                count += 1;
+                // If the pubkey is already verified, return a duplicate signature error
+                if !verified_pubkeys.insert(*pubkey) {
+                    return Err(Error::DuplicateSignature);
+                }
             }
         }
     }
 
-    count
+    Ok(verified_pubkeys.len() as u64)
 }
 
 impl BlindedMessage {
@@ -224,7 +240,7 @@ impl BlindedMessage {
 
     /// Verify P2PK conditions on [BlindedMessage]
     pub fn verify_p2pk(&self, pubkeys: &Vec<PublicKey>, required_sigs: u64) -> Result<(), Error> {
-        let mut valid_sigs = 0;
+        let mut verified_pubkeys = HashSet::new();
         if let Some(witness) = &self.witness {
             for signature in witness
                 .signatures()
@@ -236,7 +252,10 @@ impl BlindedMessage {
                     let sig = Signature::from_str(signature)?;
 
                     if v.verify(msg, &sig).is_ok() {
-                        valid_sigs += 1;
+                        // If the pubkey is already verified, return a duplicate signature error
+                        if !verified_pubkeys.insert(*v) {
+                            return Err(Error::DuplicateSignature);
+                        }
                     } else {
                         tracing::debug!(
                             "Could not verify signature: {sig} on message: {}",
@@ -247,6 +266,8 @@ impl BlindedMessage {
             }
         }
 
+        let valid_sigs = verified_pubkeys.len() as u64;
+
         if valid_sigs.ge(&required_sigs) {
             Ok(())
         } else {
@@ -928,4 +949,13 @@ mod tests {
 
         assert!(invalid_proof.verify_p2pk().is_err());
     }
+
+    #[test]
+    fn test_duplicate_signatures_counting() {
+        let proof: Proof = serde_json::from_str(
+            r#"{"amount":1,"id":"009a1f293253e41e","secret":"[\"P2PK\",{\"nonce\":\"e434a9efbc5f65d144a620e368c9a6dc12c719d0ebc57e0c74f7341864dc449a\",\"data\":\"02a60c27104cf6023581e790970fc33994a320abe36e7ceed16771b0f8d76f0666\",\"tags\":[[\"pubkeys\",\"039c6a20a6ba354b7bb92eb9750716c1098063006362a1fa2afca7421f262d45c5\",\"0203eb2f7cd72a4f725d3327216365d2df18bb4bbc810522fd973c9af987e9b05b\"],[\"locktime\",\"1744876528\"],[\"n_sigs\",\"2\"],[\"sigflag\",\"SIG_INPUTS\"]]}]","C":"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904","witness":"{\"signatures\":[\"3e9ff9e55c9eccb9e5aa0b6c62d54500b40d0eebadb06efcc8e76f3ce38e0923f956ec1bccb9080db96a17c1e98a1b857abfd1a56bb25670037cea3db1f73d81\",\"c5e29c38e60c4db720cf3f78e590358cf1291a06b9eadf77c1108ae84d533520c2707ffda224eb6a63fddaee9abd5ecf8f2cd263d2556950550e3061a5511f65\"]}"}"#,
+        ).unwrap();
+
+        assert!(proof.verify_p2pk().is_err());
+    }
 }

+ 2 - 2
crates/cashu/src/nuts/nut14/mod.rs

@@ -94,7 +94,7 @@ impl Proof {
                         .collect::<Result<Vec<Signature>, _>>()?;
 
                     // If secret includes refund keys check that there is a valid signature
-                    if valid_signatures(self.secret.as_bytes(), &refund_key, &signatures).ge(&1) {
+                    if valid_signatures(self.secret.as_bytes(), &refund_key, &signatures)?.ge(&1) {
                         return Ok(());
                     }
                 }
@@ -113,7 +113,7 @@ impl Proof {
                     .map(|s| Signature::from_str(s))
                     .collect::<Result<Vec<Signature>, _>>()?;
 
-                let valid_sigs = valid_signatures(self.secret.as_bytes(), &pubkey, &signatures);
+                let valid_sigs = valid_signatures(self.secret.as_bytes(), &pubkey, &signatures)?;
                 ensure_cdk!(valid_sigs >= req_sigs, Error::IncorrectSecretKind);
             }
         }