From 3831a4f3bbb9e6b0d618fc4aafbe5d1939e19bfd Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Tue, 9 Apr 2024 20:03:55 +0200 Subject: [PATCH] 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 --- Cargo.toml | 1 - crates/cashu-sdk/src/lib.rs | 16 +- .../cashu-sdk/src/mint/localstore/memory.rs | 60 +- .../src/mint/localstore/redb_store.rs | 37 +- crates/cashu-sdk/src/mint/mod.rs | 26 +- crates/cashu-sdk/src/utils.rs | 8 - crates/cashu-sdk/src/wallet/mod.rs | 23 +- crates/cashu/Cargo.toml | 37 +- crates/cashu/src/dhke.rs | 586 ++++++++---------- crates/cashu/src/error.rs | 33 +- crates/cashu/src/lib.rs | 15 +- crates/cashu/src/nuts/nut00.rs | 16 +- .../cashu/src/nuts/{nut01.rs => nut01/mod.rs} | 197 ++---- crates/cashu/src/nuts/nut01/public_key.rs | 146 +++++ crates/cashu/src/nuts/nut01/secret_key.rs | 126 ++++ crates/cashu/src/nuts/nut02.rs | 37 +- crates/cashu/src/nuts/nut03.rs | 5 +- crates/cashu/src/nuts/nut04.rs | 6 +- crates/cashu/src/nuts/nut05.rs | 5 +- crates/cashu/src/nuts/nut06.rs | 5 +- crates/cashu/src/nuts/nut07.rs | 5 +- crates/cashu/src/nuts/nut08.rs | 5 +- crates/cashu/src/nuts/nut09.rs | 4 +- crates/cashu/src/nuts/nut10.rs | 8 +- crates/cashu/src/nuts/nut11.rs | 210 +++---- crates/cashu/src/nuts/nut12.rs | 194 +++--- crates/cashu/src/nuts/nut13.rs | 42 +- crates/cashu/src/secret.rs | 42 +- crates/cashu/src/serde_utils.rs | 90 +-- crates/cashu/src/util/hex.rs | 147 +++++ crates/cashu/src/util/mod.rs | 41 ++ crates/cashu/src/utils.rs | 22 - 32 files changed, 1165 insertions(+), 1030 deletions(-) delete mode 100644 crates/cashu-sdk/src/utils.rs rename crates/cashu/src/nuts/{nut01.rs => nut01/mod.rs} (76%) create mode 100644 crates/cashu/src/nuts/nut01/public_key.rs create mode 100644 crates/cashu/src/nuts/nut01/secret_key.rs create mode 100644 crates/cashu/src/util/hex.rs create mode 100644 crates/cashu/src/util/mod.rs delete mode 100644 crates/cashu/src/utils.rs diff --git a/Cargo.toml b/Cargo.toml index a912ef1a..9a0eab26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,4 @@ [workspace] - members = [ "crates/cashu", "crates/cashu-sdk", diff --git a/crates/cashu-sdk/src/lib.rs b/crates/cashu-sdk/src/lib.rs index fecf307e..939613a1 100644 --- a/crates/cashu-sdk/src/lib.rs +++ b/crates/cashu-sdk/src/lib.rs @@ -1,11 +1,9 @@ -#[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, *}; + +#[cfg(feature = "wallet")] +pub mod client; +#[cfg(feature = "mint")] +pub mod mint; +#[cfg(feature = "wallet")] +pub mod wallet; diff --git a/crates/cashu-sdk/src/mint/localstore/memory.rs b/crates/cashu-sdk/src/mint/localstore/memory.rs index e4909018..b9ec9258 100644 --- a/crates/cashu-sdk/src/mint/localstore/memory.rs +++ b/crates/cashu-sdk/src/mint/localstore/memory.rs @@ -18,9 +18,9 @@ pub struct MemoryLocalStore { keysets: Arc>>, mint_quotes: Arc>>, melt_quotes: Arc>>, - pending_proofs: Arc, Proof>>>, - spent_proofs: Arc, Proof>>>, - blinded_signatures: Arc, BlindSignature>>>, + pending_proofs: Arc>>, + spent_proofs: Arc>>, + blinded_signatures: Arc>>, } impl MemoryLocalStore { @@ -33,7 +33,7 @@ impl MemoryLocalStore { melt_quotes: Vec, pending_proofs: Proofs, spent_proofs: Proofs, - blinded_signatures: HashMap, BlindSignature>, + blinded_signatures: HashMap<[u8; 33], BlindSignature>, ) -> Result { 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, 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, 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(()) } diff --git a/crates/cashu-sdk/src/mint/localstore/redb_store.rs b/crates/cashu-sdk/src/mint/localstore/redb_store.rs index dae9ea38..31e04167 100644 --- a/crates/cashu-sdk/src/mint/localstore/redb_store.rs +++ b/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); diff --git a/crates/cashu-sdk/src/mint/mod.rs b/crates/cashu-sdk/src/mint/mod.rs index 4d8987c9..54b2083f 100644 --- a/crates/cashu-sdk/src/mint/mod.rs +++ b/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> = 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> = 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 = request.outputs.iter().map(|b| b.b.clone()).collect(); + let blinded_message: Vec = request.outputs.iter().map(|b| b.b).collect(); let blinded_signatures = self .localstore diff --git a/crates/cashu-sdk/src/utils.rs b/crates/cashu-sdk/src/utils.rs deleted file mode 100644 index ddb74ed3..00000000 --- a/crates/cashu-sdk/src/utils.rs +++ /dev/null @@ -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) -} diff --git a/crates/cashu-sdk/src/wallet/mod.rs b/crates/cashu-sdk/src/wallet/mod.rs index 3628a586..c8f806dd 100644 --- a/crates/cashu-sdk/src/wallet/mod.rs +++ b/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::>() - .clone(), + .flat_map(|p| hash_to_curve(p.secret.as_bytes())) + .collect::>(), ) .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)?; } } diff --git a/crates/cashu/Cargo.toml b/crates/cashu/Cargo.toml index aefcb509..b8f4e9bf 100644 --- a/crates/cashu/Cargo.toml +++ b/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" ] } diff --git a/crates/cashu/src/dhke.rs b/crates/cashu/src/dhke.rs index c7f90589..9928c837 100644 --- a/crates/cashu/src/dhke.rs +++ b/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 { - let msg_to_hash = [DOMAIN_SEPARATOR, message].concat(); +pub fn hash_to_curve(message: &[u8]) -> Result { + let msg_to_hash: Vec = [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 = 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::>(), - ) { - 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 { Err(Error::NoValidPoint) } -pub fn hash_e(pubkeys: Vec) -> Vec { - let mut e = "".to_string(); +pub fn hash_e(public_keys: I) -> [u8; 32] +where + I: IntoIterator, +{ + let mut e: String = String::new(); - for pubkey in pubkeys { - let uncompressed_point = pubkey.to_encoded_point(false).to_bytes(); - - 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; +/// Blind Message +/// +/// `B_ = Y + rG` +pub fn blind_message( + secret: &[u8], + blinding_factor: Option, +) -> 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)) +} - use k256::{ProjectivePoint, Scalar, SecretKey}; +/// Unblind Message +/// +/// `C_ - rK` +pub fn unblind_message( + // C_ + blinded_key: &PublicKey, + r: &SecretKey, + // K + mint_pubkey: &PublicKey, +) -> Result { + let r: Scalar = Scalar::from(r.deref().to_owned()); - use super::hash_to_curve; - use crate::error; - use crate::nuts::{BlindSignature, Keys, Proof, Proofs, PublicKey, *}; - use crate::secret::Secret; + // a = r * K + let a: PublicKey = mint_pubkey.mul_tweak(&SECP256K1, &r)?.into(); - /// Blind Message Alice Step one - pub fn blind_message( - secret: &[u8], - blinding_factor: Option, - ) -> Result<(PublicKey, SecretKey), error::wallet::Error> { - let y = hash_to_curve(secret)?; + // C_ - a + let a: PublicKey = a.negate(&SECP256K1).into(); + Ok(blinded_key.combine(&a)?.into()) // C_ + (-a) +} - 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 { - // C - // Unblinded message - let c = ProjectivePoint::from(Into::::into(blinded_key).as_affine()) - - Into::::into(mint_pubkey) - .as_affine() - .mul(Scalar::from(r.as_scalar_primitive())); - - Ok(k256::PublicKey::try_from(c)?.into()) - } - - /// Construct Proof - pub fn construct_proofs( - promises: Vec, - rs: Vec, - secrets: Vec, - keys: &Keys, - ) -> Result { - 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, + rs: Vec, + secrets: Vec, + keys: &Keys, +) -> Result { + 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 unblinded_signature: PublicKey = unblind_message(&blinded_c, &r, &a)?; - let proof; + 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, - }; - } - - 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, + }; + } + + proofs.push(proof); } + + Ok(proofs) } -#[cfg(feature = "mint")] -mod mint { - use std::ops::Mul; +/// 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 { + let k: Scalar = Scalar::from(k.deref().to_owned()); + Ok(blinded_message.mul_tweak(&SECP256K1, &k)?.into()) +} - use k256::{Scalar, SecretKey}; - use log::warn; +/// 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)?; - use super::hash_to_curve; - use crate::error; + // Compute the expected unblinded message + let expected_unblinded_message: PublicKey = y.combine(&a.public_key())?.into(); - /// Sign Blinded Message (Step2 bob) - pub fn sign_message( - a: SecretKey, - blinded_message: k256::PublicKey, - ) -> Result { - Ok(k256::PublicKey::try_from( - blinded_message - .as_affine() - .mul(Scalar::from(a.as_scalar_primitive())), - )?) + // Compare the unblinded_message with the expected value + if unblinded_message == expected_unblinded_message { + return Ok(()); } - /// 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(()); - } - - warn!("Message not verifed"); - - Err(error::mint::Error::TokenNotVerifed) - } + 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", + assert_eq!(sec, r); + assert_eq!( + b, + PublicKey::from_hex( + "033b1a9737a40cc3fd9b6af4b723632b76a67a36782596304612a6c2bfb5197e6d" ) - .unwrap(); + .unwrap() + ); - let (b, r) = - blind_message(&hex::decode(message).unwrap(), Some(sec.clone().into())).unwrap(); + let message = + hex::decode("f1aaf16c2239746f369572c0784d9dd3d032d952c2d992175873fb58fae31a60") + .unwrap(); + let sec: SecretKey = + SecretKey::from_hex("f78476ea7cc9ade20f9e05e58a804cf19533f03ea805ece5fee88c8e2874ba50") + .unwrap(); - assert_eq!(sec, r.into()); + let (b, r) = blind_message(&message, Some(sec.clone())).unwrap(); - 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( + "029bdf2d716ee366eddf599ba252786c1033f47e230248a4612a5670ab931f1763" ) - .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() - ); - } - - #[test] - fn test_unblind_message() { - let blinded_key = k256::PublicKey::from_sec1_bytes( - &hex::decode("02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2") - .unwrap(), - ) - .unwrap(); - - let r = SecretKey::new(ScalarPrimitive::ONE); - let a = k256::PublicKey::from_sec1_bytes( - &hex::decode("020000000000000000000000000000000000000000000000000000000000000001") - .unwrap(), - ) - .unwrap(); - - let unblinded = unblind_message(blinded_key.into(), r, a.into()).unwrap(); - - assert_eq!( - Into::::into( - k256::PublicKey::from_sec1_bytes( - &hex::decode( - "03c724d7e6a5443b39ac8acf11f40420adc4f99a02e7cc1b57703d9391f6d129cd" - ) - .unwrap() - ) - .unwrap() - ), - unblinded - ); - } + .unwrap() + ); } - #[cfg(feature = "mint")] - mod mint_test { + #[test] + fn test_unblind_message() { + let blinded_key = PublicKey::from_hex( + "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2", + ) + .unwrap(); - use k256::SecretKey; + let r = + SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001") + .unwrap(); + let a = PublicKey::from_hex( + "020000000000000000000000000000000000000000000000000000000000000001", + ) + .unwrap(); - use super::{hash_to_curve, *}; - use crate::secret::Secret; + let unblinded = unblind_message(&blinded_key, &r, &a).unwrap(); - #[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", + assert_eq!( + PublicKey::from_hex( + "03c724d7e6a5443b39ac8acf11f40420adc4f99a02e7cc1b57703d9391f6d129cd" ) - .unwrap(); + .unwrap(), + unblinded + ); + } - // C_ - let signed = sign_message(bob_sec.into(), blinded_message.into()).unwrap(); + #[test] + fn test_sign_message() { + use super::*; + let message = "test_message"; + let sec = + SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001") + .unwrap(); - assert_eq!( - signed, - k256::PublicKey::from_sec1_bytes( - &hex::decode( - "027726f0e5757b4202a27198369a3477a17bc275b7529da518fc7cb4a1d927cc0d" - ) - .unwrap() - ) - .unwrap() - ); - } + let (blinded_message, _r) = blind_message(message.as_bytes(), Some(sec)).unwrap(); + // A + let bob_sec = + SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001") + .unwrap(); - #[ignore] - #[test] - fn test_blinded_dhke() { - // a - let bob_sec = SecretKey::random(&mut rand::thread_rng()); + // C_ + let signed = sign_message(&bob_sec, &blinded_message).unwrap(); - // A - let bob_pub = bob_sec.public_key(); + assert_eq!( + signed, + PublicKey::from_hex( + "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b" + ) + .unwrap() + ); - // let alice_sec = SecretKey::random(&mut rand::thread_rng()); + // A + let bob_sec = + SecretKey::from_hex("7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f") + .unwrap(); - let x = Secret::new(); + // C_ + let signed = sign_message(&bob_sec, &blinded_message).unwrap(); - // Y - let y = hash_to_curve(&x.to_bytes()).unwrap(); + assert_eq!( + signed, + PublicKey::from_hex( + "027726f0e5757b4202a27198369a3477a17bc275b7529da518fc7cb4a1d927cc0d" + ) + .unwrap() + ); + } - // B_ - let blinded = blind_message(&y.to_sec1_bytes(), None).unwrap(); + #[ignore] + #[test] + fn test_blinded_dhke() { + // a + let bob_sec = SecretKey::generate(); - // C_ - let signed = sign_message(bob_sec.clone(), blinded.0.into()).unwrap(); + // A + let bob_pub = bob_sec.public_key(); - // C - let c = unblind_message(signed.into(), blinded.1, bob_pub.into()).unwrap(); + // let alice_sec = SecretKey::random(&mut rand::thread_rng()); - assert!(verify_message(bob_sec, c.into(), &x.to_bytes()).is_ok()); - } + let x = Secret::generate(); + + // Y + let y = hash_to_curve(&x.to_bytes()).unwrap(); + + // 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()); } } diff --git a/crates/cashu/src/error.rs b/crates/cashu/src/error.rs index edcc46be..519a64e2 100644 --- a/crates/cashu/src/error.rs +++ b/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), + HexError(#[from] hex::Error), + /// Secp256k1 error #[error("`{0}`")] - EllipticCurve(#[from] k256::elliptic_curve::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}`")] - EllipticError(#[from] k256::elliptic_curve::Error), + Secp256k1(#[from] bitcoin::secp256k1::Error), + /// NUT01 error + #[error("`{0}`")] + NUT01(#[from] nut01::Error), #[error("`Token not verified`")] TokenNotVerifed, #[error("Invoice amount undefined")] diff --git a/crates/cashu/src/lib.rs b/crates/cashu/src/lib.rs index 2af5ffbb..1dc1eb9e 100644 --- a/crates/cashu/src/lib.rs +++ b/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> = std::result::Result; diff --git a/crates/cashu/src/nuts/nut00.rs b/crates/cashu/src/nuts/nut00.rs index 52ba68f7..4bcb2b4e 100644 --- a/crates/cashu/src/nuts/nut00.rs +++ b/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"; diff --git a/crates/cashu/src/nuts/nut01.rs b/crates/cashu/src/nuts/nut01/mod.rs similarity index 76% rename from crates/cashu/src/nuts/nut01.rs rename to crates/cashu/src/nuts/nut01/mod.rs index a4c13ab8..7368769e 100644 --- a/crates/cashu/src/nuts/nut01.rs +++ b/crates/cashu/src/nuts/nut01/mod.rs @@ -1,153 +1,35 @@ -//! Mint public key exchange -// https://github.com/cashubtc/nuts/blob/main/01.md +//! NUT-01: Mint public key exchange +//! +//! -use std::collections::{BTreeMap, HashMap}; -use std::str::FromStr; +use std::collections::BTreeMap; -use k256::elliptic_curve::generic_array::GenericArray; -#[cfg(feature = "nut11")] -use k256::schnorr::{SigningKey, VerifyingKey}; +use bitcoin::secp256k1; use serde::{Deserialize, Serialize}; +use thiserror::Error; -use super::KeySet; -use crate::error::Error; -use crate::Amount; +mod public_key; +mod secret_key; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(transparent)] -pub struct PublicKey(#[serde(with = "crate::serde_utils::serde_public_key")] k256::PublicKey); +pub use self::public_key::PublicKey; +pub use self::secret_key::SecretKey; +use super::nut02::KeySet; +use crate::amount::Amount; -impl From for k256::PublicKey { - fn from(value: PublicKey) -> k256::PublicKey { - value.0 - } +#[derive(Debug, Error)] +pub enum Error { + #[error(transparent)] + Secp256k1(#[from] secp256k1::Error), + #[error(transparent)] + Json(#[from] serde_json::Error), + #[error("Invalid public key size: expected={expected}, found={found}")] + InvalidPublicKeySize { expected: usize, found: usize }, } -impl From<&PublicKey> for k256::PublicKey { - fn from(value: &PublicKey) -> k256::PublicKey { - value.0 - } -} - -impl From for PublicKey { - fn from(value: k256::PublicKey) -> Self { - Self(value) - } -} - -#[cfg(feature = "nut11")] -impl TryFrom for VerifyingKey { - type Error = Error; - fn try_from(value: PublicKey) -> Result { - (&value).try_into() - } -} - -#[cfg(feature = "nut11")] -impl TryFrom<&PublicKey> for VerifyingKey { - type Error = Error; - fn try_from(value: &PublicKey) -> Result { - let bytes = value.0.to_sec1_bytes(); - - let bytes = if bytes.len().eq(&33) { - bytes.iter().skip(1).cloned().collect() - } else { - bytes.to_vec() - }; - - VerifyingKey::from_bytes(&bytes).map_err(|_| Error::Key) - } -} - -#[cfg(feature = "nut11")] -impl From for PublicKey { - fn from(value: VerifyingKey) -> PublicKey { - PublicKey(value.into()) - } -} - -#[cfg(feature = "nut11")] -impl From for PublicKey { - fn from(value: super::VerifyingKey) -> PublicKey { - let v: VerifyingKey = value.into(); - PublicKey(v.into()) - } -} -impl PublicKey { - pub fn to_bytes(&self) -> Box<[u8]> { - self.0.to_sec1_bytes() - } -} - -impl From for Box<[u8]> { - fn from(pubkey: PublicKey) -> Box<[u8]> { - pubkey.to_bytes() - } -} - -impl FromStr for PublicKey { - type Err = Error; - - fn from_str(hex: &str) -> Result { - let hex = hex::decode(hex)?; - Ok(PublicKey(k256::PublicKey::from_sec1_bytes(&hex)?)) - } -} - -impl std::fmt::Display for PublicKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let bytes = self.0.to_sec1_bytes(); - f.write_str(&hex::encode(bytes)) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(transparent)] -pub struct SecretKey(#[serde(with = "crate::serde_utils::serde_secret_key")] pub k256::SecretKey); - -impl From for k256::SecretKey { - fn from(value: SecretKey) -> k256::SecretKey { - value.0 - } -} - -impl From for SecretKey { - fn from(value: k256::SecretKey) -> Self { - Self(value) - } -} - -#[cfg(feature = "nut11")] -impl TryFrom for SigningKey { - type Error = Error; - fn try_from(value: SecretKey) -> Result { - Ok(value.0.into()) - } -} - -impl SecretKey { - pub fn to_hex(&self) -> String { - let bytes = self.0.to_bytes(); - - hex::encode(bytes) - } - - pub fn from_hex(hex: &str) -> Result { - Ok(k256::SecretKey::from_bytes(GenericArray::from_slice(&hex::decode(hex)?))?.into()) - } - - pub fn public_key(&self) -> PublicKey { - self.0.public_key().into() - } - - pub fn random() -> Self { - let mut rng = rand::thread_rng(); - SecretKey(k256::SecretKey::random(&mut rng)) - } -} - -/// Mint Keys [NUT-01] -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +/// Mint Keys +/// +/// +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct Keys(BTreeMap); impl From for Keys { @@ -155,34 +37,30 @@ impl From for Keys { Self( keys.0 .iter() - .map(|(amount, keypair)| (*amount, keypair.public_key.clone())) + .map(|(amount, keypair)| (*amount, keypair.public_key)) .collect(), ) } } impl Keys { + #[inline] pub fn new(keys: BTreeMap) -> Self { Self(keys) } - pub fn keys(&self) -> BTreeMap { - self.0.clone() + #[inline] + pub fn keys(&self) -> &BTreeMap { + &self.0 } + #[inline] pub fn amount_key(&self, amount: Amount) -> Option { - self.0.get(&amount).cloned() - } - - /// As serialized hashmap - pub fn as_hashmap(&self) -> HashMap { - self.0 - .iter() - .map(|(k, v)| (k.to_owned(), hex::encode(v.0.to_sec1_bytes()))) - .collect() + self.0.get(&amount).copied() } /// Iterate through the (`Amount`, `PublicKey`) entries in the Map + #[inline] pub fn iter(&self) -> impl Iterator { self.0.iter() } @@ -265,8 +143,11 @@ pub mod mint { #[cfg(test)] mod tests { + use std::str::FromStr; use super::*; + #[cfg(feature = "nut11")] + use crate::nuts::nut11::VerifyingKey; #[test] fn pubkey() { @@ -280,13 +161,14 @@ mod tests { } #[test] + #[cfg(feature = "nut11")] fn verying_key() { let key_str = "026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198"; let pubkey = PublicKey::from_str(key_str).unwrap(); let v_key: VerifyingKey = pubkey.clone().try_into().unwrap(); - let p: PublicKey = v_key.into(); + let p: PublicKey = v_key.to_normalized_public_key(); assert_eq!(p, pubkey); } @@ -302,7 +184,7 @@ mod tests { #[test] fn test_ser_der_secret() { - let secret = SecretKey::random(); + let secret = SecretKey::generate(); let json = serde_json::to_string(&secret).unwrap(); @@ -324,21 +206,18 @@ mod tests { "1":"03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38bc","2":"04fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de3625246cb2c27dac965cb7200a5986467eee92eb7d496bbf1453b074e223e481","4":"02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303","8":"02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528" }"#; let response: Result = serde_json::from_str(incorrect_1); - assert!(response.is_err()); let incorrect_1 = r#"{ "1":"03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38bc","2":"03fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de","4":"02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303","8":"02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528" }"#; let response: Result = serde_json::from_str(incorrect_1); - assert!(response.is_ok()); let incorrect_1 = r#"{ "1":"03ba786a2c0745f8c30e490288acd7a72dd53d65afd292ddefa326a4a3fa14c566","2":"03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5","4":"036e378bcf78738ddf68859293c69778035740e41138ab183c94f8fee7572214c7","8":"03909d73beaf28edfb283dbeb8da321afd40651e8902fcf5454ecc7d69788626c0","16":"028a36f0e6638ea7466665fe174d958212723019ec08f9ce6898d897f88e68aa5d","32":"03a97a40e146adee2687ac60c2ba2586a90f970de92a9d0e6cae5a4b9965f54612","64":"03ce86f0c197aab181ddba0cfc5c5576e11dfd5164d9f3d4a3fc3ffbbf2e069664","128":"0284f2c06d938a6f78794814c687560a0aabab19fe5e6f30ede38e113b132a3cb9","256":"03b99f475b68e5b4c0ba809cdecaae64eade2d9787aa123206f91cd61f76c01459","512":"03d4db82ea19a44d35274de51f78af0a710925fe7d9e03620b84e3e9976e3ac2eb","1024":"031fbd4ba801870871d46cf62228a1b748905ebc07d3b210daf48de229e683f2dc","2048":"0276cedb9a3b160db6a158ad4e468d2437f021293204b3cd4bf6247970d8aff54b","4096":"02fc6b89b403ee9eb8a7ed457cd3973638080d6e04ca8af7307c965c166b555ea2","8192":"0320265583e916d3a305f0d2687fcf2cd4e3cd03a16ea8261fda309c3ec5721e21","16384":"036e41de58fdff3cb1d8d713f48c63bc61fa3b3e1631495a444d178363c0d2ed50","32768":"0365438f613f19696264300b069d1dad93f0c60a37536b72a8ab7c7366a5ee6c04","65536":"02408426cfb6fc86341bac79624ba8708a4376b2d92debdf4134813f866eb57a8d","131072":"031063e9f11c94dc778c473e968966eac0e70b7145213fbaff5f7a007e71c65f41","262144":"02f2a3e808f9cd168ec71b7f328258d0c1dda250659c1aced14c7f5cf05aab4328","524288":"038ac10de9f1ff9395903bb73077e94dbf91e9ef98fd77d9a2debc5f74c575bc86","1048576":"0203eaee4db749b0fc7c49870d082024b2c31d889f9bc3b32473d4f1dfa3625788","2097152":"033cdb9d36e1e82ae652b7b6a08e0204569ec7ff9ebf85d80a02786dc7fe00b04c","4194304":"02c8b73f4e3a470ae05e5f2fe39984d41e9f6ae7be9f3b09c9ac31292e403ac512","8388608":"025bbe0cfce8a1f4fbd7f3a0d4a09cb6badd73ef61829dc827aa8a98c270bc25b0","16777216":"037eec3d1651a30a90182d9287a5c51386fe35d4a96839cf7969c6e2a03db1fc21","33554432":"03280576b81a04e6abd7197f305506476f5751356b7643988495ca5c3e14e5c262","67108864":"03268bfb05be1dbb33ab6e7e00e438373ca2c9b9abc018fdb452d0e1a0935e10d3","134217728":"02573b68784ceba9617bbcc7c9487836d296aa7c628c3199173a841e7a19798020","268435456":"0234076b6e70f7fbf755d2227ecc8d8169d662518ee3a1401f729e2a12ccb2b276","536870912":"03015bd88961e2a466a2163bd4248d1d2b42c7c58a157e594785e7eb34d880efc9","1073741824":"02c9b076d08f9020ebee49ac8ba2610b404d4e553a4f800150ceb539e9421aaeee","2147483648":"034d592f4c366afddc919a509600af81b489a03caf4f7517c2b3f4f2b558f9a41a","4294967296":"037c09ecb66da082981e4cbdb1ac65c0eb631fc75d85bed13efb2c6364148879b5","8589934592":"02b4ebb0dda3b9ad83b39e2e31024b777cc0ac205a96b9a6cfab3edea2912ed1b3","17179869184":"026cc4dacdced45e63f6e4f62edbc5779ccd802e7fabb82d5123db879b636176e9","34359738368":"02b2cee01b7d8e90180254459b8f09bbea9aad34c3a2fd98c85517ecfc9805af75","68719476736":"037a0c0d564540fc574b8bfa0253cca987b75466e44b295ed59f6f8bd41aace754","137438953472":"021df6585cae9b9ca431318a713fd73dbb76b3ef5667957e8633bca8aaa7214fb6","274877906944":"02b8f53dde126f8c85fa5bb6061c0be5aca90984ce9b902966941caf963648d53a","549755813888":"029cc8af2840d59f1d8761779b2496623c82c64be8e15f9ab577c657c6dd453785","1099511627776":"03e446fdb84fad492ff3a25fc1046fb9a93a5b262ebcd0151caa442ea28959a38a","2199023255552":"02d6b25bd4ab599dd0818c55f75702fde603c93f259222001246569018842d3258","4398046511104":"03397b522bb4e156ec3952d3f048e5a986c20a00718e5e52cd5718466bf494156a","8796093022208":"02d1fb9e78262b5d7d74028073075b80bb5ab281edcfc3191061962c1346340f1e","17592186044416":"030d3f2ad7a4ca115712ff7f140434f802b19a4c9b2dd1c76f3e8e80c05c6a9310","35184372088832":"03e325b691f292e1dfb151c3fb7cad440b225795583c32e24e10635a80e4221c06","70368744177664":"03bee8f64d88de3dee21d61f89efa32933da51152ddbd67466bef815e9f93f8fd1","140737488355328":"0327244c9019a4892e1f04ba3bf95fe43b327479e2d57c25979446cc508cd379ed","281474976710656":"02fb58522cd662f2f8b042f8161caae6e45de98283f74d4e99f19b0ea85e08a56d","562949953421312":"02adde4b466a9d7e59386b6a701a39717c53f30c4810613c1b55e6b6da43b7bc9a","1125899906842624":"038eeda11f78ce05c774f30e393cda075192b890d68590813ff46362548528dca9","2251799813685248":"02ec13e0058b196db80f7079d329333b330dc30c000dbdd7397cbbc5a37a664c4f","4503599627370496":"02d2d162db63675bd04f7d56df04508840f41e2ad87312a3c93041b494efe80a73","9007199254740992":"0356969d6aef2bb40121dbd07c68b6102339f4ea8e674a9008bb69506795998f49","18014398509481984":"02f4e667567ebb9f4e6e180a4113bb071c48855f657766bb5e9c776a880335d1d6","36028797018963968":"0385b4fe35e41703d7a657d957c67bb536629de57b7e6ee6fe2130728ef0fc90b0","72057594037927936":"02b2bc1968a6fddbcc78fb9903940524824b5f5bed329c6ad48a19b56068c144fd","144115188075855872":"02e0dbb24f1d288a693e8a49bc14264d1276be16972131520cf9e055ae92fba19a","288230376151711744":"03efe75c106f931a525dc2d653ebedddc413a2c7d8cb9da410893ae7d2fa7d19cc","576460752303423488":"02c7ec2bd9508a7fc03f73c7565dc600b30fd86f3d305f8f139c45c404a52d958a","1152921504606846976":"035a6679c6b25e68ff4e29d1c7ef87f21e0a8fc574f6a08c1aa45ff352c1d59f06","2305843009213693952":"033cdc225962c052d485f7cfbf55a5b2367d200fe1fe4373a347deb4cc99e9a099","4611686018427387904":"024a4b806cf413d14b294719090a9da36ba75209c7657135ad09bc65328fba9e6f","9223372036854775808":"0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad" }"#; let response: Result = serde_json::from_str(incorrect_1); - assert!(response.is_ok()); } } diff --git a/crates/cashu/src/nuts/nut01/public_key.rs b/crates/cashu/src/nuts/nut01/public_key.rs new file mode 100644 index 00000000..72d3eab0 --- /dev/null +++ b/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 for PublicKey { + fn from(inner: secp256k1::PublicKey) -> Self { + Self { inner } + } +} + +impl PublicKey { + /// Parse from `bytes` + #[inline] + pub fn from_slice(slice: &[u8]) -> Result { + Ok(Self { + inner: secp256k1::PublicKey::from_slice(slice)?, + }) + } + + /// Parse from `hex` string + #[inline] + pub fn from_hex(hex: S) -> Result + where + S: AsRef, + { + 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::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(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_hex()) + } +} + +impl<'de> Deserialize<'de> for PublicKey { + fn deserialize(deserializer: D) -> Result + 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(); + }); + } +} diff --git a/crates/cashu/src/nuts/nut01/secret_key.rs b/crates/cashu/src/nuts/nut01/secret_key.rs new file mode 100644 index 00000000..868850a4 --- /dev/null +++ b/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 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 { + Ok(Self { + inner: secp256k1::SecretKey::from_slice(slice)?, + }) + } + + /// Parse from `hex` string + pub fn from_hex(hex: S) -> Result + where + S: AsRef, + { + 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::from_hex(secret_key) + } +} + +impl Serialize for SecretKey { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_secret_hex()) + } +} + +impl<'de> Deserialize<'de> for SecretKey { + fn deserialize(deserializer: D) -> Result + 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."); + } +} diff --git a/crates/cashu/src/nuts/nut02.rs b/crates/cashu/src/nuts/nut02.rs index bb1e1e50..4146ff37 100644 --- a/crates/cashu/src/nuts/nut02.rs +++ b/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 +//! +//! 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 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 = map .iter() - .sorted_by(|(amt_a, _), (amt_b, _)| amt_a.cmp(amt_b)) .map(|(_, pubkey)| pubkey.to_bytes()) - .collect::]>>() + .collect::>() .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); } diff --git a/crates/cashu/src/nuts/nut03.rs b/crates/cashu/src/nuts/nut03.rs index 31ec8946..40d8d9bc 100644 --- a/crates/cashu/src/nuts/nut03.rs +++ b/crates/cashu/src/nuts/nut03.rs @@ -1,5 +1,6 @@ -//! Swap -// https://github.com/cashubtc/nuts/blob/main/03.md +//! NUT-03: Swap +//! +//! use serde::{Deserialize, Serialize}; diff --git a/crates/cashu/src/nuts/nut04.rs b/crates/cashu/src/nuts/nut04.rs index fd0d9f14..fc74ae3c 100644 --- a/crates/cashu/src/nuts/nut04.rs +++ b/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 +//! +//! + use serde::{Deserialize, Serialize}; use super::{BlindSignature, BlindedMessage, CurrencyUnit, PaymentMethod}; diff --git a/crates/cashu/src/nuts/nut05.rs b/crates/cashu/src/nuts/nut05.rs index 27102e6b..bc245f07 100644 --- a/crates/cashu/src/nuts/nut05.rs +++ b/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 +//! +//! use serde::{Deserialize, Serialize}; diff --git a/crates/cashu/src/nuts/nut06.rs b/crates/cashu/src/nuts/nut06.rs index 1e98d463..d5b0086a 100644 --- a/crates/cashu/src/nuts/nut06.rs +++ b/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 +//! +//! use serde::{Deserialize, Deserializer, Serialize, Serializer}; diff --git a/crates/cashu/src/nuts/nut07.rs b/crates/cashu/src/nuts/nut07.rs index 889b6db4..d61bc944 100644 --- a/crates/cashu/src/nuts/nut07.rs +++ b/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 +//! +//! use serde::{Deserialize, Serialize}; diff --git a/crates/cashu/src/nuts/nut08.rs b/crates/cashu/src/nuts/nut08.rs index f6fceddc..f1b285e6 100644 --- a/crates/cashu/src/nuts/nut08.rs +++ b/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 +//! +//! use serde::{Deserialize, Serialize}; diff --git a/crates/cashu/src/nuts/nut09.rs b/crates/cashu/src/nuts/nut09.rs index 1deed17c..c0792a44 100644 --- a/crates/cashu/src/nuts/nut09.rs +++ b/crates/cashu/src/nuts/nut09.rs @@ -1,4 +1,6 @@ -//! Nut-09: Restore signatures +//! NUT-09: Restore signatures +//! +//! use serde::{Deserialize, Serialize}; diff --git a/crates/cashu/src/nuts/nut10.rs b/crates/cashu/src/nuts/nut10.rs index ca429834..0b100c06 100644 --- a/crates/cashu/src/nuts/nut10.rs +++ b/crates/cashu/src/nuts/nut10.rs @@ -1,4 +1,8 @@ -use std::str::FromStr; +//! NUT-10: Spending conditions +//! +//! + +use core::str::FromStr; use serde::ser::SerializeTuple; use serde::{Deserialize, Serialize, Serializer}; @@ -35,7 +39,7 @@ impl Secret { where S: Into, { - let nonce = crate::secret::Secret::new().to_string(); + let nonce = crate::secret::Secret::generate().to_string(); let secret_data = SecretData { nonce, data: data.into(), diff --git a/crates/cashu/src/nuts/nut11.rs b/crates/cashu/src/nuts/nut11.rs index 14806db8..a5a7f6b3 100644 --- a/crates/cashu/src/nuts/nut11.rs +++ b/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) +//! +//! 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 = 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 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 for Vec { 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 { - Ok(VerifyingKey(k256::schnorr::VerifyingKey::from_bytes( - bytes, - )?)) + Ok(Self(XOnlyPublicKey::from_slice(bytes)?)) } - pub fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> { - self.0 - .verify(msg, signature) - .map_err(|_| Error::InvalidSignature)?; + 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], 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 { - let bytes = hex::decode(hex)?; + let bytes: Vec = 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)?, - )) + Ok(Self(XOnlyPublicKey::from_slice(bytes)?)) } } -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 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 - } -} - -impl From 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 for k256::schnorr::SigningKey { - fn from(value: SigningKey) -> k256::schnorr::SigningKey { - value.0 - } +pub struct SigningKey { + secret_key: SecretKey, + key_pair: KeyPair, } -impl From for SigningKey { - fn from(value: k256::schnorr::SigningKey) -> Self { - Self(value) - } -} +impl Deref for SigningKey { + type Target = SecretKey; -impl From 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() - } - - pub fn sign(&self, msg: &[u8]) -> Signature { - self.0.sign(msg) + #[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]) -> Result { + 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 { - 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 diff --git a/crates/cashu/src/nuts/nut12.rs b/crates/cashu/src/nuts/nut12.rs index e6a62904..30373dd5 100644 --- a/crates/cashu/src/nuts/nut12.rs +++ b/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; +//! +//! -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 e_bytes: [u8; 32] = e.to_secret_bytes(); + let e: Scalar = e.as_scalar(); - let r2 = blinded_message - .as_affine() - .mul(Scalar::from(s.as_scalar_primitive())) - - blinded_signature - .as_affine() - .mul(Scalar::from(e.as_scalar_primitive())); + // a = e*A + let a: PublicKey = mint_pubkey.mul_tweak(&SECP256K1, &e)?.into(); - let e_bytes = e.to_bytes().to_vec(); + // R1 = s*G - a + let a: PublicKey = a.negate(&SECP256K1).into(); + let r1: PublicKey = s.public_key().combine(&a)?.into(); // s*G + (-a) - let hash_e = hash_e(vec![ - k256::PublicKey::try_from(r1)?, - k256::PublicKey::try_from(r2)?, - mint_pubkey, - blinded_signature, - ]); + // b = s*B' + let s: Scalar = Scalar::from(s.deref().to_owned()); + let b: PublicKey = blinded_message.mul_tweak(&SECP256K1, &s)?.into(); - if e_bytes.ne(&hash_e) { - warn!("DLEQ on signature failed"); - debug!("e_bytes: {:?}, Hash e: {:?}", e_bytes, hash_e); + // 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 { // 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()?; + // R2 = r*B' + let r_scal: Scalar = r.as_scalar(); + let r2: PublicKey = blinded_message.mul_tweak(&SECP256K1, &r_scal)?.into(); - let e = hash_e(vec![r1, r2, mint_secretkey.public_key(), blinded_signature]); + // 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 e_sk = k256::SecretKey::from_slice(&e)?; + // s1 = e*a + let s1: SecretKey = e_sk.mul_tweak(&mint_secret_key.as_scalar())?.into(); - let s = Scalar::from(r.as_scalar_primitive()) - + Scalar::from(e_sk.as_scalar_primitive()) - * Scalar::from(mint_secretkey.as_scalar_primitive()); + // s = r + s1 + let s: SecretKey = r.add_tweak(&s1.to_scalar())?.into(); - let s: k256::SecretKey = k256::SecretKey::new(s.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); - }; + 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 c: k256::PublicKey = (&self.c).into(); - let mint_pubkey: k256::PublicKey = mint_pubkey.into(); + let r: Scalar = dleq.r.as_scalar(); + let bs1: PublicKey = mint_pubkey.mul_tweak(&SECP256K1, &r)?.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: PublicKey = self.c.combine(&bs1)?.into(); + let blinded_message: PublicKey = y.combine(&dleq.r.public_key())?.into(); - 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) + 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 { - 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()); } } diff --git a/crates/cashu/src/nuts/nut13.rs b/crates/cashu/src/nuts/nut13.rs index 9f0da6aa..a9a25d79 100644 --- a/crates/cashu/src/nuts/nut13.rs +++ b/crates/cashu/src/nuts/nut13.rs @@ -1,35 +1,45 @@ -use std::str::FromStr; +//! NUT-13: Deterministic Secrets +//! +//! + +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 { - 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 { - 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 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)?; - let private_key = signing_key.private_key(); - - 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, }; diff --git a/crates/cashu/src/secret.rs b/crates/cashu/src/secret.rs index 188031f0..a420a2db 100644 --- a/crates/cashu/src/secret.rs +++ b/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(secret: S) -> Self + where + S: Into, + { + 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 { - 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 { - 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(); diff --git a/crates/cashu/src/serde_utils.rs b/crates/cashu/src/serde_utils.rs index 5ad179c4..4527054f 100644 --- a/crates/cashu/src/serde_utils.rs +++ b/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(pubkey: &PublicKey, serializer: S) -> Result - where - S: serde::Serializer, - { - let encoded = hex::encode(pubkey.to_sec1_bytes()); - serializer.serialize_str(&encoded) - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result - 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(pubkey: &Option, serializer: S) -> Result - 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, D::Error> - where - D: Deserializer<'de>, - { - let option_str: Option = 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(seckey: &SecretKey, serializer: S) -> Result - where - S: serde::Serializer, - { - let encoded = hex::encode(seckey.to_bytes()); - serializer.serialize_str(&encoded) - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result - 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))? - } -} diff --git a/crates/cashu/src/util/hex.rs b/crates/cashu/src/util/hex.rs new file mode 100644 index 00000000..7596a634 --- /dev/null +++ b/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(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 { + 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(hex: T) -> Result, 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 = 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(); + }); + } +} diff --git a/crates/cashu/src/util/mod.rs b/crates/cashu/src/util/mod.rs new file mode 100644 index 00000000..293210c9 --- /dev/null +++ b/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> = Lazy::new(|| { + let mut ctx = Secp256k1::new(); + let mut rng = rand::thread_rng(); + ctx.randomize(&mut rng); + ctx +}); + +pub fn random_hash() -> Vec { + 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() +} diff --git a/crates/cashu/src/utils.rs b/crates/cashu/src/utils.rs deleted file mode 100644 index a7738479..00000000 --- a/crates/cashu/src/utils.rs +++ /dev/null @@ -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 { - 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) -}