diff --git a/Cargo.toml b/Cargo.toml index 310b687a..7ff2b324 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,9 +5,11 @@ edition = "2021" [dependencies] bitcoin = { version = "0.30.0", features=["serde"] } +bitcoin_hashes = "0.12.0" +hex = "0.4.3" lightning-invoice = { version = "0.22.0", features=["serde"] } minreq = { version = "2.7.0", features = ["json-using-serde", "https"] } -secp256k1 = "0.27.0" +secp256k1 = { version = "0.27.0", features = ["rand-std", "bitcoin-hashes-std"] } serde = { version = "1.0.160", features = ["derive"]} thiserror = "1.0.40" url = "2.3.1" diff --git a/src/dhke.rs b/src/dhke.rs index 4c2a4586..42829205 100644 --- a/src/dhke.rs +++ b/src/dhke.rs @@ -1 +1,148 @@ //! Diffie-Hellmann key exchange + +use bitcoin_hashes::sha256; +// use bitcoin_hashes::Hash; +use bitcoin_hashes::Hash; +use secp256k1::rand::rngs::OsRng; +use secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey}; + +use crate::error::Error; +// use crate::types::MintKeys; +// use crate::types::Promise; +// use crate::types::Proof; + +/// Hash to Curve +pub fn hash_to_curve(secret_message: &[u8]) -> Result { + let mut msg = secret_message.to_vec(); + loop { + let hash = sha256::Hash::hash(&msg); + let mut pubkey_bytes = vec![0x02]; + pubkey_bytes.extend_from_slice(&hash[..]); + + match PublicKey::from_slice(&pubkey_bytes) { + Ok(pubkey) => return Ok(pubkey), + Err(_) => { + msg = hash.to_byte_array().to_vec(); + } + } + } +} + +/// Blind Message +pub fn blind_message( + secret: &[u8], + blinding_factor: Option, +) -> Result<(PublicKey, SecretKey), Error> { + let y = hash_to_curve(secret)?; + + let secp = Secp256k1::new(); + let r: SecretKey = match blinding_factor { + Some(sec_key) => sec_key, + None => { + let (secret_key, _public_key) = secp.generate_keypair(&mut OsRng); + secret_key + } + }; + + let b = y.combine(&r.public_key(&secp))?; + + Ok((b, r)) +} + +/// Unblind Message +pub fn unblind_message( + blinded_key: PublicKey, + r: SecretKey, + a: PublicKey, +) -> Result { + let secp = Secp256k1::new(); + let a_neg = a.negate(&secp); + let blinded_key = blinded_key.combine(&a_neg).unwrap(); + let unblinded_key = + blinded_key.mul_tweak(&secp, &Scalar::from_be_bytes(r.secret_bytes()).unwrap())?; + Ok(unblinded_key) +} + +/* +/// Construct Proof +pub fn construct_proof( + promises: Vec, + rs: Vec, + secrets: Vec, + keys: MintKeys, +) -> Result, Error> { + todo!() +} +*/ + +#[cfg(test)] +mod tests { + use hex::decode; + use std::str::FromStr; + + use super::*; + + #[test] + fn test_hash_to_curve() { + let secret = "0000000000000000000000000000000000000000000000000000000000000000"; + let sec_hex = decode(secret).unwrap(); + + let y = hash_to_curve(&sec_hex).unwrap(); + let expected_y = PublicKey::from_str( + "0266687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925", + ) + .unwrap(); + assert_eq!(y, expected_y); + + let secret = "0000000000000000000000000000000000000000000000000000000000000001"; + let sec_hex = decode(secret).unwrap(); + let y = hash_to_curve(&sec_hex).unwrap(); + let expected_y = PublicKey::from_str( + "02ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5", + ) + .unwrap(); + assert_eq!(y, expected_y); + } + + #[test] + fn test_blind_message() { + let message = "test_message"; + let blinding_factor = "0000000000000000000000000000000000000000000000000000000000000001"; + let sec = SecretKey::from_str(blinding_factor).unwrap(); + + let (b, r) = blind_message(message.as_bytes(), Some(sec)).unwrap(); + + assert_eq!( + b.to_string(), + "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2".to_string() + ); + + assert_eq!(r, sec); + } + + #[test] + fn test_unblind_message() { + let blinded_key = PublicKey::from_str( + "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2", + ) + .unwrap(); + + let r = + SecretKey::from_str("0000000000000000000000000000000000000000000000000000000000000001") + .unwrap(); + let a = PublicKey::from_str( + "020000000000000000000000000000000000000000000000000000000000000001", + ) + .unwrap(); + + let unblinded = unblind_message(blinded_key, r, a).unwrap(); + + assert_eq!( + PublicKey::from_str( + "03c724d7e6a5443b39ac8acf11f40420adc4f99a02e7cc1b57703d9391f6d129cd" + ) + .unwrap(), + unblinded + ); + } +} diff --git a/src/error.rs b/src/error.rs index 73504ebe..9da20946 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,4 +6,7 @@ pub enum Error { /// Parse Url Error #[error("minreq error: {0}")] UrlParseError(#[from] url::ParseError), + /// Secp245k1 + #[error("secp256k1 error: {0}")] + Secpk256k1Error(#[from] secp256k1::Error), } diff --git a/src/lib.rs b/src/lib.rs index 7fe6efb2..973197f6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ pub mod cashu_mint; +pub mod dhke; pub mod error; pub mod types; diff --git a/src/types.rs b/src/types.rs index 6c6ce3f5..e21d66d9 100644 --- a/src/types.rs +++ b/src/types.rs @@ -17,7 +17,7 @@ pub struct BlindedMessage { pub b: String, } -/// Promise (BlindedMessage) [NIP-00] +/// Promise (BlindedSignature) [NIP-00] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Promise { pub id: String, diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 18945312..888d7174 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -52,7 +52,7 @@ async fn test_check_fees() { async fn test_get_mint_info() { let url = Url::from_str(MINTURL).unwrap(); let mint = CashuMint::new(url); - let mint_info = mint.get_info().await.unwrap(); + let _mint_info = mint.get_info().await.unwrap(); // println!("{:?}", mint_info); }