From e3d85d663c51d720f44ef5c6d7ea53610642680d Mon Sep 17 00:00:00 2001 From: curiecrypt Date: Wed, 17 Dec 2025 23:10:37 +0300 Subject: [PATCH 1/4] 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 c93a51a97e882a70d98bc8404d0babc1a58db2db Mon Sep 17 00:00:00 2001 From: curiecrypt Date: Thu, 18 Dec 2025 00:54:43 +0300 Subject: [PATCH 2/4] 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 394746b40eb649cd6b09f0d0f494fe338ecce357 Mon Sep 17 00:00:00 2001 From: curiecrypt Date: Thu, 18 Dec 2025 01:04:23 +0300 Subject: [PATCH 3/4] 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 bbc3b2e58491c03c94da47936e32f6a8c01d8ec4 Mon Sep 17 00:00:00 2001 From: curiecrypt Date: Thu, 18 Dec 2025 01:15:43 +0300 Subject: [PATCH 4/4] 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 {