diff --git a/Cargo.toml b/Cargo.toml index 739618fb..1fe9a40f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ version = "0.18.0" edition = "2018" [dependencies] -tari_utilities = { version = "0.5", default-features = false, features = ["zero"] } +tari_utilities = { version = "0.6", default-features = false, features = ["zero"] } blake2 = { version = "0.10", default-features = false } borsh = { version = "0.10" , optional = true , default-features = false} bulletproofs_plus = { package = "tari_bulletproofs_plus", version = "0.3", optional = true } @@ -27,7 +27,7 @@ snafu = { version = "0.7", default-features = false} zeroize = {version = "1" , default-features = false} [dev-dependencies] -tari_utilities = { version = "0.5", features = ["std"] } +tari_utilities = { version = "0.6", features = ["std"] } serde = { version = "1.0"} bincode = { version = "1.1" } criterion = { version = "0.5", default-features = false } diff --git a/benches/signatures.rs b/benches/signatures.rs index 224bd554..8ab2d9e9 100644 --- a/benches/signatures.rs +++ b/benches/signatures.rs @@ -46,7 +46,7 @@ fn sign_message(c: &mut Criterion) { b.iter_batched( gen_keypair, |d| { - let _sig = RistrettoSchnorr::sign_message(&d.k, d.m, &mut OsRng).unwrap(); + let _sig = RistrettoSchnorr::sign(&d.k, d.m, &mut OsRng).unwrap(); }, BatchSize::SmallInput, ); @@ -60,10 +60,10 @@ fn verify_message(c: &mut Criterion) { b.iter_batched( || { let d = gen_keypair(); - let s = RistrettoSchnorr::sign_message(&d.k, d.m, &mut OsRng).unwrap(); + let s = RistrettoSchnorr::sign(&d.k, d.m, &mut OsRng).unwrap(); (d, s) }, - |(d, s)| assert!(s.verify_message(&d.p, d.m)), + |(d, s)| assert!(s.verify(&d.p, d.m)), BatchSize::SmallInput, ); }); diff --git a/src/commitment.rs b/src/commitment.rs index 84561da0..9abfa208 100644 --- a/src/commitment.rs +++ b/src/commitment.rs @@ -68,8 +68,8 @@ where P: PublicKey impl

ByteArray for HomomorphicCommitment

where P: PublicKey { - fn from_bytes(bytes: &[u8]) -> Result { - let p = P::from_bytes(bytes)?; + fn from_canonical_bytes(bytes: &[u8]) -> Result { + let p = P::from_canonical_bytes(bytes)?; Ok(Self(p)) } diff --git a/src/errors.rs b/src/errors.rs index db0e623c..7c1a0ad6 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -70,7 +70,7 @@ pub enum HashingError { reason: String, }, /// The digest does not produce enough output - #[snafu(display("The digest does produce enough output.`{bytes}' bytes are required."))] + #[snafu(display("The digest does not produce enough output.`{bytes}' bytes are required."))] DigestTooShort { /// The number of bytes required bytes: usize, diff --git a/src/hashing.rs b/src/hashing.rs index be1b29b5..435cf87c 100644 --- a/src/hashing.rs +++ b/src/hashing.rs @@ -32,9 +32,16 @@ use alloc::string::String; use core::{marker::PhantomData, ops::Deref}; use blake2::{Blake2b, Blake2bVar}; -use digest::{consts::U32, Digest, FixedOutput, FixedOutputReset, Output, OutputSizeUser, Update}; +use digest::{ + consts::{U32, U64}, + Digest, + FixedOutput, + FixedOutputReset, + Output, + OutputSizeUser, + Update, +}; use sha3::Sha3_256; -use tari_utilities::ByteArray; use crate::{ alloc::string::ToString, @@ -209,10 +216,10 @@ impl AsRef<[u8]> for DomainSeparatedHash { /// Calculating a signature challenge /// /// ``` +/// # use blake2::Blake2b; +/// # use digest::{consts::U32, Digest}; /// # use tari_utilities::hex::{to_hex, Hex}; -/// use blake2::{Blake2b, Digest}; -/// use digest::consts::U32; -/// use tari_crypto::{ +/// # use tari_crypto::{ /// hash_domain, /// hashing::{DomainSeparatedHash, DomainSeparatedHasher, DomainSeparation}, /// }; @@ -418,6 +425,8 @@ impl LengthExtensionAttackResistant for Sha3_256 {} impl LengthExtensionAttackResistant for Blake2b {} +impl LengthExtensionAttackResistant for Blake2b {} + //------------------------------------------------ HMAC ------------------------------------------------------------ /// A domain separation tag for use in MAC derivation algorithms. pub struct MacDomain; @@ -517,6 +526,8 @@ impl Deref for Mac { /// `RistrettoKdf` is an implementation of [`DerivedKeyDomain`] that generates Ristretto keys. /// /// ``` +/// # use blake2::Blake2b; +/// # use digest::{consts::U64, Digest}; /// # use tari_utilities::ByteArray; /// # use tari_utilities::hex::Hex; /// # use tari_crypto::errors::HashingError; @@ -524,14 +535,12 @@ impl Deref for Mac { /// # use tari_crypto::keys::SecretKey; /// # use tari_crypto::ristretto::ristretto_keys::RistrettoKdf; /// # use tari_crypto::ristretto::RistrettoSecretKey; -/// # use digest::consts::U32; -/// # use blake2::Blake2b; /// /// fn wallet_keys( /// primary_key: &RistrettoSecretKey, /// index: usize, /// ) -> Result { -/// RistrettoKdf::generate::>( +/// RistrettoKdf::generate::>( /// primary_key.as_bytes(), /// &index.to_le_bytes(), /// "wallet", @@ -539,40 +548,47 @@ impl Deref for Mac { /// } /// /// let key = RistrettoSecretKey::from_hex( -/// "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c", +/// "a8fb609c5ab7cc07548b076b6c25cc3237c4526fb7a6dcb83b26f457b172c20a", /// ) /// .unwrap(); /// let key_1 = wallet_keys(&key, 1).unwrap(); /// assert_eq!( /// key_1.to_hex(), -/// "b778b8b5041fbde6c78be5bafd6d62633824bf303c97736d7337b3f6f70c4e0b" +/// "08106b88a2ff4c52d1d8b458cf34802df8655ba989a7d91351e3504e087a2e0c" /// ); /// let key_64 = wallet_keys(&key, 64).unwrap(); /// assert_eq!( /// key_64.to_hex(), -/// "09e5204c93406ef3334ff5f7a4d5d84199ceb9119fafcb98928fa95e95f0ae05" +/// "2c2206dadd2a21e71b6c52dd321572cde0f2b00e7116e1123fb580b09ed1b70e" /// ); /// ``` pub trait DerivedKeyDomain: DomainSeparation { /// The associated derived secret key type type DerivedKeyType: SecretKey; - /// Derive a key from the input key using a suitable domain separation tag and the given application label. - /// An error is returned if the supplied primary key isn't at least as long as the digest algorithm's output size. + /// Derive a key from the input key using a suitable domain separation tag and the given application label by wide + /// reduction. An error is returned if the supplied primary key isn't at least as long as the derived key. /// If the digest's output size is not sufficient to generate the derived key type, then an error will be thrown. fn generate(primary_key: &[u8], data: &[u8], label: &'static str) -> Result where Self: Sized, D: Digest + Update, { - if primary_key.as_ref().len() < ::output_size() { + // Ensure the primary key is at least as long as the derived key + if primary_key.len() < ::KEY_LEN { return Err(HashingError::InputTooShort {}); } + + // Ensure the digest length is suitable for wide reduction + if ::output_size() != ::WIDE_REDUCTION_LEN { + return Err(HashingError::InputTooShort {}); + } + let hash = DomainSeparatedHasher::::new_with_label(label) .chain(primary_key) .chain(data) .finalize(); - let derived_key = Self::DerivedKeyType::from_bytes(hash.as_ref()) + let derived_key = Self::DerivedKeyType::from_uniform_bytes(hash.as_ref()) .map_err(|e| HashingError::ConversionFromBytes { reason: e.to_string() })?; Ok(derived_key) } diff --git a/src/keys.rs b/src/keys.rs index 14f8e250..b1692f26 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -9,7 +9,7 @@ use core::ops::Add; use rand_core::{CryptoRng, RngCore}; -use tari_utilities::ByteArray; +use tari_utilities::{ByteArray, ByteArrayError}; use zeroize::{Zeroize, ZeroizeOnDrop}; /// A trait specifying common behaviour for representing `SecretKey`s. Specific elliptic curve @@ -32,6 +32,9 @@ pub trait SecretKey: /// The length of the byte encoding of a key, in bytes const KEY_LEN: usize; + /// The number of bytes used for construction by wide reduction + const WIDE_REDUCTION_LEN: usize; + /// The length of the byte encoding of a key, in bytes fn key_length() -> usize { Self::KEY_LEN @@ -39,6 +42,10 @@ pub trait SecretKey: /// Generates a random secret key fn random(rng: &mut R) -> Self; + + /// Generates a secret key from a slice of uniformly-distributed bytes using wide reduction + /// If the number of bytes is incorrect, this will fail + fn from_uniform_bytes(bytes: &[u8]) -> Result; } //---------------------------------------- Public Keys ----------------------------------------// diff --git a/src/ristretto/mod.rs b/src/ristretto/mod.rs index af3102db..de44c132 100644 --- a/src/ristretto/mod.rs +++ b/src/ristretto/mod.rs @@ -13,7 +13,6 @@ pub mod ristretto_keys; mod ristretto_sig; #[cfg(feature = "serde")] pub mod serialize; -pub mod utils; pub use self::{ ristretto_com_and_pub_sig::RistrettoComAndPubSig, diff --git a/src/ristretto/pedersen/mod.rs b/src/ristretto/pedersen/mod.rs index 1230c05c..7fd70f49 100644 --- a/src/ristretto/pedersen/mod.rs +++ b/src/ristretto/pedersen/mod.rs @@ -90,7 +90,7 @@ mod test { let (_, p) = RistrettoPublicKey::random_keypair(&mut rng); let c = PedersenCommitment::from_public_key(&p); assert_eq!(c.as_public_key(), &p); - let c2 = PedersenCommitment::from_bytes(c.as_bytes()).unwrap(); + let c2 = PedersenCommitment::from_canonical_bytes(c.as_bytes()).unwrap(); assert_eq!(c, c2); } diff --git a/src/ristretto/ristretto_com_and_pub_sig.rs b/src/ristretto/ristretto_com_and_pub_sig.rs index 907b5beb..d3fd04f1 100644 --- a/src/ristretto/ristretto_com_and_pub_sig.rs +++ b/src/ristretto/ristretto_com_and_pub_sig.rs @@ -30,9 +30,18 @@ use crate::{ /// "8063d85e151abee630e643e2b3dc47bfaeb8aa859c9d10d60847985f286aad19", /// ) /// .unwrap(); -/// let u_a = RistrettoSecretKey::from_bytes(b"10000000000000000000000010000000").unwrap(); -/// let u_x = RistrettoSecretKey::from_bytes(b"a00000000000000000000000a0000000").unwrap(); -/// let u_y = RistrettoSecretKey::from_bytes(b"a00000000000000000000000a0000000").unwrap(); +/// let u_a = RistrettoSecretKey::from_hex( +/// "a8fb609c5ab7cc07548b076b6c25cc3237c4526fb7a6dcb83b26f457b172c20a", +/// ) +/// .unwrap(); +/// let u_x = RistrettoSecretKey::from_hex( +/// "0e689df8ad4ad9d2fd5aaf8cb0a66d85cb0d4b7a380405514d453625813b0b0f", +/// ) +/// .unwrap(); +/// let u_y = RistrettoSecretKey::from_hex( +/// "f494050bd0d4ed0ec514cdce9430d0564df6b35d2a12b7daa0e99c7d94a06509", +/// ) +/// .unwrap(); /// let sig = RistrettoComAndPubSig::new(ephemeral_commitment, ephemeral_pubkey, u_a, u_x, u_y); /// ``` /// @@ -48,7 +57,7 @@ use crate::{ /// # use tari_crypto::ristretto::pedersen::*; /// use tari_crypto::ristretto::pedersen::commitment_factory::PedersenCommitmentFactory; /// use tari_utilities::hex::Hex; -/// use digest::consts::U32; +/// use digest::consts::U64; /// /// let mut rng = rand::thread_rng(); /// let a_val = RistrettoSecretKey::random(&mut rng); @@ -57,7 +66,7 @@ use crate::{ /// let a_nonce = RistrettoSecretKey::random(&mut rng); /// let x_nonce = RistrettoSecretKey::random(&mut rng); /// let y_nonce = RistrettoSecretKey::random(&mut rng); -/// let e = Blake2b::::digest(b"Maskerade"); // In real life, this should be strong Fiat-Shamir! +/// let e = Blake2b::::digest(b"Maskerade"); // In real life, this should be strong Fiat-Shamir! /// let factory = PedersenCommitmentFactory::default(); /// let commitment = factory.commit(&x_val, &a_val); /// let pubkey = RistrettoPublicKey::from_secret_key(&y_val); @@ -72,8 +81,8 @@ pub type RistrettoComAndPubSig = CommitmentAndPublicKeySignature::new() + // Challenge; doesn't use domain-separated Fiat-Shamir, so it's for testing only! + let challenge = Blake2b::::new() .chain_update(commitment.as_bytes()) .chain_update(pubkey.as_bytes()) .chain_update(ephemeral_commitment.as_bytes()) .chain_update(ephemeral_pubkey.as_bytes()) .chain_update(b"Small Gods") .finalize(); - let e_key = RistrettoSecretKey::from_bytes(&challenge).unwrap(); + let e_key = RistrettoSecretKey::from_uniform_bytes(&challenge).unwrap(); // Responses let u_a = &r_a + e_key.clone() * &a_value; @@ -178,7 +187,7 @@ mod test { assert!(!sig.verify_challenge(&commitment, &evil_pubkey, &challenge, &factory, &mut rng)); // A different challenge should fail - let evil_challenge = Blake2b::::digest(b"Guards! Guards!"); + let evil_challenge = Blake2b::::digest(b"Guards! Guards!"); assert!(!sig.verify_challenge(&commitment, &pubkey, &evil_challenge, &factory, &mut rng)); } @@ -205,7 +214,7 @@ mod test { let ephemeral_pubkey = RistrettoPublicKey::from_secret_key(&r_y); // Challenge; doesn't use proper Fiat-Shamir, so it's for testing only! - let challenge = Blake2b::::new() + let challenge = Blake2b::::new() .chain_update(commitment.as_bytes()) .chain_update(pubkey.as_bytes()) .chain_update(ephemeral_commitment.as_bytes()) @@ -292,7 +301,7 @@ mod test { let ephemeral_pubkey_bob = RistrettoPublicKey::from_secret_key(&r_y_bob); // The challenge is common to Alice and Bob; here we use an arbitrary hash - let challenge = Blake2b::::digest(b"Test challenge"); + let challenge = Blake2b::::digest(b"Test challenge"); // Alice's signature let sig_alice = RistrettoComAndPubSig::sign( @@ -338,28 +347,6 @@ mod test { assert!(sig_sum.verify_challenge(&commitment_sum, &pubkey_sum, &challenge, &factory, &mut rng)) } - /// 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] - fn challenge_from_invalid_scalar() { - let mut rng = rand::thread_rng(); - let factory = PedersenCommitmentFactory::default(); - - let a_value = RistrettoSecretKey::random(&mut rng); - let x_value = RistrettoSecretKey::random(&mut rng); - let y_value = RistrettoSecretKey::random(&mut rng); - - let message = from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap(); - - let r_a = RistrettoSecretKey::random(&mut rng); - let r_x = RistrettoSecretKey::random(&mut rng); - let r_y = RistrettoSecretKey::random(&mut rng); - - assert!( - RistrettoComAndPubSig::sign(&a_value, &x_value, &y_value, &r_a, &r_x, &r_y, &message, &factory).is_ok() - ); - } - #[test] fn to_vec() { let sig = RistrettoComAndPubSig::default(); diff --git a/src/ristretto/ristretto_com_sig.rs b/src/ristretto/ristretto_com_sig.rs index 6e81fc73..4d790b38 100644 --- a/src/ristretto/ristretto_com_sig.rs +++ b/src/ristretto/ristretto_com_sig.rs @@ -13,7 +13,7 @@ use crate::{ /// /// ## Examples /// -/// You can create a `RistrettoComSig` from it's component parts: +/// You can create a `RistrettoComSig` from its component parts: /// /// ```edition2018 /// # use tari_crypto::ristretto::*; @@ -26,8 +26,14 @@ use crate::{ /// "8063d85e151abee630e643e2b3dc47bfaeb8aa859c9d10d60847985f286aad19", /// ) /// .unwrap(); -/// let u = RistrettoSecretKey::from_bytes(b"10000000000000000000000010000000").unwrap(); -/// let v = RistrettoSecretKey::from_bytes(b"a00000000000000000000000a0000000").unwrap(); +/// let u = RistrettoSecretKey::from_hex( +/// "a8fb609c5ab7cc07548b076b6c25cc3237c4526fb7a6dcb83b26f457b172c20a", +/// ) +/// .unwrap(); +/// let v = RistrettoSecretKey::from_hex( +/// "0e689df8ad4ad9d2fd5aaf8cb0a66d85cb0d4b7a380405514d453625813b0b0f", +/// ) +/// .unwrap(); /// let sig = RistrettoComSig::new(r_pub, u, v); /// ``` /// @@ -40,22 +46,20 @@ use crate::{ /// # use digest::Digest; /// # use tari_crypto::commitment::HomomorphicCommitmentFactory; /// # use tari_crypto::ristretto::pedersen::*; +/// use blake2::Blake2b; +/// use digest::consts::U64; /// use tari_crypto::ristretto::pedersen::commitment_factory::PedersenCommitmentFactory; /// use tari_utilities::hex::Hex; -/// use blake2::Blake2b; -/// use digest::consts::U32; /// /// let mut rng = rand::thread_rng(); /// let a_val = RistrettoSecretKey::random(&mut rng); /// let x_val = RistrettoSecretKey::random(&mut rng); /// let a_nonce = RistrettoSecretKey::random(&mut rng); /// let x_nonce = RistrettoSecretKey::random(&mut rng); -/// let e = Blake2b::::digest(b"Maskerade"); +/// let e = Blake2b::::digest(b"Maskerade"); /// let factory = PedersenCommitmentFactory::default(); /// let commitment = factory.commit(&x_val, &a_val); -/// // println!("commitment: {:?}", commitment.to_hex()); /// let sig = RistrettoComSig::sign(&a_val, &x_val, &a_nonce, &x_nonce, &e, &factory).unwrap(); -/// // println!("sig: R {:?} u {:?} v {:?}", sig.public_nonce().to_hex(), sig.u().to_hex(), sig.v().to_hex()); /// assert!(sig.verify_challenge(&commitment, &e, &factory)); /// ``` /// @@ -73,27 +77,27 @@ use crate::{ /// # use tari_utilities::ByteArray; /// # use digest::Digest; /// use blake2::Blake2b; -/// use digest::consts::U32; +/// use digest::consts::U64; /// use tari_crypto::ristretto::pedersen::commitment_factory::PedersenCommitmentFactory; /// /// let commitment = HomomorphicCommitment::from_hex( -/// "167c6df11bf8106e89328c297e57423dc2a9be53df1ee63f6e50b4610104ab4a", +/// "869b83416643258f1e03d028b5d0c652dc5b09decdae4a645fc5a43d87bd0a3e", /// ) /// .unwrap(); /// let r_nonce = HomomorphicCommitment::from_hex( -/// "4033e00996e61df2ea1abd1494b751b946663e21a20e2729c6592712beb15356", +/// "665400676bdf8b07679629f703ea86e9cfc7e145f0768d2fdde4bd257009260d", /// ) /// .unwrap(); /// let u = RistrettoSecretKey::from_hex( -/// "f44bbc3374b172f77ffa8b904ddf0ad9f879b3e6183f9e440c57e7f01e851300", +/// "f62fccf7734099d32937f7f767757abcb6eca70f43b3a7fb6500b2cb9ea12b02", /// ) /// .unwrap(); /// let v = RistrettoSecretKey::from_hex( -/// "fd54afb2d8008c8a3af10272b24161247b2b7ae11687813fe9fb03e34dd7f009", +/// "cb9e34a7745cabaec0f9b2c3e217bf18fbe7ee8f4c83c1a523cead32ec9b4700", /// ) /// .unwrap(); /// let sig = RistrettoComSig::new(r_nonce, u, v); -/// let e = Blake2b::::digest(b"Maskerade"); +/// let e = Blake2b::::digest(b"Maskerade"); /// let factory = PedersenCommitmentFactory::default(); /// assert!(sig.verify_challenge(&commitment, &e, &factory)); /// ``` @@ -102,8 +106,8 @@ pub type RistrettoComSig = CommitmentSignature::new() + let challenge = Blake2b::::new() .chain_update(commitment.as_bytes()) .chain_update(nonce_commitment.as_bytes()) .chain_update(b"Small Gods") .finalize(); - let e_key = RistrettoSecretKey::from_bytes(&challenge).unwrap(); + let e_key = RistrettoSecretKey::from_uniform_bytes(&challenge).unwrap(); let u_value = &k_1 + e_key.clone() * &x_value; let v_value = &k_2 + e_key * &a_value; let sig = RistrettoComSig::sign(&a_value, &x_value, &k_2, &k_1, &challenge, &factory).unwrap(); @@ -159,7 +163,7 @@ mod test { // Doesn't work for invalid credentials assert!(!sig.verify_challenge(&nonce_commitment, &challenge, &factory)); // Doesn't work for different challenge - let wrong_challenge = Blake2b::::digest(b"Guards! Guards!"); + let wrong_challenge = Blake2b::::digest(b"Guards! Guards!"); assert!(!sig.verify_challenge(&commitment, &wrong_challenge, &factory)); } @@ -185,7 +189,7 @@ mod test { let k_2_bob = RistrettoSecretKey::random(&mut rng); let nonce_commitment_bob = factory.commit(&k_1_bob, &k_2_bob); // Each of them creates the Challenge committing to both commitments of both parties - let challenge = Blake2b::::new() + let challenge = Blake2b::::new() .chain_update(commitment_alice.as_bytes()) .chain_update(commitment_bob.as_bytes()) .chain_update(nonce_commitment_alice.as_bytes()) @@ -212,20 +216,6 @@ mod test { assert!(s_agg.verify_challenge(&combined_commitment, &challenge, &factory)); } - /// 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] - fn challenge_from_invalid_scalar() { - let mut rng = rand::thread_rng(); - let factory = PedersenCommitmentFactory::default(); - let a_value = RistrettoSecretKey::random(&mut rng); - let x_value = RistrettoSecretKey::random(&mut rng); - let message = from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap(); - let k_1 = RistrettoSecretKey::random(&mut rng); - let k_2 = RistrettoSecretKey::random(&mut rng); - assert!(RistrettoComSig::sign(&a_value, &x_value, &k_2, &k_1, &message, &factory).is_ok()); - } - #[test] fn to_vec() { let sig = RistrettoComSig::default(); diff --git a/src/ristretto/ristretto_keys.rs b/src/ristretto/ristretto_keys.rs index 6a53b287..16fc25c3 100644 --- a/src/ristretto/ristretto_keys.rs +++ b/src/ristretto/ristretto_keys.rs @@ -22,7 +22,7 @@ use digest::{consts::U64, Digest}; use once_cell::sync::OnceCell; use rand_core::{CryptoRng, RngCore}; use tari_utilities::{hex::Hex, ByteArray, ByteArrayError, Hashable}; -use zeroize::{Zeroize, ZeroizeOnDrop}; +use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing}; use crate::{ errors::HashingError, @@ -44,7 +44,7 @@ use crate::{ /// use tari_utilities::{hex::Hex, ByteArray}; /// /// let mut rng = rand::thread_rng(); -/// let _k1 = RistrettoSecretKey::from_bytes(&[ +/// let _k1 = RistrettoSecretKey::from_canonical_bytes(&[ /// 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /// 0, 0, /// ]); @@ -66,7 +66,7 @@ impl borsh::BorshDeserialize for RistrettoSecretKey { fn deserialize_reader(reader: &mut R) -> Result where R: borsh::maybestd::io::Read { let bytes: Vec = borsh::BorshDeserialize::deserialize_reader(reader)?; - Self::from_bytes(bytes.as_slice()) + Self::from_canonical_bytes(bytes.as_slice()) .map_err(|e| borsh::maybestd::io::Error::new(borsh::maybestd::io::ErrorKind::InvalidInput, e.to_string())) } } @@ -74,29 +74,49 @@ impl borsh::BorshDeserialize for RistrettoSecretKey { //----------------------------------------- Ristretto Secret Key ------------------------------------------------// impl SecretKey for RistrettoSecretKey { const KEY_LEN: usize = 32; + const WIDE_REDUCTION_LEN: usize = 64; /// Return a random secret key on the `ristretto255` curve using the supplied CSPRNG. fn random(rng: &mut R) -> Self { RistrettoSecretKey(Scalar::random(rng)) } + + /// Return a secret key computed from a uniform byte slice using wide reduction + /// If the byte array is not exactly 64 bytes, returns an error + fn from_uniform_bytes(bytes: &[u8]) -> Result { + if bytes.len() != Self::WIDE_REDUCTION_LEN { + return Err(ByteArrayError::IncorrectLength {}); + } + + let mut bytes_copied = Zeroizing::new([0u8; Self::WIDE_REDUCTION_LEN]); + bytes_copied.copy_from_slice(bytes); + + Ok(RistrettoSecretKey(Scalar::from_bytes_mod_order_wide(&bytes_copied))) + } } //------------------------------------- Ristretto Secret Key ByteArray ---------------------------------------------// impl ByteArray for RistrettoSecretKey { - /// Create a secret key on the Ristretto255 curve using the given little-endian byte array. If the byte array is - /// not exactly 32 bytes long, `from_bytes` returns an error. This function is guaranteed to return a valid key - /// in the group since it performs a mod _l_ on the input. - fn from_bytes(bytes: &[u8]) -> Result + /// Return a secret key computed from a canonical byte array + /// If the byte array is not exactly 32 bytes, returns an error + /// If the byte array does not represent a canonical encoding, returns an error + fn from_canonical_bytes(bytes: &[u8]) -> Result where Self: Sized { - if bytes.len() != 32 { + if bytes.len() != Self::KEY_LEN { return Err(ByteArrayError::IncorrectLength {}); } - let mut a = [0u8; 32]; - a.copy_from_slice(bytes); - let k = Scalar::from_bytes_mod_order(a); - a.zeroize(); - Ok(RistrettoSecretKey(k)) + + let mut bytes_copied = [0u8; 32]; + bytes_copied.copy_from_slice(bytes); + let scalar = Option::::from(Scalar::from_canonical_bytes(bytes_copied)).ok_or( + ByteArrayError::ConversionError { + reason: ("Invalid canonical scalar byte array".to_string()), + }, + )?; + bytes_copied.zeroize(); + + Ok(RistrettoSecretKey(scalar)) } /// Return the byte array for the secret key in little-endian order @@ -245,7 +265,7 @@ impl<'a> Borrow for &'a RistrettoSecretKey { /// use tari_utilities::{hex::Hex, ByteArray}; /// /// let mut rng = rand::thread_rng(); -/// let _p1 = RistrettoPublicKey::from_bytes(&[ +/// let _p1 = RistrettoPublicKey::from_canonical_bytes(&[ /// 224, 196, 24, 247, 200, 217, 196, 205, 215, 57, 91, 147, 234, 18, 79, 58, 217, 144, 33, /// 187, 104, 29, 252, 51, 2, 169, 217, 154, 46, 83, 230, 78, /// ]); @@ -273,7 +293,7 @@ impl borsh::BorshDeserialize for RistrettoPublicKey { fn deserialize_reader(reader: &mut R) -> Result where R: borsh::maybestd::io::Read { let bytes: Vec = borsh::BorshDeserialize::deserialize_reader(reader)?; - Self::from_bytes(bytes.as_slice()) + Self::from_canonical_bytes(bytes.as_slice()) .map_err(|e| borsh::maybestd::io::Error::new(borsh::maybestd::io::ErrorKind::InvalidInput, e.to_string())) } } @@ -484,7 +504,7 @@ impl ByteArray for RistrettoPublicKey { /// the following circumstances: /// * The byte array is not exactly 32 bytes /// * The byte array does not represent a valid (compressed) point on the ristretto255 curve - fn from_bytes(bytes: &[u8]) -> Result + fn from_canonical_bytes(bytes: &[u8]) -> Result where Self: Sized { // Check the length here, because The Ristretto constructor panics rather than returning an error if bytes.len() != 32 { @@ -598,7 +618,8 @@ impl From for CompressedRistretto { #[cfg(test)] mod test { - use digest::consts::U32; + use blake2::Blake2b; + use digest::consts::{U32, U64}; use tari_utilities::ByteArray; use super::*; @@ -629,7 +650,7 @@ mod test { #[test] fn invalid_secret_key_bytes() { - RistrettoSecretKey::from_bytes(&[1, 2, 3]).expect_err("Secret keys should be 32 bytes"); + RistrettoSecretKey::from_canonical_bytes(&[1, 2, 3]).expect_err("Secret keys should be 32 bytes"); } #[test] @@ -659,7 +680,7 @@ mod test { for i in 0u8..16 { let pk = RistrettoPublicKey::from_hex(encodings_of_small_multiples[i as usize]).unwrap(); bytes[0] = i; - let sk = RistrettoSecretKey::from_bytes(&bytes).unwrap(); + let sk = RistrettoSecretKey::from_canonical_bytes(&bytes).unwrap(); let pk2 = RistrettoPublicKey::from_secret_key(&sk); assert_eq!(pk, pk2); } @@ -967,35 +988,38 @@ mod test { } #[test] - fn kdf_key_too_short() { - let err = RistrettoKdf::generate::>(b"this_key_is_too_short", b"data", "test").err(); + fn kdf_too_short() { + let err = RistrettoKdf::generate::>(b"this_hasher_is_too_short", b"data", "test").err(); + assert!(matches!(err, Some(HashingError::InputTooShort {}))); + + let err = RistrettoKdf::generate::>(b"this_key_is_too_short", b"data", "test").err(); assert!(matches!(err, Some(HashingError::InputTooShort {}))); } #[test] fn kdf_test() { let key = - RistrettoSecretKey::from_hex("b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c").unwrap(); - let derived1 = RistrettoKdf::generate::>(key.as_bytes(), b"derived1", "test").unwrap(); - let derived2 = RistrettoKdf::generate::>(key.as_bytes(), b"derived2", "test").unwrap(); + RistrettoSecretKey::from_hex("45c5b950e04167785ff735bead8d746740db04bce3ee2c1f6523bdc59023e50e").unwrap(); + let derived1 = RistrettoKdf::generate::>(key.as_bytes(), b"derived1", "test").unwrap(); + let derived2 = RistrettoKdf::generate::>(key.as_bytes(), b"derived2", "test").unwrap(); assert_eq!( derived1.to_hex(), - "e8df6fa40344c1fde721e9a35d46daadb48dc66f7901a9795ebb0374474ea601" + "22deb0c38ec2dc9f741912f6e3c2cd3f76a5b33142a289da15eecdcd882bda06" ); assert_eq!( derived2.to_hex(), - "3ae035e2663d9c561300cca67743ccdb56ea07ca7dacd8394356c4354b030e0c" + "fcca9ca5c993d817581ab6040d92feef4c78529b5a485b51d0e11af427944207" ); } #[test] fn visibility_test() { let key = - RistrettoSecretKey::from_hex("b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c").unwrap(); + RistrettoSecretKey::from_hex("cd4f563f5e53a67ade2400b281e75f4b306253501da6529338e7be4f2b5c6200").unwrap(); let invisible = format!("{key:?}"); - assert!(!invisible.contains("016c")); + assert!(invisible.contains("***")); let visible = format!("{:?}", key.reveal()); - assert!(visible.contains("016c")); + assert!(visible.contains("cd4f563f5e53a67ade2400b281e75f4b306253501da6529338e7be4f2b5c6200")); } #[test] diff --git a/src/ristretto/ristretto_sig.rs b/src/ristretto/ristretto_sig.rs index 9e1c152e..055821b4 100644 --- a/src/ristretto/ristretto_sig.rs +++ b/src/ristretto/ristretto_sig.rs @@ -18,7 +18,7 @@ use crate::{ /// /// ## Creating signatures /// -/// You can create a `RisrettoSchnorr` from it's component parts: +/// You can create a `RisrettoSchnorr` from its component parts: /// /// ```edition2018 /// # use tari_crypto::ristretto::*; @@ -31,7 +31,10 @@ use crate::{ /// "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919", /// ) /// .unwrap(); -/// let s = RistrettoSecretKey::from_bytes(b"10000000000000000000000000000000").unwrap(); +/// let s = RistrettoSecretKey::from_hex( +/// "f62fccf7734099d32937f7f767757abcb6eca70f43b3a7fb6500b2cb9ea12b02", +/// ) +/// .unwrap(); /// let sig = RistrettoSchnorr::new(public_r, s); /// ``` /// @@ -55,7 +58,7 @@ use crate::{ /// let (k, P) = get_keypair(); /// let msg = "Small Gods"; /// let mut rng = thread_rng(); -/// let sig = RistrettoSchnorr::sign_message(&k, &msg, &mut rng); +/// let sig = RistrettoSchnorr::sign(&k, &msg, &mut rng); /// ``` /// /// # Verifying signatures @@ -81,8 +84,8 @@ use crate::{ /// let P = RistrettoPublicKey::from_secret_key(&k); /// let mut rng = thread_rng(); /// let sig: SchnorrSignature = -/// SchnorrSignature::sign_message(&k, msg, &mut rng).unwrap(); -/// assert!(sig.verify_message(&P, msg)); +/// SchnorrSignature::sign(&k, msg, &mut rng).unwrap(); +/// assert!(sig.verify(&P, msg)); /// ``` pub type RistrettoSchnorr = SchnorrSignature; @@ -113,17 +116,17 @@ pub type RistrettoSchnorr = SchnorrSignature = -/// SchnorrSignature::sign_message(&k, msg, &mut rng).unwrap(); -/// assert!(sig.verify_message(&P, msg)); +/// SchnorrSignature::sign(&k, msg, &mut rng).unwrap(); +/// assert!(sig.verify(&P, msg)); /// ``` pub type RistrettoSchnorrWithDomain = SchnorrSignature; #[cfg(test)] mod test { use blake2::Blake2b; - use digest::{consts::U32, Digest}; + use digest::{consts::U64, Digest}; use tari_utilities::{ - hex::{from_hex, to_hex, Hex}, + hex::{to_hex, Hex}, ByteArray, }; @@ -154,23 +157,23 @@ mod test { 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 - let e = Blake2b::::new() + let e = Blake2b::::new() .chain_update(P.as_bytes()) .chain_update(R.as_bytes()) .chain_update(b"Small Gods") .finalize(); - let e_key = RistrettoSecretKey::from_bytes(&e).unwrap(); + let e_key = RistrettoSecretKey::from_uniform_bytes(&e).unwrap(); let s = &r + &e_key * &k; - let sig = RistrettoSchnorr::sign_raw(&k, r, &e).unwrap(); + let sig = RistrettoSchnorr::sign_raw_uniform(&k, r, &e).unwrap(); let R_calc = sig.get_public_nonce(); assert_eq!(R, *R_calc); assert_eq!(sig.get_signature(), &s); - assert!(sig.verify_challenge(&P, &e)); + assert!(sig.verify_raw_uniform(&P, &e)); // Doesn't work for invalid credentials - assert!(!sig.verify_challenge(&R, &e)); + assert!(!sig.verify_raw_uniform(&R, &e)); // Doesn't work for different challenge - let wrong_challenge = Blake2b::::digest(b"Guards! Guards!"); - assert!(!sig.verify_challenge(&P, &wrong_challenge)); + let wrong_challenge = Blake2b::::digest(b"Guards! Guards!"); + assert!(!sig.verify_raw_uniform(&P, &wrong_challenge)); } /// This test checks that the linearity of Schnorr signatures hold, i.e. that s = s1 + s2 is validated by R1 + R2 @@ -185,7 +188,7 @@ mod test { let (k2, P2) = 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) - let e = Blake2b::::new() + let e = Blake2b::::new() .chain_update(R1.as_bytes()) .chain_update(R2.as_bytes()) .chain_update(P1.as_bytes()) @@ -193,25 +196,13 @@ mod test { .chain_update(b"Moving Pictures") .finalize(); // Calculate Alice's signature - let s1 = RistrettoSchnorr::sign_raw(&k1, r1, &e).unwrap(); + let s1 = RistrettoSchnorr::sign_raw_uniform(&k1, r1, &e).unwrap(); // Calculate Bob's signature - let s2 = RistrettoSchnorr::sign_raw(&k2, r2, &e).unwrap(); + let s2 = RistrettoSchnorr::sign_raw_uniform(&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()); + assert!(s_agg.verify_raw_uniform(&(P1 + P2), &e)); } #[test] @@ -222,10 +213,10 @@ mod test { let R = RistrettoPublicKey::from_hex("fa14cb581ce5717248444721242e6b195a482d503a853dea4acb513074d8d803").unwrap(); let msg = "Moving Pictures"; - let hash = SchnorrSignature::<_, _, SchnorrSigChallenge>::construct_domain_separated_challenge::<_, Blake2b>( + let hash = SchnorrSignature::<_, _, SchnorrSigChallenge>::construct_domain_separated_challenge::<_, Blake2b>( &R, &P, msg, ); - let naiive = Blake2b::::new() + let naiive = Blake2b::::new() .chain_update(R.as_bytes()) .chain_update(P.as_bytes()) .chain_update(msg) @@ -234,7 +225,7 @@ mod test { assert_ne!(hash.as_ref(), naiive.as_bytes()); assert_eq!( to_hex(hash.as_ref()), - "d8f6b29b641113c91175b8d44f265ff1167d58d5aa5ee03e6f1f521505b09d80" + "2db0656c9dd1482bf61d32f157726b05a88d567c31107bed9a5c60a02119518af35929f360726bffd846439ab12e7c9f4983cf5fab5ea735422e05e0f560ddfd" ); } @@ -256,8 +247,8 @@ mod test { // 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)); + assert!(sig1.verify(&P, msg)); + assert!(sig2.verify(&P, msg)); // But the signatures are different, for the same message, secret and nonce. assert_ne!(sig1.get_signature(), sig2.get_signature()); } @@ -267,10 +258,9 @@ mod test { fn sign_and_verify_message() { 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", &mut rng).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 sig = RistrettoSchnorr::sign(&k, "Queues are things that happen to other people", &mut rng).unwrap(); + assert!(sig.verify(&P, "Queues are things that happen to other people")); + assert!(!sig.verify(&P, "Qs are things that happen to other people")); + assert!(!sig.verify(&(&P + &P), "Queues are things that happen to other people")); } } diff --git a/src/ristretto/serialize.rs b/src/ristretto/serialize.rs index 6d7aafba..b213322b 100644 --- a/src/ristretto/serialize.rs +++ b/src/ristretto/serialize.rs @@ -51,7 +51,7 @@ impl<'de> Deserialize<'de> for RistrettoPublicKey { fn visit_bytes(self, v: &[u8]) -> Result where E: de::Error { - RistrettoPublicKey::from_bytes(v).map_err(E::custom) + RistrettoPublicKey::from_canonical_bytes(v).map_err(E::custom) } } @@ -89,7 +89,7 @@ impl<'de> Deserialize<'de> for RistrettoSecretKey { fn visit_bytes(self, v: &[u8]) -> Result where E: de::Error { - RistrettoSecretKey::from_bytes(v).map_err(E::custom) + RistrettoSecretKey::from_canonical_bytes(v).map_err(E::custom) } } diff --git a/src/ristretto/utils.rs b/src/ristretto/utils.rs deleted file mode 100644 index dfc2684a..00000000 --- a/src/ristretto/utils.rs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2019. The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -//! Handy utility functions for use in tests and demo scripts - -use alloc::vec::Vec; - -use digest::Digest; -use rand_core::{CryptoRng, RngCore}; -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], - rng: &mut R, -) -> Result { - let (nonce, public_nonce) = RistrettoPublicKey::random_keypair(rng); - let message = D::new() - .chain_update(public_nonce.as_bytes()) - .chain_update(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/commitment_and_public_key_signature.rs b/src/signatures/commitment_and_public_key_signature.rs index a02d6ccc..cf526d46 100644 --- a/src/signatures/commitment_and_public_key_signature.rs +++ b/src/signatures/commitment_and_public_key_signature.rs @@ -102,8 +102,8 @@ where for<'a> &'a K: Mul<&'a K, Output = K>, C: HomomorphicCommitmentFactory

, { - // The challenge must be a valid scalar - let e = match K::from_bytes(challenge) { + // The challenge is computed by wide reduction + let e = match K::from_uniform_bytes(challenge) { Ok(e) => e, Err(_) => return Err(CommitmentAndPublicKeySignatureError::InvalidChallenge), }; @@ -148,8 +148,8 @@ where C: HomomorphicCommitmentFactory

, R: RngCore + CryptoRng, { - // The challenge must be a valid scalar - let e = match K::from_bytes(challenge) { + // The challenge is computed by wide reduction + let e = match K::from_uniform_bytes(challenge) { Ok(e) => e, Err(_) => return false, }; diff --git a/src/signatures/commitment_signature.rs b/src/signatures/commitment_signature.rs index a114f425..401d665c 100644 --- a/src/signatures/commitment_signature.rs +++ b/src/signatures/commitment_signature.rs @@ -92,7 +92,7 @@ where for<'a> &'a K: Mul<&'a K, Output = K>, C: HomomorphicCommitmentFactory

, { - let e = match K::from_bytes(challenge) { + let e = match K::from_uniform_bytes(challenge) { Ok(e) => e, Err(_) => return Err(CommitmentSignatureError::InvalidChallenge), }; @@ -120,7 +120,7 @@ where for<'b> &'b HomomorphicCommitment

: Add<&'b HomomorphicCommitment

, Output = HomomorphicCommitment

>, C: HomomorphicCommitmentFactory

, { - let e = match K::from_bytes(challenge) { + let e = match K::from_uniform_bytes(challenge) { Ok(e) => e, Err(_) => return false, }; @@ -178,9 +178,9 @@ where if buf.len() != P::KEY_LEN + 2 * K::key_length() { return Err(ByteArrayError::IncorrectLength {}); } - let public_nonce = HomomorphicCommitment::from_public_key(&P::from_bytes(&buf[0..P::KEY_LEN])?); - let u = K::from_bytes(&buf[P::KEY_LEN..P::KEY_LEN + K::key_length()])?; - let v = K::from_bytes(&buf[P::KEY_LEN + K::key_length()..P::KEY_LEN + 2 * K::key_length()])?; + let public_nonce = HomomorphicCommitment::from_public_key(&P::from_canonical_bytes(&buf[0..P::KEY_LEN])?); + let u = K::from_canonical_bytes(&buf[P::KEY_LEN..P::KEY_LEN + K::key_length()])?; + let v = K::from_canonical_bytes(&buf[P::KEY_LEN + K::key_length()..P::KEY_LEN + 2 * K::key_length()])?; Ok(Self { public_nonce, u, v }) } diff --git a/src/signatures/schnorr.rs b/src/signatures/schnorr.rs index e9447a14..535872ef 100644 --- a/src/signatures/schnorr.rs +++ b/src/signatures/schnorr.rs @@ -13,7 +13,7 @@ use core::{ }; use blake2::Blake2b; -use digest::{consts::U32, Digest}; +use digest::{consts::U64, Digest}; use rand_core::{CryptoRng, RngCore}; use snafu::prelude::*; use tari_utilities::ByteArray; @@ -73,40 +73,43 @@ where P::from_secret_key(&self.signature) } - /// Sign a challenge with the given `secret` and private `nonce`. Returns an SchnorrSignatureError if `::from_bytes(challenge)` returns an error. + /// Generate a signature using a given secret key, nonce, and challenge byte slice. /// - /// WARNING: The public key and nonce are NOT bound to the challenge. This method assumes that the challenge has - /// been constructed such that all commitments are already included in the challenge. + /// WARNING: This is intended for use cases where the challenge byte slice was generated correctly. + /// In particlar, it _must_ be the result of securely applying a cryptographic hash function to the correct public + /// key, public nonce, and input message; further, it must be of a length suitable for scalar wide reduction. + /// This function only checks that the byte slice is of the correct length. + /// The nonce _must_ also have been sampled uniformly at random and not reused with the same secret key and a + /// different message. /// - /// Use [`sign_raw`] instead if this is what you want. (This method is a deprecated alias for `sign_raw`). - /// - /// If you want a simple API that binds the nonce and public key to the message, use [`sign_message`] instead. - #[deprecated( - since = "0.16.0", - note = "This method probably doesn't do what you think it does. Please use `sign_message` or `sign_raw` \ - instead, depending on your use case. This function will be removed in v1.0.0" - )] - #[allow(clippy::needless_pass_by_value)] - pub fn sign(secret: K, nonce: K, challenge: &[u8]) -> Result - where - K: Add, - for<'a> K: Mul<&'a K, Output = K>, - { - Self::sign_raw(&secret, nonce, challenge) + /// If you aren't sure that you can meet these requirements, and want a simple and safe API, use [`sign`]. + pub fn sign_raw_uniform<'a>(secret: &'a K, nonce: K, challenge: &[u8]) -> Result + where K: Add + Mul<&'a K, Output = K> { + // s = r + e.k + let e = match K::from_uniform_bytes(challenge) { + Ok(e) => e, + Err(_) => return Err(SchnorrSignatureError::InvalidChallenge), + }; + let public_nonce = P::from_secret_key(&nonce); + let ek = e * secret; + let s = ek + nonce; + Ok(Self::new(public_nonce, s)) } - /// Sign a challenge with the given `secret` and private `nonce`. Returns an SchnorrSignatureError if `::from_bytes(challenge)` returns an error. + /// Generate a signature using a given secret key, nonce, and challenge byte slice. /// - /// WARNING: The public key and nonce are NOT bound to the challenge. This method assumes that the challenge has - /// been constructed such that all commitments are already included in the challenge. + /// WARNING: This is intended for use cases where the challenge byte slice was generated correctly. + /// In particlar, it _must_ be the result of securely applying a cryptographic hash function to the correct public + /// key, public nonce, and input message; further, it must be the canonical representation of a scalar. + /// This function only checks that the byte slice is of the correct length. + /// The nonce _must_ also have been sampled uniformly at random and not reused with the same secret key and a + /// different message. /// - /// If you want a simple API that binds the nonce and public key to the message, use [`sign_message`] instead. - pub fn sign_raw<'a>(secret: &'a K, nonce: K, challenge: &[u8]) -> Result + /// If you aren't sure that you can meet these requirements, and want a simple and safe API, use [`sign`]. + pub fn sign_raw_canonical<'a>(secret: &'a K, nonce: K, challenge: &[u8]) -> Result where K: Add + Mul<&'a K, Output = K> { // s = r + e.k - let e = match K::from_bytes(challenge) { + let e = match K::from_canonical_bytes(challenge) { Ok(e) => e, Err(_) => return Err(SchnorrSignatureError::InvalidChallenge), }; @@ -119,11 +122,8 @@ where /// Signs a message with the given secret key. /// /// This method correctly binds a nonce and the public key to the signature challenge, using domain-separated - /// hashing. The hasher is also opinionated in the sense that Blake2b 256-bit digest is always used. - /// - /// it is possible to customise the challenge by using [`construct_domain_separated_challenge`] and [`sign_raw`] - /// yourself, or even use [`sign_raw`] using a completely custom challenge. - pub fn sign_message<'a, B, R: RngCore + CryptoRng>( + /// hashing. The hasher is also opinionated in the sense that Blake2b 512-bit digest is always used. + pub fn sign<'a, B, R: RngCore + CryptoRng>( secret: &'a K, message: B, rng: &mut R, @@ -136,16 +136,13 @@ where Self::sign_with_nonce_and_message(secret, nonce, message) } - /// Signs a message with the given secret key and provided nonce. + /// Signs a message with the given secret key and nonce. /// /// This method correctly binds the nonce and the public key to the signature challenge, using domain-separated - /// hashing. The hasher is also opinionated in the sense that Blake2b 256-bit digest is always used. - /// - /// ** Important **: It is the caller's responsibility to ensure that the nonce is unique. This API tries to - /// prevent this by taking ownership of the nonce, which means that the caller has to explicitly clone the nonce - /// in order to re-use it, which is a small deterrent, but better than nothing. + /// hashing. The hasher is also opinionated in the sense that Blake2b 512-bit digest is always used. /// - /// To delegate nonce handling to the callee, use [`Self::sign_message`] instead. + /// WARNING: The nonce _must_ also have been sampled uniformly at random and not reused with the same secret key and + /// a different message. pub fn sign_with_nonce_and_message<'a, B>( secret: &'a K, nonce: K, @@ -158,8 +155,8 @@ where let public_nonce = P::from_secret_key(&nonce); let public_key = P::from_secret_key(secret); let challenge = - Self::construct_domain_separated_challenge::<_, Blake2b>(&public_nonce, &public_key, message); - Self::sign_raw(secret, nonce, challenge.as_ref()) + Self::construct_domain_separated_challenge::<_, Blake2b>(&public_nonce, &public_key, message); + Self::sign_raw_uniform(secret, nonce, challenge.as_ref()) } /// Constructs an opinionated challenge hash for the given public nonce, public key and message. @@ -168,8 +165,8 @@ where /// the challenge. In this implementation, the challenge is constructed by means of domain separated hashing /// using the provided digest. /// - /// This challenge is used in the [`sign_message`] and [`verify_message`] methods.If you wish to use a custom - /// challenge, you can use [`sign_raw`] instead. + /// This challenge is used in the [`sign_message`] and [`verify_message`] methods. If you wish to use a custom + /// challenge, you can use [`sign_raw_canonical`] or [`sign_raw_wide`] instead. pub fn construct_domain_separated_challenge( public_nonce: &P, public_key: &P, @@ -186,36 +183,50 @@ where .finalize() } - /// Verifies a signature created by the `sign_message` method. The function returns `true` if and only if the + /// Verifies a signature created by the `sign` method. The function returns `true` if and only if the /// message was signed by the secret key corresponding to the given public key, and that the challenge was /// constructed using the domain-separation method defined in [`construct_domain_separated_challenge`]. - pub fn verify_message<'a, B>(&self, public_key: &'a P, message: B) -> bool + pub fn verify<'a, B>(&self, public_key: &'a P, message: B) -> bool where for<'b> &'b K: Mul<&'a P, Output = P>, for<'b> &'b P: Add, B: AsRef<[u8]>, { let challenge = - Self::construct_domain_separated_challenge::<_, Blake2b>(&self.public_nonce, public_key, message); - self.verify_challenge(public_key, challenge.as_ref()) + Self::construct_domain_separated_challenge::<_, Blake2b>(&self.public_nonce, public_key, message); + self.verify_raw_uniform(public_key, challenge.as_ref()) + } + + /// Verifies a signature against a given public key and challenge byte slice. + /// The byte slice is converted to a scalar using wide reduction. + pub fn verify_raw_uniform<'a>(&self, public_key: &'a P, challenge: &[u8]) -> bool + where + for<'b> &'b K: Mul<&'a P, Output = P>, + for<'b> &'b P: Add, + { + let e = match K::from_uniform_bytes(challenge) { + Ok(e) => e, + Err(_) => return false, + }; + self.verify_challenge_scalar(public_key, &e) } - /// Returns true if this signature is valid for a public key and challenge, otherwise false. This will always return - /// false if `::from_bytes(challenge)` returns an error. - pub fn verify_challenge<'a>(&self, public_key: &'a P, challenge: &[u8]) -> bool + /// Verifies a signature against a given public key and challenge byte slice. + /// The byte slice is converted to a scalar assuming a canonical representation. + pub fn verify_raw_canonical<'a>(&self, public_key: &'a P, challenge: &[u8]) -> bool where for<'b> &'b K: Mul<&'a P, Output = P>, for<'b> &'b P: Add, { - let e = match K::from_bytes(challenge) { + let e = match K::from_canonical_bytes(challenge) { Ok(e) => e, Err(_) => return false, }; - self.verify(public_key, &e) + self.verify_challenge_scalar(public_key, &e) } /// Returns true if this signature is valid for a public key and challenge scalar, otherwise false. - pub fn verify<'a>(&self, public_key: &'a P, challenge: &K) -> bool + pub fn verify_challenge_scalar<'a>(&self, public_key: &'a P, challenge: &K) -> bool where for<'b> &'b K: Mul<&'a P, Output = P>, for<'b> &'b P: Add,