瀏覽代碼

fix(NUT10): secret tag is optional

thesimplekid 9 月之前
父節點
當前提交
a97f64fa56

+ 5 - 2
bindings/cdk-js/src/nuts/nut11.rs

@@ -39,10 +39,13 @@ impl Deref for JsP2PKSpendingConditions {
 #[wasm_bindgen(js_class = P2PKSpendingConditions)]
 impl JsP2PKSpendingConditions {
     #[wasm_bindgen(constructor)]
-    pub fn new(pubkey: String, conditions: JsConditions) -> Result<JsP2PKSpendingConditions> {
+    pub fn new(
+        pubkey: String,
+        conditions: Option<JsConditions>,
+    ) -> Result<JsP2PKSpendingConditions> {
         let pubkey = PublicKey::from_str(&pubkey).map_err(into_err)?;
         Ok(Self {
-            inner: SpendingConditions::new_p2pk(pubkey, conditions.deref().clone()),
+            inner: SpendingConditions::new_p2pk(pubkey, conditions.map(|c| c.deref().clone())),
         })
     }
 }

+ 5 - 2
bindings/cdk-js/src/nuts/nut14.rs

@@ -39,9 +39,12 @@ impl Deref for JsHTLCSpendingConditions {
 #[wasm_bindgen(js_class = HTLCSpendingConditions)]
 impl JsHTLCSpendingConditions {
     #[wasm_bindgen(constructor)]
-    pub fn new(preimage: String, conditions: JsConditions) -> Result<JsHTLCSpendingConditions> {
+    pub fn new(
+        preimage: String,
+        conditions: Option<JsConditions>,
+    ) -> Result<JsHTLCSpendingConditions> {
         Ok(Self {
-            inner: SpendingConditions::new_htlc(preimage, conditions.deref().clone())
+            inner: SpendingConditions::new_htlc(preimage, conditions.map(|c| c.deref().clone()))
                 .map_err(into_err)?,
         })
     }

+ 5 - 2
crates/cdk-cli/src/sub_commands/send.rs

@@ -100,7 +100,10 @@ pub async fn send(wallet: Wallet, sub_command_args: &SendSubCommand) -> Result<(
             )
             .unwrap();
 
-            Some(SpendingConditions::new_htlc(preimage.clone(), conditions)?)
+            Some(SpendingConditions::new_htlc(
+                preimage.clone(),
+                Some(conditions),
+            )?)
         }
         None => match sub_command_args.pubkey.is_empty() {
             true => None,
@@ -136,7 +139,7 @@ pub async fn send(wallet: Wallet, sub_command_args: &SendSubCommand) -> Result<(
 
                 Some(SpendingConditions::P2PKConditions {
                     data: data_pubkey,
-                    conditions,
+                    conditions: Some(conditions),
                 })
             }
         },

+ 2 - 3
crates/cdk/examples/p2pk.rs

@@ -5,7 +5,7 @@ use std::time::Duration;
 use cdk::amount::SplitTarget;
 use cdk::cdk_database::WalletMemoryDatabase;
 use cdk::error::Error;
-use cdk::nuts::{Conditions, CurrencyUnit, SecretKey, SpendingConditions};
+use cdk::nuts::{CurrencyUnit, SecretKey, SpendingConditions};
 use cdk::wallet::Wallet;
 use cdk::{Amount, UncheckedUrl};
 use rand::Rng;
@@ -51,8 +51,7 @@ async fn main() -> Result<(), Error> {
 
     let secret = SecretKey::generate();
 
-    let spending_conditions =
-        SpendingConditions::new_p2pk(secret.public_key(), Conditions::default());
+    let spending_conditions = SpendingConditions::new_p2pk(secret.public_key(), None);
 
     let token = wallet
         .send(

+ 7 - 6
crates/cdk/src/nuts/nut10.rs

@@ -25,8 +25,8 @@ pub struct SecretData {
     /// Expresses the spending condition specific to each kind
     pub data: String,
     /// Additional data committed to and can be used for feature extensions
-    #[serde(skip_serializing_if = "Vec::is_empty")]
-    pub tags: Vec<Vec<String>>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub tags: Option<Vec<Vec<String>>>,
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
@@ -38,16 +38,17 @@ pub struct Secret {
 }
 
 impl Secret {
-    pub fn new<S, V>(kind: Kind, data: S, tags: V) -> Self
+    pub fn new<S, V>(kind: Kind, data: S, tags: Option<V>) -> Self
     where
         S: Into<String>,
         V: Into<Vec<Vec<String>>>,
     {
         let nonce = crate::secret::Secret::generate().to_string();
+
         let secret_data = SecretData {
             nonce,
             data: data.into(),
-            tags: tags.into(),
+            tags: tags.map(|v| v.into()),
         };
 
         Self { kind, secret_data }
@@ -94,11 +95,11 @@ mod tests {
                 nonce: "5d11913ee0f92fefdc82a6764fd2457a".to_string(),
                 data: "026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198"
                     .to_string(),
-                tags: vec![vec![
+                tags: Some(vec![vec![
                     "key".to_string(),
                     "value1".to_string(),
                     "value2".to_string(),
-                ]],
+                ]]),
             },
         };
 

+ 33 - 24
crates/cdk/src/nuts/nut11/mod.rs

@@ -119,7 +119,8 @@ impl Proof {
     /// Verify P2PK signature on [Proof]
     pub fn verify_p2pk(&self) -> Result<(), Error> {
         let secret: Nut10Secret = self.secret.clone().try_into()?;
-        let spending_conditions: Conditions = secret.secret_data.tags.try_into()?;
+        let spending_conditions: Conditions =
+            secret.secret_data.tags.unwrap_or_default().try_into()?;
         let msg: &[u8] = self.secret.as_bytes();
 
         let mut valid_sigs = 0;
@@ -254,18 +255,18 @@ pub enum SpendingConditions {
     /// NUT11 Spending conditions
     P2PKConditions {
         data: PublicKey,
-        conditions: Conditions,
+        conditions: Option<Conditions>,
     },
     /// NUT14 Spending conditions
     HTLCConditions {
         data: Sha256Hash,
-        conditions: Conditions,
+        conditions: Option<Conditions>,
     },
 }
 
 impl SpendingConditions {
     /// New HTLC [SpendingConditions]
-    pub fn new_htlc(preimage: String, conditions: Conditions) -> Result<Self, Error> {
+    pub fn new_htlc(preimage: String, conditions: Option<Conditions>) -> Result<Self, Error> {
         let htlc = Sha256Hash::hash(&hex::decode(preimage)?);
 
         Ok(Self::HTLCConditions {
@@ -275,7 +276,7 @@ impl SpendingConditions {
     }
 
     /// New P2PK [SpendingConditions]
-    pub fn new_p2pk(pubkey: PublicKey, conditions: Conditions) -> Self {
+    pub fn new_p2pk(pubkey: PublicKey, conditions: Option<Conditions>) -> Self {
         Self::P2PKConditions {
             data: pubkey,
             conditions,
@@ -292,8 +293,8 @@ impl SpendingConditions {
 
     pub fn num_sigs(&self) -> Option<u64> {
         match self {
-            Self::P2PKConditions { conditions, .. } => conditions.num_sigs,
-            Self::HTLCConditions { conditions, .. } => conditions.num_sigs,
+            Self::P2PKConditions { conditions, .. } => conditions.as_ref().and_then(|c| c.num_sigs),
+            Self::HTLCConditions { conditions, .. } => conditions.as_ref().and_then(|c| c.num_sigs),
         }
     }
 
@@ -301,25 +302,31 @@ impl SpendingConditions {
         match self {
             Self::P2PKConditions { data, conditions } => {
                 let mut pubkeys = vec![*data];
-                pubkeys.extend(conditions.pubkeys.clone().unwrap_or_default());
+                if let Some(conditions) = conditions {
+                    pubkeys.extend(conditions.pubkeys.clone().unwrap_or_default());
+                }
 
                 Some(pubkeys)
             }
-            Self::HTLCConditions { conditions, .. } => conditions.pubkeys.clone(),
+            Self::HTLCConditions { conditions, .. } => conditions.clone().and_then(|c| c.pubkeys),
         }
     }
 
     pub fn locktime(&self) -> Option<u64> {
         match self {
-            Self::P2PKConditions { conditions, .. } => conditions.locktime,
-            Self::HTLCConditions { conditions, .. } => conditions.locktime,
+            Self::P2PKConditions { conditions, .. } => conditions.as_ref().and_then(|c| c.locktime),
+            Self::HTLCConditions { conditions, .. } => conditions.as_ref().and_then(|c| c.locktime),
         }
     }
 
-    pub fn refund_keys(&self) -> &Option<Vec<PublicKey>> {
+    pub fn refund_keys(&self) -> Option<Vec<PublicKey>> {
         match self {
-            Self::P2PKConditions { conditions, .. } => &conditions.refund_keys,
-            Self::HTLCConditions { conditions, .. } => &conditions.refund_keys,
+            Self::P2PKConditions { conditions, .. } => {
+                conditions.clone().and_then(|c| c.refund_keys)
+            }
+            Self::HTLCConditions { conditions, .. } => {
+                conditions.clone().and_then(|c| c.refund_keys)
+            }
         }
     }
 }
@@ -339,12 +346,12 @@ impl TryFrom<Nut10Secret> for SpendingConditions {
         match secret.kind {
             Kind::P2PK => Ok(SpendingConditions::P2PKConditions {
                 data: PublicKey::from_str(&secret.secret_data.data)?,
-                conditions: secret.secret_data.tags.try_into()?,
+                conditions: secret.secret_data.tags.and_then(|t| t.try_into().ok()),
             }),
             Kind::HTLC => Ok(Self::HTLCConditions {
                 data: Sha256Hash::from_str(&secret.secret_data.data)
                     .map_err(|_| Error::InvalidHash)?,
-                conditions: secret.secret_data.tags.try_into()?,
+                conditions: secret.secret_data.tags.and_then(|t| t.try_into().ok()),
             }),
         }
     }
@@ -578,13 +585,15 @@ pub fn enforce_sig_flag(proofs: Proofs) -> (SigFlag, HashSet<PublicKey>) {
                 }
             }
 
-            if let Ok(conditions) = Conditions::try_from(secret.secret_data.tags) {
-                if conditions.sig_flag.eq(&SigFlag::SigAll) {
-                    sig_flag = SigFlag::SigAll;
-                }
+            if let Some(tags) = secret.secret_data.tags {
+                if let Ok(conditions) = Conditions::try_from(tags) {
+                    if conditions.sig_flag.eq(&SigFlag::SigAll) {
+                        sig_flag = SigFlag::SigAll;
+                    }
 
-                if let Some(pubs) = conditions.pubkeys {
-                    pubkeys.extend(pubs);
+                    if let Some(pubs) = conditions.pubkeys {
+                        pubkeys.extend(pubs);
+                    }
                 }
             }
         }
@@ -744,7 +753,7 @@ mod tests {
             sig_flag: SigFlag::SigAll,
         };
 
-        let secret: Nut10Secret = Nut10Secret::new(Kind::P2PK, data.to_string(), conditions);
+        let secret: Nut10Secret = Nut10Secret::new(Kind::P2PK, data.to_string(), Some(conditions));
 
         let secret_str = serde_json::to_string(&secret).unwrap();
 
@@ -778,7 +787,7 @@ mod tests {
             sig_flag: SigFlag::SigInputs,
         };
 
-        let secret: Secret = Nut10Secret::new(Kind::P2PK, v_key.to_string(), conditions)
+        let secret: Secret = Nut10Secret::new(Kind::P2PK, v_key.to_string(), Some(conditions))
             .try_into()
             .unwrap();
 

+ 41 - 36
crates/cdk/src/nuts/nut14/mod.rs

@@ -56,27 +56,55 @@ impl Proof {
     /// Verify HTLC
     pub fn verify_htlc(&self) -> Result<(), Error> {
         let secret: Secret = self.secret.clone().try_into()?;
-        let conditions: Conditions = secret.secret_data.tags.try_into()?;
+        let conditions: Option<Conditions> =
+            secret.secret_data.tags.and_then(|c| c.try_into().ok());
 
-        // Check locktime
-        if let Some(locktime) = conditions.locktime {
-            // If locktime is in passed and no refund keys provided anyone can spend
-            if locktime.lt(&unix_time()) && conditions.refund_keys.is_none() {
-                return Ok(());
+        let htlc_witness = match &self.witness {
+            Some(Witness::HTLCWitness(witness)) => witness,
+            _ => return Err(Error::IncorrectSecretKind),
+        };
+
+        if let Some(conditions) = conditions {
+            // Check locktime
+            if let Some(locktime) = conditions.locktime {
+                // If locktime is in passed and no refund keys provided anyone can spend
+                if locktime.lt(&unix_time()) && conditions.refund_keys.is_none() {
+                    return Ok(());
+                }
+
+                // If refund keys are provided verify p2pk signatures
+                if let (Some(refund_key), Some(signatures)) =
+                    (conditions.refund_keys, &self.witness)
+                {
+                    let signatures: Vec<Signature> = signatures
+                        .signatures()
+                        .ok_or(Error::SignaturesNotProvided)?
+                        .iter()
+                        .flat_map(|s| Signature::from_str(s))
+                        .collect();
+
+                    // If secret includes refund keys check that there is a valid signature
+                    if valid_signatures(self.secret.as_bytes(), &refund_key, &signatures).ge(&1) {
+                        return Ok(());
+                    }
+                }
             }
+            // If pubkeys are present check there is a valid signature
+            if let Some(pubkey) = conditions.pubkeys {
+                let req_sigs = conditions.num_sigs.unwrap_or(1);
+
+                let signatures = htlc_witness
+                    .signatures
+                    .as_ref()
+                    .ok_or(Error::SignaturesNotProvided)?;
 
-            // If refund keys are provided verify p2pk signatures
-            if let (Some(refund_key), Some(signatures)) = (conditions.refund_keys, &self.witness) {
                 let signatures: Vec<Signature> = signatures
-                    .signatures()
-                    .ok_or(Error::SignaturesNotProvided)?
                     .iter()
                     .flat_map(|s| Signature::from_str(s))
                     .collect();
 
-                // If secret includes refund keys check that there is a valid signature
-                if valid_signatures(self.secret.as_bytes(), &refund_key, &signatures).ge(&1) {
-                    return Ok(());
+                if valid_signatures(self.secret.as_bytes(), &pubkey, &signatures).lt(&req_sigs) {
+                    return Err(Error::IncorrectSecretKind);
                 }
             }
         }
@@ -85,11 +113,6 @@ impl Proof {
             return Err(Error::IncorrectSecretKind);
         }
 
-        let htlc_witness = match &self.witness {
-            Some(Witness::HTLCWitness(witness)) => witness,
-            _ => return Err(Error::IncorrectSecretKind),
-        };
-
         let hash_lock =
             Sha256Hash::from_str(&secret.secret_data.data).map_err(|_| Error::InvalidHash)?;
 
@@ -99,24 +122,6 @@ impl Proof {
             return Err(Error::Preimage);
         }
 
-        // If pubkeys are present check there is a valid signature
-        if let Some(pubkey) = conditions.pubkeys {
-            let req_sigs = conditions.num_sigs.unwrap_or(1);
-            let signatures = htlc_witness
-                .signatures
-                .as_ref()
-                .ok_or(Error::SignaturesNotProvided)?;
-
-            let signatures: Vec<Signature> = signatures
-                .iter()
-                .flat_map(|s| Signature::from_str(s))
-                .collect();
-
-            if valid_signatures(self.secret.as_bytes(), &pubkey, &signatures).lt(&req_sigs) {
-                return Err(Error::IncorrectSecretKind);
-            }
-        }
-
         Ok(())
     }
 

+ 24 - 12
crates/cdk/src/wallet/mod.rs

@@ -1344,7 +1344,8 @@ impl Wallet {
                         proof.secret.clone(),
                     )
                 {
-                    let conditions: Result<Conditions, _> = secret.secret_data.tags.try_into();
+                    let conditions: Result<Conditions, _> =
+                        secret.secret_data.tags.unwrap_or_default().try_into();
                     if let Ok(conditions) = conditions {
                         let mut pubkeys = conditions.pubkeys.unwrap_or_default();
 
@@ -1548,21 +1549,32 @@ impl Wallet {
             SpendingConditions::P2PKConditions { data, conditions } => {
                 let mut pubkeys = vec![data];
 
-                pubkeys.extend(conditions.pubkeys.unwrap_or_default());
+                match conditions {
+                    Some(conditions) => {
+                        pubkeys.extend(conditions.pubkeys.unwrap_or_default());
 
-                (
+                        (
+                            conditions.refund_keys,
+                            Some(pubkeys),
+                            conditions.locktime,
+                            conditions.num_sigs,
+                        )
+                    }
+                    None => (None, Some(pubkeys), None, None),
+                }
+            }
+            SpendingConditions::HTLCConditions {
+                conditions,
+                data: _,
+            } => match conditions {
+                Some(conditions) => (
                     conditions.refund_keys,
-                    Some(pubkeys),
+                    conditions.pubkeys,
                     conditions.locktime,
                     conditions.num_sigs,
-                )
-            }
-            SpendingConditions::HTLCConditions { conditions, .. } => (
-                conditions.refund_keys,
-                conditions.pubkeys,
-                conditions.locktime,
-                conditions.num_sigs,
-            ),
+                ),
+                None => (None, None, None, None),
+            },
         };
 
         if refund_keys.is_some() && locktime.is_none() {