Browse Source

feat: r and secret from seed

thesimplekid 1 year ago
parent
commit
a0a3a80377

+ 4 - 4
crates/cashu/Cargo.toml

@@ -15,20 +15,20 @@ description = "Cashu rust wallet and mint library"
 default = ["mint", "wallet", "all-nuts"]
 mint = []
 wallet = []
-all-nuts = ["nut07", "nut08", "nut09", "nut10", "nut11"]
+all-nuts = ["nut07", "nut08", "nut09", "nut10", "nut11", "nut13"]
 nut07 = []
 nut08 = []
 nut09 = []
 nut10 = []
 nut11 = ["nut10"]
+nut13 = ["dep:bip39", "dep:bip32", "nut09"]
 
 
 [dependencies]
 base64 = "0.21.0"
 bitcoin = { version = "0.30.0", features=["serde",  "rand"] }
-# TODO: Should be optional
-bip39 = "2.0.0"
-bip32 = "0.5.1"
+bip39 = { version = "2.0.0", optional = true }
+bip32 = { version = "0.5.1", optional = true }
 hex = "0.4.3"
 k256 = { version = "0.13.1", features=["arithmetic", "serde", "schnorr"] }
 lightning-invoice = { version = "0.25.0", features=["serde"] }

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

@@ -15,6 +15,8 @@ pub mod nut09;
 pub mod nut10;
 #[cfg(feature = "nut11")]
 pub mod nut11;
+#[cfg(feature = "nut13")]
+pub mod nut13;
 
 #[cfg(feature = "wallet")]
 pub use nut00::wallet::{PreMint, PreMintSecrets, Token};

+ 1 - 37
crates/cashu/src/nuts/nut00.rs

@@ -116,7 +116,6 @@ pub mod wallet {
 
     use base64::engine::{general_purpose, GeneralPurpose};
     use base64::{alphabet, Engine as _};
-    use bip39::Mnemonic;
     use serde::{Deserialize, Serialize};
     use url::Url;
 
@@ -156,7 +155,7 @@ pub mod wallet {
 
     #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)]
     pub struct PreMintSecrets {
-        secrets: Vec<PreMint>,
+        pub secrets: Vec<PreMint>,
     }
 
     impl PreMintSecrets {
@@ -229,41 +228,6 @@ pub mod wallet {
             Ok(PreMintSecrets { secrets: output })
         }
 
-        /// Generate blinded messages from predetermined secrets and blindings
-        /// factor
-        /// TODO: Put behind feature
-        pub fn from_seed(
-            keyset_id: Id,
-            counter: u64,
-            mnemonic: &Mnemonic,
-            amount: Amount,
-        ) -> Result<Self, wallet::Error> {
-            let mut pre_mint_secrets = PreMintSecrets::default();
-
-            let mut counter = counter;
-
-            for amount in amount.split() {
-                let secret = Secret::from_seed(mnemonic, keyset_id, counter);
-                let blinding_factor = SecretKey::from_seed(mnemonic, keyset_id, counter);
-
-                let (blinded, r) = blind_message(&secret.to_bytes(), Some(blinding_factor.into()))?;
-
-                let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
-
-                let pre_mint = PreMint {
-                    blinded_message,
-                    secret: secret.clone(),
-                    r: r.into(),
-                    amount: Amount::ZERO,
-                };
-
-                pre_mint_secrets.secrets.push(pre_mint);
-                counter += 1;
-            }
-
-            Ok(pre_mint_secrets)
-        }
-
         #[cfg(feature = "nut11")]
         pub fn with_p2pk_conditions(
             keyset_id: Id,

+ 2 - 20
crates/cashu/src/nuts/nut01.rs

@@ -4,14 +4,12 @@
 use std::collections::{BTreeMap, HashMap};
 use std::str::FromStr;
 
-use bip32::{DerivationPath, XPrv};
-use bip39::Mnemonic;
 use k256::elliptic_curve::generic_array::GenericArray;
 #[cfg(feature = "nut11")]
 use k256::schnorr::{SigningKey, VerifyingKey};
 use serde::{Deserialize, Serialize};
 
-use super::{Id, KeySet};
+use super::KeySet;
 use crate::error::Error;
 use crate::Amount;
 
@@ -99,7 +97,7 @@ impl std::fmt::Display for PublicKey {
 
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 #[serde(transparent)]
-pub struct SecretKey(#[serde(with = "crate::serde_utils::serde_secret_key")] k256::SecretKey);
+pub struct SecretKey(#[serde(with = "crate::serde_utils::serde_secret_key")] pub k256::SecretKey);
 
 impl From<SecretKey> for k256::SecretKey {
     fn from(value: SecretKey) -> k256::SecretKey {
@@ -136,22 +134,6 @@ impl SecretKey {
         self.0.public_key().into()
     }
 
-    // TODO: put behind feature
-    pub fn from_seed(mnemonic: &Mnemonic, keyset_id: Id, counter: u64) -> Self {
-        let path = DerivationPath::from_str(&format!(
-            "m/129372'/0'/{}'/{}'/1",
-            u64::from(keyset_id),
-            counter
-        ))
-        .unwrap();
-
-        let signing_key = XPrv::derive_from_path(mnemonic.to_seed(""), &path).unwrap();
-
-        let private_key = signing_key.private_key();
-
-        Self(private_key.into())
-    }
-
     pub fn random() -> Self {
         let mut rng = rand::thread_rng();
         SecretKey(k256::SecretKey::random(&mut rng))

+ 18 - 6
crates/cashu/src/nuts/nut02.rs

@@ -47,12 +47,16 @@ impl Id {
     const STRLEN: usize = 14;
 }
 
-impl From<Id> for u64 {
-    fn from(value: Id) -> Self {
-        value
-            .id
-            .iter()
-            .fold(0, |acc, &byte| (acc << 8) | u64::from(byte))
+impl TryFrom<Id> for u64 {
+    type Error = Error;
+    fn try_from(value: Id) -> Result<Self, Self::Error> {
+        let hex_bytes: [u8; 8] = hex::decode(value.to_string())?
+            .try_into()
+            .map_err(|_| Error::Length)?;
+
+        let int = u64::from_be_bytes(hex_bytes);
+
+        Ok(int % (2_u64.pow(31) - 1))
     }
 }
 
@@ -406,4 +410,12 @@ mod test {
 
         let _keyset_response: KeysetResponse = serde_json::from_str(h).unwrap();
     }
+
+    #[test]
+    fn test_to_int() {
+        let id = Id::from_str("009a1f293253e41e").unwrap();
+
+        let id_int = u64::try_from(id).unwrap();
+        assert_eq!(864559728, id_int)
+    }
 }

+ 134 - 0
crates/cashu/src/nuts/nut13.rs

@@ -0,0 +1,134 @@
+use std::str::FromStr;
+
+use bip32::{DerivationPath, XPrv};
+use bip39::Mnemonic;
+
+use super::{Id, SecretKey};
+use crate::error::Error;
+use crate::secret::Secret;
+
+impl Secret {
+    pub fn from_seed(mnemonic: &Mnemonic, keyset_id: Id, counter: u64) -> Result<Self, Error> {
+        let path = DerivationPath::from_str(&format!(
+            "m/129372'/0'/{}'/{}'/0",
+            u64::try_from(keyset_id).unwrap(),
+            counter
+        ))
+        .unwrap();
+
+        let xpriv = XPrv::derive_from_path(mnemonic.to_seed(""), &path).unwrap();
+
+        Ok(Self(hex::encode(xpriv.private_key().to_bytes())))
+    }
+}
+
+impl SecretKey {
+    pub fn from_seed(mnemonic: &Mnemonic, keyset_id: Id, counter: u64) -> Result<Self, Error> {
+        let path = DerivationPath::from_str(&format!(
+            "m/129372'/0'/{}'/{}'/1",
+            u64::try_from(keyset_id).unwrap(),
+            counter
+        ))
+        .unwrap();
+
+        let signing_key = XPrv::derive_from_path(mnemonic.to_seed(""), &path).unwrap();
+
+        let private_key = signing_key.private_key();
+
+        Ok(Self(private_key.into()))
+    }
+}
+
+#[cfg(feature = "wallet")]
+mod wallet {
+    use bip39::Mnemonic;
+
+    use crate::dhke::blind_message;
+    use crate::error::wallet;
+    use crate::nuts::{BlindedMessage, Id, PreMint, PreMintSecrets, SecretKey};
+    use crate::secret::Secret;
+    use crate::Amount;
+
+    impl PreMintSecrets {
+        /// Generate blinded messages from predetermined secrets and blindings
+        /// factor
+        pub fn from_seed(
+            keyset_id: Id,
+            counter: u64,
+            mnemonic: &Mnemonic,
+            amount: Amount,
+        ) -> Result<Self, wallet::Error> {
+            let mut pre_mint_secrets = PreMintSecrets::default();
+
+            let mut counter = counter;
+
+            for amount in amount.split() {
+                let secret = Secret::from_seed(mnemonic, keyset_id, counter)?;
+                let blinding_factor = SecretKey::from_seed(mnemonic, keyset_id, counter)?;
+
+                let (blinded, r) = blind_message(&secret.to_bytes(), Some(blinding_factor.into()))?;
+
+                let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
+
+                let pre_mint = PreMint {
+                    blinded_message,
+                    secret: secret.clone(),
+                    r: r.into(),
+                    amount: Amount::ZERO,
+                };
+
+                pre_mint_secrets.secrets.push(pre_mint);
+                counter += 1;
+            }
+
+            Ok(pre_mint_secrets)
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+
+    #[test]
+    fn test_secret_from_seed() {
+        let seed =
+            "half depart obvious quality work element tank gorilla view sugar picture humble";
+        let mnemonic = Mnemonic::from_str(seed).unwrap();
+        let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
+
+        let test_secrets = [
+            "485875df74771877439ac06339e284c3acfcd9be7abf3bc20b516faeadfe77ae",
+            "8f2b39e8e594a4056eb1e6dbb4b0c38ef13b1b2c751f64f810ec04ee35b77270",
+            "bc628c79accd2364fd31511216a0fab62afd4a18ff77a20deded7b858c9860c8",
+            "59284fd1650ea9fa17db2b3acf59ecd0f2d52ec3261dd4152785813ff27a33bf",
+            "576c23393a8b31cc8da6688d9c9a96394ec74b40fdaf1f693a6bb84284334ea0",
+        ];
+
+        for (i, test_secret) in test_secrets.iter().enumerate() {
+            let secret = Secret::from_seed(&mnemonic, keyset_id, i.try_into().unwrap()).unwrap();
+            assert_eq!(secret, Secret::from_str(test_secret).unwrap())
+        }
+    }
+    #[test]
+    fn test_r_from_seed() {
+        let seed =
+            "half depart obvious quality work element tank gorilla view sugar picture humble";
+        let mnemonic = Mnemonic::from_str(seed).unwrap();
+        let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
+
+        let test_rs = [
+            "ad00d431add9c673e843d4c2bf9a778a5f402b985b8da2d5550bf39cda41d679",
+            "967d5232515e10b81ff226ecf5a9e2e2aff92d66ebc3edf0987eb56357fd6248",
+            "b20f47bb6ae083659f3aa986bfa0435c55c6d93f687d51a01f26862d9b9a4899",
+            "fb5fca398eb0b1deb955a2988b5ac77d32956155f1c002a373535211a2dfdc29",
+            "5f09bfbfe27c439a597719321e061e2e40aad4a36768bb2bcc3de547c9644bf9",
+        ];
+
+        for (i, test_r) in test_rs.iter().enumerate() {
+            let r = SecretKey::from_seed(&mnemonic, keyset_id, i.try_into().unwrap()).unwrap();
+            assert_eq!(r, SecretKey::from_hex(test_r).unwrap())
+        }
+    }
+}

+ 1 - 18
crates/cashu/src/secret.rs

@@ -2,17 +2,13 @@
 
 use std::str::FromStr;
 
-use bip32::{DerivationPath, XPrv};
-use bip39::Mnemonic;
 use serde::{Deserialize, Serialize};
 use thiserror::Error;
 
-use crate::nuts::Id;
-
 /// The secret data that allows spending ecash
 #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
 #[serde(transparent)]
-pub struct Secret(String);
+pub struct Secret(pub String);
 
 #[derive(Debug, Error)]
 pub enum Error {
@@ -45,19 +41,6 @@ impl Secret {
         Self(secret)
     }
 
-    pub fn from_seed(mnemonic: &Mnemonic, keyset_id: Id, counter: u64) -> Self {
-        let path = DerivationPath::from_str(&format!(
-            "m/129372'/0'/{}'/{}'/0",
-            u64::from(keyset_id),
-            counter
-        ))
-        .unwrap();
-
-        let xpriv = XPrv::derive_from_path(mnemonic.to_seed(""), &path).unwrap();
-
-        Self(hex::encode(xpriv.private_key().to_bytes()))
-    }
-
     pub fn to_bytes(&self) -> Vec<u8> {
         self.0.clone().into_bytes()
     }