diff --git a/benches/signatures.rs b/benches/signatures.rs index e8c4755f..f53e3c82 100644 --- a/benches/signatures.rs +++ b/benches/signatures.rs @@ -6,10 +6,13 @@ use std::time::Duration; use criterion::{criterion_group, BatchSize, Criterion}; use rand::{thread_rng, RngCore}; use tari_crypto::{ + hash_domain, keys::{PublicKey, SecretKey}, ristretto::{RistrettoPublicKey, RistrettoSchnorr, RistrettoSecretKey}, }; +hash_domain!(BenchDomain, "com.example.bench"); + fn generate_secret_key(c: &mut Criterion) { c.bench_function("Generate secret key", |b| { let mut rng = thread_rng(); @@ -45,7 +48,7 @@ fn sign_message(c: &mut Criterion) { b.iter_batched( gen_keypair, |d| { - let _sig = RistrettoSchnorr::sign_message(&d.k, d.m).unwrap(); + let _sig: RistrettoSchnorr = RistrettoSchnorr::sign_message(&d.k, d.m).unwrap(); }, BatchSize::SmallInput, ); @@ -59,7 +62,7 @@ fn verify_message(c: &mut Criterion) { b.iter_batched( || { let d = gen_keypair(); - let s = RistrettoSchnorr::sign_message(&d.k, d.m).unwrap(); + let s: RistrettoSchnorr = RistrettoSchnorr::sign_message(&d.k, d.m).unwrap(); (d, s) }, |(d, s)| assert!(s.verify_message(&d.p, d.m)), diff --git a/src/ffi/keys.rs b/src/ffi/keys.rs index d4a15c57..37a4f3f1 100644 --- a/src/ffi/keys.rs +++ b/src/ffi/keys.rs @@ -20,7 +20,6 @@ use crate::{ RistrettoComAndPubSig, RistrettoComSig, RistrettoPublicKey, - RistrettoSchnorr, RistrettoSecretKey, }, }; @@ -51,87 +50,6 @@ pub unsafe extern "C" fn random_keypair(priv_key: *mut KeyArray, pub_key: *mut K OK } -/// Generate a Schnorr signature (s, R) using the provided private key and message (k, m). -/// -/// # Safety -/// The caller MUST ensure that the string is null terminated e.g. "msg\0". -/// If any args are null then the function returns -1 -/// -/// The public nonce and signature are returned in the provided mutable arrays. -#[no_mangle] -pub unsafe extern "C" fn sign( - priv_key: *const KeyArray, - msg: *const c_char, - public_nonce: *mut KeyArray, - signature: *mut KeyArray, -) -> c_int { - if public_nonce.is_null() || signature.is_null() || priv_key.is_null() || msg.is_null() { - return NULL_POINTER; - } - let k = match RistrettoSecretKey::from_bytes(&(*priv_key)) { - Ok(k) => k, - _ => return INVALID_SECRET_KEY_SER, - }; - let pubkey = RistrettoPublicKey::from_secret_key(&k); - let r = RistrettoSecretKey::random(&mut OsRng); - let pub_r = RistrettoPublicKey::from_secret_key(&r); - let msg = match CStr::from_ptr(msg).to_str() { - Ok(s) => s, - _ => return STR_CONV_ERR, - }; - let e = RistrettoSchnorr::construct_domain_separated_challenge::<_, Blake256>(&pub_r, &pubkey, msg.as_bytes()); - let sig = match RistrettoSchnorr::sign_raw(&k, r, e.as_ref()) { - Ok(sig) => sig, - _ => return SIGNING_ERROR, - }; - (*public_nonce).copy_from_slice(sig.get_public_nonce().as_bytes()); - (*signature).copy_from_slice(sig.get_signature().as_bytes()); - OK -} - -/// Verify that a Schnorr signature (s, R) is valid for the provided public key and message (P, m). -/// -/// # Safety -/// The caller MUST ensure that the string is null terminated e.g. "msg\0". -/// If any args are null then the function returns false, and sets `err_code` to -1 -#[no_mangle] -pub unsafe extern "C" fn verify( - pub_key: *const KeyArray, - msg: *const c_char, - pub_nonce: *const KeyArray, - signature: *const KeyArray, - err_code: *mut c_int, -) -> bool { - if pub_key.is_null() || msg.is_null() || pub_nonce.is_null() || signature.is_null() || err_code.is_null() { - if !err_code.is_null() { - *err_code = NULL_POINTER; - } - return false; - } - let pk = match RistrettoPublicKey::from_bytes(&(*pub_key)) { - Ok(k) => k, - _ => { - *err_code = INVALID_SECRET_KEY_SER; - return false; - }, - }; - let r_pub = match RistrettoPublicKey::from_bytes(&(*pub_nonce)) { - Ok(r) => r, - _ => return false, - }; - let sig = match RistrettoSecretKey::from_bytes(&(*signature)) { - Ok(s) => s, - _ => return false, - }; - let msg = match CStr::from_ptr(msg).to_str() { - Ok(s) => s, - _ => return false, - }; - - let sig = RistrettoSchnorr::new(r_pub, sig); - sig.verify_message(&pk, msg.as_bytes()) -} - /// Generate a Pedersen commitment (C) using the provided value and spending key (a, x). /// /// # Safety @@ -442,102 +360,4 @@ mod test { pub_key ); } - - #[test] - pub fn test_sign_invalid_params() { - unsafe { - let priv_key = [0; KEY_LENGTH]; - let msg = "msg\0"; - let mut nonce = [0; KEY_LENGTH]; - let mut signature = [0; KEY_LENGTH]; - assert_eq!( - sign(null_mut(), msg.as_ptr() as *const c_char, &mut nonce, &mut signature), - NULL_POINTER - ); - assert_eq!(sign(&priv_key, null_mut(), &mut nonce, &mut signature), NULL_POINTER); - assert_eq!( - sign(&priv_key, msg.as_ptr() as *const c_char, null_mut(), &mut signature), - NULL_POINTER - ); - assert_eq!( - sign(&priv_key, msg.as_ptr() as *const c_char, &mut nonce, null_mut()), - NULL_POINTER - ); - } - } - - #[test] - pub fn test_sign_valid_params() { - let priv_key = [1; KEY_LENGTH]; - let msg = "msg\0"; - let mut nonce = [0; KEY_LENGTH]; - let mut signature = [0; KEY_LENGTH]; - unsafe { - assert_eq!( - sign(&priv_key, msg.as_ptr() as *const c_char, &mut nonce, &mut signature), - OK - ); - } - } - - #[test] - pub fn test_verify_invalid_params() { - let pub_key = [1; KEY_LENGTH]; - let msg = "msg\0"; - let pub_nonce = [0; KEY_LENGTH]; - let signature = [0; KEY_LENGTH]; - let mut err_code = 0i32; - unsafe { - assert!(!verify( - null_mut(), - msg.as_ptr() as *const c_char, - &pub_nonce, - &signature, - &mut err_code - ),); - assert!(!verify(&pub_key, null_mut(), &pub_nonce, &signature, &mut err_code),); - assert!(!verify( - &pub_key, - msg.as_ptr() as *const c_char, - null_mut(), - &signature, - &mut err_code - ),); - assert!(!verify( - &pub_key, - msg.as_ptr() as *const c_char, - &pub_nonce, - null_mut(), - &mut err_code - ),); - assert!(!verify( - &pub_key, - msg.as_ptr() as *const c_char, - &pub_nonce, - &signature, - null_mut() - ),); - } - } - - #[test] - pub fn test_verify_success() { - let mut priv_key: KeyArray = [0; KEY_LENGTH]; - let mut pub_key: KeyArray = [0; KEY_LENGTH]; - let mut pub_nonce: KeyArray = [0; KEY_LENGTH]; - let mut signature: KeyArray = [0; KEY_LENGTH]; - let msg = "msg\0"; - let mut err_code = 0i32; - unsafe { - random_keypair(&mut priv_key, &mut pub_key); - sign(&priv_key, msg.as_ptr() as *const c_char, &mut pub_nonce, &mut signature); - assert!(verify( - &pub_key, - msg.as_ptr() as *const c_char, - &pub_nonce, - &signature, - &mut err_code - )); - } - } } diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 85777a94..6dcca6fe 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -9,16 +9,7 @@ mod error; mod keys; pub use error::lookup_error_message; -pub use keys::{ - commitment, - random_keypair, - sign, - sign_comandpubsig, - sign_comsig, - verify, - verify_comandpubsig, - verify_comsig, -}; +pub use keys::{commitment, random_keypair, sign_comandpubsig, sign_comsig, verify_comandpubsig, verify_comsig}; const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "\u{00}"); diff --git a/src/ristretto/mod.rs b/src/ristretto/mod.rs index 370acd49..20ad7f3c 100644 --- a/src/ristretto/mod.rs +++ b/src/ristretto/mod.rs @@ -12,7 +12,6 @@ mod ristretto_com_sig; pub mod ristretto_keys; mod ristretto_sig; pub mod serialize; -pub mod utils; // Re-export pub use dalek_range_proof::DalekRangeProofService; @@ -21,7 +20,7 @@ pub use self::{ ristretto_com_and_pub_sig::RistrettoComAndPubSig, ristretto_com_sig::RistrettoComSig, ristretto_keys::{RistrettoPublicKey, RistrettoSecretKey}, - ristretto_sig::{RistrettoSchnorr, RistrettoSchnorrWithDomain}, + ristretto_sig::RistrettoSchnorr, }; // test modules diff --git a/src/ristretto/ristretto_sig.rs b/src/ristretto/ristretto_sig.rs index b36f9865..f650ca57 100644 --- a/src/ristretto/ristretto_sig.rs +++ b/src/ristretto/ristretto_sig.rs @@ -3,46 +3,32 @@ use crate::{ ristretto::{RistrettoPublicKey, RistrettoSecretKey}, - signatures::{SchnorrSigChallenge, SchnorrSignature}, + signatures::SchnorrSignature, }; -/// # A Schnorr signature implementation on Ristretto +/// # A domain-separated Schnorr signature implementation on Ristretto /// /// Find out more about [Schnorr signatures](https://tlu.tarilabs.com/cryptography/digital_signatures/introduction.html). /// /// `RistrettoSchnorr` utilises the [curve25519-dalek](https://github.com/dalek-cryptography/curve25519-dalek1) /// implementation of `ristretto255` to provide Schnorr signature functionality. /// -/// In short, a Schnorr sig is made up of the pair _(R, s)_, where _R_ is a public key (of a secret nonce) and _s_ is -/// the signature. +/// You must supply a domain separator to provide context to the signature. /// -/// ## Creating signatures +/// An easy way to do this is by using the crate's `hash_domain!` macro. /// -/// You can create a `RisrettoSchnorr` from it's component parts: +/// Different signature contexts should use distinct domain separators to avoid cross-context misuse. /// -/// ```edition2018 -/// # use tari_crypto::ristretto::*; -/// # use tari_crypto::keys::*; -/// # use tari_crypto::signatures::SchnorrSignature; -/// # use tari_utilities::ByteArray; -/// # use tari_utilities::hex::Hex; -/// -/// let public_r = RistrettoPublicKey::from_hex( -/// "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919", -/// ) -/// .unwrap(); -/// let s = RistrettoSecretKey::from_bytes(b"10000000000000000000000000000000").unwrap(); -/// let sig = RistrettoSchnorr::new(public_r, s); -/// ``` +/// ## Creating signatures /// -/// or you can create a signature by signing a message: +/// Create a signature by signing a message using a secret key: /// /// ```rust +/// # use tari_crypto::hash_domain; /// # use tari_crypto::ristretto::*; /// # use tari_crypto::keys::*; -/// # use tari_crypto::signatures::SchnorrSignature; -/// # use tari_crypto::hash::blake2::Blake256; -/// # use digest::Digest; +/// +/// hash_domain!(ExampleDomain, "com.example"); /// /// fn get_keypair() -> (RistrettoSecretKey, RistrettoPublicKey) { /// let mut rng = rand::thread_rng(); @@ -51,18 +37,18 @@ use crate::{ /// (k, pk) /// } /// -/// #[allow(non_snake_case)] +/// # #[allow(non_snake_case)] /// let (k, P) = get_keypair(); /// let msg = "Small Gods"; -/// let sig = RistrettoSchnorr::sign_message(&k, &msg); +/// let sig = RistrettoSchnorr::::sign_message(&k, &msg).unwrap(); /// ``` /// -/// # Verifying signatures +/// ## Verifying signatures /// -/// Given a signature, (R,s) and a Challenge, e, you can verify that the signature is valid by calling the `verify` -/// method: +/// Verify a signature against a given public key and message using `verify_message`. /// /// ```edition2018 +/// # use tari_crypto::hash_domain; /// # use tari_crypto::ristretto::*; /// # use tari_crypto::keys::*; /// # use tari_crypto::signatures::SchnorrSignature; @@ -71,86 +57,56 @@ use crate::{ /// # use tari_utilities::ByteArray; /// # use digest::Digest; /// -/// let msg = "Maskerade"; -/// let k = RistrettoSecretKey::from_hex( -/// "bd0b253a619310340a4fa2de54cdd212eac7d088ee1dc47e305c3f6cbd020908", -/// ) -/// .unwrap(); -/// # #[allow(non_snake_case)] -/// let P = RistrettoPublicKey::from_secret_key(&k); -/// let sig: SchnorrSignature = -/// SchnorrSignature::sign_message(&k, msg).unwrap(); -/// assert!(sig.verify_message(&P, msg)); -/// ``` -pub type RistrettoSchnorr = SchnorrSignature; - -/// # A Schnorr signature implementation on Ristretto with a custom domain separation tag -/// -/// Usage is identical to [`RistrettoSchnorr`], except that you are able to specify the domain separation tag to use -/// when computing challenges for the signature. +/// hash_domain!(ExampleDomain, "com.example"); /// -/// ## Example -/// ```edition2018 -/// # use tari_crypto::ristretto::*; -/// # use tari_crypto::keys::*; -/// # use tari_crypto::hash_domain; -/// # use tari_crypto::signatures::SchnorrSignature; -/// # use tari_crypto::hash::blake2::Blake256; -/// # use tari_utilities::hex::*; -/// # use tari_utilities::ByteArray; -/// # use digest::Digest; -/// -/// hash_domain!(MyCustomDomain, "com.example.custom"); +/// fn get_keypair() -> (RistrettoSecretKey, RistrettoPublicKey) { +/// let mut rng = rand::thread_rng(); +/// let k = RistrettoSecretKey::random(&mut rng); +/// let pk = RistrettoPublicKey::from_secret_key(&k); +/// (k, pk) +/// } /// -/// let msg = "Maskerade"; -/// let k = RistrettoSecretKey::from_hex( -/// "bd0b253a619310340a4fa2de54cdd212eac7d088ee1dc47e305c3f6cbd020908", -/// ) -/// .unwrap(); /// # #[allow(non_snake_case)] -/// let P = RistrettoPublicKey::from_secret_key(&k); -/// let sig: SchnorrSignature = -/// SchnorrSignature::sign_message(&k, msg).unwrap(); +/// let (k, P) = get_keypair(); +/// let msg = "Small Gods"; +/// let sig = RistrettoSchnorr::::sign_message(&k, msg).unwrap(); +/// /// assert!(sig.verify_message(&P, msg)); /// ``` -pub type RistrettoSchnorrWithDomain = SchnorrSignature; +pub type RistrettoSchnorr = SchnorrSignature; #[cfg(test)] mod test { use digest::Digest; - use tari_utilities::{ - hex::{from_hex, to_hex, Hex}, - ByteArray, - }; + use tari_utilities::ByteArray; use crate::{ hash::blake2::Blake256, hash_domain, - keys::{PublicKey, SecretKey}, - ristretto::{ - ristretto_sig::RistrettoSchnorrWithDomain, - RistrettoPublicKey, - RistrettoSchnorr, - RistrettoSecretKey, - }, - signatures::{SchnorrSigChallenge, SchnorrSignature}, + keys::PublicKey, + ristretto::{RistrettoPublicKey, RistrettoSchnorr, RistrettoSecretKey}, }; + hash_domain!(TestDomain, "com.example.test"); + + /// Test defaults #[test] fn default() { - let sig = RistrettoSchnorr::default(); + let sig = RistrettoSchnorr::::default(); assert_eq!(sig.get_signature(), &RistrettoSecretKey::default()); assert_eq!(sig.get_public_nonce(), &RistrettoPublicKey::default()); } - /// Create a signature, and then verify it. Also checks that some invalid signatures fail to verify + /// Test raw signing and verification #[test] #[allow(non_snake_case)] fn raw_sign_and_verify_challenge() { + // Generate keys and the nonce let mut rng = rand::thread_rng(); let (k, P) = RistrettoPublicKey::random_keypair(&mut rng); let (r, R) = RistrettoPublicKey::random_keypair(&mut rng); - // Use sign raw, and bind the nonce and public key manually + + // Use raw signing, where we construct a challenge manually (but without domain separation for this example) let e = Blake256::new() .chain(P.as_bytes()) .chain(R.as_bytes()) @@ -158,115 +114,95 @@ mod test { .finalize(); let e_key = RistrettoSecretKey::from_bytes(&e).unwrap(); let s = &r + &e_key * &k; - let sig = RistrettoSchnorr::sign_raw(&k, r, &e).unwrap(); + let sig = RistrettoSchnorr::::sign_raw(&k, r, &e).unwrap(); + + // Examine the signature components let R_calc = sig.get_public_nonce(); assert_eq!(R, *R_calc); assert_eq!(sig.get_signature(), &s); + + // Assert the signature verifies against the correct challenge assert!(sig.verify_challenge(&P, &e)); - // Doesn't work for invalid credentials + + // Verification should fail if we replace the public key with anything else assert!(!sig.verify_challenge(&R, &e)); - // Doesn't work for different challenge + + // Verification should fail against any other challenge let wrong_challenge = Blake256::digest(b"Guards! Guards!"); assert!(!sig.verify_challenge(&P, &wrong_challenge)); } - /// This test checks that the linearity of Schnorr signatures hold, i.e. that s = s1 + s2 is validated by R1 + R2 - /// and P1 + P2. We do this by hand here rather than using the APIs to guard against regressions + /// Test that signatures are linear on the same key and message + /// These operations are defined by the API, but we need to check them manually just in case #[test] #[allow(non_snake_case)] fn test_signature_addition() { + // Generate keys and nonces let mut rng = rand::thread_rng(); - // Alice and Bob generate some keys and nonces let (k1, P1) = RistrettoPublicKey::random_keypair(&mut rng); - let (r1, R1) = RistrettoPublicKey::random_keypair(&mut rng); let (k2, P2) = RistrettoPublicKey::random_keypair(&mut rng); + let (r1, R1) = RistrettoPublicKey::random_keypair(&mut rng); let (r2, R2) = RistrettoPublicKey::random_keypair(&mut rng); - // Each of them creates the Challenge = H(R1 || R2 || P1 || P2 || m) + + // Generate a (non-separated) challenge with sums of components let e = Blake256::new() - .chain(R1.as_bytes()) - .chain(R2.as_bytes()) - .chain(P1.as_bytes()) - .chain(P2.as_bytes()) + .chain((&P1 + &P2).as_bytes()) + .chain((R1 + R2).as_bytes()) .chain(b"Moving Pictures") .finalize(); - // Calculate Alice's signature - let s1 = RistrettoSchnorr::sign_raw(&k1, r1, &e).unwrap(); - // Calculate Bob's signature - let s2 = RistrettoSchnorr::sign_raw(&k2, r2, &e).unwrap(); - // Now add the two signatures together - let s_agg = &s1 + &s2; - // Check that the multi-sig verifies - assert!(s_agg.verify_challenge(&(P1 + P2), &e)); - } - /// Ristretto scalars have a max value 2^255. This test checks that hashed messages above this value can still be - /// signed as a result of applying modulo arithmetic on the challenge value - #[test] - #[allow(non_snake_case)] - fn challenge_from_invalid_scalar() { - let mut rng = rand::thread_rng(); - let m = from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap(); - let k = RistrettoSecretKey::random(&mut rng); - let r = RistrettoSecretKey::random(&mut rng); - assert!(RistrettoSchnorr::sign_raw(&k, r, &m).is_ok()); + // Calculate summand signatures using each component + let s1 = RistrettoSchnorr::::sign_raw(&k1, r1, &e).unwrap(); + let s2 = RistrettoSchnorr::::sign_raw(&k2, r2, &e).unwrap(); + + // Confirm linearity holds against the nonce sum + assert!((&s1 + &s2).verify_challenge(&(P1 + P2), &e)); } + /// Test that domain-separated challenge generation isn't obviously broken #[test] #[allow(non_snake_case)] fn domain_separated_challenge() { - let P = - RistrettoPublicKey::from_hex("74896a30c89186b8194e25f8c1382f8d3081c5a182fb8f8a6d34f27fbefbfc70").unwrap(); - let R = - RistrettoPublicKey::from_hex("fa14cb581ce5717248444721242e6b195a482d503a853dea4acb513074d8d803").unwrap(); + // Generate keys and a nonce + let mut rng = rand::thread_rng(); + let (_, P) = RistrettoPublicKey::random_keypair(&mut rng); + let (_, R) = RistrettoPublicKey::random_keypair(&mut rng); + + // Generate the challenge let msg = "Moving Pictures"; - let hash = SchnorrSignature::<_, _, SchnorrSigChallenge>::construct_domain_separated_challenge::<_, Blake256>( - &R, &P, msg, - ); + let hash = RistrettoSchnorr::::construct_domain_separated_challenge::<_, Blake256>(&R, &P, msg); + + // Construct a non-separated challenge let naiive = Blake256::new() .chain(R.as_bytes()) .chain(P.as_bytes()) .chain(msg) .finalize() .to_vec(); - assert_ne!(hash.as_ref(), naiive.as_bytes()); - assert_eq!( - to_hex(hash.as_ref()), - "d8f6b29b641113c91175b8d44f265ff1167d58d5aa5ee03e6f1f521505b09d80" - ); - } - #[test] - #[allow(non_snake_case)] - fn custom_hash_domain() { - hash_domain!(TestDomain, "test.signature.com"); - let mut rng = rand::thread_rng(); - let (k, P) = RistrettoPublicKey::random_keypair(&mut rng); - let (r, _) = RistrettoPublicKey::random_keypair(&mut rng); - let msg = "Moving Pictures"; - // Using default domain - // NEVER re-use nonces in practice. This is done here explicitly to indicate that the domain separation - // prevents accidental signature duplication. - let sig1 = RistrettoSchnorr::sign_with_nonce_and_message(&k, r.clone(), msg).unwrap(); - // Using custom domain - let sig2 = RistrettoSchnorrWithDomain::::sign_with_nonce_and_message(&k, r, msg).unwrap(); - // The type system won't even let this compile :) - // assert_ne!(sig1, sig2); - // Prove that the nonces were reused. Again, NEVER do this - assert_eq!(sig1.get_public_nonce(), sig2.get_public_nonce()); - assert!(sig1.verify_message(&P, msg)); - assert!(sig2.verify_message(&P, msg)); - // But the signatures are different, for the same message, secret and nonce. - assert_ne!(sig1.get_signature(), sig2.get_signature()); + // They shouldn't match + assert_ne!(hash.as_ref(), naiive.as_bytes()); } + /// Test message signing and verification #[test] #[allow(non_snake_case)] fn sign_and_verify_message() { + // Generate keys let mut rng = rand::thread_rng(); let (k, P) = RistrettoPublicKey::random_keypair(&mut rng); - let sig = RistrettoSchnorr::sign_message(&k, "Queues are things that happen to other people").unwrap(); - assert!(sig.verify_message(&P, "Queues are things that happen to other people")); - assert!(!sig.verify_message(&P, "Qs are things that happen to other people")); - assert!(!sig.verify_message(&(&P + &P), "Queues are things that happen to other people")); + let (_, evil_P) = RistrettoPublicKey::random_keypair(&mut rng); + + // Generate messages + let msg = "Queues are things that happen to other people"; + let evil_msg = "Qs are things that happen to other people"; + + // Sign a message + let sig = RistrettoSchnorr::::sign_message(&k, msg).unwrap(); + + // Test successful and failed verification + assert!(sig.verify_message(&P, msg)); + assert!(!sig.verify_message(&P, evil_msg)); + assert!(!sig.verify_message(&evil_P, msg)); } } diff --git a/src/ristretto/utils.rs b/src/ristretto/utils.rs deleted file mode 100644 index d38bbbab..00000000 --- a/src/ristretto/utils.rs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2019. The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -//! Handy utility functions for use in tests and demo scripts - -use digest::Digest; -use tari_utilities::ByteArray; - -use crate::{ - keys::PublicKey, - ristretto::{RistrettoPublicKey, RistrettoSchnorr, RistrettoSecretKey}, - signatures::SchnorrSignatureError, -}; - -/// A set of keys and it's associated signature -pub struct SignatureSet { - /// The secret nonce - pub nonce: RistrettoSecretKey, - /// The public nonce - pub public_nonce: RistrettoPublicKey, - /// The message signed. Note that the [SignatureSet::public_nonce] is prepended to this message before signing - pub message: Vec, - /// The signature - pub signature: RistrettoSchnorr, -} - -/// Generate a random keypair and a signature for the provided message -/// -/// # Panics -/// -/// The function panics if it cannot generate a suitable signature -#[deprecated( - since = "0.16.0", - note = "Use SchnorrSignature::sign_message instead. This method will be removed in v1.0.0" -)] -pub fn sign( - private_key: &RistrettoSecretKey, - message: &[u8], -) -> Result { - let mut rng = rand::thread_rng(); - let (nonce, public_nonce) = RistrettoPublicKey::random_keypair(&mut rng); - let message = D::new() - .chain(public_nonce.as_bytes()) - .chain(message) - .finalize() - .to_vec(); - let e = RistrettoSecretKey::from_bytes(&message).map_err(|_| SchnorrSignatureError::InvalidChallenge)?; - let s = RistrettoSchnorr::sign_raw(private_key, nonce.clone(), e.as_bytes())?; - Ok(SignatureSet { - nonce, - public_nonce, - message, - signature: s, - }) -} diff --git a/src/signatures/schnorr.rs b/src/signatures/schnorr.rs index d929d657..bf17350a 100644 --- a/src/signatures/schnorr.rs +++ b/src/signatures/schnorr.rs @@ -19,14 +19,10 @@ use thiserror::Error; use crate::{ hash::blake2::Blake256, - hash_domain, hashing::{DomainSeparatedHash, DomainSeparatedHasher, DomainSeparation}, keys::{PublicKey, SecretKey}, }; -// Define the hashing domain for Schnorr signatures -hash_domain!(SchnorrSigChallenge, "com.tari.schnorr_signature", 1); - /// An error occurred during construction of a SchnorrSignature #[derive(Clone, Debug, Error, PartialEq, Eq, Deserialize, Serialize)] #[allow(missing_docs)] @@ -44,7 +40,7 @@ pub enum SchnorrSignatureError { #[allow(non_snake_case)] #[derive(Copy, Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))] -pub struct SchnorrSignature { +pub struct SchnorrSignature { public_nonce: P, signature: K, #[serde(skip)] @@ -230,7 +226,7 @@ where } } -impl<'a, 'b, P, K, H> Add<&'b SchnorrSignature> for &'a SchnorrSignature +impl<'a, 'b, P, K, H> Add<&'b SchnorrSignature> for &'a SchnorrSignature where P: PublicKey, &'a P: Add<&'b P, Output = P>, @@ -238,16 +234,16 @@ where &'a K: Add<&'b K, Output = K>, H: DomainSeparation, { - type Output = SchnorrSignature; + type Output = SchnorrSignature; - fn add(self, rhs: &'b SchnorrSignature) -> SchnorrSignature { + fn add(self, rhs: &'b SchnorrSignature) -> SchnorrSignature { let r_sum = self.get_public_nonce() + rhs.get_public_nonce(); let s_sum = self.get_signature() + rhs.get_signature(); SchnorrSignature::new(r_sum, s_sum) } } -impl<'a, P, K, H> Add> for &'a SchnorrSignature +impl<'a, P, K, H> Add> for &'a SchnorrSignature where P: PublicKey, for<'b> &'a P: Add<&'b P, Output = P>, @@ -255,9 +251,9 @@ where for<'b> &'a K: Add<&'b K, Output = K>, H: DomainSeparation, { - type Output = SchnorrSignature; + type Output = SchnorrSignature; - fn add(self, rhs: SchnorrSignature) -> SchnorrSignature { + fn add(self, rhs: SchnorrSignature) -> SchnorrSignature { let r_sum = self.get_public_nonce() + rhs.get_public_nonce(); let s_sum = self.get_signature() + rhs.get_signature(); SchnorrSignature::new(r_sum, s_sum) @@ -330,17 +326,3 @@ where self.signature.hash(state); } } - -#[cfg(test)] -mod test { - use crate::{hashing::DomainSeparation, signatures::SchnorrSigChallenge}; - - #[test] - fn schnorr_hash_domain() { - assert_eq!(SchnorrSigChallenge::domain(), "com.tari.schnorr_signature"); - assert_eq!( - SchnorrSigChallenge::domain_separation_tag("test"), - "com.tari.schnorr_signature.v1.test" - ); - } -} diff --git a/src/wasm/key_utils.rs b/src/wasm/key_utils.rs index 9c7de1d1..19d52887 100644 --- a/src/wasm/key_utils.rs +++ b/src/wasm/key_utils.rs @@ -14,17 +14,22 @@ use wasm_bindgen::prelude::*; use crate::{ hash::blake2::Blake256, + hash_domain, keys::{PublicKey, SecretKey}, ristretto::{ pedersen::{commitment_factory::PedersenCommitmentFactory, PedersenCommitment}, RistrettoComAndPubSig, RistrettoComSig, RistrettoPublicKey, - RistrettoSchnorr, RistrettoSecretKey, }, }; +// A placeholder hash domain for use in raw signature functions +// This is only here to satisfy the type system, and plays no role in signatures +// It should not be used for any other purpose, so it has a scary name +hash_domain!(UnsafeRawSignatureDomain, ""); + /// Result of calling [check_signature] and [check_comsig_signature] and [check_comandpubsig_signature] #[derive(Debug, Serialize, Deserialize, Default)] pub struct SignatureVerifyResult { @@ -34,17 +39,6 @@ pub struct SignatureVerifyResult { pub error: String, } -/// Result of calling [sign] -#[derive(Debug, Serialize, Deserialize, Default)] -pub struct SignResult { - /// The public nonce of the signature, if successful - pub public_nonce: Option, - /// The signature, if successful - pub signature: Option, - /// Will contain the error if one occurred, otherwise empty - pub error: String, -} - /// Result of calling [sign_comsig] #[derive(Debug, Serialize, Deserialize, Default)] pub struct ComSignResult { @@ -103,131 +97,6 @@ pub fn pubkey_from_secret(k: &str) -> Option { } } -/// Generate a Schnorr signature of the message using the given private key -#[wasm_bindgen] -pub fn sign(private_key: &str, msg: &str) -> JsValue { - let mut result = SignResult::default(); - let k = match RistrettoSecretKey::from_hex(private_key) { - Ok(k) => k, - _ => { - result.error = "Invalid private key".to_string(); - return serde_wasm_bindgen::to_value(&result).unwrap(); - }, - }; - sign_message_with_key(&k, msg, None, &mut result); - serde_wasm_bindgen::to_value(&result).unwrap() -} - -/// Generate a Schnorr signature of a challenge (that has already been hashed) using the given private -/// key and a specified private nonce. DO NOT reuse nonces. This method is provide for cases where a -/// public nonce has been used in the message. -#[wasm_bindgen] -pub fn sign_challenge_with_nonce(private_key: &str, private_nonce: &str, challenge_as_hex: &str) -> JsValue { - let mut result = SignResult::default(); - let k = match RistrettoSecretKey::from_hex(private_key) { - Ok(k) => k, - _ => { - result.error = "Invalid private key".to_string(); - return serde_wasm_bindgen::to_value(&result).unwrap(); - }, - }; - let r = match RistrettoSecretKey::from_hex(private_nonce) { - Ok(r) => r, - _ => { - result.error = "Invalid private nonce".to_string(); - return serde_wasm_bindgen::to_value(&result).unwrap(); - }, - }; - let pub_r = RistrettoPublicKey::from_secret_key(&r); - - let e = match from_hex(challenge_as_hex) { - Ok(e) => e, - _ => { - result.error = "Challenge was not valid HEX".to_string(); - return serde_wasm_bindgen::to_value(&result).unwrap(); - }, - }; - - let sig = match RistrettoSchnorr::sign_raw(&k, r, &e) { - Ok(s) => s, - Err(e) => { - result.error = format!("Could not create signature. {e}"); - return serde_wasm_bindgen::to_value(&result).unwrap(); - }, - }; - result.public_nonce = Some(pub_r.to_hex()); - result.signature = Some(sig.get_signature().to_hex()); - serde_wasm_bindgen::to_value(&result).unwrap() -} - -pub(super) fn sign_message_with_key( - k: &RistrettoSecretKey, - msg: &str, - r: Option<&RistrettoSecretKey>, - result: &mut SignResult, -) { - sign_with_key(k, msg.as_bytes(), r, result) -} - -#[allow(non_snake_case)] -pub(super) fn sign_with_key( - k: &RistrettoSecretKey, - msg: &[u8], - r: Option<&RistrettoSecretKey>, - result: &mut SignResult, -) { - let (r, R) = match r { - Some(r) => (r.clone(), RistrettoPublicKey::from_secret_key(r)), - None => RistrettoPublicKey::random_keypair(&mut OsRng), - }; - let P = RistrettoPublicKey::from_secret_key(k); - let e = RistrettoSchnorr::construct_domain_separated_challenge::<_, Blake256>(&R, &P, msg); - let sig = match RistrettoSchnorr::sign_raw(k, r, e.as_ref()) { - Ok(s) => s, - Err(e) => { - result.error = format!("Could not create signature. {e}"); - return; - }, - }; - result.public_nonce = Some(R.to_hex()); - result.signature = Some(sig.get_signature().to_hex()); -} - -/// Checks the validity of a Schnorr signature. Returns a [JsValue] of a serialized [SignatureVerifyResult] -#[allow(non_snake_case)] -#[wasm_bindgen] -pub fn check_signature(pub_nonce: &str, signature: &str, pub_key: &str, msg: &str) -> JsValue { - let mut result = SignatureVerifyResult::default(); - - let R = match RistrettoPublicKey::from_hex(pub_nonce) { - Ok(n) => n, - Err(_) => { - result.error = format!("{pub_nonce} is not a valid public nonce"); - return serde_wasm_bindgen::to_value(&result).unwrap(); - }, - }; - - let P = match RistrettoPublicKey::from_hex(pub_key) { - Ok(p) => p, - Err(_) => { - result.error = format!("{pub_key} is not a valid public key"); - return serde_wasm_bindgen::to_value(&result).unwrap(); - }, - }; - - let s = match RistrettoSecretKey::from_hex(signature) { - Ok(s) => s, - Err(_) => { - result.error = format!("{signature} is not a valid hex representation of a signature"); - return serde_wasm_bindgen::to_value(&result).unwrap(); - }, - }; - - let sig = RistrettoSchnorr::new(R, s); - result.result = sig.verify_message(&P, msg.as_bytes()); - serde_wasm_bindgen::to_value(&result).unwrap() -} - /// Generate a Commitment signature of the message using the given private key #[wasm_bindgen] pub fn sign_comsig(private_key_a: &str, private_key_x: &str, msg: &str) -> JsValue { @@ -720,7 +589,7 @@ mod test { use super::*; use crate::{ commitment::HomomorphicCommitmentFactory, - signatures::{CommitmentAndPublicKeySignature, CommitmentSignature, SchnorrSignature}, + signatures::{CommitmentAndPublicKeySignature, CommitmentSignature}, tari_utilities::{hex, ByteArray}, }; @@ -731,20 +600,10 @@ mod test { Blake256::digest(preimage.as_ref()) } - fn hash_hex>(preimage: T) -> String { - hex::to_hex(&hash(preimage)) - } - fn random_keypair() -> (RistrettoSecretKey, RistrettoPublicKey) { RistrettoPublicKey::random_keypair(&mut OsRng) } - fn create_signature(msg: &str) -> (RistrettoSchnorr, RistrettoPublicKey, RistrettoSecretKey) { - let (sk, pk) = random_keypair(); - let sig = SchnorrSignature::sign_message(&sk, msg.as_bytes()).unwrap(); - (sig, pk, sk) - } - fn create_commsig(msg: &str) -> (RistrettoComSig, PedersenCommitment) { let factory = PedersenCommitmentFactory::default(); let (sk_a, _) = random_keypair(); @@ -784,12 +643,6 @@ mod test { (sig, commitment, pubkey) } - fn key_hex() -> (RistrettoSecretKey, String) { - let key = RistrettoSecretKey::random(&mut OsRng); - let key_hex = key.to_hex(); - (key, key_hex) - } - #[wasm_bindgen_test] fn it_generates_a_keypair() { let (k, p): (String, String) = serde_wasm_bindgen::from_value(generate_keypair()).unwrap(); @@ -832,153 +685,6 @@ mod test { } } - mod sign { - use super::*; - - fn sign(private_key: &str, msg: &str) -> SignResult { - serde_wasm_bindgen::from_value(super::sign(private_key, msg)).unwrap() - } - - #[wasm_bindgen_test] - fn it_returns_error_if_invalid() { - assert!(!sign("", SAMPLE_CHALLENGE).error.is_empty()); - assert!(!sign(&["0"; 32].join(""), SAMPLE_CHALLENGE).error.is_empty()); - } - - #[wasm_bindgen_test] - fn it_returns_a_valid_signature() { - let (sk, pk) = random_keypair(); - let result = sign(&sk.to_hex(), SAMPLE_CHALLENGE); - assert!(result.error.is_empty()); - let p_nonce = RistrettoPublicKey::from_hex(&result.public_nonce.unwrap()).unwrap(); - let s = RistrettoSecretKey::from_hex(&result.signature.unwrap()).unwrap(); - assert!(RistrettoSchnorr::new(p_nonce, s).verify_message(&pk, SAMPLE_CHALLENGE)); - } - - #[wasm_bindgen_test] - fn it_does_not_reuse_the_nonce() { - let (sk, _) = random_keypair(); - let result = sign(&sk.to_hex(), SAMPLE_CHALLENGE); - let p_nonce1 = RistrettoPublicKey::from_hex(&result.public_nonce.unwrap()).unwrap(); - let result = sign(&sk.to_hex(), SAMPLE_CHALLENGE); - let p_nonce2 = RistrettoPublicKey::from_hex(&result.public_nonce.unwrap()).unwrap(); - assert_ne!(p_nonce1, p_nonce2); - } - } - - mod sign_challenge_with_nonce { - use super::*; - - fn sign_challenge_with_nonce(private_key: &str, private_nonce: &str, msg: &str) -> SignResult { - serde_wasm_bindgen::from_value(super::sign_challenge_with_nonce(private_key, private_nonce, msg)).unwrap() - } - - #[wasm_bindgen_test] - fn it_returns_error_if_invalid() { - let (_, key) = key_hex(); - assert!(!sign_challenge_with_nonce(&key, "", &hash_hex(SAMPLE_CHALLENGE)) - .error - .is_empty()); - assert!( - !sign_challenge_with_nonce(&["0"; 33].join(""), &key, &hash_hex(SAMPLE_CHALLENGE)) - .error - .is_empty() - ); - } - - #[wasm_bindgen_test] - fn it_returns_error_if_challenge_not_hashed() { - let (_, r) = key_hex(); - let (_, sk) = key_hex(); - let result = sign_challenge_with_nonce(&sk, &r, &hex::to_hex(SAMPLE_CHALLENGE.as_bytes())); - assert!(result.error.contains("An invalid challenge was provided")); - } - - #[wasm_bindgen_test] - fn it_returns_a_valid_signature() { - let (r, expected_pr) = random_keypair(); - let (sk, pk) = random_keypair(); - let e = hash(SAMPLE_CHALLENGE); - let result = sign_challenge_with_nonce(&sk.to_hex(), &r.to_hex(), &hex::to_hex(&e)); - assert_eq!(result.error, ""); - let p_nonce = RistrettoPublicKey::from_hex(&result.public_nonce.unwrap()).unwrap(); - assert_eq!(p_nonce, expected_pr); - let s = RistrettoSecretKey::from_hex(&result.signature.unwrap()).unwrap(); - assert!(RistrettoSchnorr::new(p_nonce, s).verify_challenge(&pk, &hash(SAMPLE_CHALLENGE))); - } - } - - mod check_signature { - use super::*; - - fn check_signature(pub_nonce: &str, signature: &str, pub_key: &str, msg: &str) -> SignatureVerifyResult { - serde_wasm_bindgen::from_value(super::check_signature(pub_nonce, signature, pub_key, msg)).unwrap() - } - - #[wasm_bindgen_test] - fn it_errors_given_invalid_data() { - fn it_errors(pub_nonce: &str, signature: &str, pub_key: &str, msg: &str) { - let result = check_signature(pub_nonce, signature, pub_key, msg); - assert!( - !result.error.is_empty(), - "check_signature did not fail with args ({}, {}, {}, {})", - pub_nonce, - signature, - pub_key, - msg - ); - assert!(!result.result); - } - - it_errors("", "", "", SAMPLE_CHALLENGE); - - let (sig, pk, _) = create_signature(SAMPLE_CHALLENGE); - it_errors(&sig.get_public_nonce().to_hex(), "", &pk.to_hex(), SAMPLE_CHALLENGE); - } - - #[wasm_bindgen_test] - fn it_fails_if_verification_is_invalid() { - fn it_fails(pub_nonce: &str, signature: &str, pub_key: &str, msg: &str) { - let result = check_signature(pub_nonce, signature, pub_key, msg); - assert!(result.error.is_empty()); - assert!(!result.result); - } - - let (sig, pk, _) = create_signature(SAMPLE_CHALLENGE); - it_fails( - &RistrettoPublicKey::default().to_hex(), - &sig.get_signature().to_hex(), - &pk.to_hex(), - SAMPLE_CHALLENGE, - ); - it_fails( - &sig.get_public_nonce().to_hex(), - &sig.get_signature().to_hex(), - &pk.to_hex(), - "wrong challenge", - ); - it_fails( - &sig.get_public_nonce().to_hex(), - &sig.get_signature().to_hex(), - &RistrettoPublicKey::default().to_hex(), - SAMPLE_CHALLENGE, - ); - } - - #[wasm_bindgen_test] - fn it_succeeds_given_valid_data() { - let (sig, pk, _) = create_signature(SAMPLE_CHALLENGE); - let result = check_signature( - &sig.get_public_nonce().to_hex(), - &sig.get_signature().to_hex(), - &pk.to_hex(), - SAMPLE_CHALLENGE, - ); - assert!(result.error.is_empty()); - assert!(result.result); - } - } - mod sign_comsig { use super::*; diff --git a/src/wasm/keyring.rs b/src/wasm/keyring.rs index ab3cbcb2..0a435e08 100644 --- a/src/wasm/keyring.rs +++ b/src/wasm/keyring.rs @@ -15,10 +15,7 @@ use crate::{ RistrettoPublicKey, RistrettoSecretKey, }, - wasm::{ - commitments::CommitmentResult, - key_utils::{sign_message_with_key, SignResult}, - }, + wasm::commitments::CommitmentResult, }; /// KeyRing is an in-memory key-value store for secret keys. Each secret key has a user-defined id associated with it. @@ -69,54 +66,6 @@ impl KeyRing { self.keys.get(id).map(|p| p.1.to_hex()) } - /// Sign a message using a private key - /// - /// Use can use a key in the keyring to generate a digital signature. To create the signature, the caller must - /// provide the `id` associated with the key, the message to sign, and a `nonce`. - /// - /// The return type is pretty unRust-like, but is structured to more closely model a JSON object. - /// - /// `keys::check_signature` is used to verify signatures. - pub fn sign(&self, id: &str, msg: &str) -> JsValue { - let mut result = SignResult::default(); - let k = self.keys.get(id); - if k.is_none() { - result.error = format!("Private key for '{id}' does not exist"); - return serde_wasm_bindgen::to_value(&result).unwrap(); - } - let k = k.unwrap(); - sign_message_with_key(&k.0, msg, None, &mut result); - serde_wasm_bindgen::to_value(&result).unwrap() - } - - /// Sign a message using a private key and a specific nonce - /// - /// Use can use a key in the keyring to generate a digital signature. To create the signature, the caller must - /// provide the `id` associated with the key, the message to sign, and a `nonce_id`. *Do not* reuse nonces. - /// This function is provided because in some signature schemes require the public nonce to be - /// part of the message. - /// - /// The return type is pretty unRust-like, but is structured to more closely model a JSON object. - /// - /// `keys::check_signature` is used to verify signatures. - pub fn sign_with_nonce(&self, id: &str, nonce_id: &str, msg: &str) -> JsValue { - let mut result = SignResult::default(); - let k = self.keys.get(id); - if k.is_none() { - result.error = format!("Private key for '{id}' does not exist"); - return serde_wasm_bindgen::to_value(&result).unwrap(); - } - let k = k.unwrap(); - let nonce = self.keys.get(nonce_id); - if nonce.is_none() { - result.error = format!("Private nonce for `{nonce_id}` does not exist"); - return serde_wasm_bindgen::to_value(&result).unwrap(); - } - let nonce = nonce.unwrap(); - sign_message_with_key(&k.0, msg, Some(&nonce.0), &mut result); - serde_wasm_bindgen::to_value(&result).unwrap() - } - /// Commits a value and private key for the given id using a Pedersen commitment. pub fn commit(&self, id: &str, value: u64) -> JsValue { let mut result = CommitmentResult::default(); @@ -151,9 +100,7 @@ mod test { use wasm_bindgen_test::*; use super::*; - use crate::{keys::SecretKey, ristretto::RistrettoSchnorr}; - - const SAMPLE_CHALLENGE: &str = "გამარჯობა"; + use crate::keys::SecretKey; fn new_keyring() -> KeyRing { let mut kr = KeyRing::new(); @@ -211,34 +158,6 @@ mod test { } } - mod sign { - use super::*; - - fn sign(kr: &KeyRing, id: &str) -> Result { - let result: SignResult = serde_wasm_bindgen::from_value(kr.sign(id, SAMPLE_CHALLENGE)).unwrap(); - if !result.error.is_empty() { - return Err(result.error); - } - let p_r = RistrettoPublicKey::from_hex(&result.public_nonce.unwrap()).unwrap(); - let s = RistrettoSecretKey::from_hex(&result.signature.unwrap()).unwrap(); - Ok(RistrettoSchnorr::new(p_r, s)) - } - - #[wasm_bindgen_test] - fn it_fails_if_key_doesnt_exist() { - let kr = new_keyring(); - sign(&kr, "doesn-exist").unwrap_err(); - } - - #[wasm_bindgen_test] - fn it_produces_a_valid_signature() { - let kr = new_keyring(); - let sig = sign(&kr, "a").unwrap(); - let pk = kr.expect_public_key("a"); - assert!(sig.verify_message(pk, SAMPLE_CHALLENGE)); - } - } - mod opens { use super::*;