diff --git a/.circleci/config.yml b/.circleci/config.yml index b72cf547ca..5f33005a87 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1290,6 +1290,7 @@ workflows: or pipeline.git.branch == "testnet" or pipeline.git.branch == "mainnet" or pipeline.git.branch == "ensure_finalize_scopes_match" + or pipeline.git.branch == "feat/sign_v2" jobs: - check-unused-dependencies # This can be cleaned up before releases - check-cargo-semver-checks # This can be cleaned up before releases diff --git a/circuit/account/src/lib.rs b/circuit/account/src/lib.rs index 26809790ae..bd886f9804 100644 --- a/circuit/account/src/lib.rs +++ b/circuit/account/src/lib.rs @@ -72,8 +72,8 @@ pub(crate) mod helpers { // Generate a signature. let message: Vec<_> = (0..num_fields).map(|_| Uniform::rand(rng)).collect(); - let signature = console::Signature::sign(&private_key, &message, rng).unwrap(); - assert!(signature.verify(&address, &message)); + let signature = console::Signature::sign_v2(&private_key, &message, rng).unwrap(); + assert!(signature.verify_v2(&address, &message)); signature } } diff --git a/circuit/account/src/signature/mod.rs b/circuit/account/src/signature/mod.rs index 6670fd66f2..48dfb8c06e 100644 --- a/circuit/account/src/signature/mod.rs +++ b/circuit/account/src/signature/mod.rs @@ -158,7 +158,7 @@ mod tests { for i in 0..ITERATIONS { // Generate a signature. let message: Vec<_> = (0..i).map(|_| Uniform::rand(rng)).collect(); - let signature = console::Signature::sign(&private_key, &message, rng)?; + let signature = console::Signature::sign_v2(&private_key, &message, rng)?; Circuit::scope(format!("New {mode}"), || { let candidate = Signature::::new(mode, signature); diff --git a/console/account/src/lib.rs b/console/account/src/lib.rs index e1d82cac5c..7a1a45f2ed 100644 --- a/console/account/src/lib.rs +++ b/console/account/src/lib.rs @@ -146,6 +146,7 @@ mod tests { } #[test] + #[allow(deprecated)] fn test_sign_bits() { let private_key = PrivateKey::::from_str(ALEO_PRIVATE_KEY).unwrap(); let address = Address::::try_from(&private_key).unwrap(); @@ -157,10 +158,15 @@ mod tests { let signature = private_key.sign_bits(&message, &mut rng).unwrap(); let verification = signature.verify_bits(&address, &message); assert!(verification); + let signature_v2 = private_key.sign_bits_v2(&message, &mut rng).unwrap(); + let verification_v2 = signature_v2.verify_bits_v2(&address, &message); + assert!(verification_v2); } + } #[test] + #[allow(deprecated)] fn test_invalid_sign_bits() { let private_key = PrivateKey::::from_str(ALEO_PRIVATE_KEY).unwrap(); let address = Address::::try_from(&private_key).unwrap(); @@ -174,10 +180,14 @@ mod tests { let signature = private_key.sign_bits(&message, &mut rng).unwrap(); let verification = signature.verify_bits(&address, &incorrect_message); assert!(!verification); + let signature_v2 = private_key.sign_bits_v2(&message, &mut rng).unwrap(); + let verification_v2 = signature_v2.verify_bits_v2(&address, &incorrect_message); + assert!(!verification_v2); } } #[test] + #[allow(deprecated)] fn test_aleo_signature_bech32() { let mut rng = TestRng::default(); @@ -192,10 +202,17 @@ mod tests { let candidate_string = &expected_signature.to_string(); assert_eq!(216, candidate_string.len(), "Update me if serialization has changed"); assert_eq!("sign1", &candidate_string[0..5], "Update me if the prefix has changed"); + + let expected_signature_v2 = private_key.sign_bits_v2(&message, &mut rng).unwrap(); + + let candidate_string = &expected_signature_v2.to_string(); + assert_eq!(216, candidate_string.len(), "Update me if serialization has changed"); + assert_eq!("sign1", &candidate_string[0..5], "Update me if the prefix has changed"); } } #[test] + #[allow(deprecated)] fn test_aleo_signature_serde_json() { let mut rng = TestRng::default(); @@ -215,10 +232,23 @@ mod tests { // Deserialize assert_eq!(expected_signature, serde_json::from_str(&candidate_string).unwrap()); assert_eq!(expected_signature, Signature::::from_str(expected_string).unwrap()); + + // Craft the Aleo signature with the v2 method. + let expected_signature_v2 = private_key.sign_bits_v2(&message, &mut rng).unwrap(); + + // Serialize + let expected_string = &expected_signature_v2.to_string(); + let candidate_string = serde_json::to_string(&expected_signature).unwrap(); + assert_eq!(expected_string, serde_json::Value::from_str(&candidate_string).unwrap().as_str().unwrap()); + + // Deserialize + assert_eq!(expected_signature_v2, serde_json::from_str(&candidate_string).unwrap()); + assert_eq!(expected_signature, Signature::::from_str(expected_string).unwrap()); } } #[test] + #[allow(deprecated)] fn test_aleo_signature_bincode() { let mut rng = TestRng::default(); @@ -239,6 +269,19 @@ mod tests { // Deserialize assert_eq!(expected_signature, bincode::deserialize(&candidate_bytes[..]).unwrap()); assert_eq!(expected_signature, Signature::::read_le(&expected_bytes[..]).unwrap()); + + // Craft the Aleo signature with the v2 method. + let expected_signature_v2 = private_key.sign_bits_v2(&message, &mut rng).unwrap(); + + // Serialize + let expected_bytes = expected_signature_v2.to_bytes_le().unwrap(); + let candidate_bytes = bincode::serialize(&expected_signature_v2).unwrap(); + assert_eq!(128, expected_bytes.len(), "Update me if serialization has changed"); + assert_eq!(&expected_bytes[..], &candidate_bytes[8..]); + + // Deserialize + assert_eq!(expected_signature_v2, bincode::deserialize(&candidate_bytes[..]).unwrap()); + assert_eq!(expected_signature_v2, Signature::::read_le(&expected_bytes[..]).unwrap()); } } } diff --git a/console/account/src/private_key/sign.rs b/console/account/src/private_key/sign.rs index 7d71e78783..f2973594e2 100644 --- a/console/account/src/private_key/sign.rs +++ b/console/account/src/private_key/sign.rs @@ -18,19 +18,52 @@ use crate::Signature; impl PrivateKey { /// Returns a signature for the given message (as field elements) using the private key. + #[deprecated(note="Please migrate to `sign_v2`")] pub fn sign(&self, message: &[Field], rng: &mut R) -> Result> { + #[allow(deprecated)] Signature::sign(self, message, rng) } /// Returns a signature for the given message (as bytes) using the private key. + #[deprecated(note="Please migrate to `sign_bytes_v2`")] pub fn sign_bytes(&self, message: &[u8], rng: &mut R) -> Result> { + #[allow(deprecated)] Signature::sign_bytes(self, message, rng) } /// Returns a signature for the given message (as bits) using the private key. + #[deprecated(note="Please migrate to `sign_bits_v2`")] pub fn sign_bits(&self, message: &[bool], rng: &mut R) -> Result> { + #[allow(deprecated)] Signature::sign_bits(self, message, rng) } + + /// Returns a signature for the given message (as field elements) using the private key. + pub fn sign_v2(&self, message: &[Field], rng: &mut R) -> Result> { + Signature::sign_v2(self, message, rng) + } + + /// Returns a signature for the given message (as bytes) using the private key. + pub fn sign_bytes_v2(&self, message: &[u8], rng: &mut R) -> Result> { + Signature::sign_bytes_v2(self, message, rng) + } + + /// Returns a signature for the given message (as bytes) using the private key. + /// Message length is not encoded and must be checked by the caller if relevant. + pub fn sign_bytes_raw_v2(&self, message: &[u8], rng: &mut R) -> Result> { + Signature::sign_bytes_raw_v2(self, message, rng) + } + + /// Returns a signature for the given message (as bits) using the private key. + pub fn sign_bits_v2(&self, message: &[bool], rng: &mut R) -> Result> { + Signature::sign_bits_v2(self, message, rng) + } + + /// Returns a signature for the given message (as bits) using the private key. + /// Message length is not encoded and must be checked by the caller if relevant. + pub fn sign_bits_raw_v2(&self, message: &[bool], rng: &mut R) -> Result> { + Signature::sign_bits_raw_v2(self, message, rng) + } } #[cfg(test)] @@ -44,6 +77,7 @@ mod tests { const ITERATIONS: u64 = 100; #[test] + #[allow(deprecated)] fn test_sign_and_verify() -> Result<()> { let rng = &mut TestRng::default(); @@ -67,6 +101,7 @@ mod tests { } #[test] + #[allow(deprecated)] fn test_sign_and_verify_bytes() -> Result<()> { let rng = &mut TestRng::default(); @@ -90,6 +125,7 @@ mod tests { } #[test] + #[allow(deprecated)] fn test_sign_and_verify_bits() -> Result<()> { let rng = &mut TestRng::default(); diff --git a/console/account/src/signature/mod.rs b/console/account/src/signature/mod.rs index e9de87cf95..c43af72986 100644 --- a/console/account/src/signature/mod.rs +++ b/console/account/src/signature/mod.rs @@ -36,6 +36,9 @@ use crate::address::Address; use snarkvm_console_network::prelude::*; use snarkvm_console_types::{Boolean, Field, Scalar}; +// Domain separator used in sign_v2 and related methods. +static SIGNATURE_V2_PREFIX: &str = "ALEO_SIGNATURE_V2"; + #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct Signature { /// The verifier challenge to check against. diff --git a/console/account/src/signature/parse.rs b/console/account/src/signature/parse.rs index 574b02822f..b613dfb01e 100644 --- a/console/account/src/signature/parse.rs +++ b/console/account/src/signature/parse.rs @@ -18,7 +18,7 @@ use super::*; static SIGNATURE_PREFIX: &str = "sign"; impl Parser for Signature { - /// Parses a string into an signature. + /// Parses a string into a signature. #[inline] fn parse(string: &str) -> ParserResult { // Prepare a parser for the Aleo signature. diff --git a/console/account/src/signature/sign.rs b/console/account/src/signature/sign.rs index 54b204fecf..6e2c60f629 100644 --- a/console/account/src/signature/sign.rs +++ b/console/account/src/signature/sign.rs @@ -15,11 +15,125 @@ use super::*; +// V1 Signing methods impl Signature { /// Returns a signature `(challenge, response, compute_key)` for a given message and RNG, where: /// challenge := HashToScalar(nonce * G, pk_sig, pr_sig, address, message) /// response := nonce - challenge * private_key.sk_sig() + #[deprecated(note="Please migrate to `sign_v2`")] pub fn sign(private_key: &PrivateKey, message: &[Field], rng: &mut R) -> Result { + // Disallowing the case message[1] == N::hash_psd2(message[0]) to separate Signature::sign from Request::sign. + if message.len() >= 2 && message[1] == N::hash_psd2(&[message[0]])? { + bail!( + "Invalid message: message[1] == N::hash_psd2([message[0]]) is disallowed. Please construct a different message or use Request::sign." + ); + } + + Self::sign_internal(private_key, message, rng, &[]) + } + + /// Returns a signature for the given message (as bytes) using the private key. + #[deprecated(note="Please migrate to `sign_bytes_v2`")] + pub fn sign_bytes( + private_key: &PrivateKey, + message: &[u8], + rng: &mut R, + ) -> Result> { + #[allow(deprecated)] + // Convert the message into bits, and sign the message. + Self::sign_bits(private_key, &message.to_bits_le(), rng) + } + + /// Returns a signature for the given message (as bits) using the private key. + #[deprecated(note="Please migrate to `sign_bits_v2`")] + pub fn sign_bits( + private_key: &PrivateKey, + message: &[bool], + rng: &mut R, + ) -> Result> { + // Pack the bits into field elements. + let fields = + message.chunks(Field::::size_in_data_bits()).map(Field::from_bits_le).collect::>>()?; + #[allow(deprecated)] + // Sign the message. + Self::sign(private_key, &fields, rng) + } +} + +// V2 Signing methods +impl Signature { + /// Returns a signature `(challenge, response, compute_key)` for a given message and RNG, where: + /// challenge := HashToScalar(ALEO_SIGNATURE_V2, nonce * G, pk_sig, pr_sig, address, message) + /// response := nonce - challenge * private_key.sk_sig() + pub fn sign_v2(private_key: &PrivateKey, message: &[Field], rng: &mut R) -> Result { + let prefix = Field::::new_domain_separator(SIGNATURE_V2_PREFIX); + Self::sign_internal(private_key, message, rng, &[prefix]) + } + + /// Returns a signature for the given message (as bytes) using the private key. + pub fn sign_bytes_v2( + private_key: &PrivateKey, + message: &[u8], + rng: &mut R, + ) -> Result> { + // Convert the message into bits, and sign the message. + Self::sign_bits_v2(private_key, &message.to_bits_le(), rng) + } + + /// Returns a signature for the given message (as bytes) using the private key. + /// Message length is not encoded and must be checked by the caller if relevant. + pub fn sign_bytes_raw_v2( + private_key: &PrivateKey, + message: &[u8], + rng: &mut R, + ) -> Result> { + // Convert the message into bits, and sign the message. + Self::sign_bits_raw_v2(private_key, &message.to_bits_le(), rng) + } + + /// Returns a signature for the given message (as bits) using the private key. + pub fn sign_bits_v2( + private_key: &PrivateKey, + message: &[bool], + rng: &mut R, + ) -> Result> { + let message_length = Field::::from_u128(u128::try_from(message.len())?); + + let mut message_with_length = Vec::with_capacity(1 + message.len()); + message_with_length.push(message_length); + // Pack the bits into field elements. + message_with_length.extend( + message.chunks(Field::::size_in_data_bits()).map(Field::from_bits_le).collect::>>()?, + ); + // Sign the message. + Self::sign_v2(private_key, &message_with_length, rng) + } + + /// Returns a signature for the given message (as bits) using the private key. + /// Message length is not encoded and must be checked by the caller if relevant. + pub fn sign_bits_raw_v2( + private_key: &PrivateKey, + message: &[bool], + rng: &mut R, + ) -> Result> { + // Pack the bits into field elements. + let fields = + message.chunks(Field::::size_in_data_bits()).map(Field::from_bits_le).collect::>>()?; + // Sign the message. + Self::sign_v2(private_key, &fields, rng) + } +} + +// Internal functions common to several signing versions. +impl Signature { + // Internal method common to sign and sign_v2 which prefaces the preimage of the challenge's + // hash with the given prefix. + fn sign_internal( + private_key: &PrivateKey, + message: &[Field], + rng: &mut R, + prefix: &[Field], + ) -> Result { // Ensure the number of field elements does not exceed the maximum allowed size. if message.len() > N::MAX_DATA_SIZE_IN_FIELDS as usize { bail!("Cannot sign the message: the message exceeds maximum allowed size") @@ -40,8 +154,9 @@ impl Signature { // Derive the address from the compute key. let address = Address::try_from(compute_key)?; - // Construct the hash input as (r * G, pk_sig, pr_sig, address, message). - let mut preimage = Vec::with_capacity(4 + message.len()); + // Construct the hash input as (prefix [if present], r * G, pk_sig, pr_sig, address, message). + let mut preimage = Vec::with_capacity(prefix.len() + 4 + message.len()); + preimage.extend(prefix); preimage.extend([g_r, pk_sig, pr_sig, *address].map(|point| point.to_x_coordinate())); preimage.extend(message); @@ -53,27 +168,44 @@ impl Signature { // Output the signature. Ok(Self { challenge, response, compute_key }) } +} - /// Returns a signature for the given message (as bytes) using the private key. - pub fn sign_bytes( - private_key: &PrivateKey, - message: &[u8], - rng: &mut R, - ) -> Result> { - // Convert the message into bits, and sign the message. - Self::sign_bits(private_key, &message.to_bits_le(), rng) - } +#[cfg(test)] +mod tests { + use super::*; + use snarkvm_console_network::MainnetV0; - /// Returns a signature for the given message (as bits) using the private key. - pub fn sign_bits( - private_key: &PrivateKey, - message: &[bool], - rng: &mut R, - ) -> Result> { - // Pack the bits into field elements. - let fields = - message.chunks(Field::::size_in_data_bits()).map(Field::from_bits_le).collect::>>()?; - // Sign the message. - Self::sign(private_key, &fields, rng) + type CurrentNetwork = MainnetV0; + + const ITERATIONS: u64 = 100; + + #[test] + #[allow(deprecated)] + fn test_sign_rejects_request_like_messages() -> Result<()> { + let mut rng = TestRng::default(); + + for _ in 0..ITERATIONS { + // Sample a new private key. + let private_key = PrivateKey::::new(&mut rng).unwrap(); + + // Request signatures begin with (tvk, tcm) where tcm = hash_psd2(tvk). + let tvk = Field::::rand(&mut rng); + let tcm = CurrentNetwork::hash_psd2(&[tvk])?; + + // Add a small number of extra field elements to the message. + let extra_fields = + (0..rng.random_range(0..10)).map(|_| Field::rand(&mut rng)).collect::>>(); + + let mut message = Vec::with_capacity(2 + extra_fields.len()); + message.extend([tvk, tcm]); + message.extend(extra_fields); + + let error = Signature::sign(&private_key, &message, &mut rng).unwrap_err(); + assert!( + error.to_string().contains("message[1] == N::hash_psd2([message[0]])"), + "unexpected error: {error}" + ); + } + Ok(()) } } diff --git a/console/account/src/signature/verify.rs b/console/account/src/signature/verify.rs index cc70934c72..e33ee4fe58 100644 --- a/console/account/src/signature/verify.rs +++ b/console/account/src/signature/verify.rs @@ -15,10 +15,110 @@ use super::*; +// V1 Verification methods impl Signature { /// Verifies (challenge == challenge') && (address == address') where: - /// challenge' := HashToScalar(G^response pk_sig^challenge, pk_sig, pr_sig, address, message) + /// challenge' := HashToScalar(response * G + challenge * pk_sig, pk_sig, pr_sig, address, message) + #[deprecated(note="Please migrate to `verify_v2`")] pub fn verify(&self, address: &Address, message: &[Field]) -> bool { + self.verify_internal(address, message, &[]) + } + + /// Verifies a signature for the given address and message (as bytes). + #[deprecated(note="Please migrate to `verify_bytes_v2`")] + pub fn verify_bytes(&self, address: &Address, message: &[u8]) -> bool { + #[allow(deprecated)] + // Convert the message into bits, and verify the signature. + self.verify_bits(address, &message.to_bits_le()) + } + + /// Verifies a signature for the given address and message (as bits). + #[deprecated(note="Please migrate to `verify_bits_v2`")] + pub fn verify_bits(&self, address: &Address, message: &[bool]) -> bool { + // Pack the bits into field elements. + match message.chunks(Field::::size_in_data_bits()).map(Field::from_bits_le).collect::>>() { + #[allow(deprecated)] + Ok(fields) => self.verify(address, &fields), + Err(error) => { + eprintln!("Failed to verify signature: {error}"); + false + } + } + } +} + +// V2 Verification methods +impl Signature { + /// Verifies (challenge == challenge') && (address == address') where: + /// challenge' := HashToScalar(ALEO_SIGNATURE_V2, response * G + challenge * pk_sig, pk_sig, pr_sig, address, message) + pub fn verify_v2(&self, address: &Address, message: &[Field]) -> bool { + let prefix = Field::::new_domain_separator(SIGNATURE_V2_PREFIX); + self.verify_internal(address, message, &[prefix]) + } + + /// Verifies a signature produced with `sign_bytes_v2` for the given address and message (as bytes). + pub fn verify_bytes_v2(&self, address: &Address, message: &[u8]) -> bool { + // Convert the message into bits, and verify the signature. + self.verify_bits_v2(address, &message.to_bits_le()) + } + + /// Verifies a signature produced with `sign_bytes_raw_v2` for the given address and message (as bytes). + /// Message length is not encoded and must be checked by the caller if relevant. + pub fn verify_bytes_raw_v2(&self, address: &Address, message: &[u8]) -> bool { + // Convert the message into bits, and verify the signature. + self.verify_bits_raw_v2(address, &message.to_bits_le()) + } + + /// Verifies a signature produced with `sign_bits_v2` for the given address and message (as bits). + pub fn verify_bits_v2(&self, address: &Address, message: &[bool]) -> bool { + // Encode the number of bits of the message as a field element: + if let Ok(message_length_u128) = u128::try_from(message.len()) { + let message_length_field = Field::::from_u128(message_length_u128); + + // Pack the bits into field elements. + match message.chunks(Field::::size_in_data_bits()).map(Field::from_bits_le).collect::>>() { + Ok(fields) => { + let mut message_with_length = Vec::with_capacity(fields.len() + 1); + message_with_length.push(message_length_field); + message_with_length.extend(fields); + self.verify_v2(address, &message_with_length) + } + Err(error) => { + eprintln!("Failed to verify signature: {error}"); + false + } + } + } else { + eprintln!("Failed to verify signature: number of bits in the mesage does not fit in a u128"); + false + } + } + + /// Verifies a signature produced with `sign_bits_raw_v2` for the given address and message (as bits). + /// Message length is not encoded and must be checked by the caller if relevant. + pub fn verify_bits_raw_v2(&self, address: &Address, message: &[bool]) -> bool { + // Pack the bits into field elements. + match message.chunks(Field::::size_in_data_bits()).map(Field::from_bits_le).collect::>>() { + // TODO (Antonio) re-introduce + // Ok(fields) => self.verify_v2(address, &fields), + Ok(fields) => { + for f in fields.iter() { + println!(" f: {f}"); + } + self.verify_v2(address, &fields) + } + Err(error) => { + eprintln!("Failed to verify signature: {error}"); + false + } + } + } +} + +// Internal functions common to several verification versions. +impl Signature { + /// Verifies a signature produced with `sign` or `sign_v2` for the given address and message. + fn verify_internal(&self, address: &Address, message: &[Field], prefix: &[Field]) -> bool { // Ensure the number of field elements does not exceed the maximum allowed size. if message.len() > N::MAX_DATA_SIZE_IN_FIELDS as usize { eprintln!("Cannot sign the signature: the signed message exceeds maximum allowed size"); @@ -33,8 +133,9 @@ impl Signature { // Compute `g_r` := (response * G) + (challenge * pk_sig). let g_r = N::g_scalar_multiply(&self.response) + (pk_sig * self.challenge); - // Construct the hash input as (r * G, pk_sig, pr_sig, address, message). - let mut preimage = Vec::with_capacity(4 + message.len()); + // Construct the hash input as (prefix [if present], r * G, pk_sig, pr_sig, address, message). + let mut preimage = Vec::with_capacity(prefix.len() + 4 + message.len()); + preimage.extend(prefix); preimage.extend([g_r, pk_sig, pr_sig, **address].map(|point| point.to_x_coordinate())); preimage.extend(message); @@ -57,24 +158,6 @@ impl Signature { // Return `true` if the candidate challenge and address are correct. self.challenge == candidate_challenge && *address == candidate_address } - - /// Verifies a signature for the given address and message (as bytes). - pub fn verify_bytes(&self, address: &Address, message: &[u8]) -> bool { - // Convert the message into bits, and verify the signature. - self.verify_bits(address, &message.to_bits_le()) - } - - /// Verifies a signature for the given address and message (as bits). - pub fn verify_bits(&self, address: &Address, message: &[bool]) -> bool { - // Pack the bits into field elements. - match message.chunks(Field::::size_in_data_bits()).map(Field::from_bits_le).collect::>>() { - Ok(fields) => self.verify(address, &fields), - Err(error) => { - eprintln!("Failed to verify signature: {error}"); - false - } - } - } } #[cfg(test)] @@ -88,6 +171,7 @@ mod tests { const ITERATIONS: u64 = 100; #[test] + #[allow(deprecated)] fn test_sign_and_verify() -> Result<()> { let rng = &mut TestRng::default(); @@ -96,21 +180,31 @@ mod tests { let private_key = PrivateKey::::new(rng)?; let address = Address::try_from(&private_key)?; - // Check that the signature is valid for the message. - let message: Vec<_> = (0..i).map(|_| Uniform::rand(rng)).collect(); - let signature = Signature::sign(&private_key, &message, rng)?; - assert!(signature.verify(&address, &message)); + // Check that the v1 and v2 signatures are valid for the message. + let message: Vec> = (0..i).map(|_| Uniform::rand(rng)).collect(); + + let signature_v1 = Signature::sign(&private_key, &message, rng)?; + assert!(signature_v1.verify(&address, &message)); + + let signature_v2 = Signature::sign_v2(&private_key, &message, rng)?; + assert!(signature_v2.verify_v2(&address, &message)); // Check that the signature is invalid for an incorrect message. - let failure_message: Vec<_> = (0..i).map(|_| Uniform::rand(rng)).collect(); + let failure_message: Vec> = (0..i).map(|_| Uniform::rand(rng)).collect(); if message != failure_message { - assert!(!signature.verify(&address, &failure_message)); + assert!(!signature_v1.verify(&address, &failure_message)); + assert!(!signature_v2.verify_v2(&address, &failure_message)); } + + // Sanity-check that the v1 signature doesn't verify under verify_v2 and viceversa + assert!(!signature_v1.verify_v2(&address, &message)); + assert!(!signature_v2.verify(&address, &message)); } Ok(()) } #[test] + #[allow(deprecated)] fn test_sign_and_verify_bytes() -> Result<()> { let rng = &mut TestRng::default(); @@ -119,21 +213,38 @@ mod tests { let private_key = PrivateKey::::new(rng)?; let address = Address::try_from(&private_key)?; - // Check that the signature is valid for the message. - let message: Vec<_> = (0..i).map(|_| Uniform::rand(rng)).collect(); - let signature = Signature::sign_bytes(&private_key, &message, rng)?; - assert!(signature.verify_bytes(&address, &message)); + // Check that the v1 and v2 signatures are valid for the message. + let message: Vec = (0..i).map(|_| Uniform::rand(rng)).collect(); - // Check that the signature is invalid for an incorrect message. - let failure_message: Vec<_> = (0..i).map(|_| Uniform::rand(rng)).collect(); + let signature_v1 = Signature::sign_bytes(&private_key, &message, rng)?; + assert!(signature_v1.verify_bytes(&address, &message)); + + let signature_v2 = Signature::sign_bytes_v2(&private_key, &message, rng)?; + assert!(signature_v2.verify_bytes_v2(&address, &message)); + + let signature_raw_v2 = Signature::sign_bytes_raw_v2(&private_key, &message, rng)?; + assert!(signature_raw_v2.verify_bytes_raw_v2(&address, &message)); + + // Check that the signatures are invalid for an incorrect message. + let failure_message: Vec = (0..i).map(|_| Uniform::rand(rng)).collect(); if message != failure_message { - assert!(!signature.verify_bytes(&address, &failure_message)); + assert!(!signature_v1.verify_bytes(&address, &failure_message)); + assert!(!signature_v2.verify_bytes_v2(&address, &failure_message)); + assert!(!signature_raw_v2.verify_bytes_raw_v2(&address, &failure_message)); } + + // Sanity-check that the v1 signature doesn't verify under verify_bytes_v2 and viceversa, + // and that the raw signature doesn't verify under verify_bytes_v2 and viceversa + assert!(!signature_v1.verify_bytes_v2(&address, &message)); + assert!(!signature_v2.verify_bytes(&address, &message)); + assert!(!signature_v2.verify_bytes_raw_v2(&address, &message)); + assert!(!signature_raw_v2.verify_bytes_v2(&address, &message)); } Ok(()) } #[test] + #[allow(deprecated)] fn test_sign_and_verify_bits() -> Result<()> { let rng = &mut TestRng::default(); @@ -142,16 +253,88 @@ mod tests { let private_key = PrivateKey::::new(rng)?; let address = Address::try_from(&private_key)?; - // Check that the signature is valid for the message. - let message: Vec<_> = (0..i).map(|_| Uniform::rand(rng)).collect(); - let signature = Signature::sign_bits(&private_key, &message, rng)?; - assert!(signature.verify_bits(&address, &message)); + // Check that the v1 and v2 signatures are valid for the message. + let message: Vec = (0..i).map(|_| Uniform::rand(rng)).collect(); + + let signature_v1 = Signature::sign_bits(&private_key, &message, rng)?; + assert!(signature_v1.verify_bits(&address, &message)); + + let signature_v2 = Signature::sign_bits_v2(&private_key, &message, rng)?; + assert!(signature_v2.verify_bits_v2(&address, &message)); + + let signature_raw_v2 = Signature::sign_bits_raw_v2(&private_key, &message, rng)?; + assert!(signature_raw_v2.verify_bits_raw_v2(&address, &message)); // Check that the signature is invalid for an incorrect message. - let failure_message: Vec<_> = (0..i).map(|_| Uniform::rand(rng)).collect(); + let failure_message: Vec = (0..i).map(|_| Uniform::rand(rng)).collect(); if message != failure_message { - assert!(!signature.verify_bits(&address, &failure_message)); + assert!(!signature_v1.verify_bits(&address, &failure_message)); + assert!(!signature_v2.verify_bits_v2(&address, &failure_message)); + assert!(!signature_raw_v2.verify_bits_raw_v2(&address, &failure_message)); } + + // Sanity-check that the v1 signature doesn't verify under verify_bits_v2 and viceversa, + // and that the raw signature doesn't verify under verify_bits_v2 and viceversa + assert!(!signature_v1.verify_bits_v2(&address, &message)); + assert!(!signature_v2.verify_bits(&address, &message)); + assert!(!signature_v2.verify_bits_raw_v2(&address, &message)); + assert!(!signature_raw_v2.verify_bits_v2(&address, &message)); + } + Ok(()) + } + + #[test] + fn test_sign_and_verify_bits_v2_padding() -> Result<()> { + let rng = &mut TestRng::default(); + + for i in 0..ITERATIONS { + // Sample an address and a private key. + let private_key = PrivateKey::::new(rng)?; + let address = Address::try_from(&private_key)?; + + // Construct a message and a copy with an extra zero. + let message: Vec = (0..i).map(|_| Uniform::rand(rng)).collect(); + let mut padded_message = message.clone(); + padded_message.push(false); + + let signature = Signature::sign_bits_v2(&private_key, &message, rng)?; + let signature_padded = Signature::sign_bits_v2(&private_key, &padded_message, rng)?; + + // Check the signature of the padded message does not verify on the unpadded one and viceversa + assert!(!signature.verify_bits_v2(&address, &padded_message)); + assert!(!signature_padded.verify_bits_v2(&address, &message)); + + // Check the two signatures verify as expected + assert!(signature.verify_bits_v2(&address, &message)); + assert!(signature_padded.verify_bits_v2(&address, &padded_message)); + } + Ok(()) + } + + #[test] + fn test_sign_and_verify_bytes_v2_padding() -> Result<()> { + let rng = &mut TestRng::default(); + + for i in 0..ITERATIONS { + // Sample an address and a private key. + let private_key = PrivateKey::::new(rng)?; + let address = Address::try_from(&private_key)?; + + // Construct a message and a copy with an extra zero byte. + let message: Vec = (0..i).map(|_| Uniform::rand(rng)).collect(); + let mut padded_message = message.clone(); + padded_message.push(0u8); + + let signature = Signature::sign_bytes_v2(&private_key, &message, rng)?; + let signature_padded = Signature::sign_bytes_v2(&private_key, &padded_message, rng)?; + + // Check the signature of the padded message does not verify on the unpadded one and viceversa + assert!(!signature.verify_bytes_v2(&address, &padded_message)); + assert!(!signature_padded.verify_bytes_v2(&address, &message)); + + // Check the two signatures verify as expected + assert!(signature.verify_bytes_v2(&address, &message)); + assert!(signature_padded.verify_bytes_v2(&address, &padded_message)); } Ok(()) } diff --git a/console/program/src/owner/mod.rs b/console/program/src/owner/mod.rs index b080a21422..1dcce2d150 100644 --- a/console/program/src/owner/mod.rs +++ b/console/program/src/owner/mod.rs @@ -35,6 +35,8 @@ impl ProgramOwner { // Derive the address. let address = Address::try_from(private_key)?; // Sign the transaction ID. + // TODO (Antonio) allow deprecated or use v2? + #[allow(deprecated)] let signature = private_key.sign(&[deployment_id], rng)?; // Return the program owner. Ok(Self { address, signature }) @@ -56,6 +58,8 @@ impl ProgramOwner { } /// Verify that the signature is valid for the given deployment ID. + // TODO (Antonio) allow deprecated or use v2? + #[allow(deprecated)] pub fn verify(&self, deployment_id: Field) -> bool { self.signature.verify(&self.address, &[deployment_id]) } diff --git a/console/program/src/request/verify.rs b/console/program/src/request/verify.rs index f88021eee9..3404915c2e 100644 --- a/console/program/src/request/verify.rs +++ b/console/program/src/request/verify.rs @@ -158,6 +158,8 @@ impl Request { } // Verify the signature. + // TODO (Antonio) allow deprecated or use v2? + #[allow(deprecated)] self.signature.verify(&self.signer, &message) } } diff --git a/ledger/benches/block.rs b/ledger/benches/block.rs index 966267393a..e7427d71f5 100644 --- a/ledger/benches/block.rs +++ b/ledger/benches/block.rs @@ -96,7 +96,7 @@ fn signature_serialization(c: &mut Criterion) { let data = rng.random(); let private_key = PrivateKey::::new(&mut rng).unwrap(); - let signature = private_key.sign(&[data], &mut rng).unwrap(); + let signature = private_key.sign_v2(&[data], &mut rng).unwrap(); bench_serialization(c, "Signature", signature); } diff --git a/ledger/narwhal/batch-header/src/lib.rs b/ledger/narwhal/batch-header/src/lib.rs index adaf6905d1..e6d15b67f7 100644 --- a/ledger/narwhal/batch-header/src/lib.rs +++ b/ledger/narwhal/batch-header/src/lib.rs @@ -122,6 +122,8 @@ impl BatchHeader { &previous_certificate_ids, )?; // Sign the preimage. + // TODO (Antonio) allow deprecated or use v2? + #[allow(deprecated)] let signature = private_key.sign(&[batch_id], rng)?; // Return the batch header. Ok(Self { @@ -178,6 +180,8 @@ impl BatchHeader { &previous_certificate_ids, )?; // Verify the signature. + // TODO (Antonio) allow deprecated or use v2? + #[allow(deprecated)] if !signature.verify(&author, &[batch_id]) { bail!("Invalid signature for the batch header"); } diff --git a/ledger/src/test_helpers/chain_builder.rs b/ledger/src/test_helpers/chain_builder.rs index 52417aa14f..e07226f827 100644 --- a/ledger/src/test_helpers/chain_builder.rs +++ b/ledger/src/test_helpers/chain_builder.rs @@ -372,6 +372,8 @@ impl TestChainBuilder { ) .unwrap(); + // TODO (Antonio) allow deprecated or use v2? + #[allow(deprecated)] // Add signatures for the batch header. let signatures = self .private_keys diff --git a/ledger/tests/helpers/mod.rs b/ledger/tests/helpers/mod.rs index 537946fcff..3737cb71fe 100644 --- a/ledger/tests/helpers/mod.rs +++ b/ledger/tests/helpers/mod.rs @@ -201,6 +201,8 @@ impl TestChainBuilder { ) .unwrap(); + // TODO (Antonio) allow deprecated or use v2? + #[allow(deprecated)] // Add signatures for the batch header. let signatures = self .private_keys