From 53ddb8bf304fe1aa9796757e00275a6cff89708a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Mej=C3=ADas=20Gil?= Date: Fri, 29 May 2026 10:53:26 +0200 Subject: [PATCH 1/8] implemented sign_v2/verify_v2 and analogous bits/bytes functions, added to tests --- console/account/src/signature/mod.rs | 3 + console/account/src/signature/parse.rs | 2 +- console/account/src/signature/sign.rs | 81 +++++++++++++++ console/account/src/signature/verify.rs | 129 +++++++++++++++++++++--- 4 files changed, 200 insertions(+), 15 deletions(-) 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..7a2aa7c8d6 100644 --- a/console/account/src/signature/sign.rs +++ b/console/account/src/signature/sign.rs @@ -15,6 +15,7 @@ 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) @@ -77,3 +78,83 @@ impl Signature { 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 bits) using the private key. + pub fn sign_bits_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) + } + + /// 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) + } +} + +// 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") + } + + // Sample a random nonce from the scalar field. + let nonce = Scalar::rand(rng); + // Compute `g_r` as `nonce * G`. + let g_r = N::g_scalar_multiply(&nonce); + + // Derive the compute key from the private key. + let compute_key = ComputeKey::try_from(private_key)?; + // Retrieve pk_sig. + let pk_sig = compute_key.pk_sig(); + // Retrieve pr_sig. + let pr_sig = compute_key.pr_sig(); + + // Derive the address from the compute key. + let address = Address::try_from(compute_key)?; + + // 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); + + // Compute the verifier challenge. + let challenge = N::hash_to_scalar_psd8(&preimage)?; + // Compute the prover response. + let response = nonce - (challenge * private_key.sk_sig()); + + // Output the signature. + Ok(Self { challenge, response, compute_key }) + } +} diff --git a/console/account/src/signature/verify.rs b/console/account/src/signature/verify.rs index cc70934c72..f4fb180851 100644 --- a/console/account/src/signature/verify.rs +++ b/console/account/src/signature/verify.rs @@ -15,9 +15,10 @@ 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) pub fn verify(&self, address: &Address, message: &[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 { @@ -77,6 +78,79 @@ impl Signature { } } +// 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_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_v2` for the given address and message (as bits). + pub fn verify_bits_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::>>() { + Ok(fields) => 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"); + return false; + } + + // Retrieve pk_sig. + let pk_sig = self.compute_key.pk_sig(); + // Retrieve pr_sig. + let pr_sig = self.compute_key.pr_sig(); + + // 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 (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); + + // Hash to derive the verifier challenge, and return `false` if this operation fails. + let candidate_challenge = match N::hash_to_scalar_psd8(&preimage) { + // Output the computed candidate challenge. + Ok(candidate_challenge) => candidate_challenge, + // Return `false` if the challenge errored. + Err(_) => return false, + }; + + // Derive the address from the compute key, and return `false` if this operation fails. + let candidate_address = match Address::try_from(self.compute_key) { + // Output the computed candidate address. + Ok(candidate_address) => candidate_address, + // Return `false` if the address errored. + Err(_) => return false, + }; + + // Return `true` if the candidate challenge and address are correct. + self.challenge == candidate_challenge && *address == candidate_address + } +} + #[cfg(test)] #[cfg(feature = "private_key")] mod tests { @@ -96,16 +170,25 @@ mod tests { let private_key = PrivateKey::::new(rng)?; let address = Address::try_from(&private_key)?; - // Check that the signature is valid for the 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 = Signature::sign(&private_key, &message, rng)?; - assert!(signature.verify(&address, &message)); + + 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(); 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(()) } @@ -119,16 +202,25 @@ mod tests { let private_key = PrivateKey::::new(rng)?; let address = Address::try_from(&private_key)?; - // Check that the signature is valid for the 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 = Signature::sign_bytes(&private_key, &message, rng)?; - assert!(signature.verify_bytes(&address, &message)); - // Check that the signature is invalid for an incorrect message. + 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)); + + // 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)); } + + // Sanity-check that the v1 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)); } Ok(()) } @@ -142,16 +234,25 @@ mod tests { let private_key = PrivateKey::::new(rng)?; let address = Address::try_from(&private_key)?; - // Check that the signature is valid for the 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 = Signature::sign_bits(&private_key, &message, rng)?; - assert!(signature.verify_bits(&address, &message)); + + 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)); // Check that the signature is invalid for an incorrect message. 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)); } + + // Sanity-check that the v1 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)); } Ok(()) } From ee5d45b99d8ff0f72aad6c03ea66c86a1ae1eac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Mej=C3=ADas=20Gil?= Date: Mon, 1 Jun 2026 10:59:41 +0200 Subject: [PATCH 2/8] disallowed message[1] == hash_psd2([message[0]]) in console::Signature::sign --- console/account/src/signature/sign.rs | 43 +++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/console/account/src/signature/sign.rs b/console/account/src/signature/sign.rs index 7a2aa7c8d6..a57db6e6ee 100644 --- a/console/account/src/signature/sign.rs +++ b/console/account/src/signature/sign.rs @@ -26,6 +26,13 @@ impl Signature { bail!("Cannot sign the message: the message exceeds maximum allowed size") } + // 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." + ); + } + // Sample a random nonce from the scalar field. let nonce = Scalar::rand(rng); // Compute `g_r` as `nonce * G`. @@ -158,3 +165,39 @@ impl Signature { Ok(Self { challenge, response, compute_key }) } } + +#[cfg(test)] +mod tests { + use super::*; + use snarkvm_console_network::MainnetV0; + + type CurrentNetwork = MainnetV0; + + const ITERATIONS: u64 = 100; + + #[test] + 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(()) + } +} From 89266df67b2b213f0d220cf2b736927fada8cd44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Mej=C3=ADas=20Gil?= Date: Mon, 1 Jun 2026 11:28:38 +0200 Subject: [PATCH 3/8] added compatibility check, all working --- console/account/src/signature/sign.rs | 3 +- console/account/src/signature/verify.rs | 60 ++++++++++++++++++++++--- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/console/account/src/signature/sign.rs b/console/account/src/signature/sign.rs index a57db6e6ee..27f46737c8 100644 --- a/console/account/src/signature/sign.rs +++ b/console/account/src/signature/sign.rs @@ -124,7 +124,8 @@ impl Signature { 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( + // TODO (Antonio) make private again + pub(super) fn sign_internal( private_key: &PrivateKey, message: &[Field], rng: &mut R, diff --git a/console/account/src/signature/verify.rs b/console/account/src/signature/verify.rs index f4fb180851..b3a8358694 100644 --- a/console/account/src/signature/verify.rs +++ b/console/account/src/signature/verify.rs @@ -171,16 +171,29 @@ mod tests { let address = Address::try_from(&private_key)?; // Check that the v1 and v2 signatures are valid for the message. - let message: Vec<_> = (0..i).map(|_| Uniform::rand(rng)).collect(); + let message: Vec> = (0..i).map(|_| Uniform::rand(rng)).collect(); + + // TODO (Antonio) remove + let seed = rng.random(); + let rng = &mut TestRng::from_seed(seed); + let rng_copy = &mut TestRng::from_seed(seed); + let manual_signature_v1 = Signature::sign_internal(&private_key, &message, rng_copy, &[])?; let signature_v1 = Signature::sign(&private_key, &message, rng)?; assert!(signature_v1.verify(&address, &message)); + // TODO (Antonio) remove + { + assert_eq!(manual_signature_v1, signature_v1); + assert!(manual_signature_v1.verify(&address, &message)); + assert!(signature_v1.verify_internal(&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_v1.verify(&address, &failure_message)); assert!(!signature_v2.verify_v2(&address, &failure_message)); @@ -203,16 +216,34 @@ mod tests { let address = Address::try_from(&private_key)?; // Check that the v1 and v2 signatures are valid for the message. - let message: Vec<_> = (0..i).map(|_| Uniform::rand(rng)).collect(); + let message: Vec = (0..i).map(|_| Uniform::rand(rng)).collect(); + + // TODO (Antonio) remove + let seed = rng.random(); + let rng = &mut TestRng::from_seed(seed); + let rng_copy = &mut TestRng::from_seed(seed); + let message_fields = message + .to_bits_le() + .chunks(Field::::size_in_data_bits()) + .map(Field::from_bits_le) + .collect::>>()?; + let manual_signature_v1 = Signature::sign_internal(&private_key, &message_fields, rng_copy, &[])?; let signature_v1 = Signature::sign_bytes(&private_key, &message, rng)?; assert!(signature_v1.verify_bytes(&address, &message)); + // TODO (Antonio) remove + { + assert_eq!(manual_signature_v1, signature_v1); + assert!(manual_signature_v1.verify_bytes(&address, &message)); + assert!(signature_v1.verify_internal(&address, &message_fields, &[])); + } + let signature_v2 = Signature::sign_bytes_v2(&private_key, &message, rng)?; assert!(signature_v2.verify_bytes_v2(&address, &message)); // Check that the signatures are 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_v1.verify_bytes(&address, &failure_message)); assert!(!signature_v2.verify_bytes_v2(&address, &failure_message)); @@ -235,16 +266,33 @@ mod tests { let address = Address::try_from(&private_key)?; // Check that the v1 and v2 signatures are valid for the message. - let message: Vec<_> = (0..i).map(|_| Uniform::rand(rng)).collect(); + let message: Vec = (0..i).map(|_| Uniform::rand(rng)).collect(); + + // TODO (Antonio) remove + let seed = rng.random(); + let rng = &mut TestRng::from_seed(seed); + let rng_copy = &mut TestRng::from_seed(seed); + let message_fields = message + .chunks(Field::::size_in_data_bits()) + .map(Field::from_bits_le) + .collect::>>()?; + let manual_signature_v1 = Signature::sign_internal(&private_key, &message_fields, rng_copy, &[])?; let signature_v1 = Signature::sign_bits(&private_key, &message, rng)?; assert!(signature_v1.verify_bits(&address, &message)); + // TODO (Antonio) remove + { + assert_eq!(manual_signature_v1, signature_v1); + assert!(manual_signature_v1.verify_bits(&address, &message)); + assert!(signature_v1.verify_internal(&address, &message_fields, &[])); + } + let signature_v2 = Signature::sign_bits_v2(&private_key, &message, rng)?; assert!(signature_v2.verify_bits_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_v1.verify_bits(&address, &failure_message)); assert!(!signature_v2.verify_bits_v2(&address, &failure_message)); From bd7026e0656570ed16037d16c00c221aca9de670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Mej=C3=ADas=20Gil?= Date: Mon, 1 Jun 2026 11:32:48 +0200 Subject: [PATCH 4/8] removed compatibility check from tests --- console/account/src/signature/sign.rs | 3 +- console/account/src/signature/verify.rs | 48 ------------------------- 2 files changed, 1 insertion(+), 50 deletions(-) diff --git a/console/account/src/signature/sign.rs b/console/account/src/signature/sign.rs index 27f46737c8..a57db6e6ee 100644 --- a/console/account/src/signature/sign.rs +++ b/console/account/src/signature/sign.rs @@ -124,8 +124,7 @@ impl Signature { impl Signature { // Internal method common to sign and sign_v2 which prefaces the preimage of the challenge's // hash with the given prefix. - // TODO (Antonio) make private again - pub(super) fn sign_internal( + fn sign_internal( private_key: &PrivateKey, message: &[Field], rng: &mut R, diff --git a/console/account/src/signature/verify.rs b/console/account/src/signature/verify.rs index b3a8358694..91d06a6df8 100644 --- a/console/account/src/signature/verify.rs +++ b/console/account/src/signature/verify.rs @@ -173,22 +173,9 @@ mod tests { // Check that the v1 and v2 signatures are valid for the message. let message: Vec> = (0..i).map(|_| Uniform::rand(rng)).collect(); - // TODO (Antonio) remove - let seed = rng.random(); - let rng = &mut TestRng::from_seed(seed); - let rng_copy = &mut TestRng::from_seed(seed); - let manual_signature_v1 = Signature::sign_internal(&private_key, &message, rng_copy, &[])?; - let signature_v1 = Signature::sign(&private_key, &message, rng)?; assert!(signature_v1.verify(&address, &message)); - // TODO (Antonio) remove - { - assert_eq!(manual_signature_v1, signature_v1); - assert!(manual_signature_v1.verify(&address, &message)); - assert!(signature_v1.verify_internal(&address, &message, &[])); - } - let signature_v2 = Signature::sign_v2(&private_key, &message, rng)?; assert!(signature_v2.verify_v2(&address, &message)); @@ -218,27 +205,9 @@ mod tests { // Check that the v1 and v2 signatures are valid for the message. let message: Vec = (0..i).map(|_| Uniform::rand(rng)).collect(); - // TODO (Antonio) remove - let seed = rng.random(); - let rng = &mut TestRng::from_seed(seed); - let rng_copy = &mut TestRng::from_seed(seed); - let message_fields = message - .to_bits_le() - .chunks(Field::::size_in_data_bits()) - .map(Field::from_bits_le) - .collect::>>()?; - let manual_signature_v1 = Signature::sign_internal(&private_key, &message_fields, rng_copy, &[])?; - let signature_v1 = Signature::sign_bytes(&private_key, &message, rng)?; assert!(signature_v1.verify_bytes(&address, &message)); - // TODO (Antonio) remove - { - assert_eq!(manual_signature_v1, signature_v1); - assert!(manual_signature_v1.verify_bytes(&address, &message)); - assert!(signature_v1.verify_internal(&address, &message_fields, &[])); - } - let signature_v2 = Signature::sign_bytes_v2(&private_key, &message, rng)?; assert!(signature_v2.verify_bytes_v2(&address, &message)); @@ -268,26 +237,9 @@ mod tests { // Check that the v1 and v2 signatures are valid for the message. let message: Vec = (0..i).map(|_| Uniform::rand(rng)).collect(); - // TODO (Antonio) remove - let seed = rng.random(); - let rng = &mut TestRng::from_seed(seed); - let rng_copy = &mut TestRng::from_seed(seed); - let message_fields = message - .chunks(Field::::size_in_data_bits()) - .map(Field::from_bits_le) - .collect::>>()?; - let manual_signature_v1 = Signature::sign_internal(&private_key, &message_fields, rng_copy, &[])?; - let signature_v1 = Signature::sign_bits(&private_key, &message, rng)?; assert!(signature_v1.verify_bits(&address, &message)); - // TODO (Antonio) remove - { - assert_eq!(manual_signature_v1, signature_v1); - assert!(manual_signature_v1.verify_bits(&address, &message)); - assert!(signature_v1.verify_internal(&address, &message_fields, &[])); - } - let signature_v2 = Signature::sign_bits_v2(&private_key, &message, rng)?; assert!(signature_v2.verify_bits_v2(&address, &message)); From 15239c8a2878081c2e7888f05dc24ac312bb8eee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Mej=C3=ADas=20Gil?= Date: Mon, 1 Jun 2026 12:23:55 +0200 Subject: [PATCH 5/8] switched sign/verify to use switch/verify_internal with prefix [] --- console/account/src/signature/sign.rs | 38 ++++--------------------- console/account/src/signature/verify.rs | 38 +------------------------ 2 files changed, 6 insertions(+), 70 deletions(-) diff --git a/console/account/src/signature/sign.rs b/console/account/src/signature/sign.rs index a57db6e6ee..f632c72638 100644 --- a/console/account/src/signature/sign.rs +++ b/console/account/src/signature/sign.rs @@ -21,11 +21,6 @@ impl Signature { /// challenge := HashToScalar(nonce * G, pk_sig, pr_sig, address, message) /// response := nonce - challenge * private_key.sk_sig() pub fn sign(private_key: &PrivateKey, message: &[Field], rng: &mut R) -> 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") - } - // 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!( @@ -33,33 +28,7 @@ impl Signature { ); } - // Sample a random nonce from the scalar field. - let nonce = Scalar::rand(rng); - // Compute `g_r` as `nonce * G`. - let g_r = N::g_scalar_multiply(&nonce); - - // Derive the compute key from the private key. - let compute_key = ComputeKey::try_from(private_key)?; - // Retrieve pk_sig. - let pk_sig = compute_key.pk_sig(); - // Retrieve pr_sig. - let pr_sig = compute_key.pr_sig(); - - // 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()); - preimage.extend([g_r, pk_sig, pr_sig, *address].map(|point| point.to_x_coordinate())); - preimage.extend(message); - - // Compute the verifier challenge. - let challenge = N::hash_to_scalar_psd8(&preimage)?; - // Compute the prover response. - let response = nonce - (challenge * private_key.sk_sig()); - - // Output the signature. - Ok(Self { challenge, response, compute_key }) + Self::sign_internal(private_key, message, rng, &[]) } /// Returns a signature for the given message (as bytes) using the private key. @@ -196,7 +165,10 @@ mod tests { 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}"); + 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 91d06a6df8..81f8c0890e 100644 --- a/console/account/src/signature/verify.rs +++ b/console/account/src/signature/verify.rs @@ -20,43 +20,7 @@ impl Signature { /// Verifies (challenge == challenge') && (address == address') where: /// challenge' := HashToScalar(response * G + challenge * pk_sig, pk_sig, pr_sig, address, message) pub fn verify(&self, address: &Address, message: &[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"); - return false; - } - - // Retrieve pk_sig. - let pk_sig = self.compute_key.pk_sig(); - // Retrieve pr_sig. - let pr_sig = self.compute_key.pr_sig(); - - // 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()); - preimage.extend([g_r, pk_sig, pr_sig, **address].map(|point| point.to_x_coordinate())); - preimage.extend(message); - - // Hash to derive the verifier challenge, and return `false` if this operation fails. - let candidate_challenge = match N::hash_to_scalar_psd8(&preimage) { - // Output the computed candidate challenge. - Ok(candidate_challenge) => candidate_challenge, - // Return `false` if the challenge errored. - Err(_) => return false, - }; - - // Derive the address from the compute key, and return `false` if this operation fails. - let candidate_address = match Address::try_from(self.compute_key) { - // Output the computed candidate address. - Ok(candidate_address) => candidate_address, - // Return `false` if the address errored. - Err(_) => return false, - }; - - // Return `true` if the candidate challenge and address are correct. - self.challenge == candidate_challenge && *address == candidate_address + self.verify_internal(address, message, &[]) } /// Verifies a signature for the given address and message (as bytes). From ccba4e6769b6917728a3b1a20e2f3d95f1b0247e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Mej=C3=ADas=20Gil?= Date: Mon, 1 Jun 2026 16:50:40 +0200 Subject: [PATCH 6/8] introduced length into message, added test --- console/account/src/private_key/sign.rs | 27 ++++++ console/account/src/signature/sign.rs | 46 +++++++-- console/account/src/signature/verify.rs | 120 +++++++++++++++++++++++- 3 files changed, 180 insertions(+), 13 deletions(-) diff --git a/console/account/src/private_key/sign.rs b/console/account/src/private_key/sign.rs index 7d71e78783..9d08126825 100644 --- a/console/account/src/private_key/sign.rs +++ b/console/account/src/private_key/sign.rs @@ -31,6 +31,33 @@ impl PrivateKey { pub fn sign_bits(&self, message: &[bool], rng: &mut R) -> Result> { 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)] diff --git a/console/account/src/signature/sign.rs b/console/account/src/signature/sign.rs index f632c72638..af5ccea9ef 100644 --- a/console/account/src/signature/sign.rs +++ b/console/account/src/signature/sign.rs @@ -65,27 +65,57 @@ impl Signature { 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. - let fields = - message.chunks(Field::::size_in_data_bits()).map(Field::from_bits_le).collect::>>()?; + 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, &fields, rng) + Self::sign_v2(private_key, &message_with_length, rng) } - /// Returns a signature for the given message (as bytes) using the private key. - pub fn sign_bytes_v2( + /// 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: &[u8], + message: &[bool], rng: &mut R, ) -> Result> { - // Convert the message into bits, and sign the message. - Self::sign_bits_v2(private_key, &message.to_bits_le(), rng) + // 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) } } diff --git a/console/account/src/signature/verify.rs b/console/account/src/signature/verify.rs index 81f8c0890e..7cd124b112 100644 --- a/console/account/src/signature/verify.rs +++ b/console/account/src/signature/verify.rs @@ -51,17 +51,57 @@ impl Signature { self.verify_internal(address, message, &[prefix]) } - /// Verifies a signature produced with `sign_v2` for the given address and message (as bytes). + /// 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_v2` for the given address and message (as bits). + /// 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::>>() { - Ok(fields) => self.verify_v2(address, &fields), + // 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 @@ -175,16 +215,23 @@ mod tests { 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_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 + // 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(()) } @@ -207,16 +254,79 @@ mod tests { 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(); if message != 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 + // 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(()) } From c534e88cafb42c43d8b1c2a1a8be02a72f66562a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Mej=C3=ADas=20Gil?= Date: Tue, 2 Jun 2026 16:46:33 +0200 Subject: [PATCH 7/8] added branch to merge flow --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) 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 From 96e926924515fe3ec1e869d0309ad919e082a5f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Mej=C3=ADas=20Gil?= Date: Tue, 2 Jun 2026 16:48:45 +0200 Subject: [PATCH 8/8] marked sign as deprecated, added allow(deprecated) or switched to sign_v2 in some instances --- circuit/account/src/lib.rs | 4 +-- circuit/account/src/signature/mod.rs | 2 +- console/account/src/lib.rs | 43 ++++++++++++++++++++++++ console/account/src/private_key/sign.rs | 9 +++++ console/account/src/signature/sign.rs | 6 ++++ console/account/src/signature/verify.rs | 8 +++++ console/program/src/owner/mod.rs | 4 +++ console/program/src/request/verify.rs | 2 ++ ledger/benches/block.rs | 2 +- ledger/narwhal/batch-header/src/lib.rs | 4 +++ ledger/src/test_helpers/chain_builder.rs | 2 ++ ledger/tests/helpers/mod.rs | 2 ++ 12 files changed, 84 insertions(+), 4 deletions(-) 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 9d08126825..f2973594e2 100644 --- a/console/account/src/private_key/sign.rs +++ b/console/account/src/private_key/sign.rs @@ -18,17 +18,23 @@ 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) } @@ -71,6 +77,7 @@ mod tests { const ITERATIONS: u64 = 100; #[test] + #[allow(deprecated)] fn test_sign_and_verify() -> Result<()> { let rng = &mut TestRng::default(); @@ -94,6 +101,7 @@ mod tests { } #[test] + #[allow(deprecated)] fn test_sign_and_verify_bytes() -> Result<()> { let rng = &mut TestRng::default(); @@ -117,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/sign.rs b/console/account/src/signature/sign.rs index af5ccea9ef..6e2c60f629 100644 --- a/console/account/src/signature/sign.rs +++ b/console/account/src/signature/sign.rs @@ -20,6 +20,7 @@ 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]])? { @@ -32,16 +33,19 @@ impl Signature { } /// 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], @@ -50,6 +54,7 @@ impl Signature { // 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) } @@ -175,6 +180,7 @@ mod tests { const ITERATIONS: u64 = 100; #[test] + #[allow(deprecated)] fn test_sign_rejects_request_like_messages() -> Result<()> { let mut rng = TestRng::default(); diff --git a/console/account/src/signature/verify.rs b/console/account/src/signature/verify.rs index 7cd124b112..e33ee4fe58 100644 --- a/console/account/src/signature/verify.rs +++ b/console/account/src/signature/verify.rs @@ -19,20 +19,25 @@ use super::*; impl Signature { /// Verifies (challenge == challenge') && (address == address') where: /// 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}"); @@ -166,6 +171,7 @@ mod tests { const ITERATIONS: u64 = 100; #[test] + #[allow(deprecated)] fn test_sign_and_verify() -> Result<()> { let rng = &mut TestRng::default(); @@ -198,6 +204,7 @@ mod tests { } #[test] + #[allow(deprecated)] fn test_sign_and_verify_bytes() -> Result<()> { let rng = &mut TestRng::default(); @@ -237,6 +244,7 @@ mod tests { } #[test] + #[allow(deprecated)] fn test_sign_and_verify_bits() -> Result<()> { let rng = &mut TestRng::default(); 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