From 11f23fbcf814a5b6320a1d6e473d83f061ed38ac Mon Sep 17 00:00:00 2001 From: curiecrypt Date: Wed, 17 Dec 2025 23:10:37 +0300 Subject: [PATCH 1/7] re-apply jubjub wrapper after rebase --- mithril-stm/benches/schnorr_sig.rs | 4 +- .../schnorr_signature/error.rs | 32 ++- .../schnorr_signature/jubjub/curve_points.rs | 175 ++++++++++++++ .../jubjub/field_elements.rs | 139 +++++++++++ .../schnorr_signature/jubjub/mod.rs | 83 +++++++ .../jubjub/poseidon_digest.rs | 14 ++ .../signature_scheme/schnorr_signature/mod.rs | 208 +++++++++++++++- .../schnorr_signature/signature.rs | 223 +++++++----------- .../schnorr_signature/signing_key.rs | 191 +++++---------- .../schnorr_signature/utils.rs | 98 -------- .../schnorr_signature/verification_key.rs | 140 ++++------- 11 files changed, 822 insertions(+), 485 deletions(-) create mode 100644 mithril-stm/src/signature_scheme/schnorr_signature/jubjub/curve_points.rs create mode 100644 mithril-stm/src/signature_scheme/schnorr_signature/jubjub/field_elements.rs create mode 100644 mithril-stm/src/signature_scheme/schnorr_signature/jubjub/mod.rs create mode 100644 mithril-stm/src/signature_scheme/schnorr_signature/jubjub/poseidon_digest.rs delete mode 100644 mithril-stm/src/signature_scheme/schnorr_signature/utils.rs diff --git a/mithril-stm/benches/schnorr_sig.rs b/mithril-stm/benches/schnorr_sig.rs index 4d76badbbb3..50b7680cf46 100644 --- a/mithril-stm/benches/schnorr_sig.rs +++ b/mithril-stm/benches/schnorr_sig.rs @@ -45,8 +45,8 @@ fn sign_and_verify(c: &mut Criterion, nr_sigs: usize) { let mut msks = Vec::new(); let mut sigs = Vec::new(); for _ in 0..nr_sigs { - let sk = SchnorrSigningKey::try_generate(&mut rng).unwrap(); - let vk = SchnorrVerificationKey::from(&sk); + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + let vk = SchnorrVerificationKey::new_from_signing_key(sk.clone()).unwrap(); let sig = sk.sign(&msg, &mut rng_sig).unwrap(); sigs.push(sig); mvks.push(vk); diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/error.rs b/mithril-stm/src/signature_scheme/schnorr_signature/error.rs index fa1701a4641..05ba095226b 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/error.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/error.rs @@ -1,5 +1,5 @@ #[cfg(feature = "future_snark")] -use super::{SchnorrSignature, SchnorrVerificationKey}; +use super::{PrimeOrderProjectivePoint, SchnorrSignature}; /// Error types for Schnorr signatures. #[cfg(feature = "future_snark")] @@ -9,19 +9,35 @@ pub enum SchnorrSignatureError { #[error("Invalid Schnorr single signature")] SignatureInvalid(Box), - /// Invalid Verification key - #[error("Invalid Schnorr Verification key")] - VerificationKeyInvalid(Box), - /// This error occurs when the serialization of the raw bytes failed #[error("Invalid bytes")] SerializationError, - /// This error occurs when the signing key fails to generate - #[error("Failed generation of the signing key")] - SigningKeyGenerationError, + /// This error occurs when the serialization of the signing key bytes failed + #[error("Invalid scalar field element bytes")] + ScalarFieldElementSerializationError, + + /// This error occurs when the serialization of the projective point bytes failed + #[error("Invalid projective point bytes")] + ProjectivePointSerializationError, + + /// This error occurs when the serialization of the prime order projective point bytes failed + #[error("Invalid prime order projective point bytes")] + PrimeOrderProjectivePointSerializationError, /// This error occurs when the random scalar fails to generate during the signature #[error("Failed generation of the signature's random scalar")] RandomScalarGenerationError, + + /// This error occurs when signing key is zero or one. + #[error("The signing key is invalid.")] + InvalidSigningKey, + + /// Given point is not on the curve + #[error("Given point is not on the curve")] + PointIsNotOnCurve(Box), + + /// Given point is not prime order + #[error("Given point is not prime order")] + PointIsNotPrimeOrder(Box), } diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/curve_points.rs b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/curve_points.rs new file mode 100644 index 00000000000..09bc0209b32 --- /dev/null +++ b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/curve_points.rs @@ -0,0 +1,175 @@ +use anyhow::anyhow; +use dusk_jubjub::{ + AffinePoint as JubjubAffinePoint, EDWARDS_D, ExtendedPoint as JubjubExtended, + SubgroupPoint as JubjubSubgroup, +}; +use group::{Group, GroupEncoding}; + +use super::{BaseFieldElement, ScalarFieldElement}; +use crate::{StmResult, signature_scheme::SchnorrSignatureError}; + +#[derive(Clone)] +pub(crate) struct AffinePoint(JubjubAffinePoint); + +impl AffinePoint { + pub(crate) fn from_projective_point(projective_point: ProjectivePoint) -> Self { + AffinePoint(JubjubAffinePoint::from(projective_point.0)) + } + + pub(crate) fn from_prime_order_projective_point( + prime_order_projective_point: &PrimeOrderProjectivePoint, + ) -> Self { + AffinePoint(JubjubAffinePoint::from( + ProjectivePoint::from_prime_order_projective_point(*prime_order_projective_point).0, + )) + } + + pub(crate) fn get_u(&self) -> BaseFieldElement { + BaseFieldElement(self.0.get_u()) + } + + pub(crate) fn get_v(&self) -> BaseFieldElement { + BaseFieldElement(self.0.get_v()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct ProjectivePoint(pub(crate) JubjubExtended); + +impl ProjectivePoint { + pub(crate) fn hash_to_projective_point(input: &[u8]) -> Self { + ProjectivePoint(JubjubExtended::hash_to_point(input)) + } + + pub(crate) fn add(&self, other: Self) -> Self { + ProjectivePoint(self.0 + other.0) + } + + pub(crate) fn scalar_multiplication(&self, scalar: &ScalarFieldElement) -> Self { + ProjectivePoint(self.0 * scalar.0) + } + + pub(crate) fn get_coordinates(&self) -> (BaseFieldElement, BaseFieldElement) { + let affine_point = AffinePoint::from_projective_point(*self); + + (affine_point.get_u(), affine_point.get_v()) + } + + pub(crate) fn to_bytes(self) -> [u8; 32] { + self.0.to_bytes() + } + + pub(crate) fn from_bytes(bytes: &[u8]) -> StmResult { + let mut projective_point_bytes = [0u8; 32]; + projective_point_bytes + .copy_from_slice(bytes.get(..32).ok_or(SchnorrSignatureError::SerializationError)?); + + match JubjubExtended::from_bytes(&projective_point_bytes).into_option() { + Some(projective_point) => Ok(Self(projective_point)), + None => Err(anyhow!( + SchnorrSignatureError::ProjectivePointSerializationError + )), + } + } + + pub(crate) fn from_prime_order_projective_point( + prime_order_projective_point: PrimeOrderProjectivePoint, + ) -> Self { + ProjectivePoint(JubjubExtended::from(prime_order_projective_point.0)) + } + + pub(crate) fn is_prime_order(self) -> bool { + self.0.is_prime_order().into() + } +} + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub(crate) struct PrimeOrderProjectivePoint(pub(crate) JubjubSubgroup); + +impl PrimeOrderProjectivePoint { + pub(crate) fn create_generator() -> Self { + PrimeOrderProjectivePoint(JubjubSubgroup::generator()) + } + + pub(crate) fn add(&self, other: Self) -> Self { + PrimeOrderProjectivePoint(self.0 + other.0) + } + + pub(crate) fn scalar_multiplication(&self, scalar: &ScalarFieldElement) -> Self { + PrimeOrderProjectivePoint(self.0 * scalar.0) + } + + /// Check if the given point is on the curve using its coordinates + pub(crate) fn is_on_curve(&self) -> StmResult { + let point_affine_representation = AffinePoint::from_prime_order_projective_point(self); + let (x, y) = ( + point_affine_representation.get_u(), + point_affine_representation.get_v(), + ); + let x_square = x.square(); + let y_square = y.square(); + + let lhs = y_square.sub(&x_square); + let mut rhs = x_square.mul(&y_square); + rhs = rhs.mul(&BaseFieldElement(EDWARDS_D)); + rhs = rhs.add(&BaseFieldElement::get_one()); + + if lhs != rhs { + return Err(anyhow!(SchnorrSignatureError::PointIsNotOnCurve(Box::new( + *self + )))); + } + Ok(*self) + } + + pub(crate) fn to_bytes(self) -> [u8; 32] { + self.0.to_bytes() + } + + pub(crate) fn from_bytes(bytes: &[u8]) -> StmResult { + let mut prime_order_projective_point_bytes = [0u8; 32]; + prime_order_projective_point_bytes + .copy_from_slice(bytes.get(..32).ok_or(SchnorrSignatureError::SerializationError)?); + + match JubjubSubgroup::from_bytes(&prime_order_projective_point_bytes).into_option() { + Some(prime_order_projective_point) => Ok(Self(prime_order_projective_point)), + None => Err(anyhow!( + SchnorrSignatureError::PrimeOrderProjectivePointSerializationError + )), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + mod golden { + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + + use super::*; + + const GOLDEN_JSON: &str = r#"[144, 52, 95, 161, 127, 253, 49, 32, 140, 217, 231, 207, 32, 238, 244, 196, 97, 241, 47, 95, 101, 9, 70, 136, 194, 66, 187, 253, 200, 32, 218, 43]"#; + + fn golden_value() -> PrimeOrderProjectivePoint { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let scalar = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let point = PrimeOrderProjectivePoint::create_generator(); + point.scalar_multiplication(&scalar) + } + + #[test] + fn golden_conversions() { + let value = serde_json::from_str(GOLDEN_JSON) + .expect("This JSON deserialization should not fail"); + assert_eq!(golden_value(), value); + + let serialized = + serde_json::to_string(&value).expect("This JSON serialization should not fail"); + let golden_serialized = serde_json::to_string(&golden_value()) + .expect("This JSON serialization should not fail"); + assert_eq!(golden_serialized, serialized); + } + } +} diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/field_elements.rs b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/field_elements.rs new file mode 100644 index 00000000000..8478bb99bb7 --- /dev/null +++ b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/field_elements.rs @@ -0,0 +1,139 @@ +use anyhow::anyhow; +use dusk_jubjub::{Fq as JubjubBase, Fr as JubjubScalar}; +use ff::Field; +use rand_core::{CryptoRng, RngCore}; + +use super::ProjectivePoint; +use crate::{StmResult, signature_scheme::SchnorrSignatureError}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct BaseFieldElement(pub(crate) JubjubBase); + +impl BaseFieldElement { + pub(crate) fn add(&self, other: &Self) -> Self { + BaseFieldElement(self.0 + other.0) + } + + pub(crate) fn sub(&self, other: &Self) -> Self { + BaseFieldElement(self.0 - other.0) + } + + pub(crate) fn mul(&self, other: &Self) -> Self { + BaseFieldElement(self.0 * other.0) + } + + pub(crate) fn square(&self) -> Self { + BaseFieldElement(self.0.square()) + } + + pub(crate) fn get_one() -> Self { + BaseFieldElement(JubjubBase::ONE) + } + + pub(crate) fn collect_coordinates_of_list_of_points( + point_list: &[ProjectivePoint], + ) -> Vec { + let mut coordinates: Vec = Vec::new(); + for point in point_list { + let (u, v) = point.get_coordinates(); + coordinates.push(u); + coordinates.push(v); + } + coordinates + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct ScalarFieldElement(pub(crate) JubjubScalar); + +impl ScalarFieldElement { + pub(crate) fn new_random_scalar(rng: &mut (impl RngCore + CryptoRng)) -> Self { + ScalarFieldElement(JubjubScalar::random(rng)) + } + + pub(crate) fn is_zero(&self) -> bool { + if self.0 == JubjubScalar::zero() { + return true; + } + false + } + + pub(crate) fn is_one(&self) -> bool { + if self.0 == JubjubScalar::one() { + return true; + } + false + } + + pub(crate) fn new_random_nonzero_scalar( + rng: &mut (impl RngCore + CryptoRng), + ) -> StmResult { + for _ in 0..100 { + let random_scalar = Self::new_random_scalar(rng); + if !random_scalar.is_zero() { + return Ok(random_scalar); + } + } + Err(anyhow!(SchnorrSignatureError::RandomScalarGenerationError)) + } + + pub(crate) fn sub(&self, other: &Self) -> Self { + ScalarFieldElement(self.0 - other.0) + } + + pub(crate) fn mul(&self, other: &Self) -> Self { + ScalarFieldElement(self.0 * other.0) + } + + pub(crate) fn to_bytes(self) -> [u8; 32] { + self.0.to_bytes() + } + + pub(crate) fn from_bytes(bytes: &[u8]) -> StmResult { + let mut scalar_bytes = [0u8; 32]; + scalar_bytes.copy_from_slice( + bytes + .get(..32) + .ok_or(SchnorrSignatureError::ScalarFieldElementSerializationError)?, + ); + + match JubjubScalar::from_bytes(&scalar_bytes).into_option() { + Some(scalar_field_element) => Ok(Self(scalar_field_element)), + None => Err(anyhow!( + SchnorrSignatureError::ScalarFieldElementSerializationError + )), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + mod golden { + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + + use super::*; + + const GOLDEN_JSON: &str = r#"[126, 191, 239, 197, 88, 151, 248, 254, 187, 143, 86, 35, 29, 62, 90, 13, 196, 71, 234, 5, 90, 124, 205, 194, 51, 192, 228, 133, 25, 140, 157, 7]"#; + + fn golden_value() -> ScalarFieldElement { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap() + } + + #[test] + fn golden_conversions() { + let value = serde_json::from_str(GOLDEN_JSON) + .expect("This JSON deserialization should not fail"); + assert_eq!(golden_value(), value); + + let serialized = + serde_json::to_string(&value).expect("This JSON serialization should not fail"); + let golden_serialized = serde_json::to_string(&golden_value()) + .expect("This JSON serialization should not fail"); + assert_eq!(golden_serialized, serialized); + } + } +} diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/mod.rs b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/mod.rs new file mode 100644 index 00000000000..72b8c8e5475 --- /dev/null +++ b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/mod.rs @@ -0,0 +1,83 @@ +mod curve_points; +mod field_elements; +mod poseidon_digest; + +pub(crate) use curve_points::*; +pub(crate) use field_elements::*; +pub(crate) use poseidon_digest::*; + +use serde::{ + de::Visitor, + {Deserialize, Deserializer, Serialize, Serializer}, +}; + +// --------------------------------------------------------------------- +// Serde implementation +// --------------------------------------------------------------------- + +macro_rules! impl_serde { + ($st:ty,$visitor:ident,$size:expr) => { + impl Serialize for $st { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + use serde::ser::SerializeTuple; + let mut seq = serializer.serialize_tuple($size)?; + for e in self.to_bytes().iter() { + seq.serialize_element(e)?; + } + seq.end() + } + } + + impl<'de> Deserialize<'de> for $st { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct $visitor; + + impl<'de> Visitor<'de> for $visitor { + type Value = $st; + + fn expecting( + &self, + formatter: &mut ::core::fmt::Formatter, + ) -> ::core::fmt::Result { + formatter.write_str(format!("a signature {}", stringify!($st)).as_str()) + } + + fn visit_seq(self, mut seq: A) -> Result<$st, A::Error> + where + A: serde::de::SeqAccess<'de>, + { + let mut bytes = [0u8; $size]; + for i in 0..$size { + bytes[i] = + seq.next_element()?.ok_or(serde::de::Error::invalid_length( + i, + &format!("expected bytes{}", $size.to_string()).as_str(), + ))?; + } + <$st>::from_bytes(&bytes).map_err(|_| { + serde::de::Error::custom( + &format!("deserialization failed [{}]", stringify!($st)).as_str(), + ) + }) + } + } + + deserializer.deserialize_tuple($size, $visitor) + } + } + }; +} +impl_serde!(ScalarFieldElement, ScalarFieldElementVisitor, 32); +impl_serde!( + PrimeOrderProjectivePoint, + PrimeOrderProjectivePointVisitor, + 32 +); +impl_serde!(ProjectivePoint, ProjectivePointVisitor, 32); +// impl_serde!(BlsSignature, SignatureVisitor, 48); diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/poseidon_digest.rs b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/poseidon_digest.rs new file mode 100644 index 00000000000..73efda7715a --- /dev/null +++ b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/poseidon_digest.rs @@ -0,0 +1,14 @@ +use dusk_jubjub::Fq as JubjubBase; +use dusk_poseidon::{Domain, Hash}; + +use super::{BaseFieldElement, ScalarFieldElement}; + +/// A DST (Domain Separation Tag) to distinguish between use of Poseidon hash +const DST_SIGNATURE: JubjubBase = JubjubBase::from_raw([0u64, 0, 0, 0]); + +pub(crate) fn compute_truncated_digest(input: &[BaseFieldElement]) -> ScalarFieldElement { + let mut poseidon_input = vec![DST_SIGNATURE]; + poseidon_input.extend(input.iter().map(|i| i.0).collect::>()); + + ScalarFieldElement(Hash::digest_truncated(Domain::Other, &poseidon_input)[0]) +} diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/mod.rs b/mithril-stm/src/signature_scheme/schnorr_signature/mod.rs index 722e38d5df4..0304a789965 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/mod.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/mod.rs @@ -1,16 +1,214 @@ mod error; +mod jubjub; mod signature; mod signing_key; -mod utils; mod verification_key; pub use error::*; +pub(crate) use jubjub::*; pub use signature::*; pub use signing_key::*; -pub(crate) use utils::*; pub use verification_key::*; -use dusk_jubjub::Fq as JubjubBase; +#[cfg(test)] +mod tests { + use proptest::prelude::*; + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; -/// A DST (Domain Separation Tag) to distinguish between use of Poseidon hash -const DST_SIGNATURE: JubjubBase = JubjubBase::from_raw([0u64, 0, 0, 0]); + use crate::signature_scheme::{ + PrimeOrderProjectivePoint, ScalarFieldElement, SchnorrSignature, SchnorrSignatureError, + SchnorrSigningKey, SchnorrVerificationKey, + }; + + proptest! { + #![proptest_config(ProptestConfig::with_cases(50))] + + #[test] + fn verification_key(seed in any::<[u8;32]>()) { + // Valid generation check + let sk = SchnorrSigningKey::generate(&mut ChaCha20Rng::from_seed(seed)).unwrap(); + let g = PrimeOrderProjectivePoint::create_generator(); + let vk = g.scalar_multiplication(&sk.0); + let vk_from_sk = SchnorrVerificationKey::new_from_signing_key(sk).unwrap(); + assert_eq!(vk, vk_from_sk.0); + + // Check if sk is 0 + let mut bytes = [0u8; 32]; + let sk = SchnorrSigningKey(ScalarFieldElement::from_bytes(&bytes).unwrap()); + SchnorrVerificationKey::new_from_signing_key(sk).expect_err("Verification key should not be generated from zero signing key"); + + // Check if sk is 1 + bytes[0] = 1; + let sk = SchnorrSigningKey(ScalarFieldElement::from_bytes(&bytes).unwrap()); + SchnorrVerificationKey::new_from_signing_key(sk).expect_err("Verification key should not be generated from one signing key"); + } + + #[test] + fn valid_signing_verification( + msg in prop::collection::vec(any::(), 1..128), + seed in any::<[u8;32]>(), + ) { + let sk_result = SchnorrSigningKey::generate(&mut ChaCha20Rng::from_seed(seed)); + assert!(sk_result.is_ok(), "Secret ket generation failed"); + let sk = sk_result.unwrap(); + + let vk = SchnorrVerificationKey::new_from_signing_key(sk.clone()).unwrap(); + + let sig_result = sk.sign(&msg, &mut ChaCha20Rng::from_seed(seed)); + assert!(sig_result.is_ok(), "Signature generation failed"); + + let sig = sig_result.unwrap(); + + assert!(sig.verify(&msg, &vk).is_ok(), "Verification failed."); + } + + #[test] + fn invalid_signature(msg in prop::collection::vec(any::(), 1..128), seed in any::<[u8;32]>()) { + let mut rng = ChaCha20Rng::from_seed(seed); + let sk1 = SchnorrSigningKey::generate(&mut rng).unwrap(); + let vk1 = SchnorrVerificationKey::new_from_signing_key(sk1).unwrap(); + let sk2 = SchnorrSigningKey::generate(&mut rng).unwrap(); + let fake_sig = sk2.sign(&msg, &mut rng).unwrap(); + + let error = fake_sig.verify(&msg, &vk1).expect_err("Fake signature should not be verified"); + + assert!( + matches!( + error.downcast_ref::(), + Some(SchnorrSignatureError::SignatureInvalid(_)) + ), + "Unexpected error: {error:?}" + ); + } + + #[test] + fn signing_key_to_from_bytes(seed in any::<[u8;32]>()) { + let mut rng = ChaCha20Rng::from_seed(seed); + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + let mut sk_bytes = sk.to_bytes(); + + // Valid conversion + let recovered_sk = SchnorrSigningKey::from_bytes(&sk_bytes).unwrap(); + assert_eq!(sk.0, recovered_sk.0, "Recovered signing key does not match with the original!"); + + // Not enough bytes + let mut short_bytes = [0u8; 31]; + short_bytes.copy_from_slice(sk_bytes.get(..31).unwrap()); + let result = SchnorrSigningKey::from_bytes(&short_bytes).expect_err("From bytes conversion of signing key should fail"); + assert!( + matches!( + result.downcast_ref::(), + Some(SchnorrSignatureError::SerializationError) + ), + "Unexpected error: {result:?}" + ); + + // Invalid bytes + sk_bytes[31] |= 0xff; + let result = SchnorrSigningKey::from_bytes(&sk_bytes).expect_err("From bytes conversion of signing key should fail"); + assert!( + matches!( + result.downcast_ref::(), + Some(SchnorrSignatureError::ScalarFieldElementSerializationError) + ), + "Unexpected error: {result:?}" + ); + } + + #[test] + fn verification_key_to_from_bytes(seed in any::<[u8;32]>()) { + let mut rng = ChaCha20Rng::from_seed(seed); + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + let vk = SchnorrVerificationKey::new_from_signing_key(sk.clone()).unwrap(); + let mut vk_bytes = vk.to_bytes(); + + // Valid conversion + let recovered_vk = SchnorrVerificationKey::from_bytes(&vk_bytes).unwrap(); + assert_eq!(vk.0, recovered_vk.0, "Recovered verification key does not match with the original!"); + + // Not enough bytes + let mut short_bytes = [0u8; 31]; + short_bytes.copy_from_slice(vk_bytes.get(..31).unwrap()); + let result = SchnorrVerificationKey::from_bytes(&short_bytes).expect_err("From bytes conversion of verification key should fail"); + assert!( + matches!( + result.downcast_ref::(), + Some(SchnorrSignatureError::SerializationError) + ), + "Unexpected error: {result:?}" + ); + + // Invalid bytes + vk_bytes[31] |= 0xff; + let result = SchnorrVerificationKey::from_bytes(&vk_bytes).expect_err("From bytes conversion of verification key should fail"); + assert!( + matches!( + result.downcast_ref::(), + Some(SchnorrSignatureError::PrimeOrderProjectivePointSerializationError) + ), + "Unexpected error: {result:?}" + ); + } + + #[test] + fn signature_to_from_bytes(msg in prop::collection::vec(any::(), 1..128), seed in any::<[u8;32]>()) { + let mut rng = ChaCha20Rng::from_seed(seed); + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + let signature = sk.sign(&msg, &mut ChaCha20Rng::from_seed(seed)).unwrap(); + let signature_bytes = signature.to_bytes(); + + // Valid conversion + let recovered_signature = SchnorrSignature::from_bytes(&signature_bytes).unwrap(); + assert_eq!(signature, recovered_signature, "Recovered signature does not match with the original!"); + + // Test invalid `commitment_point` + let mut corrupted_bytes = signature_bytes; + corrupted_bytes[31] |= 0xff; + let result = SchnorrSignature::from_bytes(&corrupted_bytes).expect_err("From bytes conversion of signature should fail"); + assert!( + matches!( + result.downcast_ref::(), + Some(SchnorrSignatureError::ProjectivePointSerializationError) + ), + "Unexpected error: {result:?}" + ); + + // Test invalid `response` + let mut corrupted_bytes = signature_bytes; + corrupted_bytes[63] |= 0xff; + let result = SchnorrSignature::from_bytes(&corrupted_bytes).expect_err("From bytes conversion should fail"); + assert!( + matches!( + result.downcast_ref::(), + Some(SchnorrSignatureError::ScalarFieldElementSerializationError) + ), + "Unexpected error: {result:?}" + ); + + // Test invalid `challenge` + let mut corrupted_bytes = signature_bytes; + corrupted_bytes[95] |= 0xff; + let result = SchnorrSignature::from_bytes(&corrupted_bytes).expect_err("From bytes conversion should fail"); + assert!( + matches!( + result.downcast_ref::(), + Some(SchnorrSignatureError::ScalarFieldElementSerializationError) + ), + "Unexpected error: {result:?}" + ); + + // Not enough bytes + let mut short_bytes = [0u8; 95]; + short_bytes.copy_from_slice(signature_bytes.get(..95).unwrap()); + let result = SchnorrSignature::from_bytes(&short_bytes).expect_err("From bytes conversion of signature should fail"); + assert!( + matches!( + result.downcast_ref::(), + Some(SchnorrSignatureError::SerializationError) + ), + "Unexpected error: {result:?}" + ); + } + } +} diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs b/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs index 4a16ebe156c..1b0455e6510 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs @@ -1,31 +1,25 @@ use anyhow::{Context, anyhow}; -use dusk_jubjub::{ - ExtendedPoint as JubjubExtended, Fq as JubjubBase, Fr as JubjubScalar, - SubgroupPoint as JubjubSubgroup, -}; -use dusk_poseidon::{Domain, Hash}; -use group::{Group, GroupEncoding}; - -use crate::StmResult; +use serde::{Deserialize, Serialize}; use super::{ - DST_SIGNATURE, SchnorrSignatureError, SchnorrVerificationKey, get_coordinates_several_points, - is_on_curve, + BaseFieldElement, PrimeOrderProjectivePoint, ProjectivePoint, ScalarFieldElement, + SchnorrSignatureError, SchnorrVerificationKey, compute_truncated_digest, }; +use crate::StmResult; /// Structure of the Schnorr signature to use with the SNARK /// -/// This signature includes a value `sigma` which depends only on +/// This signature includes a value `commitment_point` which depends only on /// the message and the signing key. /// This value is used in the lottery process to determine the correct indices. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct SchnorrSignature { /// Deterministic value depending on the message and secret key - pub(crate) sigma: JubjubExtended, + pub(crate) commitment_point: ProjectivePoint, /// Part of the Schnorr signature depending on the secret key - pub(crate) signature: JubjubScalar, + pub(crate) response: ScalarFieldElement, /// Part of the Schnorr signature NOT depending on the secret key - pub(crate) challenge: JubjubScalar, + pub(crate) challenge: ScalarFieldElement, } impl SchnorrSignature { @@ -40,56 +34,49 @@ impl SchnorrSignature { /// - Ok(()) if the signature verifies and an error if not /// /// The protocol computes: - /// - msg_hash = H(Sha256(msg)) - /// - random_point_1_recomputed = msg_hash * signature + sigma * challenge - /// - random_point_2_recomputed = generator * signature + verification_key * challenge + /// - msg_hash_point = H(Sha256(msg)) + /// - random_point_1_recomputed = response * msg_hash_point + challenge * commitment_point + /// - random_point_2_recomputed = response * prime_order_generator_point + challenge * verification_key /// - challenge_recomputed = Poseidon(DST || H(Sha256(msg)) || verification_key - /// || sigma || random_point_1_recomputed || random_point_2_recomputed) + /// || commitment_point || random_point_1_recomputed || random_point_2_recomputed) /// /// Check: challenge == challenge_recomputed /// pub fn verify(&self, msg: &[u8], verification_key: &SchnorrVerificationKey) -> StmResult<()> { - // Check that the verification key is on the curve - if !is_on_curve(verification_key.0.into()) { - return Err(anyhow!(SchnorrSignatureError::VerificationKeyInvalid( - Box::new(*verification_key) - ))); - } + // Check that the verification key is valid + verification_key + .is_valid() + .with_context(|| "Signature verification failed due to invalid verification key")?; - let generator = JubjubSubgroup::generator(); + let prime_order_generator_point = PrimeOrderProjectivePoint::create_generator(); // First hashing the message to a scalar then hashing it to a curve point - let msg_hash = JubjubExtended::hash_to_point(msg); + let msg_hash_point = ProjectivePoint::hash_to_projective_point(msg); - // Computing R1 = H(msg) * s + sigma * c - let msg_hash_times_signature = msg_hash * self.signature; - let sigma_times_challenge = self.sigma * self.challenge; - let random_point_1_recomputed = msg_hash_times_signature + sigma_times_challenge; + // Computing random_point_1_recomputed = response * H(msg) + challenge * commitment_point + let response_time_msg_hash_point = msg_hash_point.scalar_multiplication(&self.response); + let challenge_times_commitment_point = + self.commitment_point.scalar_multiplication(&self.challenge); + let random_point_1_recomputed = + response_time_msg_hash_point.add(challenge_times_commitment_point); - // Computing R2 = g * s + vk * c - let generator_times_signature = generator * self.signature; - let vk_times_challenge = verification_key.0 * self.challenge; - let random_point_2_recomputed = generator_times_signature + vk_times_challenge; + // Computing random_point_2_recomputed = response * prime_order_generator_point + challenge * vk + let response_times_generator_point = + prime_order_generator_point.scalar_multiplication(&self.response); + let challenge_times_vk = verification_key.0.scalar_multiplication(&self.challenge); + let random_point_2_recomputed = response_times_generator_point.add(challenge_times_vk); // Since the hash function takes as input scalar elements // We need to convert the EC points to their coordinates - let points_coordinates = get_coordinates_several_points(&[ - msg_hash, - verification_key.0.into(), - self.sigma, + let points_coordinates = BaseFieldElement::collect_coordinates_of_list_of_points(&[ + msg_hash_point, + ProjectivePoint::from_prime_order_projective_point(verification_key.0), + self.commitment_point, random_point_1_recomputed, - random_point_2_recomputed.into(), + ProjectivePoint::from_prime_order_projective_point(random_point_2_recomputed), ]); - let mut poseidon_input = vec![DST_SIGNATURE]; - poseidon_input.extend( - points_coordinates - .into_iter() - .flat_map(|(x, y)| [x, y]) - .collect::>(), - ); - - let challenge_recomputed = Hash::digest_truncated(Domain::Other, &poseidon_input)[0]; + let challenge_recomputed = compute_truncated_digest(&points_coordinates); if challenge_recomputed != self.challenge { return Err(anyhow!(SchnorrSignatureError::SignatureInvalid(Box::new( @@ -103,8 +90,8 @@ impl SchnorrSignature { /// Convert an `SchnorrSignature` into bytes. pub fn to_bytes(self) -> [u8; 96] { let mut out = [0; 96]; - out[0..32].copy_from_slice(&self.sigma.to_bytes()); - out[32..64].copy_from_slice(&self.signature.to_bytes()); + out[0..32].copy_from_slice(&self.commitment_point.to_bytes()); + out[32..64].copy_from_slice(&self.response.to_bytes()); out[64..96].copy_from_slice(&self.challenge.to_bytes()); out @@ -117,36 +104,38 @@ impl SchnorrSignature { .with_context(|| "Not enough bytes provided to create a signature."); } - let sigma_bytes = bytes[0..32] - .try_into() - .map_err(|_| anyhow!(SchnorrSignatureError::SerializationError)) - .with_context(|| "Failed to obtain sigma's bytes.")?; - let sigma = JubjubExtended::from_bytes(&sigma_bytes) - .into_option() - .ok_or(anyhow!(SchnorrSignatureError::SerializationError)) - .with_context(|| "Unable to convert bytes into a sigma value.")?; - - let signature_bytes = bytes[32..64] - .try_into() - .map_err(|_| anyhow!(SchnorrSignatureError::SerializationError)) - .with_context(|| "Failed to obtain signature's bytes.")?; - let signature = JubjubScalar::from_bytes(&signature_bytes) - .into_option() - .ok_or(anyhow!(SchnorrSignatureError::SerializationError)) - .with_context(|| "Unable to convert bytes into a signature value.")?; - - let challenge_bytes = bytes[64..96] - .try_into() - .map_err(|_| anyhow!(SchnorrSignatureError::SerializationError)) - .with_context(|| "Failed to obtain challenge's bytes.")?; - let challenge = JubjubScalar::from_bytes(&challenge_bytes) - .into_option() - .ok_or(anyhow!(SchnorrSignatureError::SerializationError)) - .with_context(|| "Unable to convert bytes into a challenge value.")?; + let mut u8bytes = [0u8; 32]; + + u8bytes.copy_from_slice( + bytes + .get(0..32) + .ok_or(SchnorrSignatureError::SerializationError) + .with_context(|| "Could not get the bytes of `commitment_point`")?, + ); + let commitment_point = ProjectivePoint::from_bytes(&u8bytes) + .with_context(|| "Could not convert bytes to `commitment_point`")?; + + u8bytes.copy_from_slice( + bytes + .get(32..64) + .ok_or(SchnorrSignatureError::SerializationError) + .with_context(|| "Could not get the bytes of `response`")?, + ); + let response = ScalarFieldElement::from_bytes(&u8bytes) + .with_context(|| "Could not convert the bytes to `response`")?; + + u8bytes.copy_from_slice( + bytes + .get(64..96) + .ok_or(SchnorrSignatureError::SerializationError) + .with_context(|| "Could not get the bytes of `challenge`")?, + ); + let challenge = ScalarFieldElement::from_bytes(&u8bytes) + .with_context(|| "Could not convert bytes to `challenge`")?; Ok(Self { - sigma, - signature, + commitment_point, + response, challenge, }) } @@ -154,56 +143,6 @@ impl SchnorrSignature { #[cfg(test)] mod tests { - use rand_chacha::ChaCha20Rng; - use rand_core::SeedableRng; - - use crate::signature_scheme::{SchnorrSignature, SchnorrSigningKey, SchnorrVerificationKey}; - - #[test] - fn invalid_sig() { - let msg = vec![0, 0, 0, 1]; - let msg2 = vec![0, 0, 0, 2]; - let seed = [0u8; 32]; - let mut rng = ChaCha20Rng::from_seed(seed); - let sk = SchnorrSigningKey::try_generate(&mut rng).unwrap(); - let vk = SchnorrVerificationKey::from(&sk); - let sk2 = SchnorrSigningKey::try_generate(&mut rng).unwrap(); - let vk2 = SchnorrVerificationKey::from(&sk2); - - let sig = sk.sign(&msg, &mut rng).unwrap(); - let sig2 = sk.sign(&msg2, &mut rng).unwrap(); - - // Wrong verification key is used - let result1 = sig.verify(&msg, &vk2); - let result2 = sig2.verify(&msg, &vk); - - result1.expect_err("Wrong verification key used, test should fail."); - // Wrong message is verified - result2.expect_err("Wrong message used, test should fail."); - } - - #[test] - fn serialize_deserialize_signature() { - let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - - let msg = vec![0, 0, 0, 1]; - let sk = SchnorrSigningKey::try_generate(&mut rng).unwrap(); - - let sig = sk.sign(&msg, &mut rng).unwrap(); - let sig_bytes: [u8; 96] = sig.to_bytes(); - let sig2 = SchnorrSignature::from_bytes(&sig_bytes).unwrap(); - - assert_eq!(sig, sig2); - } - - #[test] - fn from_bytes_signature_not_enough_bytes() { - let msg = vec![0u8; 95]; - - let result = SchnorrSignature::from_bytes(&msg); - - result.expect_err("Not enough bytes."); - } mod golden { @@ -212,30 +151,30 @@ mod tests { use crate::signature_scheme::{SchnorrSignature, SchnorrSigningKey}; - const GOLDEN_BYTES: &[u8; 96] = &[ - 143, 53, 198, 62, 178, 1, 88, 253, 21, 92, 100, 13, 72, 180, 198, 127, 39, 175, 102, - 69, 147, 249, 244, 224, 122, 121, 248, 68, 217, 242, 158, 113, 94, 57, 200, 241, 208, - 145, 251, 8, 92, 119, 163, 38, 81, 85, 54, 36, 193, 221, 254, 242, 21, 129, 110, 161, - 142, 184, 107, 156, 100, 34, 190, 9, 200, 20, 178, 142, 61, 253, 193, 11, 5, 180, 97, - 73, 125, 88, 162, 36, 30, 177, 225, 52, 136, 21, 138, 93, 81, 23, 19, 64, 82, 78, 229, - 3, - ]; + const GOLDEN_JSON: &str = r#" + { + "commitment_point": [143, 53, 198, 62, 178, 1, 88, 253, 21, 92, 100, 13, 72, 180, 198, 127, 39, 175, 102, 69, 147, 249, 244, 224, 122, 121, 248, 68, 217, 242, 158, 113], + "response": [94, 57, 200, 241, 208, 145, 251, 8, 92, 119, 163, 38, 81, 85, 54, 36, 193, 221, 254, 242, 21, 129, 110, 161, 142, 184, 107, 156, 100, 34, 190, 9], + "challenge": [200, 20, 178, 142, 61, 253, 193, 11, 5, 180, 97, 73, 125, 88, 162, 36, 30, 177, 225, 52, 136, 21, 138, 93, 81, 23, 19, 64, 82, 78, 229, 3] + }"#; fn golden_value() -> SchnorrSignature { let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - let sk = SchnorrSigningKey::try_generate(&mut rng).unwrap(); + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); let msg = [0u8; 32]; sk.sign(&msg, &mut rng).unwrap() } #[test] fn golden_conversions() { - let value = SchnorrSignature::from_bytes(GOLDEN_BYTES) - .expect("This from bytes should not fail"); + let value = serde_json::from_str(GOLDEN_JSON) + .expect("This JSON deserialization should not fail"); assert_eq!(golden_value(), value); - let serialized = SchnorrSignature::to_bytes(value); - let golden_serialized = SchnorrSignature::to_bytes(golden_value()); + let serialized = + serde_json::to_string(&value).expect("This JSON serialization should not fail"); + let golden_serialized = serde_json::to_string(&golden_value()) + .expect("This JSON serialization should not fail"); assert_eq!(golden_serialized, serialized); } } diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/signing_key.rs b/mithril-stm/src/signature_scheme/schnorr_signature/signing_key.rs index 2bffc1dea70..2f1913e55ed 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/signing_key.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/signing_key.rs @@ -1,55 +1,46 @@ use anyhow::{Context, anyhow}; -use dusk_jubjub::{ - ExtendedPoint as JubjubExtended, Fq as JubjubBase, Fr as JubjubScalar, - SubgroupPoint as JubjubSubgroup, -}; -use dusk_poseidon::{Domain, Hash}; -use group::Group; use rand_core::{CryptoRng, RngCore}; - -use crate::StmResult; +use serde::{Deserialize, Serialize}; use super::{ - DST_SIGNATURE, SchnorrSignature, SchnorrSignatureError, SchnorrVerificationKey, - generate_non_zero_scalar, get_coordinates_several_points, + BaseFieldElement, PrimeOrderProjectivePoint, ProjectivePoint, ScalarFieldElement, + SchnorrSignature, SchnorrSignatureError, SchnorrVerificationKey, compute_truncated_digest, }; +use crate::StmResult; /// Schnorr Signing key, it is essentially a random scalar of the Jubjub scalar field -#[derive(Debug, Clone)] -pub struct SchnorrSigningKey(pub(crate) JubjubScalar); +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct SchnorrSigningKey(pub(crate) ScalarFieldElement); impl SchnorrSigningKey { /// Generate a random scalar value to use as signing key - pub fn try_generate(rng: &mut R) -> StmResult { - let r = generate_non_zero_scalar(rng); - match r { - Ok(s) => Ok(SchnorrSigningKey(s)), - Err(_) => Err(anyhow!(SchnorrSignatureError::SigningKeyGenerationError)) - .with_context(|| "Failed to generate a non zero schnorr signing key."), - } + pub fn generate(rng: &mut R) -> StmResult { + let scalar = ScalarFieldElement::new_random_nonzero_scalar(rng) + .with_context(|| "Failed to generate a non zero Schnorr signing key.")?; + Ok(SchnorrSigningKey(scalar)) } /// This function is an adapted version of the Schnorr signature scheme that includes - /// the computation of a deterministic value (called sigma) based on the message and the signing key + /// the computation of a deterministic value (called commitment_point) based on the message and the signing key /// and works with the Jubjub elliptic curve and the Poseidon hash function. /// /// Input: /// - a message: some bytes /// - a secret key: an element of the scalar field of the Jubjub curve /// Output: - /// - a signature of the form (sigma, signature, challenge): - /// - sigma is deterministic depending only on the message and secret key - /// - the signature and challenge depends on a random value generated during the signature + /// - a signature of the form (commitment_point, response, challenge): + /// - commitment_point is deterministic depending only on the message and secret key + /// - the response and challenge depends on a random value generated during the signature /// /// The protocol computes: - /// - sigma = H(Sha256(msg)) * secret_key + /// - commitment_point = secret_key * H(Sha256(msg)) /// - random_scalar, a random value - /// - random_point_1 = H(Sha256(msg)) * random_scalar - /// - random_point_2 = generator * random_scalar, where generator is a generator of the prime-order subgroup of Jubjub - /// - challenge = Poseidon(DST || H(Sha256(msg)) || verification_key || sigma || random_point_1 || random_point_2) - /// - signature = random_scalar - challenge * signing_key + /// - random_point_1 = random_scalar * H(Sha256(msg)) + /// - random_point_2 = random_scalar * prime_order_generator_point, where generator is a generator of the prime-order subgroup of Jubjub + /// - challenge = Poseidon(DST || H(Sha256(msg)) || verification_key || commitment_point || random_point_1 || random_point_2) + /// - response = random_scalar - challenge * signing_key /// - /// Output the signature (sigma, signature, challenge) + /// Output the signature (commitment_point, response, challenge) /// pub fn sign( &self, @@ -57,45 +48,39 @@ impl SchnorrSigningKey { rng: &mut R, ) -> StmResult { // Use the subgroup generator to compute the curve points - let generator = JubjubSubgroup::generator(); - let verification_key = SchnorrVerificationKey::from(self); + let prime_order_generator_point = PrimeOrderProjectivePoint::create_generator(); + let verification_key = SchnorrVerificationKey::new_from_signing_key(self.clone()) + .with_context(|| "Could not generate verification key from signing key.")?; // First hashing the message to a scalar then hashing it to a curve point - let msg_hash = JubjubExtended::hash_to_point(msg); + let msg_hash_point = ProjectivePoint::hash_to_projective_point(msg); - let sigma = msg_hash * self.0; + let commitment_point = msg_hash_point.scalar_multiplication(&self.0); - // r1 = H(msg) * r, r2 = g * r - let random_scalar = generate_non_zero_scalar(rng) - .with_context(|| "Failed to generate a non zero scalar for the signature.")?; + let random_scalar = ScalarFieldElement::new_random_nonzero_scalar(rng) + .with_context(|| "Random scalar generation failed during signing.")?; - let random_point_1 = msg_hash * random_scalar; - let random_point_2 = generator * random_scalar; + let random_point_1 = msg_hash_point.scalar_multiplication(&random_scalar); + let random_point_2 = prime_order_generator_point.scalar_multiplication(&random_scalar); // Since the hash function takes as input scalar elements // We need to convert the EC points to their coordinates // The order must be preserved - let points_coordinates = get_coordinates_several_points(&[ - msg_hash, - verification_key.0.into(), - sigma, + let points_coordinates = BaseFieldElement::collect_coordinates_of_list_of_points(&[ + msg_hash_point, + ProjectivePoint::from_prime_order_projective_point(verification_key.0), + commitment_point, random_point_1, - random_point_2.into(), + ProjectivePoint::from_prime_order_projective_point(random_point_2), ]); - let mut poseidon_input = vec![DST_SIGNATURE]; - poseidon_input.extend( - points_coordinates - .into_iter() - .flat_map(|(x, y)| [x, y]) - .collect::>(), - ); - let challenge = Hash::digest_truncated(Domain::Other, &poseidon_input)[0]; - let signature = random_scalar - challenge * self.0; + let challenge = compute_truncated_digest(&points_coordinates); + let mut response = challenge.mul(&self.0); + response = random_scalar.sub(&response); Ok(SchnorrSignature { - sigma, - signature, + commitment_point, + response, challenge, }) } @@ -110,83 +95,18 @@ impl SchnorrSigningKey { /// The bytes must represent a Jubjub scalar or the conversion will fail pub fn from_bytes(bytes: &[u8]) -> StmResult { if bytes.len() < 32 { - return Err(anyhow!(SchnorrSignatureError::SerializationError)) - .with_context(|| "Not enough bytes provided to create a Schnorr signing key."); - } - - let signing_key_bytes = bytes[0..32] - .try_into() - .map_err(|_| anyhow!(SchnorrSignatureError::SerializationError)) - .with_context(|| "Failed to obtain the Schnorr signing key's bytes.")?; - - match JubjubScalar::from_bytes(&signing_key_bytes).into_option() { - Some(signing_key) => Ok(Self(signing_key)), - None => Err(anyhow!(SchnorrSignatureError::SerializationError)) - .with_context(|| "Failed to generate a Jubjub scalar from the given bytes."), + return Err(anyhow!(SchnorrSignatureError::SerializationError)).with_context( + || "Not enough bytes provided to re-construct a Schnorr signing key.", + ); } + let scalar_field_element = ScalarFieldElement::from_bytes(bytes) + .with_context(|| "Could not construct Schnorr signing key from given bytes.")?; + Ok(SchnorrSigningKey(scalar_field_element)) } } #[cfg(test)] mod tests { - use rand_chacha::ChaCha20Rng; - use rand_core::SeedableRng; - - use super::*; - - #[test] - fn generate_signing_key() { - let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - let _sk = SchnorrSigningKey::try_generate(&mut rng); - } - - #[test] - fn sign_and_verify() { - let msg = vec![0, 0, 0, 1]; - let seed = [0u8; 32]; - let mut rng = ChaCha20Rng::from_seed(seed); - let sk = SchnorrSigningKey::try_generate(&mut rng).unwrap(); - let vk = SchnorrVerificationKey::from(&sk); - - let sig = sk.sign(&msg, &mut rng).unwrap(); - - sig.verify(&msg, &vk).unwrap(); - } - - #[test] - fn to_from_bytes() { - let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - let sk = SchnorrSigningKey::try_generate(&mut rng).unwrap(); - - let sk_bytes = sk.to_bytes(); - let recovered_sk = SchnorrSigningKey::from_bytes(&sk_bytes).unwrap(); - - assert_eq!(sk.0, recovered_sk.0); - } - - // Failing test as the generated bytes represent a value too big to be converted - // in this manner (greater than the jubjub modulus) - #[test] - fn failing_test_from_bytes() { - let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - let mut sk = [0; 32]; - rng.fill_bytes(&mut sk); - // Setting the msb to 1 to make sk bigger than the modulus - sk[0] |= 0xff; - - let result = SchnorrSigningKey::from_bytes(&sk); - - result.expect_err("Value is not a proper sk, test should fail."); - } - - #[test] - fn from_bytes_signing_key_not_enough_bytes() { - let msg = vec![0u8; 31]; - - let result = SchnorrSigningKey::from_bytes(&msg); - - result.expect_err("Not enough bytes."); - } mod golden { @@ -195,24 +115,23 @@ mod tests { use crate::signature_scheme::SchnorrSigningKey; - const GOLDEN_BYTES: &[u8; 32] = &[ - 126, 191, 239, 197, 88, 151, 248, 254, 187, 143, 86, 35, 29, 62, 90, 13, 196, 71, 234, - 5, 90, 124, 205, 194, 51, 192, 228, 133, 25, 140, 157, 7, - ]; + const GOLDEN_JSON: &str = r#"[126, 191, 239, 197, 88, 151, 248, 254, 187, 143, 86, 35, 29, 62, 90, 13, 196, 71, 234, 5, 90, 124, 205, 194, 51, 192, 228, 133, 25, 140, 157, 7]"#; fn golden_value() -> SchnorrSigningKey { let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - SchnorrSigningKey::try_generate(&mut rng).unwrap() + SchnorrSigningKey::generate(&mut rng).unwrap() } #[test] fn golden_conversions() { - let value = SchnorrSigningKey::from_bytes(GOLDEN_BYTES) - .expect("This from bytes should not fail"); - assert_eq!(golden_value().0, value.0); - - let serialized = SchnorrSigningKey::to_bytes(&value); - let golden_serialized = SchnorrSigningKey::to_bytes(&golden_value()); + let value = serde_json::from_str(GOLDEN_JSON) + .expect("This JSON deserialization should not fail"); + assert_eq!(golden_value(), value); + + let serialized = + serde_json::to_string(&value).expect("This JSON serialization should not fail"); + let golden_serialized = serde_json::to_string(&golden_value()) + .expect("This JSON serialization should not fail"); assert_eq!(golden_serialized, serialized); } } diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/utils.rs b/mithril-stm/src/signature_scheme/schnorr_signature/utils.rs deleted file mode 100644 index 606b60f1671..00000000000 --- a/mithril-stm/src/signature_scheme/schnorr_signature/utils.rs +++ /dev/null @@ -1,98 +0,0 @@ -use anyhow::{Context, anyhow}; -use dusk_jubjub::{ - AffinePoint as JubjubAffine, EDWARDS_D, ExtendedPoint as JubjubExtended, Fq as JubjubBase, - Fr as JubjubScalar, -}; -use ff::Field; -use group::Curve; -use rand_core::{CryptoRng, RngCore}; - -use crate::StmResult; - -use super::SchnorrSignatureError; - -/// Check if the given point is on the curve using its coordinates -pub fn is_on_curve(point: JubjubExtended) -> bool { - let point_affine_representation = JubjubAffine::from(point); - let (x, y) = ( - point_affine_representation.get_u(), - point_affine_representation.get_v(), - ); - let x_square = x.square(); - let y_square = y.square(); - - let lhs = y_square - x_square; - let rhs = JubjubBase::ONE + EDWARDS_D * x_square * y_square; - - lhs == rhs -} - -/// Extract the coordinates of given points in an Extended form -/// -/// This is mainly used to feed the Poseidon hash function, the order is maintained -/// from input to output which is important for the hash function -pub fn get_coordinates_several_points(points: &[JubjubExtended]) -> Vec<(JubjubBase, JubjubBase)> { - let mut points_affine = - vec![JubjubAffine::from_raw_unchecked(JubjubBase::ZERO, JubjubBase::ZERO); points.len()]; - JubjubExtended::batch_normalize(points, &mut points_affine); - - points_affine - .into_iter() - .map(|p| (p.get_u(), p.get_v())) - .collect::>() -} - -/// Generate a random non zero value from the scalar field of Jubjub -/// -/// Tries to generate 100 times a non zero value and returns an error if it fails to do so -pub fn generate_non_zero_scalar(rng: &mut R) -> StmResult { - for _ in 0..100 { - let random_scalar = JubjubScalar::random(&mut *rng); - if random_scalar != JubjubScalar::ZERO { - return Ok(random_scalar); - } - } - Err(anyhow!(SchnorrSignatureError::RandomScalarGenerationError)) - .with_context(|| "Failed to generate a non zero signing key after 100 attempts.") -} - -#[cfg(test)] -mod tests { - use dusk_jubjub::{AffinePoint as JubjubAffine, ExtendedPoint as JubjubExtended}; - use group::Group; - use rand_chacha::ChaCha20Rng; - use rand_core::SeedableRng; - - use super::*; - - #[test] - fn get_coordinates_from_several_points() { - let seed = [0u8; 32]; - let mut rng = ChaCha20Rng::from_seed(seed); - let points = vec![ - JubjubExtended::random(&mut rng), - JubjubExtended::random(&mut rng), - JubjubExtended::random(&mut rng), - ]; - - let coordinates = get_coordinates_several_points(&points); - - let mut coordinates_iter = coordinates.iter(); - for p in points.iter().take(3) { - let (x, y) = coordinates_iter.next().unwrap(); - let point_affine = JubjubAffine::from_raw_unchecked(*x, *y); - let point_extended = JubjubExtended::from_affine(point_affine); - assert_eq!(*p, point_extended); - } - } - - #[test] - fn generation_non_zero_scalar() { - let seed = [0u8; 32]; - let mut rng = ChaCha20Rng::from_seed(seed); - - let scalar = generate_non_zero_scalar(&mut rng).unwrap(); - - assert_ne!(scalar, JubjubScalar::ZERO); - } -} diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/verification_key.rs b/mithril-stm/src/signature_scheme/schnorr_signature/verification_key.rs index 0e1787f3f96..b323ff5e0b9 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/verification_key.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/verification_key.rs @@ -1,17 +1,43 @@ -use anyhow::{Context, anyhow}; -use dusk_jubjub::SubgroupPoint as JubjubSubgroup; -use group::{Group, GroupEncoding}; +use anyhow::{Context, Ok, anyhow}; +use serde::{Deserialize, Serialize}; +use super::{PrimeOrderProjectivePoint, ProjectivePoint, SchnorrSignatureError, SchnorrSigningKey}; use crate::StmResult; -use super::{SchnorrSignatureError, SchnorrSigningKey}; - /// Schnorr verification key, it consists of a point on the Jubjub curve /// vk = g * sk, where g is a generator -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] -pub struct SchnorrVerificationKey(pub(crate) JubjubSubgroup); +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct SchnorrVerificationKey(pub(crate) PrimeOrderProjectivePoint); impl SchnorrVerificationKey { + /// Convert a Schnorr secret key into a verification key + /// + /// This is done by computing `vk = g * sk` where g is the generator + /// of the subgroup and sk is the schnorr secret key + pub fn new_from_signing_key(signing_key: SchnorrSigningKey) -> StmResult { + if signing_key.0.is_zero() | signing_key.0.is_one() { + return Err(anyhow!(SchnorrSignatureError::InvalidSigningKey)) + .with_context(|| "Verification key generation failed."); + } + let generator = PrimeOrderProjectivePoint::create_generator(); + + Ok(SchnorrVerificationKey( + generator.scalar_multiplication(&signing_key.0), + )) + } + + pub fn is_valid(&self) -> StmResult { + let projective_point = ProjectivePoint::from_prime_order_projective_point(self.0); + if !projective_point.is_prime_order() { + return Err(anyhow!(SchnorrSignatureError::PointIsNotPrimeOrder( + Box::new(self.0) + ))); + } + self.0.is_on_curve()?; + + Ok(*self) + } + /// Convert a `SchnorrVerificationKey` into bytes. pub fn to_bytes(self) -> [u8; 32] { self.0.to_bytes() @@ -23,91 +49,18 @@ impl SchnorrVerificationKey { pub fn from_bytes(bytes: &[u8]) -> StmResult { if bytes.len() < 32 { return Err(anyhow!(SchnorrSignatureError::SerializationError)).with_context( - || "Not enough bytes provided to create a Schnorr verification key.", + || "Not enough bytes provided to construct a Schnorr verification key.", ); } - let verification_key_bytes = bytes[0..32] - .try_into() - .map_err(|_| anyhow!(SchnorrSignatureError::SerializationError)) - .with_context(|| "Failed to obtain the Schnorr verification key's bytes.")?; - let point = JubjubSubgroup::from_bytes(&verification_key_bytes) - .into_option() - .ok_or(anyhow!(SchnorrSignatureError::SerializationError)) - .with_context(|| "Failed to create a JubjubSubgroup point from the given bytes.")?; - - Ok(SchnorrVerificationKey(point)) - } -} - -impl From<&SchnorrSigningKey> for SchnorrVerificationKey { - /// Convert a Schnorr secret key into a verification key - /// - /// This is done by computing `vk = g * sk` where g is the generator - /// of the subgroup and sk is the schnorr secret key - fn from(signing_key: &SchnorrSigningKey) -> Self { - let generator = JubjubSubgroup::generator(); + let prime_order_projective_point = PrimeOrderProjectivePoint::from_bytes(bytes) + .with_context(|| "Cannot construct Schnorr verification key from given bytes.")?; - SchnorrVerificationKey(generator * signing_key.0) + Ok(SchnorrVerificationKey(prime_order_projective_point)) } } #[cfg(test)] mod tests { - use dusk_jubjub::Fq as JubjubBase; - use dusk_jubjub::SubgroupPoint as JubjubSubgroup; - use ff::Field; - use group::Group; - use rand_chacha::ChaCha20Rng; - use rand_core::SeedableRng; - - use crate::signature_scheme::{SchnorrSigningKey, SchnorrVerificationKey}; - - #[test] - fn generate_verification_key() { - let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - let sk = SchnorrSigningKey::try_generate(&mut rng).unwrap(); - let g = JubjubSubgroup::generator(); - let vk = g * sk.0; - - let vk_from_sk = SchnorrVerificationKey::from(&sk); - - assert_eq!(vk, vk_from_sk.0); - } - - #[test] - fn verify_fail_verification_key_not_on_curve() { - let msg = vec![0, 0, 0, 1]; - let seed = [0u8; 32]; - let mut rng = ChaCha20Rng::from_seed(seed); - let sk = SchnorrSigningKey::try_generate(&mut rng).unwrap(); - let vk1 = SchnorrVerificationKey::from(&sk); - let sig = sk.sign(&msg, &mut rng).unwrap(); - let vk2 = SchnorrVerificationKey(JubjubSubgroup::from_raw_unchecked( - JubjubBase::ONE, - JubjubBase::ONE, - )); - - let result1 = sig.verify(&msg, &vk1); - let result2 = sig.verify(&msg, &vk2); - - result1.expect("Correct verification key used, test should pass."); - - result2.expect_err("Invalid verification key used, test should fail."); - } - - #[test] - fn serialize_deserialize_vk() { - let seed = 0; - let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(seed); - let sk = SchnorrSigningKey::try_generate(&mut rng).unwrap(); - let vk = SchnorrVerificationKey::from(&sk); - - let vk_bytes = vk.to_bytes(); - let vk2 = SchnorrVerificationKey::from_bytes(&vk_bytes).unwrap(); - - assert_eq!(vk.0, vk2.0); - } - mod golden { use rand_chacha::ChaCha20Rng; @@ -115,25 +68,24 @@ mod tests { use crate::signature_scheme::{SchnorrSigningKey, SchnorrVerificationKey}; - const GOLDEN_BYTES: &[u8; 32] = &[ - 144, 52, 95, 161, 127, 253, 49, 32, 140, 217, 231, 207, 32, 238, 244, 196, 97, 241, 47, - 95, 101, 9, 70, 136, 194, 66, 187, 253, 200, 32, 218, 43, - ]; + const GOLDEN_JSON: &str = r#"[144, 52, 95, 161, 127, 253, 49, 32, 140, 217, 231, 207, 32, 238, 244, 196, 97, 241, 47, 95, 101, 9, 70, 136, 194, 66, 187, 253, 200, 32, 218, 43]"#; fn golden_value() -> SchnorrVerificationKey { let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - let sk = SchnorrSigningKey::try_generate(&mut rng).unwrap(); - SchnorrVerificationKey::from(&sk) + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + SchnorrVerificationKey::new_from_signing_key(sk).unwrap() } #[test] fn golden_conversions() { - let value = SchnorrVerificationKey::from_bytes(GOLDEN_BYTES) - .expect("This from bytes should not fail"); + let value = serde_json::from_str(GOLDEN_JSON) + .expect("This JSON deserialization should not fail"); assert_eq!(golden_value(), value); - let serialized = SchnorrVerificationKey::to_bytes(value); - let golden_serialized = SchnorrVerificationKey::to_bytes(golden_value()); + let serialized = + serde_json::to_string(&value).expect("This JSON serialization should not fail"); + let golden_serialized = serde_json::to_string(&golden_value()) + .expect("This JSON serialization should not fail"); assert_eq!(golden_serialized, serialized); } } From 2afb902f2fa2d8cddd5d2a9c51bb10dba0d782e3 Mon Sep 17 00:00:00 2001 From: curiecrypt Date: Thu, 18 Dec 2025 00:54:43 +0300 Subject: [PATCH 2/7] some suggestions resolved --- .../schnorr_signature/error.rs | 10 +- .../schnorr_signature/jubjub/curve_points.rs | 97 +++++++++++-------- .../jubjub/field_elements.rs | 76 +++++++++------ .../signature_scheme/schnorr_signature/mod.rs | 12 +-- .../schnorr_signature/signature.rs | 50 +++++----- .../schnorr_signature/signing_key.rs | 24 +++-- .../schnorr_signature/verification_key.rs | 6 +- 7 files changed, 150 insertions(+), 125 deletions(-) diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/error.rs b/mithril-stm/src/signature_scheme/schnorr_signature/error.rs index 05ba095226b..982cb375f61 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/error.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/error.rs @@ -13,21 +13,21 @@ pub enum SchnorrSignatureError { #[error("Invalid bytes")] SerializationError, - /// This error occurs when the serialization of the signing key bytes failed + /// This error occurs when the serialization of the scalar field bytes failed #[error("Invalid scalar field element bytes")] - ScalarFieldElementSerializationError, + ScalarFieldElementSerialization, /// This error occurs when the serialization of the projective point bytes failed #[error("Invalid projective point bytes")] - ProjectivePointSerializationError, + ProjectivePointSerialization, /// This error occurs when the serialization of the prime order projective point bytes failed #[error("Invalid prime order projective point bytes")] - PrimeOrderProjectivePointSerializationError, + PrimeOrderProjectivePointSerialization, /// This error occurs when the random scalar fails to generate during the signature #[error("Failed generation of the signature's random scalar")] - RandomScalarGenerationError, + RandomScalarGeneration, /// This error occurs when signing key is zero or one. #[error("The signing key is invalid.")] diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/curve_points.rs b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/curve_points.rs index 09bc0209b32..50e4095e810 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/curve_points.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/curve_points.rs @@ -4,6 +4,7 @@ use dusk_jubjub::{ SubgroupPoint as JubjubSubgroup, }; use group::{Group, GroupEncoding}; +use std::ops::{Add, Mul}; use super::{BaseFieldElement, ScalarFieldElement}; use crate::{StmResult, signature_scheme::SchnorrSignatureError}; @@ -16,14 +17,6 @@ impl AffinePoint { AffinePoint(JubjubAffinePoint::from(projective_point.0)) } - pub(crate) fn from_prime_order_projective_point( - prime_order_projective_point: &PrimeOrderProjectivePoint, - ) -> Self { - AffinePoint(JubjubAffinePoint::from( - ProjectivePoint::from_prime_order_projective_point(*prime_order_projective_point).0, - )) - } - pub(crate) fn get_u(&self) -> BaseFieldElement { BaseFieldElement(self.0.get_u()) } @@ -33,6 +26,14 @@ impl AffinePoint { } } +impl From<&PrimeOrderProjectivePoint> for AffinePoint { + fn from(prime_order_projective_point: &PrimeOrderProjectivePoint) -> Self { + AffinePoint(JubjubAffinePoint::from(JubjubExtended::from( + prime_order_projective_point.0, + ))) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) struct ProjectivePoint(pub(crate) JubjubExtended); @@ -41,14 +42,6 @@ impl ProjectivePoint { ProjectivePoint(JubjubExtended::hash_to_point(input)) } - pub(crate) fn add(&self, other: Self) -> Self { - ProjectivePoint(self.0 + other.0) - } - - pub(crate) fn scalar_multiplication(&self, scalar: &ScalarFieldElement) -> Self { - ProjectivePoint(self.0 * scalar.0) - } - pub(crate) fn get_coordinates(&self) -> (BaseFieldElement, BaseFieldElement) { let affine_point = AffinePoint::from_projective_point(*self); @@ -66,23 +59,37 @@ impl ProjectivePoint { match JubjubExtended::from_bytes(&projective_point_bytes).into_option() { Some(projective_point) => Ok(Self(projective_point)), - None => Err(anyhow!( - SchnorrSignatureError::ProjectivePointSerializationError - )), + None => Err(anyhow!(SchnorrSignatureError::ProjectivePointSerialization)), } } - pub(crate) fn from_prime_order_projective_point( - prime_order_projective_point: PrimeOrderProjectivePoint, - ) -> Self { - ProjectivePoint(JubjubExtended::from(prime_order_projective_point.0)) - } - pub(crate) fn is_prime_order(self) -> bool { self.0.is_prime_order().into() } } +impl Add for ProjectivePoint { + type Output = ProjectivePoint; + + fn add(self, other: ProjectivePoint) -> ProjectivePoint { + ProjectivePoint(self.0 + other.0) + } +} + +impl Mul for ScalarFieldElement { + type Output = ProjectivePoint; + + fn mul(self, point: ProjectivePoint) -> ProjectivePoint { + ProjectivePoint(point.0 * self.0) + } +} + +impl From for ProjectivePoint { + fn from(prime_order_projective_point: PrimeOrderProjectivePoint) -> Self { + ProjectivePoint(JubjubExtended::from(prime_order_projective_point.0)) + } +} + #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub(crate) struct PrimeOrderProjectivePoint(pub(crate) JubjubSubgroup); @@ -91,28 +98,18 @@ impl PrimeOrderProjectivePoint { PrimeOrderProjectivePoint(JubjubSubgroup::generator()) } - pub(crate) fn add(&self, other: Self) -> Self { - PrimeOrderProjectivePoint(self.0 + other.0) - } - - pub(crate) fn scalar_multiplication(&self, scalar: &ScalarFieldElement) -> Self { - PrimeOrderProjectivePoint(self.0 * scalar.0) - } - /// Check if the given point is on the curve using its coordinates pub(crate) fn is_on_curve(&self) -> StmResult { - let point_affine_representation = AffinePoint::from_prime_order_projective_point(self); + let point_affine_representation = AffinePoint::from(self); let (x, y) = ( point_affine_representation.get_u(), point_affine_representation.get_v(), ); - let x_square = x.square(); - let y_square = y.square(); + let x_square = &x * &x; + let y_square = &y * &y; - let lhs = y_square.sub(&x_square); - let mut rhs = x_square.mul(&y_square); - rhs = rhs.mul(&BaseFieldElement(EDWARDS_D)); - rhs = rhs.add(&BaseFieldElement::get_one()); + let lhs = &y_square - &x_square; + let rhs = (x_square * y_square) * BaseFieldElement(EDWARDS_D) + BaseFieldElement::get_one(); if lhs != rhs { return Err(anyhow!(SchnorrSignatureError::PointIsNotOnCurve(Box::new( @@ -134,12 +131,28 @@ impl PrimeOrderProjectivePoint { match JubjubSubgroup::from_bytes(&prime_order_projective_point_bytes).into_option() { Some(prime_order_projective_point) => Ok(Self(prime_order_projective_point)), None => Err(anyhow!( - SchnorrSignatureError::PrimeOrderProjectivePointSerializationError + SchnorrSignatureError::PrimeOrderProjectivePointSerialization )), } } } +impl Add for PrimeOrderProjectivePoint { + type Output = PrimeOrderProjectivePoint; + + fn add(self, other: PrimeOrderProjectivePoint) -> PrimeOrderProjectivePoint { + PrimeOrderProjectivePoint(self.0 + other.0) + } +} + +impl Mul for ScalarFieldElement { + type Output = PrimeOrderProjectivePoint; + + fn mul(self, point: PrimeOrderProjectivePoint) -> PrimeOrderProjectivePoint { + PrimeOrderProjectivePoint(point.0 * self.0) + } +} + #[cfg(test)] mod tests { use super::*; @@ -156,7 +169,7 @@ mod tests { let mut rng = ChaCha20Rng::from_seed([0u8; 32]); let scalar = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); let point = PrimeOrderProjectivePoint::create_generator(); - point.scalar_multiplication(&scalar) + PrimeOrderProjectivePoint(point.0 * scalar.0) } #[test] diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/field_elements.rs b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/field_elements.rs index 8478bb99bb7..b82a44eae7b 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/field_elements.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/field_elements.rs @@ -2,44 +2,48 @@ use anyhow::anyhow; use dusk_jubjub::{Fq as JubjubBase, Fr as JubjubScalar}; use ff::Field; use rand_core::{CryptoRng, RngCore}; +use std::ops::{Add, Mul, Sub}; -use super::ProjectivePoint; use crate::{StmResult, signature_scheme::SchnorrSignatureError}; #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct BaseFieldElement(pub(crate) JubjubBase); impl BaseFieldElement { - pub(crate) fn add(&self, other: &Self) -> Self { + pub(crate) fn get_one() -> Self { + BaseFieldElement(JubjubBase::ONE) + } +} + +impl Add for BaseFieldElement { + type Output = BaseFieldElement; + + fn add(self, other: BaseFieldElement) -> BaseFieldElement { BaseFieldElement(self.0 + other.0) } +} - pub(crate) fn sub(&self, other: &Self) -> Self { +impl Sub for &BaseFieldElement { + type Output = BaseFieldElement; + + fn sub(self, other: &BaseFieldElement) -> BaseFieldElement { BaseFieldElement(self.0 - other.0) } +} - pub(crate) fn mul(&self, other: &Self) -> Self { - BaseFieldElement(self.0 * other.0) - } +impl Mul for BaseFieldElement { + type Output = BaseFieldElement; - pub(crate) fn square(&self) -> Self { - BaseFieldElement(self.0.square()) + fn mul(self, other: BaseFieldElement) -> BaseFieldElement { + BaseFieldElement(self.0 * other.0) } +} - pub(crate) fn get_one() -> Self { - BaseFieldElement(JubjubBase::ONE) - } +impl Mul for &BaseFieldElement { + type Output = BaseFieldElement; - pub(crate) fn collect_coordinates_of_list_of_points( - point_list: &[ProjectivePoint], - ) -> Vec { - let mut coordinates: Vec = Vec::new(); - for point in point_list { - let (u, v) = point.get_coordinates(); - coordinates.push(u); - coordinates.push(v); - } - coordinates + fn mul(self, other: &BaseFieldElement) -> BaseFieldElement { + BaseFieldElement(self.0 * other.0) } } @@ -74,15 +78,7 @@ impl ScalarFieldElement { return Ok(random_scalar); } } - Err(anyhow!(SchnorrSignatureError::RandomScalarGenerationError)) - } - - pub(crate) fn sub(&self, other: &Self) -> Self { - ScalarFieldElement(self.0 - other.0) - } - - pub(crate) fn mul(&self, other: &Self) -> Self { - ScalarFieldElement(self.0 * other.0) + Err(anyhow!(SchnorrSignatureError::RandomScalarGeneration)) } pub(crate) fn to_bytes(self) -> [u8; 32] { @@ -94,18 +90,34 @@ impl ScalarFieldElement { scalar_bytes.copy_from_slice( bytes .get(..32) - .ok_or(SchnorrSignatureError::ScalarFieldElementSerializationError)?, + .ok_or(SchnorrSignatureError::ScalarFieldElementSerialization)?, ); match JubjubScalar::from_bytes(&scalar_bytes).into_option() { Some(scalar_field_element) => Ok(Self(scalar_field_element)), None => Err(anyhow!( - SchnorrSignatureError::ScalarFieldElementSerializationError + SchnorrSignatureError::ScalarFieldElementSerialization )), } } } +impl Mul for ScalarFieldElement { + type Output = ScalarFieldElement; + + fn mul(self, other: ScalarFieldElement) -> ScalarFieldElement { + ScalarFieldElement(self.0 * other.0) + } +} + +impl Sub for ScalarFieldElement { + type Output = ScalarFieldElement; + + fn sub(self, other: ScalarFieldElement) -> ScalarFieldElement { + ScalarFieldElement(self.0 - other.0) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/mod.rs b/mithril-stm/src/signature_scheme/schnorr_signature/mod.rs index 0304a789965..f0599533406 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/mod.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/mod.rs @@ -29,7 +29,7 @@ mod tests { // Valid generation check let sk = SchnorrSigningKey::generate(&mut ChaCha20Rng::from_seed(seed)).unwrap(); let g = PrimeOrderProjectivePoint::create_generator(); - let vk = g.scalar_multiplication(&sk.0); + let vk = sk.0 * g; let vk_from_sk = SchnorrVerificationKey::new_from_signing_key(sk).unwrap(); assert_eq!(vk, vk_from_sk.0); @@ -110,7 +110,7 @@ mod tests { assert!( matches!( result.downcast_ref::(), - Some(SchnorrSignatureError::ScalarFieldElementSerializationError) + Some(SchnorrSignatureError::ScalarFieldElementSerialization) ), "Unexpected error: {result:?}" ); @@ -145,7 +145,7 @@ mod tests { assert!( matches!( result.downcast_ref::(), - Some(SchnorrSignatureError::PrimeOrderProjectivePointSerializationError) + Some(SchnorrSignatureError::PrimeOrderProjectivePointSerialization) ), "Unexpected error: {result:?}" ); @@ -169,7 +169,7 @@ mod tests { assert!( matches!( result.downcast_ref::(), - Some(SchnorrSignatureError::ProjectivePointSerializationError) + Some(SchnorrSignatureError::ProjectivePointSerialization) ), "Unexpected error: {result:?}" ); @@ -181,7 +181,7 @@ mod tests { assert!( matches!( result.downcast_ref::(), - Some(SchnorrSignatureError::ScalarFieldElementSerializationError) + Some(SchnorrSignatureError::ScalarFieldElementSerialization) ), "Unexpected error: {result:?}" ); @@ -193,7 +193,7 @@ mod tests { assert!( matches!( result.downcast_ref::(), - Some(SchnorrSignatureError::ScalarFieldElementSerializationError) + Some(SchnorrSignatureError::ScalarFieldElementSerialization) ), "Unexpected error: {result:?}" ); diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs b/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs index 1b0455e6510..0f6fe131e92 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs @@ -54,27 +54,28 @@ impl SchnorrSignature { let msg_hash_point = ProjectivePoint::hash_to_projective_point(msg); // Computing random_point_1_recomputed = response * H(msg) + challenge * commitment_point - let response_time_msg_hash_point = msg_hash_point.scalar_multiplication(&self.response); - let challenge_times_commitment_point = - self.commitment_point.scalar_multiplication(&self.challenge); let random_point_1_recomputed = - response_time_msg_hash_point.add(challenge_times_commitment_point); + self.response * msg_hash_point + self.challenge * self.commitment_point; // Computing random_point_2_recomputed = response * prime_order_generator_point + challenge * vk - let response_times_generator_point = - prime_order_generator_point.scalar_multiplication(&self.response); - let challenge_times_vk = verification_key.0.scalar_multiplication(&self.challenge); - let random_point_2_recomputed = response_times_generator_point.add(challenge_times_vk); + let random_point_2_recomputed = + self.response * prime_order_generator_point + self.challenge * verification_key.0; // Since the hash function takes as input scalar elements // We need to convert the EC points to their coordinates - let points_coordinates = BaseFieldElement::collect_coordinates_of_list_of_points(&[ + let points_coordinates: Vec = [ msg_hash_point, - ProjectivePoint::from_prime_order_projective_point(verification_key.0), + ProjectivePoint::from(verification_key.0), self.commitment_point, random_point_1_recomputed, - ProjectivePoint::from_prime_order_projective_point(random_point_2_recomputed), - ]); + ProjectivePoint::from(random_point_2_recomputed), + ] + .iter() + .flat_map(|point| { + let (u, v) = point.get_coordinates(); + [u, v] + }) + .collect(); let challenge_recomputed = compute_truncated_digest(&points_coordinates); @@ -87,7 +88,7 @@ impl SchnorrSignature { Ok(()) } - /// Convert an `SchnorrSignature` into bytes. + /// Convert a `SchnorrSignature` into bytes. pub fn to_bytes(self) -> [u8; 96] { let mut out = [0; 96]; out[0..32].copy_from_slice(&self.commitment_point.to_bytes()); @@ -104,34 +105,29 @@ impl SchnorrSignature { .with_context(|| "Not enough bytes provided to create a signature."); } - let mut u8bytes = [0u8; 32]; - - u8bytes.copy_from_slice( + let commitment_point = ProjectivePoint::from_bytes( bytes .get(0..32) .ok_or(SchnorrSignatureError::SerializationError) .with_context(|| "Could not get the bytes of `commitment_point`")?, - ); - let commitment_point = ProjectivePoint::from_bytes(&u8bytes) - .with_context(|| "Could not convert bytes to `commitment_point`")?; + ) + .with_context(|| "Could not convert bytes to `commitment_point`")?; - u8bytes.copy_from_slice( + let response = ScalarFieldElement::from_bytes( bytes .get(32..64) .ok_or(SchnorrSignatureError::SerializationError) .with_context(|| "Could not get the bytes of `response`")?, - ); - let response = ScalarFieldElement::from_bytes(&u8bytes) - .with_context(|| "Could not convert the bytes to `response`")?; + ) + .with_context(|| "Could not convert the bytes to `response`")?; - u8bytes.copy_from_slice( + let challenge = ScalarFieldElement::from_bytes( bytes .get(64..96) .ok_or(SchnorrSignatureError::SerializationError) .with_context(|| "Could not get the bytes of `challenge`")?, - ); - let challenge = ScalarFieldElement::from_bytes(&u8bytes) - .with_context(|| "Could not convert bytes to `challenge`")?; + ) + .with_context(|| "Could not convert bytes to `challenge`")?; Ok(Self { commitment_point, diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/signing_key.rs b/mithril-stm/src/signature_scheme/schnorr_signature/signing_key.rs index 2f1913e55ed..5306d96a2d3 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/signing_key.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/signing_key.rs @@ -55,28 +55,34 @@ impl SchnorrSigningKey { // First hashing the message to a scalar then hashing it to a curve point let msg_hash_point = ProjectivePoint::hash_to_projective_point(msg); - let commitment_point = msg_hash_point.scalar_multiplication(&self.0); + let commitment_point = self.0 * msg_hash_point; let random_scalar = ScalarFieldElement::new_random_nonzero_scalar(rng) .with_context(|| "Random scalar generation failed during signing.")?; - let random_point_1 = msg_hash_point.scalar_multiplication(&random_scalar); - let random_point_2 = prime_order_generator_point.scalar_multiplication(&random_scalar); + let random_point_1 = random_scalar * msg_hash_point; + let random_point_2 = random_scalar * prime_order_generator_point; // Since the hash function takes as input scalar elements // We need to convert the EC points to their coordinates // The order must be preserved - let points_coordinates = BaseFieldElement::collect_coordinates_of_list_of_points(&[ + let points_coordinates: Vec = [ msg_hash_point, - ProjectivePoint::from_prime_order_projective_point(verification_key.0), + ProjectivePoint::from(verification_key.0), commitment_point, random_point_1, - ProjectivePoint::from_prime_order_projective_point(random_point_2), - ]); + ProjectivePoint::from(random_point_2), + ] + .iter() + .flat_map(|point| { + let (u, v) = point.get_coordinates(); + [u, v] + }) + .collect(); let challenge = compute_truncated_digest(&points_coordinates); - let mut response = challenge.mul(&self.0); - response = random_scalar.sub(&response); + let challenge_times_sk = challenge * self.0; + let response = random_scalar - challenge_times_sk; Ok(SchnorrSignature { commitment_point, diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/verification_key.rs b/mithril-stm/src/signature_scheme/schnorr_signature/verification_key.rs index b323ff5e0b9..f2988660ba6 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/verification_key.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/verification_key.rs @@ -21,13 +21,11 @@ impl SchnorrVerificationKey { } let generator = PrimeOrderProjectivePoint::create_generator(); - Ok(SchnorrVerificationKey( - generator.scalar_multiplication(&signing_key.0), - )) + Ok(SchnorrVerificationKey(signing_key.0 * generator)) } pub fn is_valid(&self) -> StmResult { - let projective_point = ProjectivePoint::from_prime_order_projective_point(self.0); + let projective_point = ProjectivePoint::from(self.0); if !projective_point.is_prime_order() { return Err(anyhow!(SchnorrSignatureError::PointIsNotPrimeOrder( Box::new(self.0) From 652fa41327b727d6f0612ad8d58e5c09197ecbd8 Mon Sep 17 00:00:00 2001 From: curiecrypt Date: Thu, 18 Dec 2025 01:04:23 +0300 Subject: [PATCH 3/7] doc comments added for jubjub wrapper --- .../schnorr_signature/jubjub/curve_points.rs | 24 ++++++++++++++++++- .../jubjub/field_elements.rs | 16 +++++++++++++ .../jubjub/poseidon_digest.rs | 2 ++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/curve_points.rs b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/curve_points.rs index 50e4095e810..b9f1608cfa6 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/curve_points.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/curve_points.rs @@ -9,24 +9,29 @@ use std::ops::{Add, Mul}; use super::{BaseFieldElement, ScalarFieldElement}; use crate::{StmResult, signature_scheme::SchnorrSignatureError}; +/// Represents a point in affine coordinates on the Jubjub curve #[derive(Clone)] pub(crate) struct AffinePoint(JubjubAffinePoint); impl AffinePoint { + /// Converts a projective point to its affine representation pub(crate) fn from_projective_point(projective_point: ProjectivePoint) -> Self { AffinePoint(JubjubAffinePoint::from(projective_point.0)) } + /// Retrieves the u-coordinate of the affine point pub(crate) fn get_u(&self) -> BaseFieldElement { BaseFieldElement(self.0.get_u()) } + /// Retrieves the v-coordinate of the affine point pub(crate) fn get_v(&self) -> BaseFieldElement { BaseFieldElement(self.0.get_v()) } } impl From<&PrimeOrderProjectivePoint> for AffinePoint { + /// Converts a prime order projective point to its affine representation fn from(prime_order_projective_point: &PrimeOrderProjectivePoint) -> Self { AffinePoint(JubjubAffinePoint::from(JubjubExtended::from( prime_order_projective_point.0, @@ -34,24 +39,29 @@ impl From<&PrimeOrderProjectivePoint> for AffinePoint { } } +/// Represents a point in projective coordinates on the Jubjub curve #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) struct ProjectivePoint(pub(crate) JubjubExtended); impl ProjectivePoint { + /// Hashes input bytes to a projective point on the Jubjub curve pub(crate) fn hash_to_projective_point(input: &[u8]) -> Self { ProjectivePoint(JubjubExtended::hash_to_point(input)) } + /// Retrieves the (u, v) coordinates of the projective point in affine representation pub(crate) fn get_coordinates(&self) -> (BaseFieldElement, BaseFieldElement) { let affine_point = AffinePoint::from_projective_point(*self); (affine_point.get_u(), affine_point.get_v()) } + /// Converts the projective point to its byte representation pub(crate) fn to_bytes(self) -> [u8; 32] { self.0.to_bytes() } + /// Constructs a projective point from its byte representation pub(crate) fn from_bytes(bytes: &[u8]) -> StmResult { let mut projective_point_bytes = [0u8; 32]; projective_point_bytes @@ -63,6 +73,7 @@ impl ProjectivePoint { } } + /// Checks if the projective point is of prime order pub(crate) fn is_prime_order(self) -> bool { self.0.is_prime_order().into() } @@ -71,6 +82,7 @@ impl ProjectivePoint { impl Add for ProjectivePoint { type Output = ProjectivePoint; + /// Adds two projective points fn add(self, other: ProjectivePoint) -> ProjectivePoint { ProjectivePoint(self.0 + other.0) } @@ -79,26 +91,31 @@ impl Add for ProjectivePoint { impl Mul for ScalarFieldElement { type Output = ProjectivePoint; + /// Multiplies a projective point by a scalar field element + /// Returns the resulting projective point fn mul(self, point: ProjectivePoint) -> ProjectivePoint { ProjectivePoint(point.0 * self.0) } } impl From for ProjectivePoint { + /// Converts a prime order projective point to a projective point fn from(prime_order_projective_point: PrimeOrderProjectivePoint) -> Self { ProjectivePoint(JubjubExtended::from(prime_order_projective_point.0)) } } +/// Represents a point of prime order in projective coordinates on the Jubjub curve #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub(crate) struct PrimeOrderProjectivePoint(pub(crate) JubjubSubgroup); impl PrimeOrderProjectivePoint { + /// Creates the generator point of the prime order subgroup pub(crate) fn create_generator() -> Self { PrimeOrderProjectivePoint(JubjubSubgroup::generator()) } - /// Check if the given point is on the curve using its coordinates + /// Checks if the given point is on the curve using its coordinates pub(crate) fn is_on_curve(&self) -> StmResult { let point_affine_representation = AffinePoint::from(self); let (x, y) = ( @@ -119,10 +136,12 @@ impl PrimeOrderProjectivePoint { Ok(*self) } + /// Converts the prime order projective point to its byte representation pub(crate) fn to_bytes(self) -> [u8; 32] { self.0.to_bytes() } + /// Constructs a prime order projective point from its byte representation pub(crate) fn from_bytes(bytes: &[u8]) -> StmResult { let mut prime_order_projective_point_bytes = [0u8; 32]; prime_order_projective_point_bytes @@ -140,6 +159,7 @@ impl PrimeOrderProjectivePoint { impl Add for PrimeOrderProjectivePoint { type Output = PrimeOrderProjectivePoint; + /// Adds two prime order projective points fn add(self, other: PrimeOrderProjectivePoint) -> PrimeOrderProjectivePoint { PrimeOrderProjectivePoint(self.0 + other.0) } @@ -148,6 +168,8 @@ impl Add for PrimeOrderProjectivePoint { impl Mul for ScalarFieldElement { type Output = PrimeOrderProjectivePoint; + /// Multiplies a prime order projective point by a scalar field element + /// Returns the resulting prime order projective point fn mul(self, point: PrimeOrderProjectivePoint) -> PrimeOrderProjectivePoint { PrimeOrderProjectivePoint(point.0 * self.0) } diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/field_elements.rs b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/field_elements.rs index b82a44eae7b..71ebfba0775 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/field_elements.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/field_elements.rs @@ -6,10 +6,12 @@ use std::ops::{Add, Mul, Sub}; use crate::{StmResult, signature_scheme::SchnorrSignatureError}; +/// Represents an element in the base field of the Jubjub curve #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct BaseFieldElement(pub(crate) JubjubBase); impl BaseFieldElement { + /// Retrieves the multiplicative identity element of the base field pub(crate) fn get_one() -> Self { BaseFieldElement(JubjubBase::ONE) } @@ -18,6 +20,7 @@ impl BaseFieldElement { impl Add for BaseFieldElement { type Output = BaseFieldElement; + /// Adds two base field elements fn add(self, other: BaseFieldElement) -> BaseFieldElement { BaseFieldElement(self.0 + other.0) } @@ -26,6 +29,7 @@ impl Add for BaseFieldElement { impl Sub for &BaseFieldElement { type Output = BaseFieldElement; + /// Subtracts one base field element from another fn sub(self, other: &BaseFieldElement) -> BaseFieldElement { BaseFieldElement(self.0 - other.0) } @@ -34,6 +38,7 @@ impl Sub for &BaseFieldElement { impl Mul for BaseFieldElement { type Output = BaseFieldElement; + /// Multiplies two base field elements fn mul(self, other: BaseFieldElement) -> BaseFieldElement { BaseFieldElement(self.0 * other.0) } @@ -42,19 +47,23 @@ impl Mul for BaseFieldElement { impl Mul for &BaseFieldElement { type Output = BaseFieldElement; + /// Multiplies a base field element by another base field element fn mul(self, other: &BaseFieldElement) -> BaseFieldElement { BaseFieldElement(self.0 * other.0) } } +/// Represents an element in the scalar field of the Jubjub curve #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) struct ScalarFieldElement(pub(crate) JubjubScalar); impl ScalarFieldElement { + /// Generates a new random scalar field element pub(crate) fn new_random_scalar(rng: &mut (impl RngCore + CryptoRng)) -> Self { ScalarFieldElement(JubjubScalar::random(rng)) } + /// Checks if the scalar field element is zero pub(crate) fn is_zero(&self) -> bool { if self.0 == JubjubScalar::zero() { return true; @@ -62,6 +71,7 @@ impl ScalarFieldElement { false } + /// Checks if the scalar field element is one pub(crate) fn is_one(&self) -> bool { if self.0 == JubjubScalar::one() { return true; @@ -69,6 +79,8 @@ impl ScalarFieldElement { false } + /// Generates a new random non-zero scalar field element + /// Returns an error if unable to generate a non-zero scalar after 100 attempts pub(crate) fn new_random_nonzero_scalar( rng: &mut (impl RngCore + CryptoRng), ) -> StmResult { @@ -81,10 +93,12 @@ impl ScalarFieldElement { Err(anyhow!(SchnorrSignatureError::RandomScalarGeneration)) } + /// Converts the scalar field element to its byte representation pub(crate) fn to_bytes(self) -> [u8; 32] { self.0.to_bytes() } + /// Constructs a scalar field element from its byte representation pub(crate) fn from_bytes(bytes: &[u8]) -> StmResult { let mut scalar_bytes = [0u8; 32]; scalar_bytes.copy_from_slice( @@ -105,6 +119,7 @@ impl ScalarFieldElement { impl Mul for ScalarFieldElement { type Output = ScalarFieldElement; + /// Multiplies two scalar field elements fn mul(self, other: ScalarFieldElement) -> ScalarFieldElement { ScalarFieldElement(self.0 * other.0) } @@ -113,6 +128,7 @@ impl Mul for ScalarFieldElement { impl Sub for ScalarFieldElement { type Output = ScalarFieldElement; + /// Subtracts one scalar field element from another fn sub(self, other: ScalarFieldElement) -> ScalarFieldElement { ScalarFieldElement(self.0 - other.0) } diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/poseidon_digest.rs b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/poseidon_digest.rs index 73efda7715a..22bf6e55670 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/poseidon_digest.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/poseidon_digest.rs @@ -6,6 +6,8 @@ use super::{BaseFieldElement, ScalarFieldElement}; /// A DST (Domain Separation Tag) to distinguish between use of Poseidon hash const DST_SIGNATURE: JubjubBase = JubjubBase::from_raw([0u64, 0, 0, 0]); +/// Computes a truncated Poseidon digest over the provided base field elements +/// Returns a scalar field element as the digest pub(crate) fn compute_truncated_digest(input: &[BaseFieldElement]) -> ScalarFieldElement { let mut poseidon_input = vec![DST_SIGNATURE]; poseidon_input.extend(input.iter().map(|i| i.0).collect::>()); From d5604bb169bc340d7bb45ff73d6cdf30ccd64433 Mon Sep 17 00:00:00 2001 From: curiecrypt Date: Thu, 18 Dec 2025 01:15:43 +0300 Subject: [PATCH 4/7] tests for signature restored --- .../schnorr_signature/signature.rs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs b/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs index 0f6fe131e92..cc92ff1733e 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs @@ -139,6 +139,56 @@ impl SchnorrSignature { #[cfg(test)] mod tests { + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + + use crate::signature_scheme::{SchnorrSignature, SchnorrSigningKey, SchnorrVerificationKey}; + + #[test] + fn invalid_sig() { + let msg = vec![0, 0, 0, 1]; + let msg2 = vec![0, 0, 0, 2]; + let seed = [0u8; 32]; + let mut rng = ChaCha20Rng::from_seed(seed); + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + let vk = SchnorrVerificationKey::new_from_signing_key(sk.clone()).unwrap(); + let sk2 = SchnorrSigningKey::generate(&mut rng).unwrap(); + let vk2 = SchnorrVerificationKey::new_from_signing_key(sk2).unwrap(); + + let sig = sk.sign(&msg, &mut rng).unwrap(); + let sig2 = sk.sign(&msg2, &mut rng).unwrap(); + + // Wrong verification key is used + let result1 = sig.verify(&msg, &vk2); + let result2 = sig2.verify(&msg, &vk); + + result1.expect_err("Wrong verification key used, test should fail."); + // Wrong message is verified + result2.expect_err("Wrong message used, test should fail."); + } + + #[test] + fn serialize_deserialize_signature() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + + let msg = vec![0, 0, 0, 1]; + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + + let sig = sk.sign(&msg, &mut rng).unwrap(); + let sig_bytes: [u8; 96] = sig.to_bytes(); + let sig2 = SchnorrSignature::from_bytes(&sig_bytes).unwrap(); + + assert_eq!(sig, sig2); + } + + #[test] + fn from_bytes_signature_not_enough_bytes() { + let msg = vec![0u8; 95]; + + let result = SchnorrSignature::from_bytes(&msg); + + result.expect_err("Not enough bytes."); + } mod golden { From e5eb0c78e3f3960ee756b0ab690b238a81fda6cf Mon Sep 17 00:00:00 2001 From: curiecrypt Date: Fri, 19 Dec 2025 18:45:11 +0300 Subject: [PATCH 5/7] specific dst and unit tests --- .../schnorr_signature/error.rs | 2 +- .../schnorr_signature/jubjub/curve_points.rs | 234 +++++++++++++++++- .../jubjub/field_elements.rs | 160 +++++++++++- .../jubjub/poseidon_digest.rs | 9 +- .../signature_scheme/schnorr_signature/mod.rs | 6 +- .../schnorr_signature/signature.rs | 93 +++++-- .../schnorr_signature/signing_key.rs | 68 ++++- .../schnorr_signature/verification_key.rs | 110 +++++++- 8 files changed, 650 insertions(+), 32 deletions(-) diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/error.rs b/mithril-stm/src/signature_scheme/schnorr_signature/error.rs index 982cb375f61..e7548036b88 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/error.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/error.rs @@ -11,7 +11,7 @@ pub enum SchnorrSignatureError { /// This error occurs when the serialization of the raw bytes failed #[error("Invalid bytes")] - SerializationError, + Serialization, /// This error occurs when the serialization of the scalar field bytes failed #[error("Invalid scalar field element bytes")] diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/curve_points.rs b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/curve_points.rs index b9f1608cfa6..6dbd99eb824 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/curve_points.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/curve_points.rs @@ -65,7 +65,7 @@ impl ProjectivePoint { pub(crate) fn from_bytes(bytes: &[u8]) -> StmResult { let mut projective_point_bytes = [0u8; 32]; projective_point_bytes - .copy_from_slice(bytes.get(..32).ok_or(SchnorrSignatureError::SerializationError)?); + .copy_from_slice(bytes.get(..32).ok_or(SchnorrSignatureError::Serialization)?); match JubjubExtended::from_bytes(&projective_point_bytes).into_option() { Some(projective_point) => Ok(Self(projective_point)), @@ -145,7 +145,7 @@ impl PrimeOrderProjectivePoint { pub(crate) fn from_bytes(bytes: &[u8]) -> StmResult { let mut prime_order_projective_point_bytes = [0u8; 32]; prime_order_projective_point_bytes - .copy_from_slice(bytes.get(..32).ok_or(SchnorrSignatureError::SerializationError)?); + .copy_from_slice(bytes.get(..32).ok_or(SchnorrSignatureError::Serialization)?); match JubjubSubgroup::from_bytes(&prime_order_projective_point_bytes).into_option() { Some(prime_order_projective_point) => Ok(Self(prime_order_projective_point)), @@ -177,12 +177,12 @@ impl Mul for ScalarFieldElement { #[cfg(test)] mod tests { + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + use super::*; mod golden { - use rand_chacha::ChaCha20Rng; - use rand_core::SeedableRng; - use super::*; const GOLDEN_JSON: &str = r#"[144, 52, 95, 161, 127, 253, 49, 32, 140, 217, 231, 207, 32, 238, 244, 196, 97, 241, 47, 95, 101, 9, 70, 136, 194, 66, 187, 253, 200, 32, 218, 43]"#; @@ -207,4 +207,228 @@ mod tests { assert_eq!(golden_serialized, serialized); } } + + mod projective_point_arithmetic { + use super::*; + + #[test] + fn test_add() { + let mut rng = ChaCha20Rng::from_seed([1u8; 32]); + let scalar1 = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let scalar2 = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + + let point = ProjectivePoint::hash_to_projective_point(b"test_point"); + let p1 = scalar1 * point; + let p2 = scalar2 * point; + + let result = p1 + p2; + + let bytes = result.to_bytes(); + let recovered = ProjectivePoint::from_bytes(&bytes).unwrap(); + assert_eq!(result, recovered); + } + + #[test] + fn test_add_identity() { + let point = ProjectivePoint::hash_to_projective_point(b"test_point"); + let identity = ProjectivePoint(JubjubExtended::identity()); + + let result = point + identity; + assert_eq!(result, point); + } + + #[test] + fn test_add_commutativity() { + let mut rng = ChaCha20Rng::from_seed([2u8; 32]); + let scalar1 = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let scalar2 = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + + let point = ProjectivePoint::hash_to_projective_point(b"test_point"); + let p1 = scalar1 * point; + let p2 = scalar2 * point; + + assert_eq!(p1 + p2, p2 + p1); + } + + #[test] + fn test_add_associativity() { + let mut rng = ChaCha20Rng::from_seed([3u8; 32]); + let scalar1 = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let scalar2 = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let scalar3 = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + + let point = ProjectivePoint::hash_to_projective_point(b"test_point"); + let p1 = scalar1 * point; + let p2 = scalar2 * point; + let p3 = scalar3 * point; + + assert_eq!((p1 + p2) + p3, p1 + (p2 + p3)); + } + + #[test] + fn test_scalar_mul() { + let mut rng = ChaCha20Rng::from_seed([4u8; 32]); + let scalar = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let point = ProjectivePoint::hash_to_projective_point(b"test_point"); + + let result = scalar * point; + + let bytes = result.to_bytes(); + let recovered = ProjectivePoint::from_bytes(&bytes).unwrap(); + assert_eq!(result, recovered); + } + + #[test] + fn test_scalar_mul_distributivity_over_point_addition() { + let mut rng = ChaCha20Rng::from_seed([5u8; 32]); + let scalar = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let point1 = ProjectivePoint::hash_to_projective_point(b"test_point_1"); + let point2 = ProjectivePoint::hash_to_projective_point(b"test_point_2"); + + let left = scalar * (point1 + point2); + let right = (scalar * point1) + (scalar * point2); + + assert_eq!(left, right); + } + + #[test] + fn test_scalar_mul_associativity() { + let mut rng = ChaCha20Rng::from_seed([6u8; 32]); + let scalar1 = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let scalar2 = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let point = ProjectivePoint::hash_to_projective_point(b"test_point"); + + let combined_scalar = scalar1 * scalar2; + let left = combined_scalar * point; + let right = scalar1 * (scalar2 * point); + + assert_eq!(left, right); + } + } + + mod prime_order_projective_point_arithmetic { + use super::*; + + #[test] + fn test_add() { + let mut rng = ChaCha20Rng::from_seed([7u8; 32]); + let scalar1 = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let scalar2 = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + + let generator = PrimeOrderProjectivePoint::create_generator(); + let p1 = scalar1 * generator; + let p2 = scalar2 * generator; + + let result = p1 + p2; + + let bytes = result.to_bytes(); + let recovered = PrimeOrderProjectivePoint::from_bytes(&bytes).unwrap(); + assert_eq!(result, recovered); + } + + #[test] + fn test_add_identity() { + let mut rng = ChaCha20Rng::from_seed([8u8; 32]); + let scalar = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let generator = PrimeOrderProjectivePoint::create_generator(); + let point = scalar * generator; + let identity = PrimeOrderProjectivePoint::default(); + + let result = point + identity; + assert_eq!(result, point); + } + + #[test] + fn test_add_commutativity() { + let mut rng = ChaCha20Rng::from_seed([9u8; 32]); + let scalar1 = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let scalar2 = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + + let generator = PrimeOrderProjectivePoint::create_generator(); + let p1 = scalar1 * generator; + let p2 = scalar2 * generator; + + assert_eq!(p1 + p2, p2 + p1); + } + + #[test] + fn test_add_associativity() { + let mut rng = ChaCha20Rng::from_seed([10u8; 32]); + let scalar1 = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let scalar2 = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let scalar3 = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + + let generator = PrimeOrderProjectivePoint::create_generator(); + let p1 = scalar1 * generator; + let p2 = scalar2 * generator; + let p3 = scalar3 * generator; + + assert_eq!((p1 + p2) + p3, p1 + (p2 + p3)); + } + + #[test] + fn test_scalar_mul() { + let mut rng = ChaCha20Rng::from_seed([11u8; 32]); + let scalar = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let generator = PrimeOrderProjectivePoint::create_generator(); + + let result = scalar * generator; + + let bytes = result.to_bytes(); + let recovered = PrimeOrderProjectivePoint::from_bytes(&bytes).unwrap(); + assert_eq!(result, recovered); + } + + #[test] + fn test_scalar_mul_by_generator() { + let scalar = ScalarFieldElement(dusk_jubjub::Fr::one()); + let generator = PrimeOrderProjectivePoint::create_generator(); + + let result = scalar * generator; + assert_eq!(result, generator); + } + + #[test] + fn test_scalar_mul_distributivity_over_point_addition() { + let mut rng = ChaCha20Rng::from_seed([12u8; 32]); + let scalar = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let scalar1 = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let scalar2 = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + + let generator = PrimeOrderProjectivePoint::create_generator(); + let point1 = scalar1 * generator; + let point2 = scalar2 * generator; + + let left = scalar * (point1 + point2); + let right = (scalar * point1) + (scalar * point2); + + assert_eq!(left, right); + } + + #[test] + fn test_scalar_mul_associativity() { + let mut rng = ChaCha20Rng::from_seed([13u8; 32]); + let scalar1 = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let scalar2 = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let generator = PrimeOrderProjectivePoint::create_generator(); + + let combined_scalar = scalar1 * scalar2; + let left = combined_scalar * generator; + let right = scalar1 * (scalar2 * generator); + + assert_eq!(left, right); + } + + #[test] + fn test_point_on_curve() { + let mut rng = ChaCha20Rng::from_seed([14u8; 32]); + let scalar = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let generator = PrimeOrderProjectivePoint::create_generator(); + + let point = scalar * generator; + + let result = point.is_on_curve().unwrap(); + assert_eq!(result, point); + } + } } diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/field_elements.rs b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/field_elements.rs index 71ebfba0775..dfb5a781148 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/field_elements.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/field_elements.rs @@ -79,7 +79,8 @@ impl ScalarFieldElement { false } - /// Generates a new random non-zero scalar field element + /// Tries to generate a new random non-zero scalar field element in 100 attempts + /// /// Returns an error if unable to generate a non-zero scalar after 100 attempts pub(crate) fn new_random_nonzero_scalar( rng: &mut (impl RngCore + CryptoRng), @@ -136,12 +137,12 @@ impl Sub for ScalarFieldElement { #[cfg(test)] mod tests { + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + use super::*; mod golden { - use rand_chacha::ChaCha20Rng; - use rand_core::SeedableRng; - use super::*; const GOLDEN_JSON: &str = r#"[126, 191, 239, 197, 88, 151, 248, 254, 187, 143, 86, 35, 29, 62, 90, 13, 196, 71, 234, 5, 90, 124, 205, 194, 51, 192, 228, 133, 25, 140, 157, 7]"#; @@ -164,4 +165,155 @@ mod tests { assert_eq!(golden_serialized, serialized); } } + + mod base_field_arithmetic { + use super::*; + + #[test] + fn test_add() { + let a = BaseFieldElement(JubjubBase::ONE); + let b = BaseFieldElement(JubjubBase::ONE); + let result = a + b; + assert_eq!(result, BaseFieldElement(JubjubBase::ONE + JubjubBase::ONE)); + } + + #[test] + fn test_add_with_zero() { + let a = BaseFieldElement(JubjubBase::ONE); + let zero = BaseFieldElement(JubjubBase::zero()); + let result = a.clone() + zero; + assert_eq!(result, a); + } + + #[test] + fn test_sub_references() { + let a = BaseFieldElement(JubjubBase::ONE + JubjubBase::ONE); + let b = BaseFieldElement(JubjubBase::ONE); + let result = &a - &b; + assert_eq!(result, BaseFieldElement(JubjubBase::ONE)); + } + + #[test] + fn test_sub_same_values() { + let a = BaseFieldElement(JubjubBase::ONE); + let b = BaseFieldElement(JubjubBase::ONE); + let result = &a - &b; + assert_eq!(result, BaseFieldElement(JubjubBase::zero())); + } + + #[test] + fn test_mul_owned() { + let a = BaseFieldElement(JubjubBase::ONE + JubjubBase::ONE); + let b = BaseFieldElement(JubjubBase::ONE + JubjubBase::ONE); + let result = a * b; + let expected = JubjubBase::ONE + JubjubBase::ONE; + assert_eq!(result, BaseFieldElement(expected * expected)); + } + + #[test] + fn test_mul_with_one() { + let a = BaseFieldElement(JubjubBase::ONE + JubjubBase::ONE); + let one = BaseFieldElement::get_one(); + let result = a.clone() * one; + assert_eq!(result, a); + } + + #[test] + fn test_mul_with_zero() { + let a = BaseFieldElement(JubjubBase::ONE); + let zero = BaseFieldElement(JubjubBase::zero()); + let result = a * zero; + assert_eq!(result, BaseFieldElement(JubjubBase::zero())); + } + + #[test] + fn test_mul_references() { + let a = BaseFieldElement(JubjubBase::ONE + JubjubBase::ONE); + let b = BaseFieldElement(JubjubBase::ONE + JubjubBase::ONE + JubjubBase::ONE); + let result = &a * &b; + let expected = (JubjubBase::ONE + JubjubBase::ONE) + * (JubjubBase::ONE + JubjubBase::ONE + JubjubBase::ONE); + assert_eq!(result, BaseFieldElement(expected)); + } + + #[test] + fn test_chained_operations() { + let a = BaseFieldElement(JubjubBase::ONE); + let b = BaseFieldElement(JubjubBase::ONE); + let c = BaseFieldElement(JubjubBase::ONE); + let result = (a + b) * c; + assert_eq!(result, BaseFieldElement(JubjubBase::ONE + JubjubBase::ONE)); + } + } + + mod scalar_field_arithmetic { + use super::*; + + #[test] + fn test_mul() { + let mut rng = ChaCha20Rng::from_seed([1u8; 32]); + let a = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let b = ScalarFieldElement(JubjubScalar::one()); + let result = a * b; + assert_eq!(result, a); + } + + #[test] + fn test_mul_with_zero() { + let mut rng = ChaCha20Rng::from_seed([2u8; 32]); + let a = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let zero = ScalarFieldElement(JubjubScalar::zero()); + let result = a * zero; + assert!(result.is_zero()); + } + + #[test] + fn test_mul_associativity() { + let mut rng = ChaCha20Rng::from_seed([3u8; 32]); + let a = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let b = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let c = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + + let result1 = (a * b) * c; + let result2 = a * (b * c); + assert_eq!(result1, result2); + } + + #[test] + fn test_sub() { + let mut rng = ChaCha20Rng::from_seed([4u8; 32]); + let a = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let result = a - a; + assert!(result.is_zero()); + } + + #[test] + fn test_sub_with_zero() { + let mut rng = ChaCha20Rng::from_seed([5u8; 32]); + let a = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let zero = ScalarFieldElement(JubjubScalar::zero()); + let result = a - zero; + assert_eq!(result, a); + } + + #[test] + fn test_sub_specific_values() { + let two = ScalarFieldElement(JubjubScalar::one() + JubjubScalar::one()); + let one = ScalarFieldElement(JubjubScalar::one()); + let result = two - one; + assert!(result.is_one()); + } + + #[test] + fn test_combined_operations() { + let mut rng = ChaCha20Rng::from_seed([6u8; 32]); + let a = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let b = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let c = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + + let left = a * (b - c); + let right = (a * b) - (a * c); + assert_eq!(left, right); + } + } } diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/poseidon_digest.rs b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/poseidon_digest.rs index 22bf6e55670..303969af29c 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/poseidon_digest.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/poseidon_digest.rs @@ -3,8 +3,13 @@ use dusk_poseidon::{Domain, Hash}; use super::{BaseFieldElement, ScalarFieldElement}; -/// A DST (Domain Separation Tag) to distinguish between use of Poseidon hash -const DST_SIGNATURE: JubjubBase = JubjubBase::from_raw([0u64, 0, 0, 0]); +/// Domain Separation Tag (DST) for the Poseidon hash used in signature contexts. +const DST_SIGNATURE: JubjubBase = JubjubBase::from_raw([ + 0x5349_474E_5F44_5354, // "SIGN_DST" (ASCII), little-endian u64 + 0, + 0, + 0, +]); /// Computes a truncated Poseidon digest over the provided base field elements /// Returns a scalar field element as the digest diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/mod.rs b/mithril-stm/src/signature_scheme/schnorr_signature/mod.rs index f0599533406..e9f944dae89 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/mod.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/mod.rs @@ -99,7 +99,7 @@ mod tests { assert!( matches!( result.downcast_ref::(), - Some(SchnorrSignatureError::SerializationError) + Some(SchnorrSignatureError::Serialization) ), "Unexpected error: {result:?}" ); @@ -134,7 +134,7 @@ mod tests { assert!( matches!( result.downcast_ref::(), - Some(SchnorrSignatureError::SerializationError) + Some(SchnorrSignatureError::Serialization) ), "Unexpected error: {result:?}" ); @@ -205,7 +205,7 @@ mod tests { assert!( matches!( result.downcast_ref::(), - Some(SchnorrSignatureError::SerializationError) + Some(SchnorrSignatureError::Serialization) ), "Unexpected error: {result:?}" ); diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs b/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs index cc92ff1733e..8dee297a1e4 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs @@ -101,14 +101,14 @@ impl SchnorrSignature { /// Convert bytes into a `SchnorrSignature`. pub fn from_bytes(bytes: &[u8]) -> StmResult { if bytes.len() < 96 { - return Err(anyhow!(SchnorrSignatureError::SerializationError)) + return Err(anyhow!(SchnorrSignatureError::Serialization)) .with_context(|| "Not enough bytes provided to create a signature."); } let commitment_point = ProjectivePoint::from_bytes( bytes .get(0..32) - .ok_or(SchnorrSignatureError::SerializationError) + .ok_or(SchnorrSignatureError::Serialization) .with_context(|| "Could not get the bytes of `commitment_point`")?, ) .with_context(|| "Could not convert bytes to `commitment_point`")?; @@ -116,7 +116,7 @@ impl SchnorrSignature { let response = ScalarFieldElement::from_bytes( bytes .get(32..64) - .ok_or(SchnorrSignatureError::SerializationError) + .ok_or(SchnorrSignatureError::Serialization) .with_context(|| "Could not get the bytes of `response`")?, ) .with_context(|| "Could not convert the bytes to `response`")?; @@ -124,7 +124,7 @@ impl SchnorrSignature { let challenge = ScalarFieldElement::from_bytes( bytes .get(64..96) - .ok_or(SchnorrSignatureError::SerializationError) + .ok_or(SchnorrSignatureError::Serialization) .with_context(|| "Could not get the bytes of `challenge`")?, ) .with_context(|| "Could not convert bytes to `challenge`")?; @@ -145,7 +145,21 @@ mod tests { use crate::signature_scheme::{SchnorrSignature, SchnorrSigningKey, SchnorrVerificationKey}; #[test] - fn invalid_sig() { + fn valid_signature_verification() { + let msg = vec![0, 0, 0, 1]; + let seed = [0u8; 32]; + let mut rng = ChaCha20Rng::from_seed(seed); + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + let vk = SchnorrVerificationKey::new_from_signing_key(sk.clone()).unwrap(); + + let sig = sk.sign(&msg, &mut rng).unwrap(); + + sig.verify(&msg, &vk) + .expect("Valid signature should verify successfully"); + } + + #[test] + fn invalid_signature() { let msg = vec![0, 0, 0, 1]; let msg2 = vec![0, 0, 0, 2]; let seed = [0u8; 32]; @@ -168,26 +182,75 @@ mod tests { } #[test] - fn serialize_deserialize_signature() { + fn from_bytes_signature_not_enough_bytes() { + let msg = vec![0u8; 95]; + let result = SchnorrSignature::from_bytes(&msg); + result.expect_err("Not enough bytes."); + } + + #[test] + fn from_bytes_signature_exact_size() { let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let msg = vec![1, 2, 3]; + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); - let msg = vec![0, 0, 0, 1]; + let sig = sk.sign(&msg, &mut rng).unwrap(); + let sig_bytes: [u8; 96] = sig.to_bytes(); + + let sig_restored = SchnorrSignature::from_bytes(&sig_bytes).unwrap(); + assert_eq!(sig, sig_restored); + } + + #[test] + fn from_bytes_signature_extra_bytes() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let msg = vec![1, 2, 3]; let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); let sig = sk.sign(&msg, &mut rng).unwrap(); let sig_bytes: [u8; 96] = sig.to_bytes(); - let sig2 = SchnorrSignature::from_bytes(&sig_bytes).unwrap(); - assert_eq!(sig, sig2); + let mut extended_bytes = sig_bytes.to_vec(); + extended_bytes.extend_from_slice(&[0xFF; 10]); + + let sig_restored = SchnorrSignature::from_bytes(&extended_bytes).unwrap(); + assert_eq!(sig, sig_restored); } #[test] - fn from_bytes_signature_not_enough_bytes() { - let msg = vec![0u8; 95]; + fn to_bytes_is_deterministic() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let msg = vec![1, 2, 3]; + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); - let result = SchnorrSignature::from_bytes(&msg); + let sig = sk.sign(&msg, &mut rng).unwrap(); - result.expect_err("Not enough bytes."); + // Converting to bytes multiple times should give same result + let bytes1 = sig.to_bytes(); + let bytes2 = sig.to_bytes(); + + assert_eq!(bytes1, bytes2); + } + + #[test] + fn signature_roundtrip_preserves_verification() { + let mut rng = ChaCha20Rng::from_seed([42u8; 32]); + let msg = vec![5, 6, 7, 8, 9]; + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + let vk = SchnorrVerificationKey::new_from_signing_key(sk.clone()).unwrap(); + + // Create and verify original signature + let sig = sk.sign(&msg, &mut rng).unwrap(); + sig.verify(&msg, &vk).expect("Original signature should verify"); + + // Roundtrip through bytes + let sig_bytes = sig.to_bytes(); + let sig_restored = SchnorrSignature::from_bytes(&sig_bytes).unwrap(); + + // Restored signature should still verify + sig_restored + .verify(&msg, &vk) + .expect("Restored signature should verify"); } mod golden { @@ -200,8 +263,8 @@ mod tests { const GOLDEN_JSON: &str = r#" { "commitment_point": [143, 53, 198, 62, 178, 1, 88, 253, 21, 92, 100, 13, 72, 180, 198, 127, 39, 175, 102, 69, 147, 249, 244, 224, 122, 121, 248, 68, 217, 242, 158, 113], - "response": [94, 57, 200, 241, 208, 145, 251, 8, 92, 119, 163, 38, 81, 85, 54, 36, 193, 221, 254, 242, 21, 129, 110, 161, 142, 184, 107, 156, 100, 34, 190, 9], - "challenge": [200, 20, 178, 142, 61, 253, 193, 11, 5, 180, 97, 73, 125, 88, 162, 36, 30, 177, 225, 52, 136, 21, 138, 93, 81, 23, 19, 64, 82, 78, 229, 3] + "response": [5, 81, 137, 228, 235, 18, 112, 76, 71, 127, 44, 47, 60, 55, 144, 204, 254, 50, 67, 167, 67, 133, 79, 168, 10, 153, 228, 114, 147, 64, 34, 9], + "challenge": [12, 75, 91, 200, 29, 62, 12, 245, 185, 181, 67, 251, 210, 211, 37, 42, 204, 205, 133, 215, 235, 236, 193, 155, 2, 147, 83, 189, 148, 38, 71, 0] }"#; fn golden_value() -> SchnorrSignature { diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/signing_key.rs b/mithril-stm/src/signature_scheme/schnorr_signature/signing_key.rs index 5306d96a2d3..1350ba7a9ec 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/signing_key.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/signing_key.rs @@ -101,7 +101,7 @@ impl SchnorrSigningKey { /// The bytes must represent a Jubjub scalar or the conversion will fail pub fn from_bytes(bytes: &[u8]) -> StmResult { if bytes.len() < 32 { - return Err(anyhow!(SchnorrSignatureError::SerializationError)).with_context( + return Err(anyhow!(SchnorrSignatureError::Serialization)).with_context( || "Not enough bytes provided to re-construct a Schnorr signing key.", ); } @@ -113,6 +113,72 @@ impl SchnorrSigningKey { #[cfg(test)] mod tests { + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + + use crate::signature_scheme::SchnorrSigningKey; + + #[test] + fn generate_signing_key() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let sk = SchnorrSigningKey::generate(&mut rng); + + assert!(sk.is_ok(), "Signing key generation should succeed"); + } + + #[test] + fn generate_different_keys() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let sk1 = SchnorrSigningKey::generate(&mut rng).unwrap(); + let sk2 = SchnorrSigningKey::generate(&mut rng).unwrap(); + + // Keys should be different + assert_ne!(sk1, sk2, "Different keys should be generated"); + } + + #[test] + fn from_bytes_not_enough_bytes() { + let bytes = vec![0u8; 31]; + let result = SchnorrSigningKey::from_bytes(&bytes); + + result.expect_err("Should fail with insufficient bytes"); + } + + #[test] + fn from_bytes_exact_size() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + let sk_bytes = sk.to_bytes(); + + let sk_restored = SchnorrSigningKey::from_bytes(&sk_bytes).unwrap(); + + assert_eq!(sk, sk_restored); + } + + #[test] + fn from_bytes_extra_bytes() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + let sk_bytes = sk.to_bytes(); + + let mut extended_bytes = sk_bytes.to_vec(); + extended_bytes.extend_from_slice(&[0xFF; 10]); + + let sk_restored = SchnorrSigningKey::from_bytes(&extended_bytes).unwrap(); + + assert_eq!(sk, sk_restored); + } + + #[test] + fn to_bytes_is_deterministic() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + + let bytes1 = sk.to_bytes(); + let bytes2 = sk.to_bytes(); + + assert_eq!(bytes1, bytes2, "to_bytes should be deterministic"); + } mod golden { diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/verification_key.rs b/mithril-stm/src/signature_scheme/schnorr_signature/verification_key.rs index f2988660ba6..012290a97f4 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/verification_key.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/verification_key.rs @@ -46,7 +46,7 @@ impl SchnorrVerificationKey { /// The bytes must represent a Jubjub Subgroup point or the conversion will fail pub fn from_bytes(bytes: &[u8]) -> StmResult { if bytes.len() < 32 { - return Err(anyhow!(SchnorrSignatureError::SerializationError)).with_context( + return Err(anyhow!(SchnorrSignatureError::Serialization)).with_context( || "Not enough bytes provided to construct a Schnorr verification key.", ); } @@ -59,6 +59,114 @@ impl SchnorrVerificationKey { #[cfg(test)] mod tests { + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + + use crate::signature_scheme::{SchnorrSigningKey, SchnorrVerificationKey}; + + #[test] + fn create_verification_key_from_signing_key() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + + let vk = SchnorrVerificationKey::new_from_signing_key(sk); + + assert!( + vk.is_ok(), + "Verification key creation should succeed for valid signing key" + ); + } + + #[test] + fn different_signing_keys_produce_different_verification_keys() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let sk1 = SchnorrSigningKey::generate(&mut rng).unwrap(); + let sk2 = SchnorrSigningKey::generate(&mut rng).unwrap(); + + let vk1 = SchnorrVerificationKey::new_from_signing_key(sk1).unwrap(); + let vk2 = SchnorrVerificationKey::new_from_signing_key(sk2).unwrap(); + + assert_ne!( + vk1, vk2, + "Different signing keys should produce different verification keys" + ); + } + + #[test] + fn same_signing_key_produces_same_verification_key() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + + let vk1 = SchnorrVerificationKey::new_from_signing_key(sk.clone()).unwrap(); + let vk2 = SchnorrVerificationKey::new_from_signing_key(sk).unwrap(); + + assert_eq!( + vk1, vk2, + "Same signing key should produce same verification key" + ); + } + + #[test] + fn valid_verification_key_passes_validation() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + let vk = SchnorrVerificationKey::new_from_signing_key(sk).unwrap(); + + let result = vk.is_valid(); + + assert!( + result.is_ok(), + "Valid verification key should pass validation" + ); + } + + #[test] + fn from_bytes_not_enough_bytes() { + let bytes = vec![0u8; 31]; + let result = SchnorrVerificationKey::from_bytes(&bytes); + + result.expect_err("Should fail with insufficient bytes"); + } + + #[test] + fn from_bytes_exact_size() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + let vk = SchnorrVerificationKey::new_from_signing_key(sk).unwrap(); + let vk_bytes = vk.to_bytes(); + + let vk_restored = SchnorrVerificationKey::from_bytes(&vk_bytes).unwrap(); + + assert_eq!(vk, vk_restored); + } + + #[test] + fn from_bytes_extra_bytes() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + let vk = SchnorrVerificationKey::new_from_signing_key(sk).unwrap(); + let vk_bytes = vk.to_bytes(); + + let mut extended_bytes = vk_bytes.to_vec(); + extended_bytes.extend_from_slice(&[0xFF; 10]); + + let vk_restored = SchnorrVerificationKey::from_bytes(&extended_bytes).unwrap(); + + assert_eq!(vk, vk_restored); + } + + #[test] + fn to_bytes_is_deterministic() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + let vk = SchnorrVerificationKey::new_from_signing_key(sk).unwrap(); + + let bytes1 = vk.to_bytes(); + let bytes2 = vk.to_bytes(); + + assert_eq!(bytes1, bytes2, "to_bytes should be deterministic"); + } + mod golden { use rand_chacha::ChaCha20Rng; From bfa13143ec0154da9a9de3d890e9a10df67faf69 Mon Sep 17 00:00:00 2001 From: curiecrypt Date: Mon, 22 Dec 2025 18:23:04 +0300 Subject: [PATCH 6/7] change log and crates version --- Cargo.lock | 2 +- mithril-stm/CHANGELOG.md | 6 ++++++ mithril-stm/Cargo.toml | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b285df629b8..8f40064a92b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4273,7 +4273,7 @@ dependencies = [ [[package]] name = "mithril-stm" -version = "0.8.0" +version = "0.8.1" dependencies = [ "anyhow", "blake2 0.10.6", diff --git a/mithril-stm/CHANGELOG.md b/mithril-stm/CHANGELOG.md index 5e06ebce7ee..97cd903d48d 100644 --- a/mithril-stm/CHANGELOG.md +++ b/mithril-stm/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.8.1 (12-22-2025) + +### Added + +- Jubjub wrapper is added for `schnorr_signature` module. + ## 0.8.0 (12-17-2025) ### Changed diff --git a/mithril-stm/Cargo.toml b/mithril-stm/Cargo.toml index 757553eec15..2bdaa0919df 100644 --- a/mithril-stm/Cargo.toml +++ b/mithril-stm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-stm" -version = "0.8.0" +version = "0.8.1" edition = { workspace = true } authors = { workspace = true } homepage = { workspace = true } From 792dafac1ddd69aad264d9a6baed35845628fb16 Mon Sep 17 00:00:00 2001 From: curiecrypt Date: Mon, 22 Dec 2025 18:37:26 +0300 Subject: [PATCH 7/7] revert the bytes golden tests --- .../schnorr_signature/jubjub/mod.rs | 1 - .../schnorr_signature/signature.rs | 33 +++++++++++++++++-- .../schnorr_signature/signing_key.rs | 27 +++++++++++++-- .../schnorr_signature/verification_key.rs | 28 ++++++++++++++-- 4 files changed, 79 insertions(+), 10 deletions(-) diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/mod.rs b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/mod.rs index 72b8c8e5475..f06633afc2b 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/mod.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/mod.rs @@ -80,4 +80,3 @@ impl_serde!( 32 ); impl_serde!(ProjectivePoint, ProjectivePointVisitor, 32); -// impl_serde!(BlsSignature, SignatureVisitor, 48); diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs b/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs index 8dee297a1e4..b46f669b920 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs @@ -254,11 +254,38 @@ mod tests { } mod golden { + use super::*; - use rand_chacha::ChaCha20Rng; - use rand_core::SeedableRng; + const GOLDEN_BYTES: &[u8; 96] = &[ + 143, 53, 198, 62, 178, 1, 88, 253, 21, 92, 100, 13, 72, 180, 198, 127, 39, 175, 102, + 69, 147, 249, 244, 224, 122, 121, 248, 68, 217, 242, 158, 113, 5, 81, 137, 228, 235, + 18, 112, 76, 71, 127, 44, 47, 60, 55, 144, 204, 254, 50, 67, 167, 67, 133, 79, 168, 10, + 153, 228, 114, 147, 64, 34, 9, 12, 75, 91, 200, 29, 62, 12, 245, 185, 181, 67, 251, + 210, 211, 37, 42, 204, 205, 133, 215, 235, 236, 193, 155, 2, 147, 83, 189, 148, 38, 71, + 0, + ]; - use crate::signature_scheme::{SchnorrSignature, SchnorrSigningKey}; + fn golden_value() -> SchnorrSignature { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + let msg = [0u8; 32]; + sk.sign(&msg, &mut rng).unwrap() + } + + #[test] + fn golden_conversions() { + let value = SchnorrSignature::from_bytes(GOLDEN_BYTES) + .expect("This from bytes should not fail"); + assert_eq!(golden_value(), value); + + let serialized = SchnorrSignature::to_bytes(value); + let golden_serialized = SchnorrSignature::to_bytes(golden_value()); + assert_eq!(golden_serialized, serialized); + } + } + + mod golden_json { + use super::*; const GOLDEN_JSON: &str = r#" { diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/signing_key.rs b/mithril-stm/src/signature_scheme/schnorr_signature/signing_key.rs index 1350ba7a9ec..e72df77f9d8 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/signing_key.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/signing_key.rs @@ -181,11 +181,32 @@ mod tests { } mod golden { + use super::*; - use rand_chacha::ChaCha20Rng; - use rand_core::SeedableRng; + const GOLDEN_BYTES: &[u8; 32] = &[ + 126, 191, 239, 197, 88, 151, 248, 254, 187, 143, 86, 35, 29, 62, 90, 13, 196, 71, 234, + 5, 90, 124, 205, 194, 51, 192, 228, 133, 25, 140, 157, 7, + ]; - use crate::signature_scheme::SchnorrSigningKey; + fn golden_value() -> SchnorrSigningKey { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + SchnorrSigningKey::generate(&mut rng).unwrap() + } + + #[test] + fn golden_conversions() { + let value = SchnorrSigningKey::from_bytes(GOLDEN_BYTES) + .expect("This from bytes should not fail"); + assert_eq!(golden_value().0, value.0); + + let serialized = SchnorrSigningKey::to_bytes(&value); + let golden_serialized = SchnorrSigningKey::to_bytes(&golden_value()); + assert_eq!(golden_serialized, serialized); + } + } + + mod golden_json { + use super::*; const GOLDEN_JSON: &str = r#"[126, 191, 239, 197, 88, 151, 248, 254, 187, 143, 86, 35, 29, 62, 90, 13, 196, 71, 234, 5, 90, 124, 205, 194, 51, 192, 228, 133, 25, 140, 157, 7]"#; diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/verification_key.rs b/mithril-stm/src/signature_scheme/schnorr_signature/verification_key.rs index 012290a97f4..a6b0fd7bd06 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/verification_key.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/verification_key.rs @@ -168,11 +168,33 @@ mod tests { } mod golden { + use super::*; - use rand_chacha::ChaCha20Rng; - use rand_core::SeedableRng; + const GOLDEN_BYTES: &[u8; 32] = &[ + 144, 52, 95, 161, 127, 253, 49, 32, 140, 217, 231, 207, 32, 238, 244, 196, 97, 241, 47, + 95, 101, 9, 70, 136, 194, 66, 187, 253, 200, 32, 218, 43, + ]; - use crate::signature_scheme::{SchnorrSigningKey, SchnorrVerificationKey}; + fn golden_value() -> SchnorrVerificationKey { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + SchnorrVerificationKey::new_from_signing_key(sk).unwrap() + } + + #[test] + fn golden_conversions() { + let value = SchnorrVerificationKey::from_bytes(GOLDEN_BYTES) + .expect("This from bytes should not fail"); + assert_eq!(golden_value(), value); + + let serialized = SchnorrVerificationKey::to_bytes(value); + let golden_serialized = SchnorrVerificationKey::to_bytes(golden_value()); + assert_eq!(golden_serialized, serialized); + } + } + + mod golden_json { + use super::*; const GOLDEN_JSON: &str = r#"[144, 52, 95, 161, 127, 253, 49, 32, 140, 217, 231, 207, 32, 238, 244, 196, 97, 241, 47, 95, 101, 9, 70, 136, 194, 66, 187, 253, 200, 32, 218, 43]"#;