Browse Source

cashu: adj. dependencies

* Remove `k256`, `bip32`, `hex`, `log`, `rand` and `itertools` deps
* Add `once_cell` and `instant` deps
* Downgrade `bitcoin` to `v0.30` and `base64` to `v0.21`
* Replace `utils` module with `util`
* Remove `utils` from `cashu-sdk`
* Some cleanups

Closes https://github.com/thesimplekid/cashu-crab/issues/35

Signed-off-by: Yuki Kishimoto <yukikishimoto@protonmail.com>
Yuki Kishimoto 1 year ago
parent
commit
3831a4f3bb

+ 0 - 1
Cargo.toml

@@ -1,5 +1,4 @@
 [workspace]
-
 members = [
     "crates/cashu",
     "crates/cashu-sdk",

+ 3 - 5
crates/cashu-sdk/src/lib.rs

@@ -1,11 +1,9 @@
+pub use bip39::Mnemonic;
+pub use cashu::{self, *};
+
 #[cfg(feature = "wallet")]
 pub mod client;
-
 #[cfg(feature = "mint")]
 pub mod mint;
-pub mod utils;
 #[cfg(feature = "wallet")]
 pub mod wallet;
-
-pub use bip39::Mnemonic;
-pub use cashu::{self, *};

+ 16 - 44
crates/cashu-sdk/src/mint/localstore/memory.rs

@@ -18,9 +18,9 @@ pub struct MemoryLocalStore {
     keysets: Arc<Mutex<HashMap<Id, KeySet>>>,
     mint_quotes: Arc<Mutex<HashMap<String, MintQuote>>>,
     melt_quotes: Arc<Mutex<HashMap<String, MeltQuote>>>,
-    pending_proofs: Arc<Mutex<HashMap<Vec<u8>, Proof>>>,
-    spent_proofs: Arc<Mutex<HashMap<Vec<u8>, Proof>>>,
-    blinded_signatures: Arc<Mutex<HashMap<Box<[u8]>, BlindSignature>>>,
+    pending_proofs: Arc<Mutex<HashMap<[u8; 33], Proof>>>,
+    spent_proofs: Arc<Mutex<HashMap<[u8; 33], Proof>>>,
+    blinded_signatures: Arc<Mutex<HashMap<[u8; 33], BlindSignature>>>,
 }
 
 impl MemoryLocalStore {
@@ -33,7 +33,7 @@ impl MemoryLocalStore {
         melt_quotes: Vec<MeltQuote>,
         pending_proofs: Proofs,
         spent_proofs: Proofs,
-        blinded_signatures: HashMap<Box<[u8]>, BlindSignature>,
+        blinded_signatures: HashMap<[u8; 33], BlindSignature>,
     ) -> Result<Self, Error> {
         Ok(Self {
             mint_info: Arc::new(Mutex::new(mint_info)),
@@ -48,29 +48,13 @@ impl MemoryLocalStore {
             pending_proofs: Arc::new(Mutex::new(
                 pending_proofs
                     .into_iter()
-                    .map(|p| {
-                        (
-                            hash_to_curve(&p.secret.to_bytes())
-                                .unwrap()
-                                .to_sec1_bytes()
-                                .to_vec(),
-                            p,
-                        )
-                    })
+                    .map(|p| (hash_to_curve(&p.secret.to_bytes()).unwrap().to_bytes(), p))
                     .collect(),
             )),
             spent_proofs: Arc::new(Mutex::new(
                 spent_proofs
                     .into_iter()
-                    .map(|p| {
-                        (
-                            hash_to_curve(&p.secret.to_bytes())
-                                .unwrap()
-                                .to_sec1_bytes()
-                                .to_vec(),
-                            p,
-                        )
-                    })
+                    .map(|p| (hash_to_curve(&p.secret.to_bytes()).unwrap().to_bytes(), p))
                     .collect(),
             )),
             blinded_signatures: Arc::new(Mutex::new(blinded_signatures)),
@@ -163,7 +147,7 @@ impl LocalStore for MemoryLocalStore {
         self.spent_proofs
             .lock()
             .await
-            .insert(secret_point.to_sec1_bytes().to_vec(), proof);
+            .insert(secret_point.to_bytes(), proof);
         Ok(())
     }
 
@@ -172,26 +156,19 @@ impl LocalStore for MemoryLocalStore {
             .spent_proofs
             .lock()
             .await
-            .get(&hash_to_curve(&secret.to_bytes())?.to_sec1_bytes().to_vec())
+            .get(&hash_to_curve(&secret.to_bytes())?.to_bytes())
             .cloned())
     }
 
     async fn get_spent_proof_by_y(&self, y: &PublicKey) -> Result<Option<Proof>, Error> {
-        Ok(self
-            .spent_proofs
-            .lock()
-            .await
-            .get(&y.to_bytes().to_vec())
-            .cloned())
+        Ok(self.spent_proofs.lock().await.get(&y.to_bytes()).cloned())
     }
 
     async fn add_pending_proof(&self, proof: Proof) -> Result<(), Error> {
-        self.pending_proofs.lock().await.insert(
-            hash_to_curve(&proof.secret.to_bytes())?
-                .to_sec1_bytes()
-                .to_vec(),
-            proof,
-        );
+        self.pending_proofs
+            .lock()
+            .await
+            .insert(hash_to_curve(&proof.secret.to_bytes())?.to_bytes(), proof);
         Ok(())
     }
 
@@ -201,17 +178,12 @@ impl LocalStore for MemoryLocalStore {
             .pending_proofs
             .lock()
             .await
-            .get(&secret_point.to_sec1_bytes().to_vec())
+            .get(&secret_point.to_bytes())
             .cloned())
     }
 
     async fn get_pending_proof_by_y(&self, y: &PublicKey) -> Result<Option<Proof>, Error> {
-        Ok(self
-            .pending_proofs
-            .lock()
-            .await
-            .get(&y.to_bytes().to_vec())
-            .cloned())
+        Ok(self.pending_proofs.lock().await.get(&y.to_bytes()).cloned())
     }
 
     async fn remove_pending_proof(&self, secret: &Secret) -> Result<(), Error> {
@@ -219,7 +191,7 @@ impl LocalStore for MemoryLocalStore {
         self.pending_proofs
             .lock()
             .await
-            .remove(&secret_point.to_sec1_bytes().to_vec());
+            .remove(&secret_point.to_bytes());
         Ok(())
     }
 

+ 17 - 20
crates/cashu-sdk/src/mint/localstore/redb_store.rs

@@ -19,11 +19,13 @@ const ACTIVE_KEYSETS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("
 const KEYSETS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("keysets");
 const MINT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mint_quotes");
 const MELT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("melt_quotes");
-const PENDING_PROOFS_TABLE: TableDefinition<&[u8], &str> = TableDefinition::new("pending_proofs");
-const SPENT_PROOFS_TABLE: TableDefinition<&[u8], &str> = TableDefinition::new("spent_proofs");
+const PENDING_PROOFS_TABLE: TableDefinition<[u8; 33], &str> =
+    TableDefinition::new("pending_proofs");
+const SPENT_PROOFS_TABLE: TableDefinition<[u8; 33], &str> = TableDefinition::new("spent_proofs");
 const CONFIG_TABLE: TableDefinition<&str, &str> = TableDefinition::new("config");
 // Key is hex blinded_message B_ value is blinded_signature
-const BLINDED_SIGNATURES: TableDefinition<&[u8], &str> = TableDefinition::new("blinded_signatures");
+const BLINDED_SIGNATURES: TableDefinition<[u8; 33], &str> =
+    TableDefinition::new("blinded_signatures");
 
 const DATABASE_VERSION: u64 = 0;
 
@@ -307,11 +309,8 @@ impl LocalStore for RedbLocalStore {
 
         {
             let mut table = write_txn.open_table(SPENT_PROOFS_TABLE)?;
-            let y: PublicKey = hash_to_curve(&proof.secret.to_bytes())?.into();
-            table.insert(
-                y.to_bytes().as_ref(),
-                serde_json::to_string(&proof)?.as_str(),
-            )?;
+            let y: PublicKey = hash_to_curve(&proof.secret.to_bytes())?;
+            table.insert(y.to_bytes(), serde_json::to_string(&proof)?.as_str())?;
         }
         write_txn.commit()?;
         debug!("Added spend secret: {}", proof.secret.to_string());
@@ -324,7 +323,7 @@ impl LocalStore for RedbLocalStore {
         let read_txn = db.begin_read()?;
         let table = read_txn.open_table(SPENT_PROOFS_TABLE)?;
 
-        let proof = table.get(y.to_bytes().as_ref())?;
+        let proof = table.get(y.to_bytes())?;
 
         if let Some(proof) = proof {
             Ok(serde_json::from_str(proof.value())?)
@@ -338,9 +337,9 @@ impl LocalStore for RedbLocalStore {
         let read_txn = db.begin_read()?;
         let table = read_txn.open_table(SPENT_PROOFS_TABLE)?;
 
-        let y: PublicKey = hash_to_curve(&secret.to_bytes())?.into();
+        let y: PublicKey = hash_to_curve(&secret.to_bytes())?;
 
-        let proof = table.get(y.to_bytes().as_ref())?;
+        let proof = table.get(y.to_bytes())?;
 
         debug!("Checking secret: {}", secret.to_string());
 
@@ -359,9 +358,7 @@ impl LocalStore for RedbLocalStore {
         {
             let mut table = write_txn.open_table(PENDING_PROOFS_TABLE)?;
             table.insert(
-                hash_to_curve(&proof.secret.to_bytes())?
-                    .to_sec1_bytes()
-                    .as_ref(),
+                hash_to_curve(&proof.secret.to_bytes())?.to_bytes(),
                 serde_json::to_string(&proof)?.as_str(),
             )?;
         }
@@ -375,7 +372,7 @@ impl LocalStore for RedbLocalStore {
         let read_txn = db.begin_read()?;
         let table = read_txn.open_table(PENDING_PROOFS_TABLE)?;
 
-        let proof = table.get(y.to_bytes().as_ref())?;
+        let proof = table.get(y.to_bytes())?;
 
         if let Some(proof) = proof {
             Ok(serde_json::from_str(proof.value())?)
@@ -391,7 +388,7 @@ impl LocalStore for RedbLocalStore {
 
         let secret_hash = hash_to_curve(&secret.to_bytes())?;
 
-        let proof = table.get(secret_hash.to_sec1_bytes().as_ref())?;
+        let proof = table.get(secret_hash.to_bytes())?;
 
         if let Some(proof) = proof {
             Ok(serde_json::from_str(proof.value())?)
@@ -408,7 +405,7 @@ impl LocalStore for RedbLocalStore {
         {
             let mut table = write_txn.open_table(PENDING_PROOFS_TABLE)?;
             let secret_hash = hash_to_curve(&secret.to_bytes())?;
-            table.remove(secret_hash.to_sec1_bytes().as_ref())?;
+            table.remove(secret_hash.to_bytes())?;
         }
         write_txn.commit()?;
 
@@ -426,7 +423,7 @@ impl LocalStore for RedbLocalStore {
         {
             let mut table = write_txn.open_table(BLINDED_SIGNATURES)?;
             table.insert(
-                blinded_message.to_bytes().as_ref(),
+                blinded_message.to_bytes(),
                 serde_json::to_string(&blinded_signature)?.as_str(),
             )?;
         }
@@ -444,7 +441,7 @@ impl LocalStore for RedbLocalStore {
         let read_txn = db.begin_read()?;
         let table = read_txn.open_table(BLINDED_SIGNATURES)?;
 
-        if let Some(blinded_signature) = table.get(blinded_message.to_bytes().as_ref())? {
+        if let Some(blinded_signature) = table.get(blinded_message.to_bytes())? {
             return Ok(serde_json::from_str(blinded_signature.value())?);
         }
 
@@ -462,7 +459,7 @@ impl LocalStore for RedbLocalStore {
         let mut signatures = Vec::with_capacity(blinded_messages.len());
 
         for blinded_message in blinded_messages {
-            if let Some(blinded_signature) = table.get(blinded_message.to_bytes().as_ref())? {
+            if let Some(blinded_signature) = table.get(blinded_message.to_bytes())? {
                 signatures.push(Some(serde_json::from_str(blinded_signature.value())?))
             } else {
                 signatures.push(None);

+ 11 - 15
crates/cashu-sdk/src/mint/mod.rs

@@ -355,7 +355,7 @@ impl Mint {
             return Err(Error::AmountKey);
         };
 
-        let c = sign_message(key_pair.secret_key.clone().into(), b.clone().into())?;
+        let c = sign_message(&key_pair.secret_key, b)?;
 
         let blinded_signature;
         #[cfg(not(feature = "nut12"))]
@@ -369,9 +369,9 @@ impl Mint {
 
         #[cfg(feature = "nut12")]
         {
-            blinded_signature = BlindSignature::new_dleq(
+            blinded_signature = BlindSignature::new(
                 *amount,
-                c.into(),
+                c,
                 keyset.id,
                 &blinded_message.b,
                 key_pair.secret_key.clone(),
@@ -407,11 +407,11 @@ impl Mint {
 
         let proof_count = swap_request.inputs.len();
 
-        let secrets: HashSet<Vec<u8>> = swap_request
+        let secrets: HashSet<[u8; 33]> = swap_request
             .inputs
             .iter()
             .flat_map(|p| hash_to_curve(&p.secret.to_bytes()))
-            .map(|p| p.to_sec1_bytes().to_vec())
+            .map(|p| p.to_bytes())
             .collect();
 
         // Check that there are no duplicate proofs in request
@@ -524,7 +524,7 @@ impl Mint {
             }
         }
 
-        let y: PublicKey = hash_to_curve(&proof.secret.to_bytes())?.into();
+        let y: PublicKey = hash_to_curve(&proof.secret.to_bytes())?;
 
         if self.localstore.get_spent_proof_by_y(&y).await?.is_some() {
             return Err(Error::TokenSpent);
@@ -544,11 +544,7 @@ impl Mint {
             return Err(Error::AmountKey);
         };
 
-        verify_message(
-            keypair.secret_key.clone().into(),
-            proof.c.clone().into(),
-            &proof.secret.to_bytes(),
-        )?;
+        verify_message(&keypair.secret_key, proof.c, proof.secret.as_bytes())?;
 
         Ok(())
     }
@@ -570,7 +566,7 @@ impl Mint {
             };
 
             states.push(ProofState {
-                y: y.clone(),
+                y: *y,
                 state,
                 witness: None,
             })
@@ -643,11 +639,11 @@ impl Mint {
             return Err(Error::MultipleUnits);
         }
 
-        let secrets: HashSet<Vec<u8>> = melt_request
+        let secrets: HashSet<[u8; 33]> = melt_request
             .inputs
             .iter()
             .flat_map(|p| hash_to_curve(&p.secret.to_bytes()))
-            .map(|p| p.to_sec1_bytes().to_vec())
+            .map(|p| p.to_bytes())
             .collect();
 
         // Ensure proofs are unique and not being double spent
@@ -761,7 +757,7 @@ impl Mint {
         let mut outputs = Vec::with_capacity(output_len);
         let mut signatures = Vec::with_capacity(output_len);
 
-        let blinded_message: Vec<PublicKey> = request.outputs.iter().map(|b| b.b.clone()).collect();
+        let blinded_message: Vec<PublicKey> = request.outputs.iter().map(|b| b.b).collect();
 
         let blinded_signatures = self
             .localstore

+ 0 - 8
crates/cashu-sdk/src/utils.rs

@@ -1,8 +0,0 @@
-use std::time::SystemTime;
-
-pub fn unix_time() -> u64 {
-    SystemTime::now()
-        .duration_since(SystemTime::UNIX_EPOCH)
-        .map(|x| x.as_secs())
-        .unwrap_or(0)
-}

+ 9 - 14
crates/cashu-sdk/src/wallet/mod.rs

@@ -20,13 +20,13 @@ use cashu::nuts::{
 };
 use cashu::types::{MeltQuote, Melted, MintQuote};
 use cashu::url::UncheckedUrl;
+use cashu::util::unix_time;
 use cashu::{Amount, Bolt11Invoice};
 use localstore::LocalStore;
 use thiserror::Error;
 use tracing::{debug, warn};
 
 use crate::client::Client;
-use crate::utils::unix_time;
 
 pub mod localstore;
 
@@ -200,13 +200,10 @@ impl Wallet {
             .post_check_state(
                 mint_url.try_into()?,
                 proofs
-                    .clone()
                     .into_iter()
                     // Find Y for the secret
-                    .flat_map(|p| hash_to_curve(&p.secret.to_bytes()))
-                    .map(|y| y.into())
-                    .collect::<Vec<PublicKey>>()
-                    .clone(),
+                    .flat_map(|p| hash_to_curve(p.secret.as_bytes()))
+                    .collect::<Vec<PublicKey>>(),
             )
             .await?;
 
@@ -369,7 +366,7 @@ impl Wallet {
             for (sig, premint) in mint_res.signatures.iter().zip(&premint_secrets.secrets) {
                 let keys = self.get_keyset_keys(&mint_url, sig.keyset_id).await?;
                 let key = keys.amount_key(sig.amount).ok_or(Error::UnknownKey)?;
-                match sig.verify_dleq(&key, &premint.blinded_message.b) {
+                match sig.verify_dleq(key, premint.blinded_message.b) {
                     Ok(_) => (),
                     Err(cashu::nuts::nut12::Error::MissingDleqProof) => (),
                     Err(_) => return Err(Error::CouldNotVerifyDleq),
@@ -420,7 +417,7 @@ impl Wallet {
                 for proof in proofs {
                     let keys = self.get_keyset_keys(mint_url, proof.keyset_id).await?;
                     let key = keys.amount_key(proof.amount).ok_or(Error::UnknownKey)?;
-                    match proof.verify_dleq(&key) {
+                    match proof.verify_dleq(key) {
                         Ok(_) => continue,
                         Err(cashu::nuts::nut12::Error::MissingDleqProof) => continue,
                         Err(_) => return Err(Error::CouldNotVerifyDleq),
@@ -581,7 +578,7 @@ impl Wallet {
                     .await?
                     .ok_or(Error::UnknownKey)?;
                 let key = keys.amount_key(promise.amount).ok_or(Error::UnknownKey)?;
-                match promise.verify_dleq(&key, &premint.blinded_message.b) {
+                match promise.verify_dleq(key, premint.blinded_message.b) {
                     Ok(_) => (),
                     Err(cashu::nuts::nut12::Error::MissingDleqProof) => (),
                     Err(_) => return Err(Error::CouldNotVerifyDleq),
@@ -597,9 +594,7 @@ impl Wallet {
                 .unwrap()
                 .to_owned();
 
-            let blinded_c = promise.c.clone();
-
-            let unblinded_sig = unblind_message(blinded_c, premint.r.into(), a).unwrap();
+            let unblinded_sig = unblind_message(&promise.c, &premint.r, &a).unwrap();
 
             let count = proof_count.get(&promise.keyset_id).unwrap_or(&0);
             proof_count.insert(promise.keyset_id, count + 1);
@@ -1012,7 +1007,7 @@ impl Wallet {
                 {
                     let keys = self.localstore.get_keys(&proof.keyset_id).await?.unwrap();
                     let key = keys.amount_key(proof.amount).unwrap();
-                    proof.verify_dleq(&key).unwrap();
+                    proof.verify_dleq(key).unwrap();
                 }
 
                 if let Ok(secret) =
@@ -1309,7 +1304,7 @@ impl Wallet {
                 .ok_or(Error::UnknownKey)?;
 
                 proof
-                    .verify_dleq(&mint_pubkey)
+                    .verify_dleq(mint_pubkey)
                     .map_err(|_| Error::CouldNotVerifyDleq)?;
             }
         }

+ 15 - 22
crates/cashu/Cargo.toml

@@ -22,29 +22,22 @@ nut09 = []
 nut10 = []
 nut11 = ["nut10"]
 nut12 = []
-nut13 = ["dep:bip39", "dep:bip32", "nut09"]
-
+nut13 = ["dep:bip39", "nut09"]
 
 [dependencies]
-base64 = "0.22.0"
-bitcoin = { version = "0.31.0", features=["serde",  "rand"] }
-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.29.0", features=["serde"] }
-log = "0.4.2"
-rand = "0.8.5"
-serde = { workspace = true }
-serde_json = { workspace = true }
-serde_with = "3.4.0"
-url = { workspace = true }
-itertools = "0.12.0"
-thiserror = { workspace = true }
-uuid = { version = "1.6.1", features = ["v4"] }
+base64 = "0.21" # bitcoin uses v0.21 (optional dep)
+bip39 = { version = "2.0", optional = true }
+bitcoin = { version = "0.30", features = ["serde", "rand", "rand-std"] } # lightning-invoice uses v0.30
+lightning-invoice = { version = "0.29", features = ["serde"] }
+once_cell = "1.19"
+serde.workspace = true
+serde_json.workspace = true
+serde_with = "3.4"
+url.workspace = true
+thiserror.workspace = true
+tracing.workspace = true
+uuid = { version = "1.6", features = ["v4"] }
 
 [target.'cfg(target_arch = "wasm32")'.dependencies]
-getrandom = { workspace = true }
-
-[dev-dependencies]
-# tokio = {version = "1.27.0", features = ["rt", "macros"] }
+getrandom.workspace = true
+instant = { version = "0.1", features = [ "wasm-bindgen", "inaccurate" ] }

+ 268 - 338
crates/cashu/src/dhke.rs

@@ -1,36 +1,39 @@
 //! Diffie-Hellmann key exchange
 
-use bitcoin::hashes::{sha256, Hash};
-use k256::elliptic_curve::sec1::ToEncodedPoint;
-#[cfg(feature = "mint")]
-pub use mint::{sign_message, verify_message};
-#[cfg(feature = "wallet")]
-pub use wallet::{blind_message, construct_proofs, unblind_message};
+use std::ops::Deref;
 
-use crate::error::Error;
+use bitcoin::hashes::sha256::Hash as Sha256Hash;
+use bitcoin::hashes::Hash;
+use bitcoin::secp256k1::{Parity, PublicKey as NormalizedPublicKey, Scalar, XOnlyPublicKey};
+
+use crate::error::{self, Error};
+use crate::nuts::nut01::{PublicKey, SecretKey};
+#[cfg(feature = "nut12")]
+use crate::nuts::nut12::ProofDleq;
+use crate::nuts::{BlindSignature, Keys, Proof, Proofs};
+use crate::secret::Secret;
+use crate::util::hex;
+use crate::SECP256K1;
 
 const DOMAIN_SEPARATOR: &[u8; 28] = b"Secp256k1_HashToCurve_Cashu_";
 
-pub fn hash_to_curve(message: &[u8]) -> Result<k256::PublicKey, Error> {
-    let msg_to_hash = [DOMAIN_SEPARATOR, message].concat();
+pub fn hash_to_curve(message: &[u8]) -> Result<PublicKey, Error> {
+    let msg_to_hash: Vec<u8> = [DOMAIN_SEPARATOR, message].concat();
 
-    let msg_hash = sha256::Hash::hash(&msg_to_hash).to_byte_array();
+    let msg_hash: [u8; 32] = Sha256Hash::hash(&msg_to_hash).to_byte_array();
 
-    let mut counter = 0;
+    let mut counter: u32 = 0;
     while counter < 2_u32.pow(16) {
-        let mut bytes_to_hash = Vec::with_capacity(36);
+        let mut bytes_to_hash: Vec<u8> = Vec::with_capacity(36);
         bytes_to_hash.extend_from_slice(&msg_hash);
         bytes_to_hash.extend_from_slice(&counter.to_le_bytes());
+        let hash: [u8; 32] = Sha256Hash::hash(&bytes_to_hash).to_byte_array();
 
-        let hash = sha256::Hash::hash(&bytes_to_hash);
-        match k256::PublicKey::from_sec1_bytes(
-            &[0x02u8]
-                .iter()
-                .chain(&hash.to_byte_array())
-                .cloned()
-                .collect::<Vec<u8>>(),
-        ) {
-            Ok(pubkey) => return Ok(pubkey),
+        // Try to parse public key
+        match XOnlyPublicKey::from_slice(&hash) {
+            Ok(pk) => {
+                return Ok(NormalizedPublicKey::from_x_only_public_key(pk, Parity::Even).into())
+            }
             Err(_) => {
                 counter += 1;
             }
@@ -40,215 +43,182 @@ pub fn hash_to_curve(message: &[u8]) -> Result<k256::PublicKey, Error> {
     Err(Error::NoValidPoint)
 }
 
-pub fn hash_e(pubkeys: Vec<k256::PublicKey>) -> Vec<u8> {
-    let mut e = "".to_string();
-
-    for pubkey in pubkeys {
-        let uncompressed_point = pubkey.to_encoded_point(false).to_bytes();
+pub fn hash_e<I>(public_keys: I) -> [u8; 32]
+where
+    I: IntoIterator<Item = PublicKey>,
+{
+    let mut e: String = String::new();
 
-        e.push_str(&hex::encode(uncompressed_point));
+    for public_key in public_keys.into_iter() {
+        let uncompressed: [u8; 65] = public_key.to_uncompressed_bytes();
+        e.push_str(&hex::encode(uncompressed));
     }
 
-    sha256::Hash::hash(e.as_bytes()).to_byte_array().to_vec()
+    Sha256Hash::hash(e.as_bytes()).to_byte_array()
 }
 
-#[cfg(feature = "wallet")]
-mod wallet {
-    use std::ops::Mul;
-
-    use k256::{ProjectivePoint, Scalar, SecretKey};
-
-    use super::hash_to_curve;
-    use crate::error;
-    use crate::nuts::{BlindSignature, Keys, Proof, Proofs, PublicKey, *};
-    use crate::secret::Secret;
-
-    /// Blind Message Alice Step one
-    pub fn blind_message(
-        secret: &[u8],
-        blinding_factor: Option<SecretKey>,
-    ) -> Result<(PublicKey, SecretKey), error::wallet::Error> {
-        let y = hash_to_curve(secret)?;
-
-        let r: SecretKey = match blinding_factor {
-            Some(sec_key) => sec_key,
-            None => SecretKey::random(&mut rand::thread_rng()),
-        };
-
-        let b = ProjectivePoint::from(y) + ProjectivePoint::from(&r.public_key());
-
-        Ok((k256::PublicKey::try_from(b)?.into(), r))
-    }
-
-    /// Unblind Message (Alice Step 3)
-    pub fn unblind_message(
-        // C_
-        blinded_key: PublicKey,
-        r: SecretKey,
-        // A
-        mint_pubkey: PublicKey,
-    ) -> Result<PublicKey, error::wallet::Error> {
-        // C
-        // Unblinded message
-        let c = ProjectivePoint::from(Into::<k256::PublicKey>::into(blinded_key).as_affine())
-            - Into::<k256::PublicKey>::into(mint_pubkey)
-                .as_affine()
-                .mul(Scalar::from(r.as_scalar_primitive()));
+/// Blind Message
+///
+/// `B_ = Y + rG`
+pub fn blind_message(
+    secret: &[u8],
+    blinding_factor: Option<SecretKey>,
+) -> Result<(PublicKey, SecretKey), error::wallet::Error> {
+    let y: PublicKey = hash_to_curve(secret)?;
+    let r: SecretKey = blinding_factor.unwrap_or_else(SecretKey::generate);
+    Ok((y.combine(&r.public_key())?.into(), r))
+}
 
-        Ok(k256::PublicKey::try_from(c)?.into())
-    }
+/// Unblind Message
+///
+/// `C_ - rK`
+pub fn unblind_message(
+    // C_
+    blinded_key: &PublicKey,
+    r: &SecretKey,
+    // K
+    mint_pubkey: &PublicKey,
+) -> Result<PublicKey, error::wallet::Error> {
+    let r: Scalar = Scalar::from(r.deref().to_owned());
+
+    // a = r * K
+    let a: PublicKey = mint_pubkey.mul_tweak(&SECP256K1, &r)?.into();
+
+    // C_ - a
+    let a: PublicKey = a.negate(&SECP256K1).into();
+    Ok(blinded_key.combine(&a)?.into()) // C_ + (-a)
+}
 
-    /// Construct Proof
-    pub fn construct_proofs(
-        promises: Vec<BlindSignature>,
-        rs: Vec<nut01::SecretKey>,
-        secrets: Vec<Secret>,
-        keys: &Keys,
-    ) -> Result<Proofs, error::wallet::Error> {
-        let mut proofs = vec![];
-        for ((blinded_signature, r), secret) in promises.into_iter().zip(rs).zip(secrets) {
-            let blinded_c = blinded_signature.c;
-            let a: PublicKey = keys
-                .amount_key(blinded_signature.amount)
+/// Construct Proof
+pub fn construct_proofs(
+    promises: Vec<BlindSignature>,
+    rs: Vec<SecretKey>,
+    secrets: Vec<Secret>,
+    keys: &Keys,
+) -> Result<Proofs, error::wallet::Error> {
+    let mut proofs = vec![];
+    for ((blinded_signature, r), secret) in promises.into_iter().zip(rs).zip(secrets) {
+        let blinded_c: PublicKey = blinded_signature.c;
+        let a: PublicKey =
+            keys.amount_key(blinded_signature.amount)
                 .ok_or(error::wallet::Error::CustomError(
                     "Could not get proofs".to_string(),
-                ))?
-                .to_owned();
-
-            let unblinded_signature = unblind_message(blinded_c, r.clone().into(), a)?;
-
-            let proof;
-
-            #[cfg(not(feature = "nut12"))]
-            {
-                proof = Proof {
-                    amount: blinded_signature.amount,
-                    keyset_id: blinded_signature.keyset_id,
-                    secret,
-                    c: unblinded_signature,
-                    #[cfg(feature = "nut11")]
-                    witness: None,
-                };
-            }
+                ))?;
 
-            #[cfg(feature = "nut12")]
-            {
-                let dleq = if let Some(dleq) = blinded_signature.dleq {
-                    Some(ProofDleq {
-                        e: dleq.e,
-                        s: dleq.s,
-                        r,
-                    })
-                } else {
-                    None
-                };
-
-                proof = Proof {
-                    amount: blinded_signature.amount,
-                    keyset_id: blinded_signature.keyset_id,
-                    secret,
-                    c: unblinded_signature,
-                    #[cfg(feature = "nut11")]
-                    witness: None,
-                    dleq,
-                };
-            }
+        let unblinded_signature: PublicKey = unblind_message(&blinded_c, &r, &a)?;
+
+        let proof;
 
-            proofs.push(proof);
+        #[cfg(not(feature = "nut12"))]
+        {
+            proof = Proof {
+                amount: blinded_signature.amount,
+                keyset_id: blinded_signature.keyset_id,
+                secret,
+                c: unblinded_signature,
+                #[cfg(feature = "nut11")]
+                witness: None,
+            };
         }
 
-        Ok(proofs)
-    }
-}
+        #[cfg(feature = "nut12")]
+        {
+            let dleq = if let Some(dleq) = blinded_signature.dleq {
+                Some(ProofDleq {
+                    e: dleq.e,
+                    s: dleq.s,
+                    r,
+                })
+            } else {
+                None
+            };
+
+            proof = Proof {
+                amount: blinded_signature.amount,
+                keyset_id: blinded_signature.keyset_id,
+                secret,
+                c: unblinded_signature,
+                #[cfg(feature = "nut11")]
+                witness: None,
+                dleq,
+            };
+        }
 
-#[cfg(feature = "mint")]
-mod mint {
-    use std::ops::Mul;
-
-    use k256::{Scalar, SecretKey};
-    use log::warn;
-
-    use super::hash_to_curve;
-    use crate::error;
-
-    /// Sign Blinded Message (Step2 bob)
-    pub fn sign_message(
-        a: SecretKey,
-        blinded_message: k256::PublicKey,
-    ) -> Result<k256::PublicKey, error::mint::Error> {
-        Ok(k256::PublicKey::try_from(
-            blinded_message
-                .as_affine()
-                .mul(Scalar::from(a.as_scalar_primitive())),
-        )?)
+        proofs.push(proof);
     }
 
-    /// Verify Message
-    pub fn verify_message(
-        a: SecretKey,
-        unblinded_message: k256::PublicKey,
-        msg: &[u8],
-    ) -> Result<(), error::mint::Error> {
-        // Y
-        let y = hash_to_curve(msg)?;
-
-        if unblinded_message
-            == k256::PublicKey::try_from(*y.as_affine() * Scalar::from(a.as_scalar_primitive()))?
-        {
-            return Ok(());
-        }
+    Ok(proofs)
+}
 
-        warn!("Message not verifed");
+/// Sign Blinded Message
+///
+/// `C_ = k * B_`, where:
+/// * `k` is the private key of mint (one for each amount)
+/// * `B_` is the blinded message
+#[inline]
+pub fn sign_message(
+    k: &SecretKey,
+    blinded_message: &PublicKey,
+) -> Result<PublicKey, error::mint::Error> {
+    let k: Scalar = Scalar::from(k.deref().to_owned());
+    Ok(blinded_message.mul_tweak(&SECP256K1, &k)?.into())
+}
 
-        Err(error::mint::Error::TokenNotVerifed)
+/// Verify Message
+pub fn verify_message(
+    a: &SecretKey,
+    unblinded_message: PublicKey,
+    msg: &[u8],
+) -> Result<(), error::mint::Error> {
+    // Y
+    let y: PublicKey = hash_to_curve(msg)?;
+
+    // Compute the expected unblinded message
+    let expected_unblinded_message: PublicKey = y.combine(&a.public_key())?.into();
+
+    // Compare the unblinded_message with the expected value
+    if unblinded_message == expected_unblinded_message {
+        return Ok(());
     }
+
+    Err(error::mint::Error::TokenNotVerifed)
 }
 
 #[cfg(test)]
 mod tests {
-    use std::str::FromStr;
-
-    use hex::decode;
-    use k256::elliptic_curve::scalar::ScalarPrimitive;
+    use core::str::FromStr;
 
     use super::*;
-    use crate::nuts::PublicKey;
 
     #[test]
     fn test_hash_to_curve() {
         let secret = "0000000000000000000000000000000000000000000000000000000000000000";
-        let sec_hex = decode(secret).unwrap();
+        let sec_hex = hex::decode(secret).unwrap();
 
         let y = hash_to_curve(&sec_hex).unwrap();
-        let expected_y = k256::PublicKey::from_sec1_bytes(
-            &hex::decode("024cce997d3b518f739663b757deaec95bcd9473c30a14ac2fd04023a739d1a725")
-                .unwrap(),
+        let expected_y = PublicKey::from_hex(
+            "024cce997d3b518f739663b757deaec95bcd9473c30a14ac2fd04023a739d1a725",
         )
         .unwrap();
-        println!("{}", hex::encode(y.to_sec1_bytes()));
         assert_eq!(y, expected_y);
 
         let secret = "0000000000000000000000000000000000000000000000000000000000000001";
-        let sec_hex = decode(secret).unwrap();
+        let sec_hex = hex::decode(secret).unwrap();
         let y = hash_to_curve(&sec_hex).unwrap();
-        let expected_y = k256::PublicKey::from_sec1_bytes(
-            &hex::decode("022e7158e11c9506f1aa4248bf531298daa7febd6194f003edcd9b93ade6253acf")
-                .unwrap(),
+        let expected_y = PublicKey::from_hex(
+            "022e7158e11c9506f1aa4248bf531298daa7febd6194f003edcd9b93ade6253acf",
         )
         .unwrap();
-        println!("{}", hex::encode(y.to_sec1_bytes()));
         assert_eq!(y, expected_y);
         // Note that this message will take a few iterations of the loop before finding
         // a valid point
         let secret = "0000000000000000000000000000000000000000000000000000000000000002";
-        let sec_hex = decode(secret).unwrap();
+        let sec_hex = hex::decode(secret).unwrap();
         let y = hash_to_curve(&sec_hex).unwrap();
-        let expected_y = k256::PublicKey::from_sec1_bytes(
-            &hex::decode("026cdbe15362df59cd1dd3c9c11de8aedac2106eca69236ecd9fbe117af897be4f")
-                .unwrap(),
+        let expected_y = PublicKey::from_hex(
+            "026cdbe15362df59cd1dd3c9c11de8aedac2106eca69236ecd9fbe117af897be4f",
         )
         .unwrap();
-        println!("{}", hex::encode(y.to_sec1_bytes()));
         assert_eq!(y, expected_y);
     }
 
@@ -283,178 +253,138 @@ mod tests {
         )
     }
 
-    #[cfg(feature = "wallet")]
-    mod wallet_tests {
+    #[test]
+    fn test_blind_message() {
+        let message =
+            hex::decode("d341ee4871f1f889041e63cf0d3823c713eea6aff01e80f1719f08f9e5be98f6")
+                .unwrap();
+        let sec: SecretKey =
+            SecretKey::from_hex("99fce58439fc37412ab3468b73db0569322588f62fb3a49182d67e23d877824a")
+                .unwrap();
 
-        use k256::SecretKey;
+        let (b, r) = blind_message(&message, Some(sec.clone())).unwrap();
 
-        use super::*;
-        use crate::nuts::PublicKey;
-
-        #[test]
-        fn test_blind_message() {
-            let message = "d341ee4871f1f889041e63cf0d3823c713eea6aff01e80f1719f08f9e5be98f6";
-            let sec: crate::nuts::SecretKey = crate::nuts::SecretKey::from_hex(
-                "99fce58439fc37412ab3468b73db0569322588f62fb3a49182d67e23d877824a",
-            )
-            .unwrap();
-
-            let (b, r) =
-                blind_message(&hex::decode(message).unwrap(), Some(sec.clone().into())).unwrap();
-
-            assert_eq!(sec, r.into());
-
-            assert_eq!(
-                b.to_string(),
-                PublicKey::from(
-                    k256::PublicKey::from_sec1_bytes(
-                        &hex::decode(
-                            "033b1a9737a40cc3fd9b6af4b723632b76a67a36782596304612a6c2bfb5197e6d"
-                        )
-                        .unwrap()
-                    )
-                    .unwrap()
-                )
-                .to_string()
-            );
-
-            let message = "f1aaf16c2239746f369572c0784d9dd3d032d952c2d992175873fb58fae31a60";
-            let sec: crate::nuts::SecretKey = crate::nuts::SecretKey::from_hex(
-                "f78476ea7cc9ade20f9e05e58a804cf19533f03ea805ece5fee88c8e2874ba50",
+        assert_eq!(sec, r);
+        assert_eq!(
+            b,
+            PublicKey::from_hex(
+                "033b1a9737a40cc3fd9b6af4b723632b76a67a36782596304612a6c2bfb5197e6d"
             )
-            .unwrap();
-
-            let (b, r) =
-                blind_message(&hex::decode(message).unwrap(), Some(sec.clone().into())).unwrap();
-
-            assert_eq!(sec, r.into());
-
-            assert_eq!(
-                b.to_string(),
-                PublicKey::from(
-                    k256::PublicKey::from_sec1_bytes(
-                        &hex::decode(
-                            "029bdf2d716ee366eddf599ba252786c1033f47e230248a4612a5670ab931f1763"
-                        )
-                        .unwrap()
-                    )
-                    .unwrap()
-                )
-                .to_string()
-            );
-        }
+            .unwrap()
+        );
 
-        #[test]
-        fn test_unblind_message() {
-            let blinded_key = k256::PublicKey::from_sec1_bytes(
-                &hex::decode("02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2")
-                    .unwrap(),
+        let message =
+            hex::decode("f1aaf16c2239746f369572c0784d9dd3d032d952c2d992175873fb58fae31a60")
+                .unwrap();
+        let sec: SecretKey =
+            SecretKey::from_hex("f78476ea7cc9ade20f9e05e58a804cf19533f03ea805ece5fee88c8e2874ba50")
+                .unwrap();
+
+        let (b, r) = blind_message(&message, Some(sec.clone())).unwrap();
+
+        assert_eq!(sec, r);
+        assert_eq!(
+            b,
+            PublicKey::from_hex(
+                "029bdf2d716ee366eddf599ba252786c1033f47e230248a4612a5670ab931f1763"
             )
-            .unwrap();
+            .unwrap()
+        );
+    }
+
+    #[test]
+    fn test_unblind_message() {
+        let blinded_key = PublicKey::from_hex(
+            "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
+        )
+        .unwrap();
+
+        let r =
+            SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
+                .unwrap();
+        let a = PublicKey::from_hex(
+            "020000000000000000000000000000000000000000000000000000000000000001",
+        )
+        .unwrap();
 
-            let r = SecretKey::new(ScalarPrimitive::ONE);
-            let a = k256::PublicKey::from_sec1_bytes(
-                &hex::decode("020000000000000000000000000000000000000000000000000000000000000001")
-                    .unwrap(),
+        let unblinded = unblind_message(&blinded_key, &r, &a).unwrap();
+
+        assert_eq!(
+            PublicKey::from_hex(
+                "03c724d7e6a5443b39ac8acf11f40420adc4f99a02e7cc1b57703d9391f6d129cd"
             )
-            .unwrap();
-
-            let unblinded = unblind_message(blinded_key.into(), r, a.into()).unwrap();
-
-            assert_eq!(
-                Into::<PublicKey>::into(
-                    k256::PublicKey::from_sec1_bytes(
-                        &hex::decode(
-                            "03c724d7e6a5443b39ac8acf11f40420adc4f99a02e7cc1b57703d9391f6d129cd"
-                        )
-                        .unwrap()
-                    )
-                    .unwrap()
-                ),
-                unblinded
-            );
-        }
+            .unwrap(),
+            unblinded
+        );
     }
 
-    #[cfg(feature = "mint")]
-    mod mint_test {
-
-        use k256::SecretKey;
-
-        use super::{hash_to_curve, *};
-        use crate::secret::Secret;
-
-        #[test]
-        fn test_sign_message() {
-            use super::*;
-            let message = "test_message";
-            let sec = SecretKey::new(ScalarPrimitive::ONE);
-
-            let (blinded_message, _r) = blind_message(message.as_bytes(), Some(sec)).unwrap();
-            // A
-            let bob_sec = SecretKey::new(ScalarPrimitive::ONE);
-
-            // C_
-            let signed = sign_message(bob_sec, blinded_message.clone().into()).unwrap();
-
-            assert_eq!(
-                signed,
-                k256::PublicKey::from_sec1_bytes(
-                    &hex::decode(
-                        "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b"
-                    )
-                    .unwrap()
-                )
-                .unwrap()
-            );
-
-            // A
-            let bob_sec = crate::nuts::SecretKey::from_hex(
-                "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
+    #[test]
+    fn test_sign_message() {
+        use super::*;
+        let message = "test_message";
+        let sec =
+            SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
+                .unwrap();
+
+        let (blinded_message, _r) = blind_message(message.as_bytes(), Some(sec)).unwrap();
+        // A
+        let bob_sec =
+            SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
+                .unwrap();
+
+        // C_
+        let signed = sign_message(&bob_sec, &blinded_message).unwrap();
+
+        assert_eq!(
+            signed,
+            PublicKey::from_hex(
+                "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b"
             )
-            .unwrap();
-
-            // C_
-            let signed = sign_message(bob_sec.into(), blinded_message.into()).unwrap();
-
-            assert_eq!(
-                signed,
-                k256::PublicKey::from_sec1_bytes(
-                    &hex::decode(
-                        "027726f0e5757b4202a27198369a3477a17bc275b7529da518fc7cb4a1d927cc0d"
-                    )
-                    .unwrap()
-                )
-                .unwrap()
-            );
-        }
+            .unwrap()
+        );
 
-        #[ignore]
-        #[test]
-        fn test_blinded_dhke() {
-            // a
-            let bob_sec = SecretKey::random(&mut rand::thread_rng());
+        // A
+        let bob_sec =
+            SecretKey::from_hex("7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f")
+                .unwrap();
 
-            // A
-            let bob_pub = bob_sec.public_key();
+        // C_
+        let signed = sign_message(&bob_sec, &blinded_message).unwrap();
 
-            // let alice_sec = SecretKey::random(&mut rand::thread_rng());
+        assert_eq!(
+            signed,
+            PublicKey::from_hex(
+                "027726f0e5757b4202a27198369a3477a17bc275b7529da518fc7cb4a1d927cc0d"
+            )
+            .unwrap()
+        );
+    }
 
-            let x = Secret::new();
+    #[ignore]
+    #[test]
+    fn test_blinded_dhke() {
+        // a
+        let bob_sec = SecretKey::generate();
 
-            // Y
-            let y = hash_to_curve(&x.to_bytes()).unwrap();
+        // A
+        let bob_pub = bob_sec.public_key();
 
-            // B_
-            let blinded = blind_message(&y.to_sec1_bytes(), None).unwrap();
+        // let alice_sec = SecretKey::random(&mut rand::thread_rng());
 
-            // C_
-            let signed = sign_message(bob_sec.clone(), blinded.0.into()).unwrap();
+        let x = Secret::generate();
 
-            // C
-            let c = unblind_message(signed.into(), blinded.1, bob_pub.into()).unwrap();
+        // Y
+        let y = hash_to_curve(&x.to_bytes()).unwrap();
 
-            assert!(verify_message(bob_sec, c.into(), &x.to_bytes()).is_ok());
-        }
+        // B_
+        let blinded = blind_message(&y.to_bytes(), None).unwrap();
+
+        // C_
+        let signed = sign_message(&bob_sec, &blinded.0).unwrap();
+
+        // C
+        let c = unblind_message(&signed, &blinded.1, &bob_pub).unwrap();
+
+        assert!(verify_message(&bob_sec, c, &x.to_bytes()).is_ok());
     }
 }

+ 22 - 11
crates/cashu/src/error.rs

@@ -3,6 +3,8 @@ use std::string::FromUtf8Error;
 use serde::{Deserialize, Serialize};
 use thiserror::Error;
 
+use crate::util::hex;
+
 #[derive(Debug, Error)]
 pub enum Error {
     /// Parse Url Error
@@ -19,11 +21,10 @@ pub enum Error {
     Base64Error(#[from] base64::DecodeError),
     /// From hex error
     #[error("`{0}`")]
-    HexError(#[from] hex::FromHexError),
-    #[error("`{0}`")]
-    EllipticCurve(#[from] k256::elliptic_curve::Error),
+    HexError(#[from] hex::Error),
+    /// Secp256k1 error
     #[error("`{0}`")]
-    ECDSA(#[from] k256::ecdsa::Error),
+    Secp256k1(#[from] bitcoin::secp256k1::Error),
     #[error("No Key for Amoun")]
     AmountKey,
     #[error("Amount miss match")]
@@ -55,10 +56,12 @@ pub enum Error {
     #[error("`{0}`")]
     Secret(#[from] super::secret::Error),
     #[error("`{0}`")]
+    NUT01(#[from] crate::nuts::nut01::Error),
+    #[error("`{0}`")]
     NUT02(#[from] crate::nuts::nut02::Error),
     #[cfg(feature = "nut13")]
     #[error("`{0}`")]
-    Bip32(#[from] bip32::Error),
+    Bip32(#[from] bitcoin::bip32::Error),
     #[error("`{0}`")]
     ParseInt(#[from] std::num::ParseIntError),
     /// Custom error
@@ -87,20 +90,24 @@ impl ErrorResponse {
     }
 }
 
-#[cfg(feature = "wallet")]
 pub mod wallet {
     use std::string::FromUtf8Error;
 
     use thiserror::Error;
 
+    use crate::nuts::nut01;
+
     #[derive(Debug, Error)]
     pub enum Error {
         /// Serde Json error
         #[error("`{0}`")]
         SerdeJsonError(#[from] serde_json::Error),
-        /// From elliptic curve
+        /// Secp256k1 error
         #[error("`{0}`")]
-        EllipticError(#[from] k256::elliptic_curve::Error),
+        Secp256k1(#[from] bitcoin::secp256k1::Error),
+        /// NUT01 error
+        #[error("`{0}`")]
+        NUT01(#[from] nut01::Error),
         /// Insufficient Funds
         #[error("Insufficient funds")]
         InsufficientFunds,
@@ -135,10 +142,11 @@ pub mod wallet {
     }
 }
 
-#[cfg(feature = "mint")]
 pub mod mint {
     use thiserror::Error;
 
+    use crate::nuts::nut01;
+
     #[derive(Debug, Error)]
     pub enum Error {
         #[error("No key for amount")]
@@ -147,9 +155,12 @@ pub mod mint {
         Amount,
         #[error("Token Already Spent")]
         TokenSpent,
-        /// From elliptic curve
+        /// Secp256k1 error
+        #[error("`{0}`")]
+        Secp256k1(#[from] bitcoin::secp256k1::Error),
+        /// NUT01 error
         #[error("`{0}`")]
-        EllipticError(#[from] k256::elliptic_curve::Error),
+        NUT01(#[from] nut01::Error),
         #[error("`Token not verified`")]
         TokenNotVerifed,
         #[error("Invoice amount undefined")]

+ 10 - 5
crates/cashu/src/lib.rs

@@ -1,3 +1,9 @@
+extern crate core;
+
+pub use bitcoin::hashes::sha256::Hash as Sha256;
+pub use bitcoin::secp256k1;
+pub use lightning_invoice::{self, Bolt11Invoice};
+
 pub mod amount;
 #[cfg(any(feature = "wallet", feature = "mint"))]
 pub mod dhke;
@@ -7,10 +13,9 @@ pub mod secret;
 pub mod serde_utils;
 pub mod types;
 pub mod url;
-pub mod utils;
+pub mod util;
+
+pub use self::amount::Amount;
+pub use self::util::SECP256K1;
 
-pub use amount::Amount;
-pub use bitcoin::hashes::sha256::Hash as Sha256;
-pub use lightning_invoice::Bolt11Invoice;
-pub use {k256, lightning_invoice};
 pub type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;

+ 10 - 6
crates/cashu/src/nuts/nut00.rs

@@ -168,7 +168,7 @@ pub mod wallet {
             let mut output = Vec::with_capacity(amount_split.len());
 
             for amount in amount_split {
-                let secret = Secret::new();
+                let secret = Secret::generate();
                 let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
 
                 let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
@@ -176,7 +176,7 @@ pub mod wallet {
                 output.push(PreMint {
                     secret,
                     blinded_message,
-                    r: r.into(),
+                    r,
                     amount,
                 });
             }
@@ -199,7 +199,7 @@ pub mod wallet {
                 output.push(PreMint {
                     secret,
                     blinded_message,
-                    r: r.into(),
+                    r,
                     amount,
                 });
             }
@@ -214,7 +214,7 @@ pub mod wallet {
             let mut output = Vec::with_capacity(count as usize);
 
             for _i in 0..count {
-                let secret = Secret::new();
+                let secret = Secret::generate();
                 let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
 
                 let blinded_message = BlindedMessage::new(Amount::ZERO, keyset_id, blinded);
@@ -222,7 +222,7 @@ pub mod wallet {
                 output.push(PreMint {
                     secret,
                     blinded_message,
-                    r: r.into(),
+                    r,
                     amount: Amount::ZERO,
                 })
             }
@@ -249,7 +249,7 @@ pub mod wallet {
                 output.push(PreMint {
                     secret,
                     blinded_message,
-                    r: r.into(),
+                    r,
                     amount,
                 });
             }
@@ -490,6 +490,7 @@ impl PartialOrd for Proof {
 mod tests {
     use std::str::FromStr;
 
+    #[cfg(feature = "wallet")]
     use super::wallet::*;
     use super::*;
 
@@ -507,6 +508,7 @@ mod tests {
     }
 
     #[test]
+    #[cfg(feature = "wallet")]
     fn test_token_str_round_trip() {
         let token_str = "cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9";
 
@@ -530,6 +532,7 @@ mod tests {
     }
 
     #[test]
+    #[cfg(feature = "wallet")]
     fn test_blank_blinded_messages() {
         // TODO: Need to update id to new type in proof
         let b = PreMintSecrets::blank(
@@ -546,6 +549,7 @@ mod tests {
     }
 
     #[test]
+    #[cfg(feature = "wallet")]
     fn incorrect_tokens() {
         let incorrect_prefix = "casshuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9";
 

File diff suppressed because it is too large
+ 43 - 163
crates/cashu/src/nuts/nut01/mod.rs


+ 146 - 0
crates/cashu/src/nuts/nut01/public_key.rs

@@ -0,0 +1,146 @@
+use core::fmt;
+use core::ops::Deref;
+use core::str::FromStr;
+
+use bitcoin::secp256k1;
+use serde::{Deserialize, Deserializer, Serialize};
+
+use super::Error;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct PublicKey {
+    inner: secp256k1::PublicKey,
+}
+
+impl Deref for PublicKey {
+    type Target = secp256k1::PublicKey;
+
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+impl From<secp256k1::PublicKey> for PublicKey {
+    fn from(inner: secp256k1::PublicKey) -> Self {
+        Self { inner }
+    }
+}
+
+impl PublicKey {
+    /// Parse from `bytes`
+    #[inline]
+    pub fn from_slice(slice: &[u8]) -> Result<Self, Error> {
+        Ok(Self {
+            inner: secp256k1::PublicKey::from_slice(slice)?,
+        })
+    }
+
+    /// Parse from `hex` string
+    #[inline]
+    pub fn from_hex<S>(hex: S) -> Result<Self, Error>
+    where
+        S: AsRef<str>,
+    {
+        let hex: &str = hex.as_ref();
+
+        // Check size
+        if hex.len() != 33 * 2 {
+            return Err(Error::InvalidPublicKeySize {
+                expected: 33,
+                found: hex.len() / 2,
+            });
+        }
+
+        Ok(Self {
+            inner: secp256k1::PublicKey::from_str(hex)?,
+        })
+    }
+
+    #[inline]
+    pub fn to_bytes(&self) -> [u8; 33] {
+        self.inner.serialize()
+    }
+
+    #[inline]
+    pub fn to_uncompressed_bytes(&self) -> [u8; 65] {
+        self.inner.serialize_uncompressed()
+    }
+
+    /// Get public key as `hex` string
+    #[inline]
+    pub fn to_hex(&self) -> String {
+        self.inner.to_string()
+    }
+}
+
+impl FromStr for PublicKey {
+    type Err = Error;
+
+    fn from_str(hex: &str) -> Result<Self, Self::Err> {
+        Self::from_hex(hex)
+    }
+}
+
+impl fmt::Display for PublicKey {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", self.to_hex())
+    }
+}
+
+impl Serialize for PublicKey {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        serializer.serialize_str(&self.to_hex())
+    }
+}
+
+impl<'de> Deserialize<'de> for PublicKey {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let public_key: String = String::deserialize(deserializer)?;
+        Self::from_hex(public_key).map_err(serde::de::Error::custom)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    pub fn test_public_key_from_hex() {
+        // Compressed
+        assert!(
+            (PublicKey::from_hex(
+                "02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104"
+            )
+            .is_ok())
+        );
+    }
+
+    #[test]
+    pub fn test_invalid_public_key_from_hex() {
+        // Uncompressed (is valid but is cashu must be compressed?)
+        assert!((PublicKey::from_hex("04fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de3625246cb2c27dac965cb7200a5986467eee92eb7d496bbf1453b074e223e481")
+            .is_err()))
+    }
+}
+
+#[cfg(bench)]
+mod benches {
+    use test::{black_box, Bencher};
+
+    use super::*;
+
+    const HEX: &str = "02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104";
+
+    #[bench]
+    pub fn public_key_from_hex(bh: &mut Bencher) {
+        bh.iter(|| {
+            black_box(PublicKey::from_hex(HEX)).unwrap();
+        });
+    }
+}

+ 126 - 0
crates/cashu/src/nuts/nut01/secret_key.rs

@@ -0,0 +1,126 @@
+use core::fmt;
+use core::ops::Deref;
+use core::str::FromStr;
+
+use bitcoin::secp256k1;
+use bitcoin::secp256k1::rand::rngs::OsRng;
+use bitcoin::secp256k1::Scalar;
+use serde::{Deserialize, Deserializer, Serialize};
+
+use super::{Error, PublicKey};
+use crate::SECP256K1;
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct SecretKey {
+    inner: secp256k1::SecretKey,
+}
+
+impl Deref for SecretKey {
+    type Target = secp256k1::SecretKey;
+
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+impl From<secp256k1::SecretKey> for SecretKey {
+    fn from(inner: secp256k1::SecretKey) -> Self {
+        Self { inner }
+    }
+}
+
+impl fmt::Display for SecretKey {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", self.to_secret_hex())
+    }
+}
+
+impl SecretKey {
+    /// Parse from `bytes`
+    pub fn from_slice(slice: &[u8]) -> Result<Self, Error> {
+        Ok(Self {
+            inner: secp256k1::SecretKey::from_slice(slice)?,
+        })
+    }
+
+    /// Parse from `hex` string
+    pub fn from_hex<S>(hex: S) -> Result<Self, Error>
+    where
+        S: AsRef<str>,
+    {
+        Ok(Self {
+            inner: secp256k1::SecretKey::from_str(hex.as_ref())?,
+        })
+    }
+
+    /// Generate random secret key
+    pub fn generate() -> Self {
+        let (secret_key, _) = SECP256K1.generate_keypair(&mut OsRng);
+        Self { inner: secret_key }
+    }
+
+    /// Get secret key as `hex` string
+    pub fn to_secret_hex(&self) -> String {
+        self.inner.display_secret().to_string()
+    }
+
+    /// Get secret key as `bytes`
+    pub fn as_secret_bytes(&self) -> &[u8] {
+        self.inner.as_ref()
+    }
+
+    /// Get secret key as `bytes`
+    pub fn to_secret_bytes(&self) -> [u8; 32] {
+        self.inner.secret_bytes()
+    }
+
+    /// Get public key
+    pub fn public_key(&self) -> PublicKey {
+        self.inner.public_key(&SECP256K1).into()
+    }
+
+    #[inline]
+    pub fn to_scalar(self) -> Scalar {
+        Scalar::from(self.inner)
+    }
+
+    #[inline]
+    pub fn as_scalar(&self) -> Scalar {
+        Scalar::from(self.inner)
+    }
+}
+
+impl FromStr for SecretKey {
+    type Err = Error;
+
+    /// Try to parse [SecretKey] from `hex` or `bech32`
+    fn from_str(secret_key: &str) -> Result<Self, Self::Err> {
+        Self::from_hex(secret_key)
+    }
+}
+
+impl Serialize for SecretKey {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        serializer.serialize_str(&self.to_secret_hex())
+    }
+}
+
+impl<'de> Deserialize<'de> for SecretKey {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let secret_key: String = String::deserialize(deserializer)?;
+        Self::from_hex(secret_key).map_err(serde::de::Error::custom)
+    }
+}
+
+impl Drop for SecretKey {
+    fn drop(&mut self) {
+        self.inner.non_secure_erase();
+        tracing::trace!("Secret Key dropped.");
+    }
+}

+ 20 - 17
crates/cashu/src/nuts/nut02.rs

@@ -1,22 +1,23 @@
-//! Keysets and keyset ID
-// https://github.com/cashubtc/nuts/blob/main/02.md
+//! NUT-02: Keysets and keyset ID
+//!
+//! <https://github.com/cashubtc/nuts/blob/main/02.md>
 
 use core::fmt;
-use std::str::FromStr;
+use core::str::FromStr;
 
 use bitcoin::hashes::{sha256, Hash};
-use itertools::Itertools;
 use serde::{Deserialize, Serialize};
 use serde_with::{serde_as, VecSkipError};
 use thiserror::Error;
 
 use super::nut01::Keys;
 use super::CurrencyUnit;
+use crate::util::hex;
 
 #[derive(Debug, Error, PartialEq)]
 pub enum Error {
-    #[error("`{0}`")]
-    HexError(#[from] hex::FromHexError),
+    #[error(transparent)]
+    HexError(#[from] hex::Error),
     #[error("NUT02: ID length invalid")]
     Length,
 }
@@ -25,8 +26,9 @@ pub enum Error {
 pub enum KeySetVersion {
     Version00,
 }
-impl std::fmt::Display for KeySetVersion {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+
+impl fmt::Display for KeySetVersion {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
             KeySetVersion::Version00 => f.write_str("00"),
         }
@@ -60,8 +62,8 @@ impl TryFrom<Id> for u64 {
     }
 }
 
-impl std::fmt::Display for Id {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+impl fmt::Display for Id {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         f.write_str(&format!(
             "{}{}",
             self.version,
@@ -105,7 +107,7 @@ impl<'de> serde::de::Deserialize<'de> for Id {
         impl<'de> serde::de::Visitor<'de> for IdVisitor {
             type Value = Id;
 
-            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                 formatter.write_str("Expecting a 14 char hex string")
             }
 
@@ -141,11 +143,12 @@ impl From<&Keys> for Id {
             5 - prefix it with a keyset ID version byte
         */
 
-        let pubkeys_concat = map
+        // Note: Keys are a BTreeMap so are already sorted by amount in ascending order
+
+        let pubkeys_concat: Vec<u8> = map
             .iter()
-            .sorted_by(|(amt_a, _), (amt_b, _)| amt_a.cmp(amt_b))
             .map(|(_, pubkey)| pubkey.to_bytes())
-            .collect::<Box<[Box<[u8]>]>>()
+            .collect::<Vec<[u8; 33]>>()
             .concat();
 
         let hash = sha256::Hash::hash(&pubkeys_concat);
@@ -219,11 +222,11 @@ pub mod mint {
 
     use bitcoin::hashes::sha256::Hash as Sha256;
     use bitcoin::hashes::{Hash, HashEngine};
-    use k256::SecretKey;
     use serde::{Deserialize, Serialize};
 
     use super::Id;
     use crate::nuts::nut01::mint::{KeyPair, Keys};
+    use crate::nuts::nut01::SecretKey;
     use crate::nuts::CurrencyUnit;
     use crate::Amount;
 
@@ -262,8 +265,8 @@ pub mod mint {
                 let mut e = engine.clone();
                 e.input(i.to_string().as_bytes());
                 let hash = Sha256::from_engine(e);
-                let secret_key = SecretKey::from_slice(&hash.to_byte_array()).unwrap();
-                let keypair = KeyPair::from_secret_key(secret_key.into());
+                let secret_key = SecretKey::from_slice(&hash.to_byte_array()).unwrap(); // TODO: remove unwrap
+                let keypair = KeyPair::from_secret_key(secret_key);
                 map.insert(amount, keypair);
             }
 

+ 3 - 2
crates/cashu/src/nuts/nut03.rs

@@ -1,5 +1,6 @@
-//! Swap
-// https://github.com/cashubtc/nuts/blob/main/03.md
+//! NUT-03: Swap
+//!
+//! <https://github.com/cashubtc/nuts/blob/main/03.md>
 
 use serde::{Deserialize, Serialize};
 

+ 4 - 2
crates/cashu/src/nuts/nut04.rs

@@ -1,5 +1,7 @@
-//! Mint Tokens via Bolt11
-// https://github.com/cashubtc/nuts/blob/main/04.md
+//! NUT-04: Mint Tokens via Bolt11
+//!
+//! <https://github.com/cashubtc/nuts/blob/main/04.md>
+
 use serde::{Deserialize, Serialize};
 
 use super::{BlindSignature, BlindedMessage, CurrencyUnit, PaymentMethod};

+ 3 - 2
crates/cashu/src/nuts/nut05.rs

@@ -1,5 +1,6 @@
-//! Melting Tokens
-// https://github.com/cashubtc/nuts/blob/main/05.md
+//! NUT-05: Melting Tokens
+//!
+//! <https://github.com/cashubtc/nuts/blob/main/05.md>
 
 use serde::{Deserialize, Serialize};
 

+ 3 - 2
crates/cashu/src/nuts/nut06.rs

@@ -1,5 +1,6 @@
-//! Mint Information
-// https://github.com/cashubtc/nuts/blob/main/09.md
+//! NUT-06: Mint Information
+//!
+//! <https://github.com/cashubtc/nuts/blob/main/06.md>
 
 use serde::{Deserialize, Deserializer, Serialize, Serializer};
 

+ 3 - 2
crates/cashu/src/nuts/nut07.rs

@@ -1,5 +1,6 @@
-//! Spendable Check
-// https://github.com/cashubtc/nuts/blob/main/07.md
+//! NUT-07: Spendable Check
+//!
+//! <https://github.com/cashubtc/nuts/blob/main/07.md>
 
 use serde::{Deserialize, Serialize};
 

+ 3 - 2
crates/cashu/src/nuts/nut08.rs

@@ -1,5 +1,6 @@
-//! Lightning fee return
-// https://github.com/cashubtc/nuts/blob/main/08.md
+//! NUT-08: Lightning fee return
+//!
+//! <https://github.com/cashubtc/nuts/blob/main/08.md>
 
 use serde::{Deserialize, Serialize};
 

+ 3 - 1
crates/cashu/src/nuts/nut09.rs

@@ -1,4 +1,6 @@
-//! Nut-09: Restore signatures
+//! NUT-09: Restore signatures
+//!
+//! <https://github.com/cashubtc/nuts/blob/main/09.md>
 
 use serde::{Deserialize, Serialize};
 

+ 6 - 2
crates/cashu/src/nuts/nut10.rs

@@ -1,4 +1,8 @@
-use std::str::FromStr;
+//! NUT-10: Spending conditions
+//!
+//! <https://github.com/cashubtc/nuts/blob/main/10.md>
+
+use core::str::FromStr;
 
 use serde::ser::SerializeTuple;
 use serde::{Deserialize, Serialize, Serializer};
@@ -35,7 +39,7 @@ impl Secret {
     where
         S: Into<String>,
     {
-        let nonce = crate::secret::Secret::new().to_string();
+        let nonce = crate::secret::Secret::generate().to_string();
         let secret_data = SecretData {
             nonce,
             data: data.into(),

+ 95 - 115
crates/cashu/src/nuts/nut11.rs

@@ -1,13 +1,18 @@
-//! Pay to Public Key (P2PK)
-// https://github.com/cashubtc/nuts/blob/main/11.md
+//! NUT-11: Pay to Public Key (P2PK)
+//!
+//! <https://github.com/cashubtc/nuts/blob/main/11.md>
 
 use std::collections::HashMap;
 use std::fmt;
+use std::ops::Deref;
 use std::str::FromStr;
 
-use k256::schnorr::signature::{Signer, Verifier};
-use k256::schnorr::Signature;
-use log::debug;
+use bitcoin::hashes::sha256::Hash as Sha256Hash;
+use bitcoin::hashes::Hash;
+use bitcoin::secp256k1::schnorr::Signature;
+use bitcoin::secp256k1::{
+    KeyPair, Message, Parity, PublicKey as NormalizedPublicKey, XOnlyPublicKey,
+};
 use serde::de::Error as DeserializerError;
 use serde::ser::SerializeSeq;
 use serde::{de, ser, Deserialize, Deserializer, Serialize, Serializer};
@@ -17,7 +22,8 @@ use super::nut10::{Secret, SecretData};
 use super::{Proof, SecretKey};
 use crate::error::Error;
 use crate::nuts::nut00::BlindedMessage;
-use crate::utils::unix_time;
+use crate::util::{hex, unix_time};
+use crate::SECP256K1;
 
 #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 pub struct Signatures {
@@ -54,26 +60,25 @@ impl Proof {
         }
 
         let secret: Secret = self.secret.clone().try_into()?;
-
         let spending_conditions: P2PKConditions = secret.clone().try_into()?;
+        let msg: &[u8] = self.secret.as_bytes();
 
         let mut valid_sigs = 0;
 
-        let msg = &self.secret.to_bytes();
         if let Some(witness) = &self.witness {
-            for signature in &witness.signatures {
+            for signature in witness.signatures.iter() {
                 let mut pubkeys = spending_conditions.pubkeys.clone();
-                let data_key = VerifyingKey::from_str(&secret.secret_data.data)?;
-                pubkeys.push(data_key);
+
+                pubkeys.push(VerifyingKey::from_str(&secret.secret_data.data)?);
+
                 for v in &spending_conditions.pubkeys {
-                    let sig = Signature::try_from(hex::decode(signature)?.as_slice())?;
+                    let sig = Signature::from_str(signature)?;
 
                     if v.verify(msg, &sig).is_ok() {
                         valid_sigs += 1;
                     } else {
-                        debug!(
-                            "Could not verify signature: {} on message: {}",
-                            hex::encode(sig.to_bytes()),
+                        tracing::debug!(
+                            "Could not verify signature: {sig} on message: {}",
                             self.secret.to_string()
                         )
                     }
@@ -81,7 +86,7 @@ impl Proof {
             }
         }
 
-        if valid_sigs.ge(&spending_conditions.num_sigs.unwrap_or(1)) {
+        if valid_sigs >= spending_conditions.num_sigs.unwrap_or(1) {
             return Ok(());
         }
 
@@ -94,8 +99,8 @@ impl Proof {
                 if let Some(signatures) = &self.witness {
                     for s in &signatures.signatures {
                         for v in &refund_keys {
-                            let sig = Signature::try_from(hex::decode(s)?.as_slice())
-                                .map_err(|_| Error::InvalidSignature)?;
+                            let sig =
+                                Signature::from_str(s).map_err(|_| Error::InvalidSignature)?;
 
                             // As long as there is one valid refund signature it can be spent
                             if v.verify(msg, &sig).is_ok() {
@@ -111,15 +116,14 @@ impl Proof {
     }
 
     pub fn sign_p2pk(&mut self, secret_key: SigningKey) -> Result<(), Error> {
-        let msg_to_sign = &self.secret.to_bytes();
-
-        let signature = secret_key.sign(msg_to_sign);
+        let msg: Vec<u8> = self.secret.to_bytes();
+        let signature: Signature = secret_key.sign(&msg)?;
 
         self.witness
             .as_mut()
             .unwrap_or(&mut Signatures::default())
             .signatures
-            .push(hex::encode(signature.to_bytes()));
+            .push(signature.to_string());
 
         Ok(())
     }
@@ -127,15 +131,14 @@ impl Proof {
 
 impl BlindedMessage {
     pub fn sign_p2pk(&mut self, secret_key: SigningKey) -> Result<(), Error> {
-        let msg_to_sign = hex::decode(self.b.to_string())?;
-
-        let signature = secret_key.sign(&msg_to_sign);
+        let msg: [u8; 33] = self.b.to_bytes();
+        let signature: Signature = secret_key.sign(&msg)?;
 
         self.witness
             .as_mut()
             .unwrap_or(&mut Signatures::default())
             .signatures
-            .push(hex::encode(signature.to_bytes()));
+            .push(signature.to_string());
 
         Ok(())
     }
@@ -150,16 +153,12 @@ impl BlindedMessage {
             for signature in &witness.signatures {
                 for v in pubkeys {
                     let msg = &self.b.to_bytes();
-                    let sig = Signature::try_from(hex::decode(signature)?.as_slice())?;
+                    let sig = Signature::from_str(signature)?;
 
                     if v.verify(msg, &sig).is_ok() {
                         valid_sigs += 1;
                     } else {
-                        debug!(
-                            "Could not verify signature: {} on message: {}",
-                            hex::encode(sig.to_bytes()),
-                            self.b.to_string()
-                        )
+                        tracing::debug!("Could not verify signature: {sig} on message: {}", self.b)
                     }
                 }
             }
@@ -225,11 +224,11 @@ impl TryFrom<P2PKConditions> for Secret {
             return Err(Error::Amount);
         }
 
-        let data: PublicKey = pubkeys[0].clone().into();
+        let data: PublicKey = pubkeys[0].clone().to_normalized_public_key();
 
         let data = data.to_string();
 
-        let mut tags = vec![];
+        let mut tags = Vec::new();
 
         if pubkeys.len().gt(&1) {
             tags.push(Tag::PubKeys(pubkeys.into_iter().skip(1).collect()).as_vec());
@@ -488,8 +487,8 @@ impl From<Tag> for Vec<String> {
             Tag::PubKeys(pubkeys) => {
                 let mut tag = vec![TagKind::Pubkeys.to_string()];
 
-                for pubkey in pubkeys {
-                    let pubkey: PublicKey = pubkey.into();
+                for pubkey in pubkeys.into_iter() {
+                    let pubkey: PublicKey = pubkey.to_normalized_public_key();
                     tag.push(pubkey.to_string())
                 }
                 tag
@@ -533,19 +532,26 @@ impl<'de> Deserialize<'de> for Tag {
 
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 #[serde(transparent)]
-pub struct VerifyingKey(k256::schnorr::VerifyingKey);
+pub struct VerifyingKey(XOnlyPublicKey);
 
 impl VerifyingKey {
     pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
-        Ok(VerifyingKey(k256::schnorr::VerifyingKey::from_bytes(
-            bytes,
-        )?))
+        Ok(Self(XOnlyPublicKey::from_slice(bytes)?))
+    }
+
+    pub fn from_public_key(public_key: PublicKey) -> Self {
+        let (pk, ..) = public_key.x_only_public_key();
+        Self(pk)
+    }
+
+    pub fn to_normalized_public_key(self) -> PublicKey {
+        NormalizedPublicKey::from_x_only_public_key(self.0, Parity::Even).into()
     }
 
-    pub fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> {
-        self.0
-            .verify(msg, signature)
-            .map_err(|_| Error::InvalidSignature)?;
+    pub fn verify(&self, msg: &[u8], sig: &Signature) -> Result<(), Error> {
+        let hash: Sha256Hash = Sha256Hash::hash(msg);
+        let msg = Message::from_slice(hash.as_ref())?;
+        SECP256K1.verify_schnorr(sig, &msg, &self.0)?;
         Ok(())
     }
 }
@@ -554,42 +560,22 @@ impl FromStr for VerifyingKey {
     type Err = Error;
 
     fn from_str(hex: &str) -> Result<Self, Self::Err> {
-        let bytes = hex::decode(hex)?;
+        let bytes: Vec<u8> = hex::decode(hex)?;
 
-        let bytes = if bytes.len().eq(&33) {
-            bytes.iter().skip(1).cloned().collect()
+        // Check len
+        let bytes: &[u8] = if bytes.len() == 33 {
+            &bytes[1..]
         } else {
-            bytes.to_vec()
+            &bytes
         };
 
-        Ok(VerifyingKey(
-            k256::schnorr::VerifyingKey::from_bytes(&bytes).map_err(|_| Error::Key)?,
-        ))
-    }
-}
-
-impl std::fmt::Display for VerifyingKey {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        let bytes = self.0.to_bytes();
-        f.write_str(&hex::encode(bytes))
-    }
-}
-
-impl From<VerifyingKey> for k256::schnorr::VerifyingKey {
-    fn from(value: VerifyingKey) -> k256::schnorr::VerifyingKey {
-        value.0
-    }
-}
-
-impl From<&VerifyingKey> for k256::schnorr::VerifyingKey {
-    fn from(value: &VerifyingKey) -> k256::schnorr::VerifyingKey {
-        value.0
+        Ok(Self(XOnlyPublicKey::from_slice(bytes)?))
     }
 }
 
-impl From<k256::schnorr::VerifyingKey> for VerifyingKey {
-    fn from(value: k256::schnorr::VerifyingKey) -> VerifyingKey {
-        VerifyingKey(value)
+impl fmt::Display for VerifyingKey {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", self.0)
     }
 }
 
@@ -616,38 +602,37 @@ impl TryFrom<&PublicKey> for VerifyingKey {
 }
 
 #[derive(Clone, Serialize, Deserialize)]
-#[serde(transparent)]
-pub struct SigningKey(k256::schnorr::SigningKey);
-
-impl From<SigningKey> for k256::schnorr::SigningKey {
-    fn from(value: SigningKey) -> k256::schnorr::SigningKey {
-        value.0
-    }
+pub struct SigningKey {
+    secret_key: SecretKey,
+    key_pair: KeyPair,
 }
 
-impl From<k256::schnorr::SigningKey> for SigningKey {
-    fn from(value: k256::schnorr::SigningKey) -> Self {
-        Self(value)
-    }
-}
+impl Deref for SigningKey {
+    type Target = SecretKey;
 
-impl From<SecretKey> for SigningKey {
-    fn from(value: SecretKey) -> SigningKey {
-        value.into()
+    fn deref(&self) -> &Self::Target {
+        &self.secret_key
     }
 }
 
 impl SigningKey {
-    pub fn public_key(&self) -> VerifyingKey {
-        (*self.0.verifying_key()).into()
+    #[inline]
+    pub fn new(secret_key: SecretKey) -> Self {
+        Self {
+            key_pair: KeyPair::from_secret_key(&SECP256K1, &secret_key),
+            secret_key,
+        }
     }
-
-    pub fn sign(&self, msg: &[u8]) -> Signature {
-        self.0.sign(msg)
+    pub fn sign(&self, msg: &[u8]) -> Result<Signature, Error> {
+        let hash: Sha256Hash = Sha256Hash::hash(msg);
+        let msg = Message::from_slice(hash.as_ref())?;
+        Ok(SECP256K1.sign_schnorr(&msg, &self.key_pair))
     }
 
+    #[inline]
     pub fn verifying_key(&self) -> VerifyingKey {
-        VerifyingKey(*self.0.verifying_key())
+        let public_key: PublicKey = self.public_key();
+        VerifyingKey::from_public_key(public_key)
     }
 }
 
@@ -655,30 +640,19 @@ impl FromStr for SigningKey {
     type Err = Error;
 
     fn from_str(hex: &str) -> Result<Self, Self::Err> {
-        let bytes = hex::decode(hex)?;
-
-        let bytes = if bytes.len().eq(&33) {
-            bytes.iter().skip(1).cloned().collect()
-        } else {
-            bytes.to_vec()
-        };
-
-        Ok(SigningKey(
-            k256::schnorr::SigningKey::from_bytes(&bytes).map_err(|_| Error::Key)?,
-        ))
+        let secret_key = SecretKey::from_hex(hex)?;
+        Ok(Self::new(secret_key))
     }
 }
 
-impl std::fmt::Display for SigningKey {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        let bytes = self.0.to_bytes();
-
-        f.write_str(&hex::encode(bytes))
+impl fmt::Display for SigningKey {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", self.secret_key)
     }
 }
+
 #[cfg(test)]
 mod tests {
-
     use std::str::FromStr;
 
     use super::*;
@@ -771,10 +745,16 @@ mod tests {
     #[test]
     fn test_verify() {
         // Proof with a valid signature
-        let valid_proof = r#"{"amount":1,"secret":"[\"P2PK\",{\"nonce\":\"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\",\"data\":\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\",\"tags\":[[\"sigflag\",\"SIG_INPUTS\"]]}]","C":"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904","id":"009a1f293253e41e","witness":"{\"signatures\":[\"60f3c9b766770b46caac1d27e1ae6b77c8866ebaeba0b9489fe6a15a837eaa6fcd6eaa825499c72ac342983983fd3ba3a8a41f56677cc99ffd73da68b59e1383\"]}"}"#;
-
-        let valid_proof: Proof = serde_json::from_str(valid_proof).unwrap();
-
+        let json: &str = r#"{
+            "amount":1,
+            "secret":"[\"P2PK\",{\"nonce\":\"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\",\"data\":\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\",\"tags\":[[\"sigflag\",\"SIG_INPUTS\"]]}]",
+            "C":"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
+            "id":"009a1f293253e41e",
+            "witness":"{\"signatures\":[\"60f3c9b766770b46caac1d27e1ae6b77c8866ebaeba0b9489fe6a15a837eaa6fcd6eaa825499c72ac342983983fd3ba3a8a41f56677cc99ffd73da68b59e1383\"]}"
+        }"#;
+        let valid_proof: Proof = serde_json::from_str(json).unwrap();
+
+        valid_proof.verify_p2pk().unwrap();
         assert!(valid_proof.verify_p2pk().is_ok());
 
         // Proof with a signature that is in a different secret

+ 99 - 109
crates/cashu/src/nuts/nut12.rs

@@ -1,15 +1,16 @@
 //! NUT-12: Offline ecash signature validation
-//! https://github.com/cashubtc/nuts/blob/main/12.md
-use std::ops::Mul;
+//!
+//! <https://github.com/cashubtc/nuts/blob/main/12.md>
 
-use k256::Scalar;
-use log::{debug, warn};
+use core::ops::Deref;
+
+use bitcoin::secp256k1::{self, Scalar};
 use serde::{Deserialize, Serialize};
 use thiserror::Error;
 
 use super::{BlindSignature, Id, Proof, PublicKey, SecretKey};
 use crate::dhke::{hash_e, hash_to_curve};
-use crate::Amount;
+use crate::{Amount, SECP256K1};
 
 #[derive(Debug, Error)]
 pub enum Error {
@@ -20,9 +21,11 @@ pub enum Error {
     #[error("Invalid Dleq Prood")]
     InvalidDleqProof,
     #[error("`{0}`")]
-    EllipticCurve(#[from] k256::elliptic_curve::Error),
-    #[error("`{0}`")]
     Cashu(#[from] crate::error::Error),
+    #[error("`{0}`")]
+    NUT01(#[from] crate::nuts::nut01::Error),
+    #[error("`{0}`")]
+    Secp256k1(#[from] secp256k1::Error),
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@@ -38,37 +41,41 @@ pub struct ProofDleq {
     pub r: SecretKey,
 }
 
+/// Verify DLEQ
 fn verify_dleq(
-    blinded_message: k256::PublicKey,
-    blinded_signature: k256::PublicKey,
-    e: k256::SecretKey,
-    s: k256::SecretKey,
-    mint_pubkey: k256::PublicKey,
+    blinded_message: PublicKey,   // B'
+    blinded_signature: PublicKey, // C'
+    e: &SecretKey,
+    s: &SecretKey,
+    mint_pubkey: PublicKey, // A
 ) -> Result<(), Error> {
-    let r1 = s.public_key().to_projective()
-        - mint_pubkey
-            .as_affine()
-            .mul(Scalar::from(e.as_scalar_primitive()));
-
-    let r2 = blinded_message
-        .as_affine()
-        .mul(Scalar::from(s.as_scalar_primitive()))
-        - blinded_signature
-            .as_affine()
-            .mul(Scalar::from(e.as_scalar_primitive()));
-
-    let e_bytes = e.to_bytes().to_vec();
-
-    let hash_e = hash_e(vec![
-        k256::PublicKey::try_from(r1)?,
-        k256::PublicKey::try_from(r2)?,
-        mint_pubkey,
-        blinded_signature,
-    ]);
-
-    if e_bytes.ne(&hash_e) {
-        warn!("DLEQ on signature failed");
-        debug!("e_bytes: {:?}, Hash e: {:?}", e_bytes, hash_e);
+    let e_bytes: [u8; 32] = e.to_secret_bytes();
+    let e: Scalar = e.as_scalar();
+
+    // a = e*A
+    let a: PublicKey = mint_pubkey.mul_tweak(&SECP256K1, &e)?.into();
+
+    // R1 = s*G - a
+    let a: PublicKey = a.negate(&SECP256K1).into();
+    let r1: PublicKey = s.public_key().combine(&a)?.into(); // s*G + (-a)
+
+    // b = s*B'
+    let s: Scalar = Scalar::from(s.deref().to_owned());
+    let b: PublicKey = blinded_message.mul_tweak(&SECP256K1, &s)?.into();
+
+    // c = e*C'
+    let c: PublicKey = blinded_signature.mul_tweak(&SECP256K1, &e)?.into();
+
+    // R2 = b - c
+    let c: PublicKey = c.negate(&SECP256K1).into();
+    let r2: PublicKey = b.combine(&c)?.into();
+
+    // hash(R1,R2,A,C')
+    let hash_e: [u8; 32] = hash_e([r1, r2, mint_pubkey, blinded_signature]);
+
+    if e_bytes != hash_e {
+        tracing::warn!("DLEQ on signature failed");
+        tracing::debug!("e_bytes: {:?}, hash_e: {:?}", e_bytes, hash_e);
         return Err(Error::InvalidDleqProof);
     }
 
@@ -76,103 +83,90 @@ fn verify_dleq(
 }
 
 fn calculate_dleq(
-    blinded_signature: k256::PublicKey,
-    blinded_message: &k256::PublicKey,
-    mint_secretkey: &k256::SecretKey,
+    blinded_signature: PublicKey, // C'
+    blinded_message: &PublicKey,  // B'
+    mint_secret_key: &SecretKey,  // a
 ) -> Result<BlindSignatureDleq, Error> {
     // Random nonce
-    let r: k256::SecretKey = SecretKey::random().into();
+    let r: SecretKey = SecretKey::generate();
 
+    // R1 = r*G
     let r1 = r.public_key();
 
-    let r2: k256::PublicKey = blinded_message
-        .as_affine()
-        .mul(Scalar::from(r.as_scalar_primitive()))
-        .try_into()?;
-
-    let e = hash_e(vec![r1, r2, mint_secretkey.public_key(), blinded_signature]);
+    // R2 = r*B'
+    let r_scal: Scalar = r.as_scalar();
+    let r2: PublicKey = blinded_message.mul_tweak(&SECP256K1, &r_scal)?.into();
 
-    let e_sk = k256::SecretKey::from_slice(&e)?;
+    // e = hash(R1,R2,A,C')
+    let e: [u8; 32] = hash_e([r1, r2, mint_secret_key.public_key(), blinded_signature]);
+    let e_sk: SecretKey = SecretKey::from_slice(&e)?;
 
-    let s = Scalar::from(r.as_scalar_primitive())
-        + Scalar::from(e_sk.as_scalar_primitive())
-            * Scalar::from(mint_secretkey.as_scalar_primitive());
+    // s1 = e*a
+    let s1: SecretKey = e_sk.mul_tweak(&mint_secret_key.as_scalar())?.into();
 
-    let s: k256::SecretKey = k256::SecretKey::new(s.into());
+    // s = r + s1
+    let s: SecretKey = r.add_tweak(&s1.to_scalar())?.into();
 
-    Ok(BlindSignatureDleq {
-        e: e_sk.into(),
-        s: s.into(),
-    })
+    Ok(BlindSignatureDleq { e: e_sk, s })
 }
 
 impl Proof {
-    pub fn verify_dleq(&self, mint_pubkey: &PublicKey) -> Result<(), Error> {
-        let (e, s, blinding_factor): (k256::SecretKey, k256::SecretKey, k256::SecretKey) =
-            if let Some(dleq) = self.dleq.clone() {
-                (dleq.e.into(), dleq.s.into(), dleq.r.into())
-            } else {
-                return Err(Error::MissingDleqProof);
-            };
-
-        let c: k256::PublicKey = (&self.c).into();
-        let mint_pubkey: k256::PublicKey = mint_pubkey.into();
-
-        let y = hash_to_curve(self.secret.0.as_bytes())?;
-        let blinded_signature = c.to_projective()
-            + mint_pubkey
-                .as_affine()
-                .mul(Scalar::from(blinding_factor.as_scalar_primitive()));
-        let blinded_message = y.to_projective() + blinding_factor.public_key().to_projective();
-
-        let blinded_signature = k256::PublicKey::try_from(blinded_signature)?;
-        let blinded_message = k256::PublicKey::try_from(blinded_message)?;
-
-        verify_dleq(blinded_message, blinded_signature, e, s, mint_pubkey)
+    pub fn verify_dleq(&self, mint_pubkey: PublicKey) -> Result<(), Error> {
+        match &self.dleq {
+            Some(dleq) => {
+                let y = hash_to_curve(self.secret.as_bytes())?;
+
+                let r: Scalar = dleq.r.as_scalar();
+                let bs1: PublicKey = mint_pubkey.mul_tweak(&SECP256K1, &r)?.into();
+
+                let blinded_signature: PublicKey = self.c.combine(&bs1)?.into();
+                let blinded_message: PublicKey = y.combine(&dleq.r.public_key())?.into();
+
+                verify_dleq(
+                    blinded_message,
+                    blinded_signature,
+                    &dleq.e,
+                    &dleq.s,
+                    mint_pubkey,
+                )
+            }
+            None => Err(Error::MissingDleqProof),
+        }
     }
 }
 
 impl BlindSignature {
-    pub fn new_dleq(
+    /// New DLEQ
+    #[inline]
+    pub fn new(
         amount: Amount,
         blinded_signature: PublicKey,
         keyset_id: Id,
         blinded_message: &PublicKey,
         mint_secretkey: SecretKey,
     ) -> Result<Self, Error> {
-        let blinded_message: k256::PublicKey = blinded_message.into();
-        let mint_secretkey: k256::SecretKey = mint_secretkey.into();
-
-        let dleq = calculate_dleq(
-            blinded_signature.clone().into(),
-            &blinded_message,
-            &mint_secretkey,
-        )?;
-
-        Ok(BlindSignature {
+        Ok(Self {
             amount,
             keyset_id,
             c: blinded_signature,
-            dleq: Some(dleq),
+            dleq: Some(calculate_dleq(
+                blinded_signature,
+                blinded_message,
+                &mint_secretkey,
+            )?),
         })
     }
 
+    #[inline]
     pub fn verify_dleq(
         &self,
-        mint_pubkey: &PublicKey,
-        blinded_message: &PublicKey,
+        mint_pubkey: PublicKey,
+        blinded_message: PublicKey,
     ) -> Result<(), Error> {
-        let (e, s): (k256::SecretKey, k256::SecretKey) = if let Some(dleq) = &self.dleq {
-            (dleq.e.clone().into(), dleq.s.clone().into())
-        } else {
-            return Err(Error::MissingDleqProof);
-        };
-
-        let mint_pubkey: k256::PublicKey = mint_pubkey.into();
-        let blinded_message: k256::PublicKey = blinded_message.into();
-
-        let c: k256::PublicKey = (&self.c).into();
-        verify_dleq(blinded_message, c, e, s, mint_pubkey)
+        match &self.dleq {
+            Some(dleq) => verify_dleq(blinded_message, self.c, &dleq.e, &dleq.s, mint_pubkey),
+            None => Err(Error::MissingDleqProof),
+        }
     }
 
     /*
@@ -188,12 +182,8 @@ impl BlindSignature {
         blinded_message: &PublicKey,
         mint_secretkey: &SecretKey,
     ) -> Result<(), Error> {
-        let blinded_message: k256::PublicKey = blinded_message.into();
-        let mint_secretkey: k256::SecretKey = mint_secretkey.clone().into();
-
-        let dleq = calculate_dleq(self.c.clone().into(), &blinded_message, &mint_secretkey)?;
+        let dleq: BlindSignatureDleq = calculate_dleq(self.c, blinded_message, mint_secretkey)?;
         self.dleq = Some(dleq);
-
         Ok(())
     }
 }
@@ -222,7 +212,7 @@ mod tests {
         )
         .unwrap();
 
-        blinded.verify_dleq(&mint_key, &blinded_secret).unwrap()
+        blinded.verify_dleq(mint_key, blinded_secret).unwrap()
     }
 
     #[test]
@@ -237,6 +227,6 @@ mod tests {
         )
         .unwrap();
 
-        assert!(proof.verify_dleq(&a).is_ok());
+        assert!(proof.verify_dleq(a).is_ok());
     }
 }

+ 26 - 16
crates/cashu/src/nuts/nut13.rs

@@ -1,35 +1,45 @@
-use std::str::FromStr;
+//! NUT-13: Deterministic Secrets
+//!
+//! <https://github.com/cashubtc/nuts/blob/main/13.md>
+
+use core::str::FromStr;
 
-use bip32::{DerivationPath, XPrv};
 use bip39::Mnemonic;
-use log::debug;
+use bitcoin::bip32::{DerivationPath, ExtendedPrivKey};
+use bitcoin::Network;
 
 use super::{Id, SecretKey};
 use crate::error::Error;
 use crate::secret::Secret;
+use crate::util::hex;
+use crate::SECP256K1;
 
 impl Secret {
     pub fn from_seed(mnemonic: &Mnemonic, keyset_id: Id, counter: u64) -> Result<Self, Error> {
-        debug!(
+        tracing::debug!(
             "Deriving secret for {} with count {}",
             keyset_id.to_string(),
             counter.to_string()
         );
-        let path = DerivationPath::from_str(&format!(
+        let path: DerivationPath = DerivationPath::from_str(&format!(
             "m/129372'/0'/{}'/{}'/0",
             u64::try_from(keyset_id)?,
             counter
         ))?;
 
-        let xpriv = XPrv::derive_from_path(mnemonic.to_seed(""), &path).unwrap();
+        let seed: [u8; 64] = mnemonic.to_seed("");
+        let bip32_root_key = ExtendedPrivKey::new_master(Network::Bitcoin, &seed)?;
+        let derived_xpriv = bip32_root_key.derive_priv(&SECP256K1, &path)?;
 
-        Ok(Self(hex::encode(xpriv.private_key().to_bytes())))
+        Ok(Self::new(hex::encode(
+            derived_xpriv.private_key.secret_bytes(),
+        )))
     }
 }
 
 impl SecretKey {
     pub fn from_seed(mnemonic: &Mnemonic, keyset_id: Id, counter: u64) -> Result<Self, Error> {
-        debug!(
+        tracing::debug!(
             "Deriving key for {} with count {}",
             keyset_id.to_string(),
             counter.to_string()
@@ -40,11 +50,11 @@ impl SecretKey {
             counter
         ))?;
 
-        let signing_key = XPrv::derive_from_path(mnemonic.to_seed(""), &path)?;
-
-        let private_key = signing_key.private_key();
+        let seed: [u8; 64] = mnemonic.to_seed("");
+        let bip32_root_key = ExtendedPrivKey::new_master(Network::Bitcoin, &seed)?;
+        let derived_xpriv = bip32_root_key.derive_priv(&SECP256K1, &path)?;
 
-        Ok(Self(private_key.into()))
+        Ok(Self::from(derived_xpriv.private_key))
     }
 }
 
@@ -76,7 +86,7 @@ mod wallet {
                 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, r) = blind_message(&secret.to_bytes(), Some(blinding_factor))?;
 
                 let amount = if zero_amount { Amount::ZERO } else { amount };
 
@@ -85,7 +95,7 @@ mod wallet {
                 let pre_mint = PreMint {
                     blinded_message,
                     secret: secret.clone(),
-                    r: r.into(),
+                    r,
                     amount,
                 };
 
@@ -110,14 +120,14 @@ mod wallet {
                 let secret = Secret::from_seed(mnemonic, keyset_id, i)?;
                 let blinding_factor = SecretKey::from_seed(mnemonic, keyset_id, i)?;
 
-                let (blinded, r) = blind_message(&secret.to_bytes(), Some(blinding_factor.into()))?;
+                let (blinded, r) = blind_message(&secret.to_bytes(), Some(blinding_factor))?;
 
                 let blinded_message = BlindedMessage::new(Amount::ZERO, keyset_id, blinded);
 
                 let pre_mint = PreMint {
                     blinded_message,
                     secret: secret.clone(),
-                    r: r.into(),
+                    r,
                     amount: Amount::ZERO,
                 };
 

+ 29 - 13
crates/cashu/src/secret.rs

@@ -1,35 +1,45 @@
 //! Secret
 
+use core::fmt;
 use std::str::FromStr;
 
+use bitcoin::secp256k1::rand::{self, RngCore};
 use serde::{Deserialize, Serialize};
 use thiserror::Error;
 
+use crate::util::hex;
+
 /// The secret data that allows spending ecash
 #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
 #[serde(transparent)]
-pub struct Secret(pub String);
+pub struct Secret(String);
 
 #[derive(Debug, Error)]
 pub enum Error {
     #[error("Invalid secret length: `{0}`")]
     InvalidLength(u64),
-    #[error("Hex error: `{0}`")]
-    Hex(#[from] hex::FromHexError),
+    #[error(transparent)]
+    Hex(#[from] hex::Error),
 }
 
 impl Default for Secret {
     fn default() -> Self {
-        Self::new()
+        Self::generate()
     }
 }
 
 impl Secret {
+    #[inline]
+    pub fn new<S>(secret: S) -> Self
+    where
+        S: Into<String>,
+    {
+        Self(secret.into())
+    }
+
     /// Create secret value
     /// Generate a new random secret as the recommended 32 byte hex
-    pub fn new() -> Self {
-        use rand::RngCore;
-
+    pub fn generate() -> Self {
         let mut rng = rand::thread_rng();
 
         let mut random_bytes = [0u8; 32];
@@ -41,8 +51,14 @@ impl Secret {
         Self(secret)
     }
 
+    #[inline]
+    pub fn as_bytes(&self) -> &[u8] {
+        self.0.as_bytes()
+    }
+
+    #[inline]
     pub fn to_bytes(&self) -> Vec<u8> {
-        self.0.clone().into_bytes()
+        self.as_bytes().to_vec()
     }
 
     #[cfg(feature = "nut11")]
@@ -66,13 +82,13 @@ impl FromStr for Secret {
     type Err = Error;
 
     fn from_str(s: &str) -> Result<Self, Self::Err> {
-        Ok(Secret(s.to_string()))
+        Ok(Self(s.to_string()))
     }
 }
 
-impl ToString for Secret {
-    fn to_string(&self) -> String {
-        self.0.clone()
+impl fmt::Display for Secret {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
     }
 }
 
@@ -115,7 +131,7 @@ mod tests {
 
     #[test]
     fn test_secret_from_str() {
-        let secret = Secret::new();
+        let secret = Secret::generate();
 
         let secret_str = secret.to_string();
 

+ 2 - 88
crates/cashu/src/serde_utils.rs

@@ -1,5 +1,7 @@
 //! Utilities for serde
 
+// TODO: remove this module
+
 pub mod serde_url {
     use serde::Deserialize;
     use url::Url;
@@ -44,91 +46,3 @@ pub mod bytes_base64 {
         Ok(decoded)
     }
 }
-
-pub mod serde_public_key {
-    use k256::PublicKey;
-    use serde::Deserialize;
-
-    pub fn serialize<S>(pubkey: &PublicKey, serializer: S) -> Result<S::Ok, S::Error>
-    where
-        S: serde::Serializer,
-    {
-        let encoded = hex::encode(pubkey.to_sec1_bytes());
-        serializer.serialize_str(&encoded)
-    }
-
-    pub fn deserialize<'de, D>(deserializer: D) -> Result<PublicKey, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        let encoded = String::deserialize(deserializer)?;
-        let decoded = hex::decode(encoded).map_err(serde::de::Error::custom)?;
-        if decoded.len().ne(&33) {
-            return Err(serde::de::Error::custom(format!(
-                "Invalid key length: {}",
-                decoded.len()
-            )));
-        }
-        PublicKey::from_sec1_bytes(&decoded).map_err(serde::de::Error::custom)
-    }
-
-    pub mod opt {
-        use k256::PublicKey;
-        use serde::{Deserialize, Deserializer};
-
-        pub fn serialize<S>(pubkey: &Option<PublicKey>, serializer: S) -> Result<S::Ok, S::Error>
-        where
-            S: serde::Serializer,
-        {
-            match pubkey {
-                Some(pubkey) => {
-                    let encoded = hex::encode(pubkey.to_sec1_bytes());
-                    serializer.serialize_str(&encoded)
-                }
-                None => serializer.serialize_none(),
-            }
-        }
-
-        pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<PublicKey>, D::Error>
-        where
-            D: Deserializer<'de>,
-        {
-            let option_str: Option<String> = Option::deserialize(deserializer)?;
-
-            match option_str {
-                Some(encoded) => {
-                    let bytes = hex::decode(encoded).map_err(serde::de::Error::custom)?;
-                    let pubkey =
-                        PublicKey::from_sec1_bytes(&bytes).map_err(serde::de::Error::custom)?;
-                    Ok(Some(pubkey))
-                }
-                None => Ok(None),
-            }
-        }
-    }
-}
-
-pub mod serde_secret_key {
-    use k256::elliptic_curve::generic_array::GenericArray;
-    use k256::SecretKey;
-    use serde::Deserialize;
-
-    pub fn serialize<S>(seckey: &SecretKey, serializer: S) -> Result<S::Ok, S::Error>
-    where
-        S: serde::Serializer,
-    {
-        let encoded = hex::encode(seckey.to_bytes());
-        serializer.serialize_str(&encoded)
-    }
-
-    pub fn deserialize<'de, D>(deserializer: D) -> Result<SecretKey, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        let encoded = String::deserialize(deserializer)?;
-        Ok(k256::SecretKey::from_bytes(GenericArray::from_slice(
-            &hex::decode(encoded).map_err(serde::de::Error::custom)?,
-        ))
-        .map_err(serde::de::Error::custom))?
-    }
-}

+ 147 - 0
crates/cashu/src/util/hex.rs

@@ -0,0 +1,147 @@
+// Copyright (c) 2022-2023 Yuki Kishimoto
+// Distributed under the MIT software license
+
+//! Hex
+
+use core::fmt;
+
+/// Hex error
+#[derive(Debug, PartialEq, Eq)]
+pub enum Error {
+    /// An invalid character was found
+    InvalidHexCharacter {
+        /// Char
+        c: char,
+        /// Char index
+        index: usize,
+    },
+    /// A hex string's length needs to be even, as two digits correspond to
+    /// one byte.
+    OddLength,
+}
+
+impl std::error::Error for Error {}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::InvalidHexCharacter { c, index } => {
+                write!(f, "Invalid character {} at position {}", c, index)
+            }
+            Self::OddLength => write!(f, "Odd number of digits"),
+        }
+    }
+}
+
+#[inline]
+fn from_digit(num: u8) -> char {
+    if num < 10 {
+        (b'0' + num) as char
+    } else {
+        (b'a' + num - 10) as char
+    }
+}
+
+/// Hex encode
+pub fn encode<T>(data: T) -> String
+where
+    T: AsRef<[u8]>,
+{
+    let bytes: &[u8] = data.as_ref();
+    let mut hex: String = String::with_capacity(2 * bytes.len());
+    for byte in bytes.iter() {
+        hex.push(from_digit(byte >> 4));
+        hex.push(from_digit(byte & 0xF));
+    }
+    hex
+}
+
+const fn val(c: u8, idx: usize) -> Result<u8, Error> {
+    match c {
+        b'A'..=b'F' => Ok(c - b'A' + 10),
+        b'a'..=b'f' => Ok(c - b'a' + 10),
+        b'0'..=b'9' => Ok(c - b'0'),
+        _ => Err(Error::InvalidHexCharacter {
+            c: c as char,
+            index: idx,
+        }),
+    }
+}
+
+/// Hex decode
+pub fn decode<T>(hex: T) -> Result<Vec<u8>, Error>
+where
+    T: AsRef<[u8]>,
+{
+    let hex = hex.as_ref();
+    let len = hex.len();
+
+    if len % 2 != 0 {
+        return Err(Error::OddLength);
+    }
+
+    let mut bytes: Vec<u8> = Vec::with_capacity(len / 2);
+
+    for i in (0..len).step_by(2) {
+        let high = val(hex[i], i)?;
+        let low = val(hex[i + 1], i + 1)?;
+        bytes.push(high << 4 | low);
+    }
+
+    Ok(bytes)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_encode() {
+        assert_eq!(encode("foobar"), "666f6f626172");
+    }
+
+    #[test]
+    fn test_decode() {
+        assert_eq!(
+            decode("666f6f626172"),
+            Ok(String::from("foobar").into_bytes())
+        );
+    }
+
+    #[test]
+    pub fn test_invalid_length() {
+        assert_eq!(decode("1").unwrap_err(), Error::OddLength);
+        assert_eq!(decode("666f6f6261721").unwrap_err(), Error::OddLength);
+    }
+
+    #[test]
+    pub fn test_invalid_char() {
+        assert_eq!(
+            decode("66ag").unwrap_err(),
+            Error::InvalidHexCharacter { c: 'g', index: 3 }
+        );
+    }
+}
+
+#[cfg(bench)]
+mod benches {
+    use super::*;
+    use crate::test::{black_box, Bencher};
+
+    const EVENT_JSON: &str = r#"{"content":"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA==","created_at":1640839235,"id":"2be17aa3031bdcb006f0fce80c146dea9c1c0268b0af2398bb673365c6444d45","kind":4,"pubkey":"f86c44a2de95d9149b51c6a29afeabba264c18e2fa7c49de93424a0c56947785","sig":"a5d9290ef9659083c490b303eb7ee41356d8778ff19f2f91776c8dc4443388a64ffcf336e61af4c25c05ac3ae952d1ced889ed655b67790891222aaa15b99fdd","tags":[["p","13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d"]]}"#;
+
+    #[bench]
+    pub fn hex_encode(bh: &mut Bencher) {
+        bh.iter(|| {
+            black_box(encode(EVENT_JSON));
+        });
+    }
+
+    #[bench]
+    pub fn hex_decode(bh: &mut Bencher) {
+        let h = "7b22636f6e74656e74223a227552757659723538354238304c3672534a69486f63773d3d3f69763d6f68364c5671647359596f6c334a66466e58546250413d3d222c22637265617465645f6174223a313634303833393233352c226964223a2232626531376161333033316264636230303666306663653830633134366465613963316330323638623061663233393862623637333336356336343434643435222c226b696e64223a342c227075626b6579223a2266383663343461326465393564393134396235316336613239616665616262613236346331386532666137633439646539333432346130633536393437373835222c22736967223a226135643932393065663936353930383363343930623330336562376565343133353664383737386666313966326639313737366338646334343433333838613634666663663333366536316166346332356330356163336165393532643163656438383965643635356236373739303839313232326161613135623939666464222c2274616773223a5b5b2270222c2231336164633531316465376531636663663163366237663633363566623561303334343264376263616366353635656135376661373737303931326330323364225d5d7d";
+        bh.iter(|| {
+            black_box(decode(h)).unwrap();
+        });
+    }
+}

+ 41 - 0
crates/cashu/src/util/mod.rs

@@ -0,0 +1,41 @@
+//! Util
+
+#[cfg(not(target_arch = "wasm32"))]
+use std::time::{SystemTime, UNIX_EPOCH};
+
+use bitcoin::hashes::sha256::Hash as Sha256;
+use bitcoin::hashes::Hash;
+use bitcoin::secp256k1::rand::{self, RngCore};
+use bitcoin::secp256k1::{All, Secp256k1};
+#[cfg(target_arch = "wasm32")]
+use instant::SystemTime;
+use once_cell::sync::Lazy;
+
+pub mod hex;
+
+#[cfg(target_arch = "wasm32")]
+const UNIX_EPOCH: SystemTime = SystemTime::UNIX_EPOCH;
+
+/// Secp256k1 global context
+pub static SECP256K1: Lazy<Secp256k1<All>> = Lazy::new(|| {
+    let mut ctx = Secp256k1::new();
+    let mut rng = rand::thread_rng();
+    ctx.randomize(&mut rng);
+    ctx
+});
+
+pub fn random_hash() -> Vec<u8> {
+    let mut rng = rand::thread_rng();
+    let mut random_bytes: [u8; 32] = [0u8; Sha256::LEN];
+    rng.fill_bytes(&mut random_bytes);
+
+    let hash = Sha256::hash(&random_bytes);
+    hash.to_byte_array().to_vec()
+}
+
+pub fn unix_time() -> u64 {
+    SystemTime::now()
+        .duration_since(UNIX_EPOCH)
+        .unwrap_or_default()
+        .as_secs()
+}

+ 0 - 22
crates/cashu/src/utils.rs

@@ -1,22 +0,0 @@
-//! Utils
-
-use std::time::SystemTime;
-
-use bitcoin::hashes::sha256::Hash as Sha256;
-use bitcoin::hashes::Hash;
-use rand::prelude::*;
-
-pub fn random_hash() -> Vec<u8> {
-    let mut rng = rand::thread_rng();
-    let mut random_bytes = [0u8; Sha256::LEN];
-    rng.fill_bytes(&mut random_bytes);
-    let hash = Sha256::hash(&random_bytes);
-    hash.to_byte_array().to_vec()
-}
-
-pub fn unix_time() -> u64 {
-    SystemTime::now()
-        .duration_since(SystemTime::UNIX_EPOCH)
-        .map(|x| x.as_secs())
-        .unwrap_or(0)
-}

Some files were not shown because too many files changed in this diff