From 74d7030408dc9793c23065b13f3350176a5b9ce4 Mon Sep 17 00:00:00 2001 From: Dam Date: Mon, 3 Nov 2025 13:35:22 +0100 Subject: [PATCH 01/21] Added file structure for Schnorr signature module. --- mithril-stm/Cargo.toml | 3 +- mithril-stm/src/lib.rs | 2 + mithril-stm/src/schnorr_signatures/helper.rs | 63 ++++++++++ mithril-stm/src/schnorr_signatures/mod.rs | 116 ++++++++++++++++++ .../src/schnorr_signatures/signature.rs | 70 +++++++++++ .../src/schnorr_signatures/signing_key.rs | 83 +++++++++++++ .../schnorr_signatures/verification_key.rs | 71 +++++++++++ 7 files changed, 407 insertions(+), 1 deletion(-) create mode 100644 mithril-stm/src/schnorr_signatures/helper.rs create mode 100644 mithril-stm/src/schnorr_signatures/mod.rs create mode 100644 mithril-stm/src/schnorr_signatures/signature.rs create mode 100644 mithril-stm/src/schnorr_signatures/signing_key.rs create mode 100644 mithril-stm/src/schnorr_signatures/verification_key.rs diff --git a/mithril-stm/Cargo.toml b/mithril-stm/Cargo.toml index 27248eb7503..e56c119519a 100644 --- a/mithril-stm/Cargo.toml +++ b/mithril-stm/Cargo.toml @@ -14,11 +14,12 @@ include = ["**/*.rs", "Cargo.toml", "README.md", ".gitignore"] crate-type = ["lib", "cdylib", "staticlib"] [features] -default = ["rug-backend"] +default = ["rug-backend", "future_snark"] rug-backend = ["rug/default"] num-integer-backend = ["num-bigint", "num-rational", "num-traits"] benchmark-internals = [] # For benchmarking multi_sig future_proof_system = [] # For activating future proof systems +future_snark = [] # For activating snark features [dependencies] blake2 = "0.10.6" diff --git a/mithril-stm/src/lib.rs b/mithril-stm/src/lib.rs index 2ca7313521b..5245b2ba9c8 100644 --- a/mithril-stm/src/lib.rs +++ b/mithril-stm/src/lib.rs @@ -118,6 +118,8 @@ mod merkle_tree; mod parameters; mod participant; mod single_signature; +#[cfg(feature = "future_snark")] +mod schnorr_signatures; pub use aggregate_signature::{ AggregateSignature, AggregateSignatureType, AggregateVerificationKey, BasicVerifier, Clerk, diff --git a/mithril-stm/src/schnorr_signatures/helper.rs b/mithril-stm/src/schnorr_signatures/helper.rs new file mode 100644 index 00000000000..b6cebf4f861 --- /dev/null +++ b/mithril-stm/src/schnorr_signatures/helper.rs @@ -0,0 +1,63 @@ +use midnight_circuits::{ + ecc::{ + hash_to_curve::HashToCurveGadget, + native::EccChip, + }, + hash::poseidon::PoseidonChip, + instructions::{ + HashToCurveCPU, + hash::HashCPU, + }, + types::AssignedNative, +}; + +pub use midnight_curves::{ + Bls12, EDWARDS_D, Fq as JubjubBase, Fq as BlsScalar, Fr as JubjubScalar, + G1Affine as BlstG1Affine, G1Projective as BlstG1, G2Affine as BlstG2Affine, JubjubAffine, + JubjubExtended as Jubjub, JubjubExtended, JubjubSubgroup, MODULUS, +}; + + + +use ff::Field; +use group::Group; +use rand_core::{CryptoRng, RngCore}; +use subtle::{Choice, ConstantTimeEq}; +use thiserror::Error; + +// use crate::schnorr_signatures::{get_coordinates, jubjub_base_to_scalar, is_on_curve}; + + + +pub fn get_coordinates(point: JubjubSubgroup) -> (JubjubBase, JubjubBase) { + let extended: JubjubExtended = point.into(); // Convert to JubjubExtended + let affine: JubjubAffine = extended.into(); // Convert to JubjubAffine (affine coordinates) + let x = affine.get_u(); // Get x-coordinate + let y = affine.get_v(); // Get y-coordinate + (x, y) +} + +pub fn jubjub_base_to_scalar(x: JubjubBase) -> JubjubScalar { + let bytes = x.to_bytes_le(); + JubjubScalar::from_raw([ + u64::from_le_bytes(bytes[0..8].try_into().unwrap()), + u64::from_le_bytes(bytes[8..16].try_into().unwrap()), + u64::from_le_bytes(bytes[16..24].try_into().unwrap()), + u64::from_le_bytes(bytes[24..32].try_into().unwrap()), + ]) +} + + +pub fn is_on_curve(u: JubjubBase, v: JubjubBase) -> Choice { + let u2 = u.square(); + let v2 = v.square(); + + // Left-hand side: v² - u² + let lhs = v2 - u2; + + // Right-hand side: 1 + EDWARDS_D * (u² * v²) + let rhs = JubjubBase::ONE + EDWARDS_D * u2 * v2; + + // Compare in constant time + lhs.ct_eq(&rhs) +} diff --git a/mithril-stm/src/schnorr_signatures/mod.rs b/mithril-stm/src/schnorr_signatures/mod.rs new file mode 100644 index 00000000000..5a4b48a2be4 --- /dev/null +++ b/mithril-stm/src/schnorr_signatures/mod.rs @@ -0,0 +1,116 @@ +pub use midnight_curves::{ + Bls12, EDWARDS_D, Fq as JubjubBase, Fq as BlsScalar, Fr as JubjubScalar, + G1Affine as BlstG1Affine, G1Projective as BlstG1, G2Affine as BlstG2Affine, JubjubAffine, + JubjubExtended as Jubjub, JubjubExtended, JubjubSubgroup, MODULUS, +}; + +use midnight_circuits::{ + ecc::{ + hash_to_curve::HashToCurveGadget, + native::EccChip, + }, + hash::poseidon::PoseidonChip, + instructions::{ + HashToCurveCPU, + hash::HashCPU, + }, + types::AssignedNative, +}; + +use ff::{Field}; +use group::Group; + +use subtle::{Choice, ConstantTimeEq}; +use thiserror::Error; + +pub mod helper; +mod signature; +mod signing_key; +mod verification_key; + +pub use signature::*; +pub use signing_key::*; +pub use verification_key::*; + + + +type JubjubHashToCurve = HashToCurveGadget< + JubjubBase, + Jubjub, + AssignedNative, + PoseidonChip, + EccChip, +>; + +type PoseidonHash = PoseidonChip; + +pub const DST_SIGNATURE: JubjubBase = JubjubBase::from_raw([2u64, 0, 0, 0]); + + +#[derive(Debug, Error)] +pub enum SignatureError { + #[error("Verification failed: Signature is invalid.")] + VerificationFailed, + /// This error occurs when the serialization of the raw bytes failed + #[error("Invalid bytes")] + SerializationError, +} + + + + +#[cfg(test)] +mod tests { + use super::*; + use rand_core::OsRng; + + /// Test signing functionality. + #[test] + fn test_signature_verification_valid() { + let mut rng = OsRng; + let sk = SigningKey::generate(&mut rng); + let msg = JubjubBase::random(&mut rng); + + // Sign the message + let signature = sk.sign(msg, &mut rng); + + // Ensure the components of the signature are non-default values + assert_ne!( + signature.sigma, + JubjubSubgroup::identity(), + "Signature sigma should not be the identity element." + ); + assert_ne!( + signature.s, + JubjubScalar::ZERO, + "Signature s component should not be zero." + ); + assert_ne!( + signature.c, + JubjubBase::ZERO, + "Signature c component should not be zero." + ); + + signature.verify(msg, &VerificationKey::from(&sk)).unwrap(); + } + + #[test] + fn test_signature_verification_invalid_signature() { + let mut rng = OsRng; + let sk = SigningKey::generate(&mut rng); + let msg = JubjubBase::random(&mut rng); + let vk: VerificationKey = (&sk).into(); + + // Generate signature and tamper with it + let mut signature = sk.sign(msg, &mut rng); + signature.s = JubjubScalar::random(&mut rng); // Modify `s` component + + // Verify the modified signature + let result = signature.verify(msg, &vk); + assert!( + result.is_err(), + "Invalid signature should fail verification, but it passed." + ); + } + +} \ No newline at end of file diff --git a/mithril-stm/src/schnorr_signatures/signature.rs b/mithril-stm/src/schnorr_signatures/signature.rs new file mode 100644 index 00000000000..ebac173dc29 --- /dev/null +++ b/mithril-stm/src/schnorr_signatures/signature.rs @@ -0,0 +1,70 @@ + +use midnight_circuits::{ + instructions::{ + HashToCurveCPU, + hash::HashCPU, + }, +}; + +pub use midnight_curves::{Fq as JubjubBase, Fr as JubjubScalar, + JubjubSubgroup, +}; + + +use group::Group; + +use crate::schnorr_signatures::helper::{get_coordinates, jubjub_base_to_scalar, is_on_curve}; +use crate::schnorr_signatures::verification_key::*; +use crate::schnorr_signatures::{JubjubHashToCurve, SignatureError, PoseidonHash, DST_SIGNATURE}; + + + +/// Schnorr signature including the value sigma used for the lottery +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SchnorrSignature { + pub sigma: JubjubSubgroup, + pub s: JubjubScalar, + pub c: JubjubBase, +} + +impl SchnorrSignature { + /// Verify a signature against a verification key. + pub fn verify(&self, msg: JubjubBase, vk: &VerificationKey) -> Result<(), SignatureError> { + let g = JubjubSubgroup::generator(); + let hash = JubjubHashToCurve::hash_to_curve(&[msg]); + let c_scalar = jubjub_base_to_scalar(self.c); + + let (hx, hy) = get_coordinates(hash); + let (vk_x, vk_y) = get_coordinates(vk.0); + let (sigma_x, sigma_y) = get_coordinates(self.sigma); + let cap_r_1_prime = &hash * &self.s + &self.sigma * &c_scalar; + let cap_r_2_prime = &g * &self.s + &vk.0 * &c_scalar; + let (cap_r_1_x_prime, cap_r_1_y_prime) = get_coordinates(cap_r_1_prime); + let (cap_r_2_x_prime, cap_r_2_y_prime) = get_coordinates(cap_r_2_prime); + let c_prime = PoseidonHash::hash(&[ + DST_SIGNATURE, + hx, + hy, + vk_x, + vk_y, + sigma_x, + sigma_y, + cap_r_1_x_prime, + cap_r_1_y_prime, + cap_r_2_x_prime, + cap_r_2_y_prime, + ]); + + if c_prime != self.c { + return Err(SignatureError::VerificationFailed); + } + + Ok(()) + } + + pub fn sigma(&self) -> (JubjubBase, JubjubBase) { + let (x, y) = get_coordinates(self.sigma); + (x, y) + } +} + diff --git a/mithril-stm/src/schnorr_signatures/signing_key.rs b/mithril-stm/src/schnorr_signatures/signing_key.rs new file mode 100644 index 00000000000..5a01784182e --- /dev/null +++ b/mithril-stm/src/schnorr_signatures/signing_key.rs @@ -0,0 +1,83 @@ + +pub use midnight_curves::{Fq as JubjubBase, Fr as JubjubScalar, + JubjubExtended as Jubjub, JubjubExtended, JubjubSubgroup, +}; +use midnight_circuits::{ + instructions::{ + HashToCurveCPU, + hash::HashCPU, + }, +}; + +use ff::Field; +use group::Group; +use rand_core::{CryptoRng, RngCore}; +use thiserror::Error; + +use crate::schnorr_signatures::helper::{get_coordinates, jubjub_base_to_scalar, is_on_curve}; +use crate::schnorr_signatures::verification_key::*; +use crate::schnorr_signatures::signature::*; + +use crate::schnorr_signatures::{JubjubHashToCurve, SignatureError, PoseidonHash, DST_SIGNATURE}; + + + +/// The signing key is a scalar from the Jubjub scalar field +#[derive(Debug, Clone)] +pub struct SigningKey(JubjubScalar); + +/// Implementation of the Schnorr signature scheme using the Jubjub curve +impl SigningKey { + pub fn generate(rng: &mut (impl RngCore + CryptoRng)) -> Self { + let sk = JubjubScalar::random(rng); + SigningKey(sk) + } + + /// A slightly modified version of the regular Schnorr signature (I think) + /// We include the computation of sigma, a value depending only on the msg + /// and the secret key as it is used for the lottery process + pub fn sign(&self, msg: JubjubBase, rng: &mut (impl RngCore + CryptoRng)) -> SchnorrSignature { + let g = JubjubSubgroup::generator(); + let vk = &g * &self.0; + + let hash = JubjubHashToCurve::hash_to_curve(&[msg]); + let sigma = &hash * &self.0; + let r = JubjubScalar::random(rng); + let cap_r_1 = &hash * &r; + let cap_r_2 = &g * &r; + + let (hx, hy) = get_coordinates(hash); + let (vk_x, vk_y) = get_coordinates(vk); + let (sigma_x, sigma_y) = get_coordinates(sigma); + let (cap_r_1_x, cap_r_1_y) = get_coordinates(cap_r_1); + let (cap_r_2_x, cap_r_2_y) = get_coordinates(cap_r_2); + + let c = PoseidonHash::hash(&[ + DST_SIGNATURE, + hx, + hy, + vk_x, + vk_y, + sigma_x, + sigma_y, + cap_r_1_x, + cap_r_1_y, + cap_r_2_x, + cap_r_2_y, + ]); + let c_scalar = jubjub_base_to_scalar(c); + let s = r - self.0 * c_scalar; + + SchnorrSignature { sigma, s, c } + } + +} + + +impl From<&SigningKey> for VerificationKey { + fn from(sk: &SigningKey) -> Self { + let g = JubjubSubgroup::generator(); + let vk = &g * &sk.0; + VerificationKey(vk) + } +} \ No newline at end of file diff --git a/mithril-stm/src/schnorr_signatures/verification_key.rs b/mithril-stm/src/schnorr_signatures/verification_key.rs new file mode 100644 index 00000000000..76fcc41ed9b --- /dev/null +++ b/mithril-stm/src/schnorr_signatures/verification_key.rs @@ -0,0 +1,71 @@ +use midnight_circuits::{ + ecc::{ + hash_to_curve::HashToCurveGadget, + native::EccChip, + }, + hash::poseidon::PoseidonChip, + instructions::{ + HashToCurveCPU, + hash::HashCPU, + }, + types::AssignedNative, +}; + +pub use midnight_curves::{Fq as JubjubBase, Fr as JubjubScalar, + JubjubExtended as Jubjub, JubjubExtended, JubjubSubgroup, +}; + + +use ff::Field; +use group::Group; +use rand_core::{CryptoRng, RngCore}; +use thiserror::Error; + +use crate::schnorr_signatures::helper::{get_coordinates, jubjub_base_to_scalar, is_on_curve}; +use crate::schnorr_signatures::{JubjubHashToCurve, SignatureError, PoseidonHash, DST_SIGNATURE}; + + + +#[derive(Debug, Clone, Copy, Default)] +pub struct VerificationKey(pub JubjubSubgroup); + + +impl VerificationKey { + pub fn to_field(&self) -> [JubjubBase; 2] { + let (x, y) = get_coordinates(self.0); + [x, y] + } + + pub fn to_bytes(&self) -> [u8; 64] { + let (x, y) = get_coordinates(self.0); + let mut bytes = [0u8; 64]; + bytes[0..32].copy_from_slice(&x.to_bytes_le()); + bytes[32..64].copy_from_slice(&y.to_bytes_le()); + bytes + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + let bytes = bytes.get(0..64).ok_or(SignatureError::SerializationError)?; + let mut u_bytes = [0u8; 32]; + u_bytes.copy_from_slice(&bytes[0..32]); + let mut v_bytes = [0u8; 32]; + v_bytes.copy_from_slice(&bytes[32..64]); + + let u = JubjubBase::from_bytes_le(&u_bytes) + .into_option() + .ok_or(SignatureError::SerializationError)?; + let v = JubjubBase::from_bytes_le(&v_bytes) + .into_option() + .ok_or(SignatureError::SerializationError)?; + if !bool::from(is_on_curve(u, v)) { + return Err(SignatureError::SerializationError); + } + + let point = JubjubSubgroup::from_raw_unchecked(u, v); + if !bool::from(JubjubExtended::from(point).is_prime_order()) { + return Err(SignatureError::SerializationError); + } + + Ok(VerificationKey(point)) + } +} \ No newline at end of file From c6bb7d6c00f1e4da13e0a22d019c06ce6b9e1285 Mon Sep 17 00:00:00 2001 From: Dam Date: Tue, 4 Nov 2025 11:39:04 +0100 Subject: [PATCH 02/21] Added to/from bytes for SchnorrSignature and started adding tests. --- mithril-stm/src/schnorr_signatures/helper.rs | 26 +--- mithril-stm/src/schnorr_signatures/mod.rs | 120 +++++++++++------- .../src/schnorr_signatures/signature.rs | 2 +- .../src/schnorr_signatures/signing_key.rs | 40 ++++-- .../schnorr_signatures/verification_key.rs | 6 +- 5 files changed, 109 insertions(+), 85 deletions(-) diff --git a/mithril-stm/src/schnorr_signatures/helper.rs b/mithril-stm/src/schnorr_signatures/helper.rs index b6cebf4f861..1d26ba889a9 100644 --- a/mithril-stm/src/schnorr_signatures/helper.rs +++ b/mithril-stm/src/schnorr_signatures/helper.rs @@ -1,32 +1,12 @@ -use midnight_circuits::{ - ecc::{ - hash_to_curve::HashToCurveGadget, - native::EccChip, - }, - hash::poseidon::PoseidonChip, - instructions::{ - HashToCurveCPU, - hash::HashCPU, - }, - types::AssignedNative, -}; - pub use midnight_curves::{ - Bls12, EDWARDS_D, Fq as JubjubBase, Fq as BlsScalar, Fr as JubjubScalar, - G1Affine as BlstG1Affine, G1Projective as BlstG1, G2Affine as BlstG2Affine, JubjubAffine, - JubjubExtended as Jubjub, JubjubExtended, JubjubSubgroup, MODULUS, + EDWARDS_D, Fq as JubjubBase, Fr as JubjubScalar, + JubjubAffine, JubjubExtended, JubjubSubgroup, }; - - use ff::Field; -use group::Group; -use rand_core::{CryptoRng, RngCore}; use subtle::{Choice, ConstantTimeEq}; -use thiserror::Error; - -// use crate::schnorr_signatures::{get_coordinates, jubjub_base_to_scalar, is_on_curve}; +use std::slice; pub fn get_coordinates(point: JubjubSubgroup) -> (JubjubBase, JubjubBase) { diff --git a/mithril-stm/src/schnorr_signatures/mod.rs b/mithril-stm/src/schnorr_signatures/mod.rs index 5a4b48a2be4..b4a2bedc809 100644 --- a/mithril-stm/src/schnorr_signatures/mod.rs +++ b/mithril-stm/src/schnorr_signatures/mod.rs @@ -44,7 +44,7 @@ type JubjubHashToCurve = HashToCurveGadget< type PoseidonHash = PoseidonChip; -pub const DST_SIGNATURE: JubjubBase = JubjubBase::from_raw([2u64, 0, 0, 0]); +pub(crate) const DST_SIGNATURE: JubjubBase = JubjubBase::from_raw([2u64, 0, 0, 0]); #[derive(Debug, Error)] @@ -58,59 +58,81 @@ pub enum SignatureError { - #[cfg(test)] mod tests { + // use blst::{blst_p1, blst_p2}; + use proptest::prelude::*; + use rand_chacha::ChaCha20Rng; + use rand_core::{RngCore, SeedableRng, OsRng}; + + // use crate::bls_multi_signature::helper::unsafe_helpers::{p1_affine_to_sig, p2_affine_to_vk}; + use crate::error::{MultiSignatureError, RegisterError}; + use crate::key_registration::KeyRegistration; + use super::*; - use rand_core::OsRng; - - /// Test signing functionality. - #[test] - fn test_signature_verification_valid() { - let mut rng = OsRng; - let sk = SigningKey::generate(&mut rng); - let msg = JubjubBase::random(&mut rng); - - // Sign the message - let signature = sk.sign(msg, &mut rng); - - // Ensure the components of the signature are non-default values - assert_ne!( - signature.sigma, - JubjubSubgroup::identity(), - "Signature sigma should not be the identity element." - ); - assert_ne!( - signature.s, - JubjubScalar::ZERO, - "Signature s component should not be zero." - ); - assert_ne!( - signature.c, - JubjubBase::ZERO, - "Signature c component should not be zero." - ); - - signature.verify(msg, &VerificationKey::from(&sk)).unwrap(); + + impl PartialEq for SchnorrSigningKey { + fn eq(&self, other: &Self) -> bool { + self.to_bytes() == other.to_bytes() + } } - #[test] - fn test_signature_verification_invalid_signature() { - let mut rng = OsRng; - let sk = SigningKey::generate(&mut rng); - let msg = JubjubBase::random(&mut rng); - let vk: VerificationKey = (&sk).into(); - - // Generate signature and tamper with it - let mut signature = sk.sign(msg, &mut rng); - signature.s = JubjubScalar::random(&mut rng); // Modify `s` component - - // Verify the modified signature - let result = signature.verify(msg, &vk); - assert!( - result.is_err(), - "Invalid signature should fail verification, but it passed." - ); + // impl Eq for SchnorrSigningKey {} + + proptest! { + #![proptest_config(ProptestConfig::with_cases(1000))] + + /// Test signing functionality. + #[test] + fn test_signature_verification_valid(seed in any::()) { + let mut rng = OsRng; + let sk = SchnorrSigningKey::generate(&mut rng); + let msg = JubjubBase::random(&mut rng); + + // Sign the message + let signature = sk.sign(msg, &mut rng); + + // Ensure the components of the signature are non-default values + assert_ne!( + signature.sigma, + JubjubSubgroup::identity(), + "Signature sigma should not be the identity element." + ); + assert_ne!( + signature.s, + JubjubScalar::ZERO, + "Signature s component should not be zero." + ); + assert_ne!( + signature.c, + JubjubBase::ZERO, + "Signature c component should not be zero." + ); + + signature.verify(msg, &SchnorrVerificationKey::from(&sk)).unwrap(); + } + + #[test] + fn test_signature_verification_invalid_signature(seed in any::()) { + let mut rng = OsRng; + let sk = SchnorrSigningKey::generate(&mut rng); + let msg = JubjubBase::random(&mut rng); + let vk: SchnorrVerificationKey = (&sk).into(); + + // Generate signature and tamper with it + let mut signature = sk.sign(msg, &mut rng); + signature.s = JubjubScalar::random(&mut rng); // Modify `s` component + + // Verify the modified signature + let result = signature.verify(msg, &vk); + assert!( + result.is_err(), + "Invalid signature should fail verification, but it passed." + ); + } + + } + } \ No newline at end of file diff --git a/mithril-stm/src/schnorr_signatures/signature.rs b/mithril-stm/src/schnorr_signatures/signature.rs index ebac173dc29..d7f79f4806c 100644 --- a/mithril-stm/src/schnorr_signatures/signature.rs +++ b/mithril-stm/src/schnorr_signatures/signature.rs @@ -29,7 +29,7 @@ pub struct SchnorrSignature { impl SchnorrSignature { /// Verify a signature against a verification key. - pub fn verify(&self, msg: JubjubBase, vk: &VerificationKey) -> Result<(), SignatureError> { + pub fn verify(&self, msg: JubjubBase, vk: &SchnorrVerificationKey) -> Result<(), SignatureError> { let g = JubjubSubgroup::generator(); let hash = JubjubHashToCurve::hash_to_curve(&[msg]); let c_scalar = jubjub_base_to_scalar(self.c); diff --git a/mithril-stm/src/schnorr_signatures/signing_key.rs b/mithril-stm/src/schnorr_signatures/signing_key.rs index 5a01784182e..ca72b11d8be 100644 --- a/mithril-stm/src/schnorr_signatures/signing_key.rs +++ b/mithril-stm/src/schnorr_signatures/signing_key.rs @@ -1,6 +1,6 @@ pub use midnight_curves::{Fq as JubjubBase, Fr as JubjubScalar, - JubjubExtended as Jubjub, JubjubExtended, JubjubSubgroup, + JubjubExtended as Jubjub, JubjubExtended, JubjubSubgroup }; use midnight_circuits::{ instructions::{ @@ -12,9 +12,10 @@ use midnight_circuits::{ use ff::Field; use group::Group; use rand_core::{CryptoRng, RngCore}; +use subtle::CtOption; use thiserror::Error; -use crate::schnorr_signatures::helper::{get_coordinates, jubjub_base_to_scalar, is_on_curve}; +use crate::{error::MultiSignatureError, schnorr_signatures::helper::{get_coordinates, is_on_curve, jubjub_base_to_scalar}}; use crate::schnorr_signatures::verification_key::*; use crate::schnorr_signatures::signature::*; @@ -24,13 +25,13 @@ use crate::schnorr_signatures::{JubjubHashToCurve, SignatureError, PoseidonHash, /// The signing key is a scalar from the Jubjub scalar field #[derive(Debug, Clone)] -pub struct SigningKey(JubjubScalar); +pub struct SchnorrSigningKey(JubjubScalar); /// Implementation of the Schnorr signature scheme using the Jubjub curve -impl SigningKey { +impl SchnorrSigningKey { pub fn generate(rng: &mut (impl RngCore + CryptoRng)) -> Self { let sk = JubjubScalar::random(rng); - SigningKey(sk) + SchnorrSigningKey(sk) } /// A slightly modified version of the regular Schnorr signature (I think) @@ -71,13 +72,34 @@ impl SigningKey { SchnorrSignature { sigma, s, c } } -} + /// Convert the schnorr secret key into byte string. + /// Uses midnight curve implem for the conversion + pub fn to_bytes(&self) -> [u8; 32] { + self.0.to_bytes() + } + /// Convert a string of bytes into a `SchnorrSigningKey`. + /// + /// # Error + /// Fails if the byte string represents a scalar larger than the group order. + pub fn from_bytes(bytes: &[u8]) -> Result { + // This is a bit ugly, I'll try to find a better way to do it + let bytes = bytes.get(..32).ok_or(MultiSignatureError::SerializationError)?.try_into().unwrap(); + // Jubjub returs a CtChoice so I convert it to an option that looses the const time property + match JubjubScalar::from_bytes(bytes).into_option().ok_or(MultiSignatureError::SerializationError) { + Ok(sk) => Ok(Self(sk)), + // the error should be updated + Err(e) => Err(e) + } + } + +} -impl From<&SigningKey> for VerificationKey { - fn from(sk: &SigningKey) -> Self { +// Should we have this implementation? +impl From<&SchnorrSigningKey> for SchnorrVerificationKey { + fn from(sk: &SchnorrSigningKey) -> Self { let g = JubjubSubgroup::generator(); let vk = &g * &sk.0; - VerificationKey(vk) + SchnorrVerificationKey(vk) } } \ No newline at end of file diff --git a/mithril-stm/src/schnorr_signatures/verification_key.rs b/mithril-stm/src/schnorr_signatures/verification_key.rs index 76fcc41ed9b..14efdbe206d 100644 --- a/mithril-stm/src/schnorr_signatures/verification_key.rs +++ b/mithril-stm/src/schnorr_signatures/verification_key.rs @@ -27,10 +27,10 @@ use crate::schnorr_signatures::{JubjubHashToCurve, SignatureError, PoseidonHash, #[derive(Debug, Clone, Copy, Default)] -pub struct VerificationKey(pub JubjubSubgroup); +pub struct SchnorrVerificationKey(pub JubjubSubgroup); -impl VerificationKey { +impl SchnorrVerificationKey { pub fn to_field(&self) -> [JubjubBase; 2] { let (x, y) = get_coordinates(self.0); [x, y] @@ -66,6 +66,6 @@ impl VerificationKey { return Err(SignatureError::SerializationError); } - Ok(VerificationKey(point)) + Ok(SchnorrVerificationKey(point)) } } \ No newline at end of file From 09ad4521be5243e4a128be07d7e1df0fe78a3314 Mon Sep 17 00:00:00 2001 From: Dam Date: Tue, 4 Nov 2025 12:57:15 +0100 Subject: [PATCH 03/21] Moved test out of proptest. --- mithril-stm/src/schnorr_signatures/mod.rs | 163 +++++++++++++++------- 1 file changed, 110 insertions(+), 53 deletions(-) diff --git a/mithril-stm/src/schnorr_signatures/mod.rs b/mithril-stm/src/schnorr_signatures/mod.rs index b4a2bedc809..f9771cff4ad 100644 --- a/mithril-stm/src/schnorr_signatures/mod.rs +++ b/mithril-stm/src/schnorr_signatures/mod.rs @@ -69,6 +69,10 @@ mod tests { use crate::error::{MultiSignatureError, RegisterError}; use crate::key_registration::KeyRegistration; + use blake2::{Blake2b, Blake2s256,Blake2b512, digest::{Digest, FixedOutput, consts::U32}}; + + type Blake2b256 = Blake2b; + use super::*; impl PartialEq for SchnorrSigningKey { @@ -77,61 +81,114 @@ mod tests { } } - // impl Eq for SchnorrSigningKey {} - - proptest! { - #![proptest_config(ProptestConfig::with_cases(1000))] - - /// Test signing functionality. - #[test] - fn test_signature_verification_valid(seed in any::()) { - let mut rng = OsRng; - let sk = SchnorrSigningKey::generate(&mut rng); - let msg = JubjubBase::random(&mut rng); - - // Sign the message - let signature = sk.sign(msg, &mut rng); - - // Ensure the components of the signature are non-default values - assert_ne!( - signature.sigma, - JubjubSubgroup::identity(), - "Signature sigma should not be the identity element." - ); - assert_ne!( - signature.s, - JubjubScalar::ZERO, - "Signature s component should not be zero." - ); - assert_ne!( - signature.c, - JubjubBase::ZERO, - "Signature c component should not be zero." - ); - - signature.verify(msg, &SchnorrVerificationKey::from(&sk)).unwrap(); + impl PartialEq for SchnorrVerificationKey { + fn eq(&self, other: &Self) -> bool { + self.to_bytes() == other.to_bytes() } + } - #[test] - fn test_signature_verification_invalid_signature(seed in any::()) { - let mut rng = OsRng; - let sk = SchnorrSigningKey::generate(&mut rng); - let msg = JubjubBase::random(&mut rng); - let vk: SchnorrVerificationKey = (&sk).into(); - - // Generate signature and tamper with it - let mut signature = sk.sign(msg, &mut rng); - signature.s = JubjubScalar::random(&mut rng); // Modify `s` component - - // Verify the modified signature - let result = signature.verify(msg, &vk); - assert!( - result.is_err(), - "Invalid signature should fail verification, but it passed." - ); - } - - + impl Eq for SchnorrSigningKey {} + + #[test] + fn test_sig( + ) { + + let msg = vec![0,0,0,1]; + + let mut rng = OsRng; + + let sk = SchnorrSigningKey::generate(&mut ChaCha20Rng::from_entropy()); + let vk = SchnorrVerificationKey::from(&sk); + + let mut hash = Blake2b256::new(); + hash.update(msg); + let hmsg = hash.finalize(); + let mut output = [0u8; 32]; + output.copy_from_slice(hmsg.as_slice()); + + let msg = JubjubBase::from_bytes_be(&output).unwrap(); + + let sig = sk.sign(msg, &mut rng); + + sig.verify(msg, &vk).unwrap(); + } + + /// Test signing functionality. + #[test] + fn test_signature_verification_valid() { + let msg = vec![0,0,0,1]; + let mut hash = Blake2b256::new(); + hash.update(msg); + let hmsg = hash.finalize(); + let mut output = [0u8; 32]; + output.copy_from_slice(hmsg.as_slice()); + let msg = JubjubBase::from_bytes_be(&output).unwrap(); + + let mut rng = OsRng; + let sk = SchnorrSigningKey::generate(&mut rng); + // let msg = JubjubBase::random(&mut rng); + + // Sign the message + let signature = sk.sign(msg, &mut rng); + + // Ensure the components of the signature are non-default values + assert_ne!( + signature.sigma, + JubjubSubgroup::identity(), + "Signature sigma should not be the identity element." + ); + assert_ne!( + signature.s, + JubjubScalar::ZERO, + "Signature s component should not be zero." + ); + assert_ne!( + signature.c, + JubjubBase::ZERO, + "Signature c component should not be zero." + ); + + signature.verify(msg, &SchnorrVerificationKey::from(&sk)).unwrap(); + } + + #[test] + fn test_signature_verification_invalid_signature() { + let mut rng = OsRng; + let sk = SchnorrSigningKey::generate(&mut rng); + let msg = JubjubBase::random(&mut rng); + let vk: SchnorrVerificationKey = (&sk).into(); + + // Generate signature and tamper with it + let mut signature = sk.sign(msg, &mut rng); + signature.s = JubjubScalar::random(&mut rng); // Modify `s` component + + // Verify the modified signature + let result = signature.verify(msg, &vk); + assert!( + result.is_err(), + "Invalid signature should fail verification, but it passed." + ); + } + + #[test] + fn serialize_deserialize_vk() { + let seed = 0; + let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(seed); + let sk = SchnorrSigningKey::generate(&mut rng); + let vk = SchnorrVerificationKey::from(&sk); + let vk_bytes = vk.to_bytes(); + let vk2 = SchnorrVerificationKey::from_bytes(&vk_bytes).unwrap(); + assert_eq!(vk, vk2); + } + + #[test] + fn serialize_deserialize_sk() { + let seed = 0; + let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(seed); + let sk = SchnorrSigningKey::generate(&mut rng); + let sk_bytes: [u8; 32] = sk.to_bytes(); + let sk2 = SchnorrSigningKey::from_bytes(&sk_bytes).unwrap(); + assert_eq!(sk, sk2); } From 2a992dfcde2281c29e2953eb8dfa120195e3119c Mon Sep 17 00:00:00 2001 From: Dam Date: Tue, 4 Nov 2025 16:30:20 +0100 Subject: [PATCH 04/21] Added more test and helper function to convert msg to base field. --- Cargo.lock | 2 + mithril-stm/Cargo.toml | 8 ++ mithril-stm/src/lib.rs | 2 +- mithril-stm/src/schnorr_signatures/helper.rs | 18 ++++- mithril-stm/src/schnorr_signatures/mod.rs | 77 +++++++++++-------- .../src/schnorr_signatures/signature.rs | 26 +++---- .../src/schnorr_signatures/signing_key.rs | 41 +++++----- .../schnorr_signatures/verification_key.rs | 24 ++---- 8 files changed, 108 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1976d4de071..a589459fe52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4163,6 +4163,8 @@ dependencies = [ "serde", "serde_json", "thiserror 2.0.17", + "sha2", + "subtle", ] [[package]] diff --git a/mithril-stm/Cargo.toml b/mithril-stm/Cargo.toml index e56c119519a..80cc65c2d77 100644 --- a/mithril-stm/Cargo.toml +++ b/mithril-stm/Cargo.toml @@ -30,6 +30,14 @@ rand_core = { workspace = true } rayon = { workspace = true } serde = { workspace = true } thiserror = { workspace = true } +midnight-circuits = { git = "https://github.com/midnightntwrk/midnight-zk", rev = "c88a50c2169f060120a52ad0980de90f08bc9535" } +midnight-curves = { git = "https://github.com/midnightntwrk/midnight-zk", rev = "c88a50c2169f060120a52ad0980de90f08bc9535" } +ff = "0.13.1" +group = "0.13.0" +num-traits = "0.2.19" +subtle = "2.6.1" +sha2 = "0.10.9" + [target.'cfg(any(target_family = "wasm", target_env = "musl", windows))'.dependencies] # WASM and Windows don't support rug backend, fallback to num-integer only diff --git a/mithril-stm/src/lib.rs b/mithril-stm/src/lib.rs index 5245b2ba9c8..21b11cc4e8e 100644 --- a/mithril-stm/src/lib.rs +++ b/mithril-stm/src/lib.rs @@ -117,9 +117,9 @@ mod key_registration; mod merkle_tree; mod parameters; mod participant; -mod single_signature; #[cfg(feature = "future_snark")] mod schnorr_signatures; +mod single_signature; pub use aggregate_signature::{ AggregateSignature, AggregateSignatureType, AggregateVerificationKey, BasicVerifier, Clerk, diff --git a/mithril-stm/src/schnorr_signatures/helper.rs b/mithril-stm/src/schnorr_signatures/helper.rs index 1d26ba889a9..f7449d8bf1b 100644 --- a/mithril-stm/src/schnorr_signatures/helper.rs +++ b/mithril-stm/src/schnorr_signatures/helper.rs @@ -1,14 +1,13 @@ pub use midnight_curves::{ - EDWARDS_D, Fq as JubjubBase, Fr as JubjubScalar, - JubjubAffine, JubjubExtended, JubjubSubgroup, + EDWARDS_D, Fq as JubjubBase, Fr as JubjubScalar, JubjubAffine, JubjubExtended, JubjubSubgroup, }; use ff::Field; +use sha2::{Digest, Sha256}; use subtle::{Choice, ConstantTimeEq}; use std::slice; - pub fn get_coordinates(point: JubjubSubgroup) -> (JubjubBase, JubjubBase) { let extended: JubjubExtended = point.into(); // Convert to JubjubExtended let affine: JubjubAffine = extended.into(); // Convert to JubjubAffine (affine coordinates) @@ -27,7 +26,6 @@ pub fn jubjub_base_to_scalar(x: JubjubBase) -> JubjubScalar { ]) } - pub fn is_on_curve(u: JubjubBase, v: JubjubBase) -> Choice { let u2 = u.square(); let v2 = v.square(); @@ -41,3 +39,15 @@ pub fn is_on_curve(u: JubjubBase, v: JubjubBase) -> Choice { // Compare in constant time lhs.ct_eq(&rhs) } + +pub fn hash_msg_to_base(msg: &[u8]) -> JubjubBase { + let mut hash = Sha256::new(); + hash.update(msg); + let hmsg = hash.finalize(); + let mut output = [0u8; 32]; + output.copy_from_slice(hmsg.as_slice()); + + output[31] &= 0x0f; + + JubjubBase::from_bytes_le(&output).unwrap() +} diff --git a/mithril-stm/src/schnorr_signatures/mod.rs b/mithril-stm/src/schnorr_signatures/mod.rs index f9771cff4ad..a92a1d0797c 100644 --- a/mithril-stm/src/schnorr_signatures/mod.rs +++ b/mithril-stm/src/schnorr_signatures/mod.rs @@ -5,20 +5,15 @@ pub use midnight_curves::{ }; use midnight_circuits::{ - ecc::{ - hash_to_curve::HashToCurveGadget, - native::EccChip, - }, + ecc::{hash_to_curve::HashToCurveGadget, native::EccChip}, hash::poseidon::PoseidonChip, - instructions::{ - HashToCurveCPU, - hash::HashCPU, - }, + instructions::{HashToCurveCPU, hash::HashCPU}, types::AssignedNative, }; -use ff::{Field}; +use ff::Field; use group::Group; +use sha2::{Digest, Sha256}; use subtle::{Choice, ConstantTimeEq}; use thiserror::Error; @@ -28,12 +23,11 @@ mod signature; mod signing_key; mod verification_key; +pub use helper::*; pub use signature::*; pub use signing_key::*; pub use verification_key::*; - - type JubjubHashToCurve = HashToCurveGadget< JubjubBase, Jubjub, @@ -46,7 +40,6 @@ type PoseidonHash = PoseidonChip; pub(crate) const DST_SIGNATURE: JubjubBase = JubjubBase::from_raw([2u64, 0, 0, 0]); - #[derive(Debug, Error)] pub enum SignatureError { #[error("Verification failed: Signature is invalid.")] @@ -56,20 +49,30 @@ pub enum SignatureError { SerializationError, } - +fn u64s_from_bytes(bytes: &[u8; 32]) -> [u64; 4] { + [ + u64::from_le_bytes(bytes[0..8].try_into().unwrap()), + u64::from_le_bytes(bytes[8..16].try_into().unwrap()), + u64::from_le_bytes(bytes[16..24].try_into().unwrap()), + u64::from_le_bytes(bytes[24..32].try_into().unwrap()), + ] +} #[cfg(test)] mod tests { // use blst::{blst_p1, blst_p2}; use proptest::prelude::*; use rand_chacha::ChaCha20Rng; - use rand_core::{RngCore, SeedableRng, OsRng}; + use rand_core::{OsRng, RngCore, SeedableRng}; // use crate::bls_multi_signature::helper::unsafe_helpers::{p1_affine_to_sig, p2_affine_to_vk}; use crate::error::{MultiSignatureError, RegisterError}; use crate::key_registration::KeyRegistration; - use blake2::{Blake2b, Blake2s256,Blake2b512, digest::{Digest, FixedOutput, consts::U32}}; + use blake2::{ + Blake2b, Blake2b512, Blake2s256, + digest::{Digest, FixedOutput, consts::U32}, + }; type Blake2b256 = Blake2b; @@ -89,17 +92,38 @@ mod tests { impl Eq for SchnorrSigningKey {} + // Testing conversion from arbitrary message to base field element #[test] - fn test_sig( - ) { - - let msg = vec![0,0,0,1]; + fn test_hash_msg_to_bas() { + let msg = vec![0, 0, 0, 1]; + let h = hash_msg_to_base(&msg); + println!("{:?}", h); + } + // Testing basic signature using Sha256 to hash the message + #[test] + fn test_sig() { + let msg = vec![0, 0, 0, 1]; let mut rng = OsRng; let sk = SchnorrSigningKey::generate(&mut ChaCha20Rng::from_entropy()); let vk = SchnorrVerificationKey::from(&sk); + let msg = hash_msg_to_base(&msg); + + let sig = sk.sign(msg, &mut rng); + + sig.verify(msg, &vk).unwrap(); + } + + // Testing basic signature using Blake2b256 to hash the message + #[test] + fn test_sig_blake() { + let mut rng = OsRng; + let msg = vec![0, 0, 0, 1]; + let sk = SchnorrSigningKey::generate(&mut ChaCha20Rng::from_entropy()); + let vk = SchnorrVerificationKey::from(&sk); + let mut hash = Blake2b256::new(); hash.update(msg); let hmsg = hash.finalize(); @@ -107,22 +131,15 @@ mod tests { output.copy_from_slice(hmsg.as_slice()); let msg = JubjubBase::from_bytes_be(&output).unwrap(); - let sig = sk.sign(msg, &mut rng); - sig.verify(msg, &vk).unwrap(); } /// Test signing functionality. #[test] fn test_signature_verification_valid() { - let msg = vec![0,0,0,1]; - let mut hash = Blake2b256::new(); - hash.update(msg); - let hmsg = hash.finalize(); - let mut output = [0u8; 32]; - output.copy_from_slice(hmsg.as_slice()); - let msg = JubjubBase::from_bytes_be(&output).unwrap(); + let msg = vec![0, 0, 0, 1]; + let msg = hash_msg_to_base(&msg); let mut rng = OsRng; let sk = SchnorrSigningKey::generate(&mut rng); @@ -190,6 +207,4 @@ mod tests { let sk2 = SchnorrSigningKey::from_bytes(&sk_bytes).unwrap(); assert_eq!(sk, sk2); } - - -} \ No newline at end of file +} diff --git a/mithril-stm/src/schnorr_signatures/signature.rs b/mithril-stm/src/schnorr_signatures/signature.rs index d7f79f4806c..767dd2017fe 100644 --- a/mithril-stm/src/schnorr_signatures/signature.rs +++ b/mithril-stm/src/schnorr_signatures/signature.rs @@ -1,23 +1,12 @@ +use midnight_circuits::instructions::{HashToCurveCPU, hash::HashCPU}; -use midnight_circuits::{ - instructions::{ - HashToCurveCPU, - hash::HashCPU, - }, -}; - -pub use midnight_curves::{Fq as JubjubBase, Fr as JubjubScalar, - JubjubSubgroup, -}; - +pub use midnight_curves::{Fq as JubjubBase, Fr as JubjubScalar, JubjubSubgroup}; use group::Group; -use crate::schnorr_signatures::helper::{get_coordinates, jubjub_base_to_scalar, is_on_curve}; +use crate::schnorr_signatures::helper::{get_coordinates, is_on_curve, jubjub_base_to_scalar}; use crate::schnorr_signatures::verification_key::*; -use crate::schnorr_signatures::{JubjubHashToCurve, SignatureError, PoseidonHash, DST_SIGNATURE}; - - +use crate::schnorr_signatures::{DST_SIGNATURE, JubjubHashToCurve, PoseidonHash, SignatureError}; /// Schnorr signature including the value sigma used for the lottery #[derive(Debug, Clone, PartialEq, Eq)] @@ -29,7 +18,11 @@ pub struct SchnorrSignature { impl SchnorrSignature { /// Verify a signature against a verification key. - pub fn verify(&self, msg: JubjubBase, vk: &SchnorrVerificationKey) -> Result<(), SignatureError> { + pub fn verify( + &self, + msg: JubjubBase, + vk: &SchnorrVerificationKey, + ) -> Result<(), SignatureError> { let g = JubjubSubgroup::generator(); let hash = JubjubHashToCurve::hash_to_curve(&[msg]); let c_scalar = jubjub_base_to_scalar(self.c); @@ -67,4 +60,3 @@ impl SchnorrSignature { (x, y) } } - diff --git a/mithril-stm/src/schnorr_signatures/signing_key.rs b/mithril-stm/src/schnorr_signatures/signing_key.rs index ca72b11d8be..a87b921c733 100644 --- a/mithril-stm/src/schnorr_signatures/signing_key.rs +++ b/mithril-stm/src/schnorr_signatures/signing_key.rs @@ -1,12 +1,6 @@ - -pub use midnight_curves::{Fq as JubjubBase, Fr as JubjubScalar, - JubjubExtended as Jubjub, JubjubExtended, JubjubSubgroup -}; -use midnight_circuits::{ - instructions::{ - HashToCurveCPU, - hash::HashCPU, - }, +use midnight_circuits::instructions::{HashToCurveCPU, hash::HashCPU}; +pub use midnight_curves::{ + Fq as JubjubBase, Fr as JubjubScalar, JubjubExtended as Jubjub, JubjubExtended, JubjubSubgroup, }; use ff::Field; @@ -15,13 +9,14 @@ use rand_core::{CryptoRng, RngCore}; use subtle::CtOption; use thiserror::Error; -use crate::{error::MultiSignatureError, schnorr_signatures::helper::{get_coordinates, is_on_curve, jubjub_base_to_scalar}}; -use crate::schnorr_signatures::verification_key::*; use crate::schnorr_signatures::signature::*; +use crate::schnorr_signatures::verification_key::*; +use crate::{ + error::MultiSignatureError, + schnorr_signatures::helper::{get_coordinates, is_on_curve, jubjub_base_to_scalar}, +}; -use crate::schnorr_signatures::{JubjubHashToCurve, SignatureError, PoseidonHash, DST_SIGNATURE}; - - +use crate::schnorr_signatures::{DST_SIGNATURE, JubjubHashToCurve, PoseidonHash, SignatureError}; /// The signing key is a scalar from the Jubjub scalar field #[derive(Debug, Clone)] @@ -35,7 +30,7 @@ impl SchnorrSigningKey { } /// A slightly modified version of the regular Schnorr signature (I think) - /// We include the computation of sigma, a value depending only on the msg + /// We include the computation of sigma, a value depending only on the msg /// and the secret key as it is used for the lottery process pub fn sign(&self, msg: JubjubBase, rng: &mut (impl RngCore + CryptoRng)) -> SchnorrSignature { let g = JubjubSubgroup::generator(); @@ -84,15 +79,21 @@ impl SchnorrSigningKey { /// Fails if the byte string represents a scalar larger than the group order. pub fn from_bytes(bytes: &[u8]) -> Result { // This is a bit ugly, I'll try to find a better way to do it - let bytes = bytes.get(..32).ok_or(MultiSignatureError::SerializationError)?.try_into().unwrap(); + let bytes = bytes + .get(..32) + .ok_or(MultiSignatureError::SerializationError)? + .try_into() + .unwrap(); // Jubjub returs a CtChoice so I convert it to an option that looses the const time property - match JubjubScalar::from_bytes(bytes).into_option().ok_or(MultiSignatureError::SerializationError) { + match JubjubScalar::from_bytes(bytes) + .into_option() + .ok_or(MultiSignatureError::SerializationError) + { Ok(sk) => Ok(Self(sk)), // the error should be updated - Err(e) => Err(e) + Err(e) => Err(e), } } - } // Should we have this implementation? @@ -102,4 +103,4 @@ impl From<&SchnorrSigningKey> for SchnorrVerificationKey { let vk = &g * &sk.0; SchnorrVerificationKey(vk) } -} \ No newline at end of file +} diff --git a/mithril-stm/src/schnorr_signatures/verification_key.rs b/mithril-stm/src/schnorr_signatures/verification_key.rs index 14efdbe206d..20f25d2f389 100644 --- a/mithril-stm/src/schnorr_signatures/verification_key.rs +++ b/mithril-stm/src/schnorr_signatures/verification_key.rs @@ -1,35 +1,25 @@ use midnight_circuits::{ - ecc::{ - hash_to_curve::HashToCurveGadget, - native::EccChip, - }, + ecc::{hash_to_curve::HashToCurveGadget, native::EccChip}, hash::poseidon::PoseidonChip, - instructions::{ - HashToCurveCPU, - hash::HashCPU, - }, + instructions::{HashToCurveCPU, hash::HashCPU}, types::AssignedNative, }; -pub use midnight_curves::{Fq as JubjubBase, Fr as JubjubScalar, - JubjubExtended as Jubjub, JubjubExtended, JubjubSubgroup, +pub use midnight_curves::{ + Fq as JubjubBase, Fr as JubjubScalar, JubjubExtended as Jubjub, JubjubExtended, JubjubSubgroup, }; - use ff::Field; use group::Group; use rand_core::{CryptoRng, RngCore}; use thiserror::Error; -use crate::schnorr_signatures::helper::{get_coordinates, jubjub_base_to_scalar, is_on_curve}; -use crate::schnorr_signatures::{JubjubHashToCurve, SignatureError, PoseidonHash, DST_SIGNATURE}; - - +use crate::schnorr_signatures::helper::{get_coordinates, is_on_curve, jubjub_base_to_scalar}; +use crate::schnorr_signatures::{DST_SIGNATURE, JubjubHashToCurve, PoseidonHash, SignatureError}; #[derive(Debug, Clone, Copy, Default)] pub struct SchnorrVerificationKey(pub JubjubSubgroup); - impl SchnorrVerificationKey { pub fn to_field(&self) -> [JubjubBase; 2] { let (x, y) = get_coordinates(self.0); @@ -68,4 +58,4 @@ impl SchnorrVerificationKey { Ok(SchnorrVerificationKey(point)) } -} \ No newline at end of file +} From 1e07c0752272b9825c6942a572ff6ad765ef01ed Mon Sep 17 00:00:00 2001 From: Dam Date: Tue, 4 Nov 2025 16:35:55 +0100 Subject: [PATCH 05/21] applied clippy. --- mithril-stm/src/schnorr_signatures/helper.rs | 1 - .../src/schnorr_signatures/signature.rs | 6 +++--- .../src/schnorr_signatures/signing_key.rs | 18 ++++++++---------- .../src/schnorr_signatures/verification_key.rs | 16 +++------------- 4 files changed, 14 insertions(+), 27 deletions(-) diff --git a/mithril-stm/src/schnorr_signatures/helper.rs b/mithril-stm/src/schnorr_signatures/helper.rs index f7449d8bf1b..c263044d67e 100644 --- a/mithril-stm/src/schnorr_signatures/helper.rs +++ b/mithril-stm/src/schnorr_signatures/helper.rs @@ -6,7 +6,6 @@ use ff::Field; use sha2::{Digest, Sha256}; use subtle::{Choice, ConstantTimeEq}; -use std::slice; pub fn get_coordinates(point: JubjubSubgroup) -> (JubjubBase, JubjubBase) { let extended: JubjubExtended = point.into(); // Convert to JubjubExtended diff --git a/mithril-stm/src/schnorr_signatures/signature.rs b/mithril-stm/src/schnorr_signatures/signature.rs index 767dd2017fe..29cf28606c9 100644 --- a/mithril-stm/src/schnorr_signatures/signature.rs +++ b/mithril-stm/src/schnorr_signatures/signature.rs @@ -4,7 +4,7 @@ pub use midnight_curves::{Fq as JubjubBase, Fr as JubjubScalar, JubjubSubgroup}; use group::Group; -use crate::schnorr_signatures::helper::{get_coordinates, is_on_curve, jubjub_base_to_scalar}; +use crate::schnorr_signatures::helper::{get_coordinates, jubjub_base_to_scalar}; use crate::schnorr_signatures::verification_key::*; use crate::schnorr_signatures::{DST_SIGNATURE, JubjubHashToCurve, PoseidonHash, SignatureError}; @@ -30,8 +30,8 @@ impl SchnorrSignature { let (hx, hy) = get_coordinates(hash); let (vk_x, vk_y) = get_coordinates(vk.0); let (sigma_x, sigma_y) = get_coordinates(self.sigma); - let cap_r_1_prime = &hash * &self.s + &self.sigma * &c_scalar; - let cap_r_2_prime = &g * &self.s + &vk.0 * &c_scalar; + let cap_r_1_prime = hash * self.s + self.sigma * c_scalar; + let cap_r_2_prime = g * self.s + vk.0 * c_scalar; let (cap_r_1_x_prime, cap_r_1_y_prime) = get_coordinates(cap_r_1_prime); let (cap_r_2_x_prime, cap_r_2_y_prime) = get_coordinates(cap_r_2_prime); let c_prime = PoseidonHash::hash(&[ diff --git a/mithril-stm/src/schnorr_signatures/signing_key.rs b/mithril-stm/src/schnorr_signatures/signing_key.rs index a87b921c733..469e5053bf8 100644 --- a/mithril-stm/src/schnorr_signatures/signing_key.rs +++ b/mithril-stm/src/schnorr_signatures/signing_key.rs @@ -1,22 +1,20 @@ use midnight_circuits::instructions::{HashToCurveCPU, hash::HashCPU}; pub use midnight_curves::{ - Fq as JubjubBase, Fr as JubjubScalar, JubjubExtended as Jubjub, JubjubExtended, JubjubSubgroup, + Fq as JubjubBase, Fr as JubjubScalar, JubjubSubgroup, }; use ff::Field; use group::Group; use rand_core::{CryptoRng, RngCore}; -use subtle::CtOption; -use thiserror::Error; use crate::schnorr_signatures::signature::*; use crate::schnorr_signatures::verification_key::*; use crate::{ error::MultiSignatureError, - schnorr_signatures::helper::{get_coordinates, is_on_curve, jubjub_base_to_scalar}, + schnorr_signatures::helper::{get_coordinates, jubjub_base_to_scalar}, }; -use crate::schnorr_signatures::{DST_SIGNATURE, JubjubHashToCurve, PoseidonHash, SignatureError}; +use crate::schnorr_signatures::{DST_SIGNATURE, JubjubHashToCurve, PoseidonHash}; /// The signing key is a scalar from the Jubjub scalar field #[derive(Debug, Clone)] @@ -34,13 +32,13 @@ impl SchnorrSigningKey { /// and the secret key as it is used for the lottery process pub fn sign(&self, msg: JubjubBase, rng: &mut (impl RngCore + CryptoRng)) -> SchnorrSignature { let g = JubjubSubgroup::generator(); - let vk = &g * &self.0; + let vk = g * self.0; let hash = JubjubHashToCurve::hash_to_curve(&[msg]); - let sigma = &hash * &self.0; + let sigma = hash * self.0; let r = JubjubScalar::random(rng); - let cap_r_1 = &hash * &r; - let cap_r_2 = &g * &r; + let cap_r_1 = hash * r; + let cap_r_2 = g * r; let (hx, hy) = get_coordinates(hash); let (vk_x, vk_y) = get_coordinates(vk); @@ -100,7 +98,7 @@ impl SchnorrSigningKey { impl From<&SchnorrSigningKey> for SchnorrVerificationKey { fn from(sk: &SchnorrSigningKey) -> Self { let g = JubjubSubgroup::generator(); - let vk = &g * &sk.0; + let vk = g * sk.0; SchnorrVerificationKey(vk) } } diff --git a/mithril-stm/src/schnorr_signatures/verification_key.rs b/mithril-stm/src/schnorr_signatures/verification_key.rs index 20f25d2f389..55eea32442f 100644 --- a/mithril-stm/src/schnorr_signatures/verification_key.rs +++ b/mithril-stm/src/schnorr_signatures/verification_key.rs @@ -1,21 +1,11 @@ -use midnight_circuits::{ - ecc::{hash_to_curve::HashToCurveGadget, native::EccChip}, - hash::poseidon::PoseidonChip, - instructions::{HashToCurveCPU, hash::HashCPU}, - types::AssignedNative, -}; pub use midnight_curves::{ - Fq as JubjubBase, Fr as JubjubScalar, JubjubExtended as Jubjub, JubjubExtended, JubjubSubgroup, + Fq as JubjubBase, JubjubExtended, JubjubSubgroup, }; -use ff::Field; -use group::Group; -use rand_core::{CryptoRng, RngCore}; -use thiserror::Error; -use crate::schnorr_signatures::helper::{get_coordinates, is_on_curve, jubjub_base_to_scalar}; -use crate::schnorr_signatures::{DST_SIGNATURE, JubjubHashToCurve, PoseidonHash, SignatureError}; +use crate::schnorr_signatures::helper::{get_coordinates, is_on_curve}; +use crate::schnorr_signatures::SignatureError; #[derive(Debug, Clone, Copy, Default)] pub struct SchnorrVerificationKey(pub JubjubSubgroup); From 7c929c12c78d3044000005ad5a939a9b4077d956 Mon Sep 17 00:00:00 2001 From: Dam Date: Wed, 5 Nov 2025 09:49:25 +0100 Subject: [PATCH 06/21] Cargo.lock change. --- Cargo.lock | 326 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 319 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a589459fe52..08712e24166 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -185,6 +185,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "asn1-rs" version = "0.7.1" @@ -552,6 +558,18 @@ dependencies = [ "serde", ] +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake2" version = "0.9.2" @@ -572,6 +590,27 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "blake2b_simd" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -581,6 +620,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + [[package]] name = "blst" version = "0.3.16" @@ -660,6 +705,12 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + [[package]] name = "bytecount" version = "0.6.9" @@ -1045,6 +1096,35 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" +[[package]] +name = "const_num_bigint" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ddae0ac5d546ee2446704bc529e011d584921305af4f0ae27590accc8cf9251" +dependencies = [ + "const_num_bigint_derive", + "const_std_vec", + "num-bigint", +] + +[[package]] +name = "const_num_bigint_derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1b18965eb9d280ddde32faf948cfe5c6e5bf6885cbf98fadacc28278b8f331f" +dependencies = [ + "num-bigint", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "const_std_vec" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee6a4701b947a8608d1991b1db0cf5bab74164bf2627e5cecc9aa69dd33f172" + [[package]] name = "constant_time_eq" version = "0.3.1" @@ -1395,7 +1475,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.106", ] [[package]] @@ -1471,7 +1551,7 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "crypto-common", "subtle", ] @@ -1675,6 +1755,17 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "bitvec", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -1807,6 +1898,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.31" @@ -2102,6 +2199,19 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand 0.8.5", + "rand_core 0.6.4", + "rand_xorshift 0.3.0", + "subtle", +] + [[package]] name = "h2" version = "0.4.12" @@ -2132,6 +2242,47 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "halo2curves" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d380afeef3f1d4d3245b76895172018cfb087d9976a7cabcd5597775b2933e07" +dependencies = [ + "blake2 0.10.6", + "digest 0.10.7", + "ff", + "group", + "halo2derive", + "lazy_static", + "num-bigint", + "num-integer", + "num-traits", + "pairing", + "pasta_curves", + "paste", + "rand 0.8.5", + "rand_core 0.6.4", + "rayon", + "sha2", + "static_assertions", + "subtle", + "unroll", +] + +[[package]] +name = "halo2derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb99e7492b4f5ff469d238db464131b86c2eaac814a78715acba369f64d2c76" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -2931,6 +3082,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "libbz2-rs-sys" @@ -3143,7 +3297,7 @@ dependencies = [ "smallvec", "thiserror 2.0.17", "tracing", - "uint", + "uint 0.10.0", "web-time", ] @@ -3234,7 +3388,7 @@ dependencies = [ "pin-project", "rand 0.8.5", "salsa20", - "sha3", + "sha3 0.10.8", "tracing", ] @@ -3497,6 +3651,72 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "midnight-circuits" +version = "4.0.0" +source = "git+https://github.com/midnightntwrk/midnight-zk?rev=c88a50c2169f060120a52ad0980de90f08bc9535#c88a50c2169f060120a52ad0980de90f08bc9535" +dependencies = [ + "arrayvec", + "base64 0.13.1", + "bitvec", + "blake2b_simd", + "const_num_bigint", + "ff", + "group", + "halo2curves", + "lazy_static", + "midnight-curves", + "midnight-proofs", + "num-bigint", + "num-integer", + "num-traits", + "pasta_curves", + "rand 0.8.5", + "sha2", + "subtle", + "uint 0.9.5", +] + +[[package]] +name = "midnight-curves" +version = "0.1.0" +source = "git+https://github.com/midnightntwrk/midnight-zk?rev=c88a50c2169f060120a52ad0980de90f08bc9535#c88a50c2169f060120a52ad0980de90f08bc9535" +dependencies = [ + "bitvec", + "blst", + "byte-slice-cast", + "ff", + "getrandom 0.2.16", + "group", + "halo2curves", + "num-bigint", + "pairing", + "rand_core 0.6.4", + "serde", + "subtle", +] + +[[package]] +name = "midnight-proofs" +version = "0.4.0" +source = "git+https://github.com/midnightntwrk/midnight-zk?rev=c88a50c2169f060120a52ad0980de90f08bc9535#c88a50c2169f060120a52ad0980de90f08bc9535" +dependencies = [ + "blake2b_simd", + "ff", + "getrandom 0.2.16", + "group", + "halo2curves", + "midnight-curves", + "num-bigint", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "rayon", + "serde", + "serde_derive", + "sha3 0.9.1", + "tracing", +] + [[package]] name = "mime" version = "0.3.17" @@ -4151,6 +4371,10 @@ dependencies = [ "blst", "criterion", "digest 0.10.7", + "ff", + "group", + "midnight-circuits", + "midnight-curves", "num-bigint", "num-rational", "num-traits", @@ -4162,9 +4386,9 @@ dependencies = [ "rug", "serde", "serde_json", - "thiserror 2.0.17", "sha2", "subtle", + "thiserror 2.0.17", ] [[package]] @@ -4660,6 +4884,15 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group", +] + [[package]] name = "pallas-addresses" version = "0.33.0" @@ -4824,6 +5057,21 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "pasta_curves" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "lazy_static", + "rand 0.8.5", + "static_assertions", + "subtle", +] + [[package]] name = "paste" version = "1.0.15" @@ -5190,7 +5438,7 @@ dependencies = [ "num-traits", "rand 0.9.2", "rand_chacha 0.9.0", - "rand_xorshift", + "rand_xorshift 0.4.0", "regex-syntax", "rusty-fork", "tempfile", @@ -5348,6 +5596,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -5413,6 +5667,15 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rand_xorshift" version = "0.4.0" @@ -6142,6 +6405,18 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "keccak", + "opaque-debug", +] + [[package]] name = "sha3" version = "0.10.8" @@ -6544,6 +6819,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tar" version = "0.4.44" @@ -7103,6 +7384,18 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "uint" version = "0.10.0" @@ -7161,6 +7454,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "unroll" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad948c1cb799b1a70f836077721a92a35ac177d4daddf4c20a633786d4cf618" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "unsigned-varint" version = "0.7.2" @@ -7535,7 +7838,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -7929,6 +8232,15 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x25519-dalek" version = "2.0.1" From f936f56896e428615aa86bb3592b8c3a953df48b Mon Sep 17 00:00:00 2001 From: Dam Date: Wed, 5 Nov 2025 16:31:34 +0100 Subject: [PATCH 07/21] Update msg to base field conversion. --- mithril-stm/src/schnorr_signatures/helper.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/mithril-stm/src/schnorr_signatures/helper.rs b/mithril-stm/src/schnorr_signatures/helper.rs index c263044d67e..076695ccf1f 100644 --- a/mithril-stm/src/schnorr_signatures/helper.rs +++ b/mithril-stm/src/schnorr_signatures/helper.rs @@ -44,9 +44,11 @@ pub fn hash_msg_to_base(msg: &[u8]) -> JubjubBase { hash.update(msg); let hmsg = hash.finalize(); let mut output = [0u8; 32]; - output.copy_from_slice(hmsg.as_slice()); - - output[31] &= 0x0f; - - JubjubBase::from_bytes_le(&output).unwrap() + output.copy_from_slice(&hmsg); + JubjubBase::from_raw([ + u64::from_le_bytes(output[0..8].try_into().unwrap()), + u64::from_le_bytes(output[8..16].try_into().unwrap()), + u64::from_le_bytes(output[16..24].try_into().unwrap()), + u64::from_le_bytes(output[24..32].try_into().unwrap()), + ]) } From bc9aa2411d127508ed3c39460bea5299bfd0035f Mon Sep 17 00:00:00 2001 From: Dam Date: Wed, 5 Nov 2025 17:48:31 +0100 Subject: [PATCH 08/21] Add to and from bytes for SchnorrSignature. --- mithril-stm/src/schnorr_signatures/helper.rs | 16 +++++- mithril-stm/src/schnorr_signatures/mod.rs | 53 +++++++++++-------- .../src/schnorr_signatures/signature.rs | 46 +++++++++++++--- .../src/schnorr_signatures/signing_key.rs | 8 ++- .../schnorr_signatures/verification_key.rs | 1 + 5 files changed, 89 insertions(+), 35 deletions(-) diff --git a/mithril-stm/src/schnorr_signatures/helper.rs b/mithril-stm/src/schnorr_signatures/helper.rs index 076695ccf1f..890258ea280 100644 --- a/mithril-stm/src/schnorr_signatures/helper.rs +++ b/mithril-stm/src/schnorr_signatures/helper.rs @@ -1,5 +1,5 @@ pub use midnight_curves::{ - EDWARDS_D, Fq as JubjubBase, Fr as JubjubScalar, JubjubAffine, JubjubExtended, JubjubSubgroup, + EDWARDS_D, Fq as JubjubBase, Fr as JubjubScalar, JubjubAffine, JubjubExtended, JubjubSubgroup, batch_normalize }; use ff::Field; @@ -15,6 +15,20 @@ pub fn get_coordinates(point: JubjubSubgroup) -> (JubjubBase, JubjubBase) { (x, y) } + +pub fn batch_get_coordinates(points: &[JubjubSubgroup]) -> Vec<(JubjubBase, JubjubBase)> { + + let mut extended = points.iter().map(|&p| p.into()).collect::>(); + + // Convert to JubjubAffine (affine coordinates) + let affine: Vec = batch_normalize(&mut extended).collect(); + let coordinates: Vec<(JubjubBase,JubjubBase)> = affine + .iter() + .map(|a| (a.get_u(), a.get_v())) + .collect(); + coordinates +} + pub fn jubjub_base_to_scalar(x: JubjubBase) -> JubjubScalar { let bytes = x.to_bytes_le(); JubjubScalar::from_raw([ diff --git a/mithril-stm/src/schnorr_signatures/mod.rs b/mithril-stm/src/schnorr_signatures/mod.rs index a92a1d0797c..e0dfce93b45 100644 --- a/mithril-stm/src/schnorr_signatures/mod.rs +++ b/mithril-stm/src/schnorr_signatures/mod.rs @@ -58,6 +58,7 @@ fn u64s_from_bytes(bytes: &[u8; 32]) -> [u64; 4] { ] } +// TODO: change msg and seed to random values #[cfg(test)] mod tests { // use blst::{blst_p1, blst_p2}; @@ -108,12 +109,15 @@ mod tests { let sk = SchnorrSigningKey::generate(&mut ChaCha20Rng::from_entropy()); let vk = SchnorrVerificationKey::from(&sk); - - let msg = hash_msg_to_base(&msg); - - let sig = sk.sign(msg, &mut rng); - - sig.verify(msg, &vk).unwrap(); + let sig = sk.sign(&msg, &mut rng); + println!("{:?}", sig.clone().to_bytes()); + let sig_bytes = [2, 25, 144, 67, 3, 221, 175, 238, 228, 69, 48, 49, 107, 27, 40, 89, 114, 228, 242, + 40, 19, 26, 194, 227, 39, 148, 16, 74, 174, 210, 106, 24, 147, 119, 52, 3, 242, 155, 134, 197, 98, 2, 226, + 186, 137, 3, 35, 122, 133, 85, 244, 147, 3, 9, 152, 12, 119, 43, 16, 119, 26, 72, 158, 5, 25, 7, 168, 188, + 29, 43, 115, 214, 175, 85, 221, 181, 101, 39, 224, 15, 243, 141, 37, 100, 49, 179, 92, 96, 34, 201, 120, 160, + 56, 91, 35, 111]; + let correct_sig = SchnorrSignature::from_bytes(&sig_bytes); + sig.verify(&msg, &vk).unwrap(); } // Testing basic signature using Blake2b256 to hash the message @@ -124,29 +128,20 @@ mod tests { let sk = SchnorrSigningKey::generate(&mut ChaCha20Rng::from_entropy()); let vk = SchnorrVerificationKey::from(&sk); - let mut hash = Blake2b256::new(); - hash.update(msg); - let hmsg = hash.finalize(); - let mut output = [0u8; 32]; - output.copy_from_slice(hmsg.as_slice()); - - let msg = JubjubBase::from_bytes_be(&output).unwrap(); - let sig = sk.sign(msg, &mut rng); - sig.verify(msg, &vk).unwrap(); + let sig = sk.sign(&msg, &mut rng); + sig.verify(&msg, &vk).unwrap(); } /// Test signing functionality. #[test] fn test_signature_verification_valid() { let msg = vec![0, 0, 0, 1]; - let msg = hash_msg_to_base(&msg); - let mut rng = OsRng; let sk = SchnorrSigningKey::generate(&mut rng); // let msg = JubjubBase::random(&mut rng); // Sign the message - let signature = sk.sign(msg, &mut rng); + let signature = sk.sign(&msg, &mut rng); // Ensure the components of the signature are non-default values assert_ne!( @@ -165,22 +160,22 @@ mod tests { "Signature c component should not be zero." ); - signature.verify(msg, &SchnorrVerificationKey::from(&sk)).unwrap(); + signature.verify(&msg, &SchnorrVerificationKey::from(&sk)).unwrap(); } #[test] fn test_signature_verification_invalid_signature() { let mut rng = OsRng; let sk = SchnorrSigningKey::generate(&mut rng); - let msg = JubjubBase::random(&mut rng); + let msg = vec![0, 0, 0, 1]; let vk: SchnorrVerificationKey = (&sk).into(); // Generate signature and tamper with it - let mut signature = sk.sign(msg, &mut rng); + let mut signature = sk.sign(&msg, &mut rng); signature.s = JubjubScalar::random(&mut rng); // Modify `s` component // Verify the modified signature - let result = signature.verify(msg, &vk); + let result = signature.verify(&msg, &vk); assert!( result.is_err(), "Invalid signature should fail verification, but it passed." @@ -207,4 +202,18 @@ mod tests { let sk2 = SchnorrSigningKey::from_bytes(&sk_bytes).unwrap(); assert_eq!(sk, sk2); } + + #[test] + fn serialize_deserialize_signature() { + let seed = 0; + let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(seed); + let msg = vec![0, 0, 0, 1]; + let sk = SchnorrSigningKey::generate(&mut rng); + + let sig = sk.sign(&msg, &mut rng); + + let sig_bytes: [u8; 96] = sig.clone().to_bytes(); + let sig2 = SchnorrSignature::from_bytes(&sig_bytes).unwrap(); + assert_eq!(sig, sig2); + } } diff --git a/mithril-stm/src/schnorr_signatures/signature.rs b/mithril-stm/src/schnorr_signatures/signature.rs index 29cf28606c9..aa94aa0f984 100644 --- a/mithril-stm/src/schnorr_signatures/signature.rs +++ b/mithril-stm/src/schnorr_signatures/signature.rs @@ -2,10 +2,11 @@ use midnight_circuits::instructions::{HashToCurveCPU, hash::HashCPU}; pub use midnight_curves::{Fq as JubjubBase, Fr as JubjubScalar, JubjubSubgroup}; -use group::Group; +use group::{Group, GroupEncoding}; +use crate::error::MultiSignatureError; use crate::schnorr_signatures::helper::{get_coordinates, jubjub_base_to_scalar}; -use crate::schnorr_signatures::verification_key::*; +use crate::schnorr_signatures::{hash_msg_to_base, verification_key::*}; use crate::schnorr_signatures::{DST_SIGNATURE, JubjubHashToCurve, PoseidonHash, SignatureError}; /// Schnorr signature including the value sigma used for the lottery @@ -20,11 +21,12 @@ impl SchnorrSignature { /// Verify a signature against a verification key. pub fn verify( &self, - msg: JubjubBase, + msg: &[u8], vk: &SchnorrVerificationKey, ) -> Result<(), SignatureError> { + let g = JubjubSubgroup::generator(); - let hash = JubjubHashToCurve::hash_to_curve(&[msg]); + let hash = JubjubHashToCurve::hash_to_curve(&[hash_msg_to_base(msg)]); let c_scalar = jubjub_base_to_scalar(self.c); let (hx, hy) = get_coordinates(hash); @@ -34,6 +36,7 @@ impl SchnorrSignature { let cap_r_2_prime = g * self.s + vk.0 * c_scalar; let (cap_r_1_x_prime, cap_r_1_y_prime) = get_coordinates(cap_r_1_prime); let (cap_r_2_x_prime, cap_r_2_y_prime) = get_coordinates(cap_r_2_prime); + let c_prime = PoseidonHash::hash(&[ DST_SIGNATURE, hx, @@ -55,8 +58,37 @@ impl SchnorrSignature { Ok(()) } - pub fn sigma(&self) -> (JubjubBase, JubjubBase) { - let (x, y) = get_coordinates(self.sigma); - (x, y) + // pub fn sigma(&self) -> (JubjubBase, JubjubBase) { + // let (x, y) = get_coordinates(self.sigma); + // (x, y) + // } + + /// Convert an `SchnorrSignature` to its compressed byte representation. + 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.s.to_bytes()); + out[64..96].copy_from_slice(&self.c.to_bytes_le()); + out } + + /// Convert a string of bytes into a `SchnorrSignature`. + /// Not happy with the current CtChoice handling, have to looking into cleaner way to do it + pub fn from_bytes(bytes: &[u8]) -> Result { + let bytes = bytes.get(..96).ok_or(MultiSignatureError::SerializationError)?; + let sigma = JubjubSubgroup::from_bytes(&bytes[0..32].try_into().unwrap()) + .into_option() + .ok_or(MultiSignatureError::SerializationError); + let s = JubjubScalar::from_bytes(&bytes[32..64].try_into().unwrap()) + .into_option() + .ok_or(MultiSignatureError::SerializationError); + let c = JubjubBase::from_bytes_le(&bytes[64..96].try_into().unwrap()) + .into_option() + .ok_or(MultiSignatureError::SerializationError); + match (sigma, s, c) { + (Ok(sigma),Ok(s), Ok(c)) => Ok(Self{sigma,s,c}), + _ => Err(MultiSignatureError::SerializationError) + } + } + } diff --git a/mithril-stm/src/schnorr_signatures/signing_key.rs b/mithril-stm/src/schnorr_signatures/signing_key.rs index 469e5053bf8..3ed11a0845e 100644 --- a/mithril-stm/src/schnorr_signatures/signing_key.rs +++ b/mithril-stm/src/schnorr_signatures/signing_key.rs @@ -7,6 +7,7 @@ use ff::Field; use group::Group; use rand_core::{CryptoRng, RngCore}; +use crate::schnorr_signatures::hash_msg_to_base; use crate::schnorr_signatures::signature::*; use crate::schnorr_signatures::verification_key::*; use crate::{ @@ -30,11 +31,11 @@ impl SchnorrSigningKey { /// A slightly modified version of the regular Schnorr signature (I think) /// We include the computation of sigma, a value depending only on the msg /// and the secret key as it is used for the lottery process - pub fn sign(&self, msg: JubjubBase, rng: &mut (impl RngCore + CryptoRng)) -> SchnorrSignature { + pub fn sign(&self, msg: &[u8], rng: &mut (impl RngCore + CryptoRng)) -> SchnorrSignature { let g = JubjubSubgroup::generator(); let vk = g * self.0; - let hash = JubjubHashToCurve::hash_to_curve(&[msg]); + let hash = JubjubHashToCurve::hash_to_curve(&[hash_msg_to_base(msg)]); let sigma = hash * self.0; let r = JubjubScalar::random(rng); let cap_r_1 = hash * r; @@ -72,9 +73,6 @@ impl SchnorrSigningKey { } /// Convert a string of bytes into a `SchnorrSigningKey`. - /// - /// # Error - /// Fails if the byte string represents a scalar larger than the group order. pub fn from_bytes(bytes: &[u8]) -> Result { // This is a bit ugly, I'll try to find a better way to do it let bytes = bytes diff --git a/mithril-stm/src/schnorr_signatures/verification_key.rs b/mithril-stm/src/schnorr_signatures/verification_key.rs index 55eea32442f..a419c9ae9c5 100644 --- a/mithril-stm/src/schnorr_signatures/verification_key.rs +++ b/mithril-stm/src/schnorr_signatures/verification_key.rs @@ -24,6 +24,7 @@ impl SchnorrVerificationKey { bytes } + /// Do we really need to separate the coordinates? pub fn from_bytes(bytes: &[u8]) -> Result { let bytes = bytes.get(0..64).ok_or(SignatureError::SerializationError)?; let mut u_bytes = [0u8; 32]; From bac59063bc71bb9d9ab1f3dada783dbe9f12bc99 Mon Sep 17 00:00:00 2001 From: Dam Date: Thu, 6 Nov 2025 14:56:28 +0100 Subject: [PATCH 09/21] Removed prototype implementation of schnorr signature and started new one. --- mithril-stm/Cargo.toml | 17 +- mithril-stm/src/lib.rs | 2 +- mithril-stm/src/schnorr_signature/mod.rs | 5 + .../src/schnorr_signature/signature.rs | 7 + .../src/schnorr_signature/signing_key.rs | 3 + .../src/schnorr_signature/verification_key.rs | 3 + mithril-stm/src/schnorr_signatures/helper.rs | 68 ------ mithril-stm/src/schnorr_signatures/mod.rs | 219 ------------------ .../src/schnorr_signatures/signature.rs | 94 -------- .../src/schnorr_signatures/signing_key.rs | 102 -------- .../schnorr_signatures/verification_key.rs | 52 ----- 11 files changed, 27 insertions(+), 545 deletions(-) create mode 100644 mithril-stm/src/schnorr_signature/mod.rs create mode 100644 mithril-stm/src/schnorr_signature/signature.rs create mode 100644 mithril-stm/src/schnorr_signature/signing_key.rs create mode 100644 mithril-stm/src/schnorr_signature/verification_key.rs delete mode 100644 mithril-stm/src/schnorr_signatures/helper.rs delete mode 100644 mithril-stm/src/schnorr_signatures/mod.rs delete mode 100644 mithril-stm/src/schnorr_signatures/signature.rs delete mode 100644 mithril-stm/src/schnorr_signatures/signing_key.rs delete mode 100644 mithril-stm/src/schnorr_signatures/verification_key.rs diff --git a/mithril-stm/Cargo.toml b/mithril-stm/Cargo.toml index 80cc65c2d77..7c93e64e105 100644 --- a/mithril-stm/Cargo.toml +++ b/mithril-stm/Cargo.toml @@ -14,7 +14,7 @@ include = ["**/*.rs", "Cargo.toml", "README.md", ".gitignore"] crate-type = ["lib", "cdylib", "staticlib"] [features] -default = ["rug-backend", "future_snark"] +default = ["rug-backend"] rug-backend = ["rug/default"] num-integer-backend = ["num-bigint", "num-rational", "num-traits"] benchmark-internals = [] # For benchmarking multi_sig @@ -26,18 +26,17 @@ blake2 = "0.10.6" # Enforce blst portable feature for runtime detection of Intel ADX instruction set. blst = { version = "0.3.16", features = ["portable"] } digest = { workspace = true } -rand_core = { workspace = true } -rayon = { workspace = true } -serde = { workspace = true } -thiserror = { workspace = true } -midnight-circuits = { git = "https://github.com/midnightntwrk/midnight-zk", rev = "c88a50c2169f060120a52ad0980de90f08bc9535" } -midnight-curves = { git = "https://github.com/midnightntwrk/midnight-zk", rev = "c88a50c2169f060120a52ad0980de90f08bc9535" } ff = "0.13.1" group = "0.13.0" +midnight-circuits = { git = "https://github.com/midnightntwrk/midnight-zk", rev = "c88a50c2169f060120a52ad0980de90f08bc9535" } +midnight-curves = { git = "https://github.com/midnightntwrk/midnight-zk", rev = "c88a50c2169f060120a52ad0980de90f08bc9535" } num-traits = "0.2.19" -subtle = "2.6.1" +rand_core = { workspace = true } +rayon = { workspace = true } +serde = { workspace = true } sha2 = "0.10.9" - +subtle = "2.6.1" +thiserror = { workspace = true } [target.'cfg(any(target_family = "wasm", target_env = "musl", windows))'.dependencies] # WASM and Windows don't support rug backend, fallback to num-integer only diff --git a/mithril-stm/src/lib.rs b/mithril-stm/src/lib.rs index 21b11cc4e8e..f70a2d8cb26 100644 --- a/mithril-stm/src/lib.rs +++ b/mithril-stm/src/lib.rs @@ -118,7 +118,7 @@ mod merkle_tree; mod parameters; mod participant; #[cfg(feature = "future_snark")] -mod schnorr_signatures; +mod schnorr_signature; mod single_signature; pub use aggregate_signature::{ diff --git a/mithril-stm/src/schnorr_signature/mod.rs b/mithril-stm/src/schnorr_signature/mod.rs new file mode 100644 index 00000000000..28db9f452f3 --- /dev/null +++ b/mithril-stm/src/schnorr_signature/mod.rs @@ -0,0 +1,5 @@ +#![allow(dead_code)] + +mod signature; +mod signing_key; +mod verification_key; diff --git a/mithril-stm/src/schnorr_signature/signature.rs b/mithril-stm/src/schnorr_signature/signature.rs new file mode 100644 index 00000000000..a1a6a4afaef --- /dev/null +++ b/mithril-stm/src/schnorr_signature/signature.rs @@ -0,0 +1,7 @@ +pub use midnight_curves::{Fq as JubjubBase, Fr as JubjubScalar, JubjubSubgroup}; + +pub struct SchnorrSignature { + sigma: JubjubSubgroup, + s: JubjubScalar, + c: JubjubBase, +} diff --git a/mithril-stm/src/schnorr_signature/signing_key.rs b/mithril-stm/src/schnorr_signature/signing_key.rs new file mode 100644 index 00000000000..9efa97ad75a --- /dev/null +++ b/mithril-stm/src/schnorr_signature/signing_key.rs @@ -0,0 +1,3 @@ +pub use midnight_curves::Fr as JubjubScalar; + +pub struct SchnorrSigningKey(JubjubScalar); diff --git a/mithril-stm/src/schnorr_signature/verification_key.rs b/mithril-stm/src/schnorr_signature/verification_key.rs new file mode 100644 index 00000000000..4460ca5768c --- /dev/null +++ b/mithril-stm/src/schnorr_signature/verification_key.rs @@ -0,0 +1,3 @@ +pub use midnight_curves::JubjubSubgroup; + +pub struct SchnorrVerificationKey(JubjubSubgroup); diff --git a/mithril-stm/src/schnorr_signatures/helper.rs b/mithril-stm/src/schnorr_signatures/helper.rs deleted file mode 100644 index 890258ea280..00000000000 --- a/mithril-stm/src/schnorr_signatures/helper.rs +++ /dev/null @@ -1,68 +0,0 @@ -pub use midnight_curves::{ - EDWARDS_D, Fq as JubjubBase, Fr as JubjubScalar, JubjubAffine, JubjubExtended, JubjubSubgroup, batch_normalize -}; - -use ff::Field; -use sha2::{Digest, Sha256}; -use subtle::{Choice, ConstantTimeEq}; - - -pub fn get_coordinates(point: JubjubSubgroup) -> (JubjubBase, JubjubBase) { - let extended: JubjubExtended = point.into(); // Convert to JubjubExtended - let affine: JubjubAffine = extended.into(); // Convert to JubjubAffine (affine coordinates) - let x = affine.get_u(); // Get x-coordinate - let y = affine.get_v(); // Get y-coordinate - (x, y) -} - - -pub fn batch_get_coordinates(points: &[JubjubSubgroup]) -> Vec<(JubjubBase, JubjubBase)> { - - let mut extended = points.iter().map(|&p| p.into()).collect::>(); - - // Convert to JubjubAffine (affine coordinates) - let affine: Vec = batch_normalize(&mut extended).collect(); - let coordinates: Vec<(JubjubBase,JubjubBase)> = affine - .iter() - .map(|a| (a.get_u(), a.get_v())) - .collect(); - coordinates -} - -pub fn jubjub_base_to_scalar(x: JubjubBase) -> JubjubScalar { - let bytes = x.to_bytes_le(); - JubjubScalar::from_raw([ - u64::from_le_bytes(bytes[0..8].try_into().unwrap()), - u64::from_le_bytes(bytes[8..16].try_into().unwrap()), - u64::from_le_bytes(bytes[16..24].try_into().unwrap()), - u64::from_le_bytes(bytes[24..32].try_into().unwrap()), - ]) -} - -pub fn is_on_curve(u: JubjubBase, v: JubjubBase) -> Choice { - let u2 = u.square(); - let v2 = v.square(); - - // Left-hand side: v² - u² - let lhs = v2 - u2; - - // Right-hand side: 1 + EDWARDS_D * (u² * v²) - let rhs = JubjubBase::ONE + EDWARDS_D * u2 * v2; - - // Compare in constant time - lhs.ct_eq(&rhs) -} - -pub fn hash_msg_to_base(msg: &[u8]) -> JubjubBase { - let mut hash = Sha256::new(); - hash.update(msg); - let hmsg = hash.finalize(); - let mut output = [0u8; 32]; - output.copy_from_slice(&hmsg); - JubjubBase::from_raw([ - u64::from_le_bytes(output[0..8].try_into().unwrap()), - u64::from_le_bytes(output[8..16].try_into().unwrap()), - u64::from_le_bytes(output[16..24].try_into().unwrap()), - u64::from_le_bytes(output[24..32].try_into().unwrap()), - ]) -} diff --git a/mithril-stm/src/schnorr_signatures/mod.rs b/mithril-stm/src/schnorr_signatures/mod.rs deleted file mode 100644 index e0dfce93b45..00000000000 --- a/mithril-stm/src/schnorr_signatures/mod.rs +++ /dev/null @@ -1,219 +0,0 @@ -pub use midnight_curves::{ - Bls12, EDWARDS_D, Fq as JubjubBase, Fq as BlsScalar, Fr as JubjubScalar, - G1Affine as BlstG1Affine, G1Projective as BlstG1, G2Affine as BlstG2Affine, JubjubAffine, - JubjubExtended as Jubjub, JubjubExtended, JubjubSubgroup, MODULUS, -}; - -use midnight_circuits::{ - ecc::{hash_to_curve::HashToCurveGadget, native::EccChip}, - hash::poseidon::PoseidonChip, - instructions::{HashToCurveCPU, hash::HashCPU}, - types::AssignedNative, -}; - -use ff::Field; -use group::Group; -use sha2::{Digest, Sha256}; - -use subtle::{Choice, ConstantTimeEq}; -use thiserror::Error; - -pub mod helper; -mod signature; -mod signing_key; -mod verification_key; - -pub use helper::*; -pub use signature::*; -pub use signing_key::*; -pub use verification_key::*; - -type JubjubHashToCurve = HashToCurveGadget< - JubjubBase, - Jubjub, - AssignedNative, - PoseidonChip, - EccChip, ->; - -type PoseidonHash = PoseidonChip; - -pub(crate) const DST_SIGNATURE: JubjubBase = JubjubBase::from_raw([2u64, 0, 0, 0]); - -#[derive(Debug, Error)] -pub enum SignatureError { - #[error("Verification failed: Signature is invalid.")] - VerificationFailed, - /// This error occurs when the serialization of the raw bytes failed - #[error("Invalid bytes")] - SerializationError, -} - -fn u64s_from_bytes(bytes: &[u8; 32]) -> [u64; 4] { - [ - u64::from_le_bytes(bytes[0..8].try_into().unwrap()), - u64::from_le_bytes(bytes[8..16].try_into().unwrap()), - u64::from_le_bytes(bytes[16..24].try_into().unwrap()), - u64::from_le_bytes(bytes[24..32].try_into().unwrap()), - ] -} - -// TODO: change msg and seed to random values -#[cfg(test)] -mod tests { - // use blst::{blst_p1, blst_p2}; - use proptest::prelude::*; - use rand_chacha::ChaCha20Rng; - use rand_core::{OsRng, RngCore, SeedableRng}; - - // use crate::bls_multi_signature::helper::unsafe_helpers::{p1_affine_to_sig, p2_affine_to_vk}; - use crate::error::{MultiSignatureError, RegisterError}; - use crate::key_registration::KeyRegistration; - - use blake2::{ - Blake2b, Blake2b512, Blake2s256, - digest::{Digest, FixedOutput, consts::U32}, - }; - - type Blake2b256 = Blake2b; - - use super::*; - - impl PartialEq for SchnorrSigningKey { - fn eq(&self, other: &Self) -> bool { - self.to_bytes() == other.to_bytes() - } - } - - impl PartialEq for SchnorrVerificationKey { - fn eq(&self, other: &Self) -> bool { - self.to_bytes() == other.to_bytes() - } - } - - impl Eq for SchnorrSigningKey {} - - // Testing conversion from arbitrary message to base field element - #[test] - fn test_hash_msg_to_bas() { - let msg = vec![0, 0, 0, 1]; - let h = hash_msg_to_base(&msg); - println!("{:?}", h); - } - - // Testing basic signature using Sha256 to hash the message - #[test] - fn test_sig() { - let msg = vec![0, 0, 0, 1]; - let mut rng = OsRng; - - let sk = SchnorrSigningKey::generate(&mut ChaCha20Rng::from_entropy()); - let vk = SchnorrVerificationKey::from(&sk); - let sig = sk.sign(&msg, &mut rng); - println!("{:?}", sig.clone().to_bytes()); - let sig_bytes = [2, 25, 144, 67, 3, 221, 175, 238, 228, 69, 48, 49, 107, 27, 40, 89, 114, 228, 242, - 40, 19, 26, 194, 227, 39, 148, 16, 74, 174, 210, 106, 24, 147, 119, 52, 3, 242, 155, 134, 197, 98, 2, 226, - 186, 137, 3, 35, 122, 133, 85, 244, 147, 3, 9, 152, 12, 119, 43, 16, 119, 26, 72, 158, 5, 25, 7, 168, 188, - 29, 43, 115, 214, 175, 85, 221, 181, 101, 39, 224, 15, 243, 141, 37, 100, 49, 179, 92, 96, 34, 201, 120, 160, - 56, 91, 35, 111]; - let correct_sig = SchnorrSignature::from_bytes(&sig_bytes); - sig.verify(&msg, &vk).unwrap(); - } - - // Testing basic signature using Blake2b256 to hash the message - #[test] - fn test_sig_blake() { - let mut rng = OsRng; - let msg = vec![0, 0, 0, 1]; - let sk = SchnorrSigningKey::generate(&mut ChaCha20Rng::from_entropy()); - let vk = SchnorrVerificationKey::from(&sk); - - let sig = sk.sign(&msg, &mut rng); - sig.verify(&msg, &vk).unwrap(); - } - - /// Test signing functionality. - #[test] - fn test_signature_verification_valid() { - let msg = vec![0, 0, 0, 1]; - let mut rng = OsRng; - let sk = SchnorrSigningKey::generate(&mut rng); - // let msg = JubjubBase::random(&mut rng); - - // Sign the message - let signature = sk.sign(&msg, &mut rng); - - // Ensure the components of the signature are non-default values - assert_ne!( - signature.sigma, - JubjubSubgroup::identity(), - "Signature sigma should not be the identity element." - ); - assert_ne!( - signature.s, - JubjubScalar::ZERO, - "Signature s component should not be zero." - ); - assert_ne!( - signature.c, - JubjubBase::ZERO, - "Signature c component should not be zero." - ); - - signature.verify(&msg, &SchnorrVerificationKey::from(&sk)).unwrap(); - } - - #[test] - fn test_signature_verification_invalid_signature() { - let mut rng = OsRng; - let sk = SchnorrSigningKey::generate(&mut rng); - let msg = vec![0, 0, 0, 1]; - let vk: SchnorrVerificationKey = (&sk).into(); - - // Generate signature and tamper with it - let mut signature = sk.sign(&msg, &mut rng); - signature.s = JubjubScalar::random(&mut rng); // Modify `s` component - - // Verify the modified signature - let result = signature.verify(&msg, &vk); - assert!( - result.is_err(), - "Invalid signature should fail verification, but it passed." - ); - } - - #[test] - fn serialize_deserialize_vk() { - let seed = 0; - let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(seed); - let sk = SchnorrSigningKey::generate(&mut rng); - let vk = SchnorrVerificationKey::from(&sk); - let vk_bytes = vk.to_bytes(); - let vk2 = SchnorrVerificationKey::from_bytes(&vk_bytes).unwrap(); - assert_eq!(vk, vk2); - } - - #[test] - fn serialize_deserialize_sk() { - let seed = 0; - let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(seed); - let sk = SchnorrSigningKey::generate(&mut rng); - let sk_bytes: [u8; 32] = sk.to_bytes(); - let sk2 = SchnorrSigningKey::from_bytes(&sk_bytes).unwrap(); - assert_eq!(sk, sk2); - } - - #[test] - fn serialize_deserialize_signature() { - let seed = 0; - let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(seed); - let msg = vec![0, 0, 0, 1]; - let sk = SchnorrSigningKey::generate(&mut rng); - - let sig = sk.sign(&msg, &mut rng); - - let sig_bytes: [u8; 96] = sig.clone().to_bytes(); - let sig2 = SchnorrSignature::from_bytes(&sig_bytes).unwrap(); - assert_eq!(sig, sig2); - } -} diff --git a/mithril-stm/src/schnorr_signatures/signature.rs b/mithril-stm/src/schnorr_signatures/signature.rs deleted file mode 100644 index aa94aa0f984..00000000000 --- a/mithril-stm/src/schnorr_signatures/signature.rs +++ /dev/null @@ -1,94 +0,0 @@ -use midnight_circuits::instructions::{HashToCurveCPU, hash::HashCPU}; - -pub use midnight_curves::{Fq as JubjubBase, Fr as JubjubScalar, JubjubSubgroup}; - -use group::{Group, GroupEncoding}; - -use crate::error::MultiSignatureError; -use crate::schnorr_signatures::helper::{get_coordinates, jubjub_base_to_scalar}; -use crate::schnorr_signatures::{hash_msg_to_base, verification_key::*}; -use crate::schnorr_signatures::{DST_SIGNATURE, JubjubHashToCurve, PoseidonHash, SignatureError}; - -/// Schnorr signature including the value sigma used for the lottery -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct SchnorrSignature { - pub sigma: JubjubSubgroup, - pub s: JubjubScalar, - pub c: JubjubBase, -} - -impl SchnorrSignature { - /// Verify a signature against a verification key. - pub fn verify( - &self, - msg: &[u8], - vk: &SchnorrVerificationKey, - ) -> Result<(), SignatureError> { - - let g = JubjubSubgroup::generator(); - let hash = JubjubHashToCurve::hash_to_curve(&[hash_msg_to_base(msg)]); - let c_scalar = jubjub_base_to_scalar(self.c); - - let (hx, hy) = get_coordinates(hash); - let (vk_x, vk_y) = get_coordinates(vk.0); - let (sigma_x, sigma_y) = get_coordinates(self.sigma); - let cap_r_1_prime = hash * self.s + self.sigma * c_scalar; - let cap_r_2_prime = g * self.s + vk.0 * c_scalar; - let (cap_r_1_x_prime, cap_r_1_y_prime) = get_coordinates(cap_r_1_prime); - let (cap_r_2_x_prime, cap_r_2_y_prime) = get_coordinates(cap_r_2_prime); - - let c_prime = PoseidonHash::hash(&[ - DST_SIGNATURE, - hx, - hy, - vk_x, - vk_y, - sigma_x, - sigma_y, - cap_r_1_x_prime, - cap_r_1_y_prime, - cap_r_2_x_prime, - cap_r_2_y_prime, - ]); - - if c_prime != self.c { - return Err(SignatureError::VerificationFailed); - } - - Ok(()) - } - - // pub fn sigma(&self) -> (JubjubBase, JubjubBase) { - // let (x, y) = get_coordinates(self.sigma); - // (x, y) - // } - - /// Convert an `SchnorrSignature` to its compressed byte representation. - 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.s.to_bytes()); - out[64..96].copy_from_slice(&self.c.to_bytes_le()); - out - } - - /// Convert a string of bytes into a `SchnorrSignature`. - /// Not happy with the current CtChoice handling, have to looking into cleaner way to do it - pub fn from_bytes(bytes: &[u8]) -> Result { - let bytes = bytes.get(..96).ok_or(MultiSignatureError::SerializationError)?; - let sigma = JubjubSubgroup::from_bytes(&bytes[0..32].try_into().unwrap()) - .into_option() - .ok_or(MultiSignatureError::SerializationError); - let s = JubjubScalar::from_bytes(&bytes[32..64].try_into().unwrap()) - .into_option() - .ok_or(MultiSignatureError::SerializationError); - let c = JubjubBase::from_bytes_le(&bytes[64..96].try_into().unwrap()) - .into_option() - .ok_or(MultiSignatureError::SerializationError); - match (sigma, s, c) { - (Ok(sigma),Ok(s), Ok(c)) => Ok(Self{sigma,s,c}), - _ => Err(MultiSignatureError::SerializationError) - } - } - -} diff --git a/mithril-stm/src/schnorr_signatures/signing_key.rs b/mithril-stm/src/schnorr_signatures/signing_key.rs deleted file mode 100644 index 3ed11a0845e..00000000000 --- a/mithril-stm/src/schnorr_signatures/signing_key.rs +++ /dev/null @@ -1,102 +0,0 @@ -use midnight_circuits::instructions::{HashToCurveCPU, hash::HashCPU}; -pub use midnight_curves::{ - Fq as JubjubBase, Fr as JubjubScalar, JubjubSubgroup, -}; - -use ff::Field; -use group::Group; -use rand_core::{CryptoRng, RngCore}; - -use crate::schnorr_signatures::hash_msg_to_base; -use crate::schnorr_signatures::signature::*; -use crate::schnorr_signatures::verification_key::*; -use crate::{ - error::MultiSignatureError, - schnorr_signatures::helper::{get_coordinates, jubjub_base_to_scalar}, -}; - -use crate::schnorr_signatures::{DST_SIGNATURE, JubjubHashToCurve, PoseidonHash}; - -/// The signing key is a scalar from the Jubjub scalar field -#[derive(Debug, Clone)] -pub struct SchnorrSigningKey(JubjubScalar); - -/// Implementation of the Schnorr signature scheme using the Jubjub curve -impl SchnorrSigningKey { - pub fn generate(rng: &mut (impl RngCore + CryptoRng)) -> Self { - let sk = JubjubScalar::random(rng); - SchnorrSigningKey(sk) - } - - /// A slightly modified version of the regular Schnorr signature (I think) - /// We include the computation of sigma, a value depending only on the msg - /// and the secret key as it is used for the lottery process - pub fn sign(&self, msg: &[u8], rng: &mut (impl RngCore + CryptoRng)) -> SchnorrSignature { - let g = JubjubSubgroup::generator(); - let vk = g * self.0; - - let hash = JubjubHashToCurve::hash_to_curve(&[hash_msg_to_base(msg)]); - let sigma = hash * self.0; - let r = JubjubScalar::random(rng); - let cap_r_1 = hash * r; - let cap_r_2 = g * r; - - let (hx, hy) = get_coordinates(hash); - let (vk_x, vk_y) = get_coordinates(vk); - let (sigma_x, sigma_y) = get_coordinates(sigma); - let (cap_r_1_x, cap_r_1_y) = get_coordinates(cap_r_1); - let (cap_r_2_x, cap_r_2_y) = get_coordinates(cap_r_2); - - let c = PoseidonHash::hash(&[ - DST_SIGNATURE, - hx, - hy, - vk_x, - vk_y, - sigma_x, - sigma_y, - cap_r_1_x, - cap_r_1_y, - cap_r_2_x, - cap_r_2_y, - ]); - let c_scalar = jubjub_base_to_scalar(c); - let s = r - self.0 * c_scalar; - - SchnorrSignature { sigma, s, c } - } - - /// Convert the schnorr secret key into byte string. - /// Uses midnight curve implem for the conversion - pub fn to_bytes(&self) -> [u8; 32] { - self.0.to_bytes() - } - - /// Convert a string of bytes into a `SchnorrSigningKey`. - pub fn from_bytes(bytes: &[u8]) -> Result { - // This is a bit ugly, I'll try to find a better way to do it - let bytes = bytes - .get(..32) - .ok_or(MultiSignatureError::SerializationError)? - .try_into() - .unwrap(); - // Jubjub returs a CtChoice so I convert it to an option that looses the const time property - match JubjubScalar::from_bytes(bytes) - .into_option() - .ok_or(MultiSignatureError::SerializationError) - { - Ok(sk) => Ok(Self(sk)), - // the error should be updated - Err(e) => Err(e), - } - } -} - -// Should we have this implementation? -impl From<&SchnorrSigningKey> for SchnorrVerificationKey { - fn from(sk: &SchnorrSigningKey) -> Self { - let g = JubjubSubgroup::generator(); - let vk = g * sk.0; - SchnorrVerificationKey(vk) - } -} diff --git a/mithril-stm/src/schnorr_signatures/verification_key.rs b/mithril-stm/src/schnorr_signatures/verification_key.rs deleted file mode 100644 index a419c9ae9c5..00000000000 --- a/mithril-stm/src/schnorr_signatures/verification_key.rs +++ /dev/null @@ -1,52 +0,0 @@ - -pub use midnight_curves::{ - Fq as JubjubBase, JubjubExtended, JubjubSubgroup, -}; - - -use crate::schnorr_signatures::helper::{get_coordinates, is_on_curve}; -use crate::schnorr_signatures::SignatureError; - -#[derive(Debug, Clone, Copy, Default)] -pub struct SchnorrVerificationKey(pub JubjubSubgroup); - -impl SchnorrVerificationKey { - pub fn to_field(&self) -> [JubjubBase; 2] { - let (x, y) = get_coordinates(self.0); - [x, y] - } - - pub fn to_bytes(&self) -> [u8; 64] { - let (x, y) = get_coordinates(self.0); - let mut bytes = [0u8; 64]; - bytes[0..32].copy_from_slice(&x.to_bytes_le()); - bytes[32..64].copy_from_slice(&y.to_bytes_le()); - bytes - } - - /// Do we really need to separate the coordinates? - pub fn from_bytes(bytes: &[u8]) -> Result { - let bytes = bytes.get(0..64).ok_or(SignatureError::SerializationError)?; - let mut u_bytes = [0u8; 32]; - u_bytes.copy_from_slice(&bytes[0..32]); - let mut v_bytes = [0u8; 32]; - v_bytes.copy_from_slice(&bytes[32..64]); - - let u = JubjubBase::from_bytes_le(&u_bytes) - .into_option() - .ok_or(SignatureError::SerializationError)?; - let v = JubjubBase::from_bytes_le(&v_bytes) - .into_option() - .ok_or(SignatureError::SerializationError)?; - if !bool::from(is_on_curve(u, v)) { - return Err(SignatureError::SerializationError); - } - - let point = JubjubSubgroup::from_raw_unchecked(u, v); - if !bool::from(JubjubExtended::from(point).is_prime_order()) { - return Err(SignatureError::SerializationError); - } - - Ok(SchnorrVerificationKey(point)) - } -} From 5ef9fc0b47c3238fcc9ef09e8f8ccde19d34afa4 Mon Sep 17 00:00:00 2001 From: Dam Date: Thu, 6 Nov 2025 15:16:01 +0100 Subject: [PATCH 10/21] Added generate function for Schnorr signature. --- .../src/schnorr_signature/signing_key.rs | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/mithril-stm/src/schnorr_signature/signing_key.rs b/mithril-stm/src/schnorr_signature/signing_key.rs index 9efa97ad75a..eaf7a96f82b 100644 --- a/mithril-stm/src/schnorr_signature/signing_key.rs +++ b/mithril-stm/src/schnorr_signature/signing_key.rs @@ -1,3 +1,24 @@ -pub use midnight_curves::Fr as JubjubScalar; +use ff::Field; +use midnight_curves::Fr as JubjubScalar; +use rand_core::{CryptoRng, RngCore}; -pub struct SchnorrSigningKey(JubjubScalar); +struct SchnorrSigningKey(JubjubScalar); + +impl SchnorrSigningKey { + fn generate(rng: &mut (impl RngCore + CryptoRng)) -> Self { + SchnorrSigningKey(JubjubScalar::random(rng)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + + #[test] + fn test_generate_signing_key() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let _sk = SchnorrSigningKey::generate(&mut rng); + } +} From 8672cf28e113ff64a357a605d0372fe17029286f Mon Sep 17 00:00:00 2001 From: Dam Date: Thu, 6 Nov 2025 15:29:38 +0100 Subject: [PATCH 11/21] Added conversion from sk to vk for use in signature. --- .../src/schnorr_signature/signing_key.rs | 23 +++++++++++++--- .../src/schnorr_signature/verification_key.rs | 27 +++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/mithril-stm/src/schnorr_signature/signing_key.rs b/mithril-stm/src/schnorr_signature/signing_key.rs index eaf7a96f82b..f56bc202a75 100644 --- a/mithril-stm/src/schnorr_signature/signing_key.rs +++ b/mithril-stm/src/schnorr_signature/signing_key.rs @@ -1,13 +1,30 @@ use ff::Field; -use midnight_curves::Fr as JubjubScalar; +use midnight_curves::{Fr as JubjubScalar, JubjubSubgroup}; use rand_core::{CryptoRng, RngCore}; -struct SchnorrSigningKey(JubjubScalar); +use group::Group; + +use crate::schnorr_signature::signature::SchnorrSignature; + +pub(crate) struct SchnorrSigningKey(pub(crate) JubjubScalar); impl SchnorrSigningKey { - fn generate(rng: &mut (impl RngCore + CryptoRng)) -> Self { + pub(crate) fn generate(rng: &mut (impl RngCore + CryptoRng)) -> Self { SchnorrSigningKey(JubjubScalar::random(rng)) } + + fn sign(&self, msg: &[u8], rng: &mut (impl RngCore + CryptoRng)) -> SchnorrSignature { + // Use the subgroup generator to compute the curve points + let g = JubjubSubgroup::generator(); + let vk = todo!(); + + // SchnorrSignature { + // sigma: 0, + // s: 0, + // c: 0, + // } + todo!() + } } #[cfg(test)] diff --git a/mithril-stm/src/schnorr_signature/verification_key.rs b/mithril-stm/src/schnorr_signature/verification_key.rs index 4460ca5768c..c7df5bd91b4 100644 --- a/mithril-stm/src/schnorr_signature/verification_key.rs +++ b/mithril-stm/src/schnorr_signature/verification_key.rs @@ -1,3 +1,30 @@ +use group::Group; pub use midnight_curves::JubjubSubgroup; +use crate::schnorr_signature::signing_key::SchnorrSigningKey; + pub struct SchnorrVerificationKey(JubjubSubgroup); + +impl From<&SchnorrSigningKey> for SchnorrVerificationKey { + /// Convert a Shnorr 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(sk: &SchnorrSigningKey) -> Self { + let g = JubjubSubgroup::generator(); + SchnorrVerificationKey(g * sk.0) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + + #[test] + fn test_generate_signing_key() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let sk = SchnorrSigningKey::generate(&mut rng); + let _vk = SchnorrVerificationKey::from(&sk); + } +} From 660377ce71dfc6222da236928d24d523309b817f Mon Sep 17 00:00:00 2001 From: Dam Date: Thu, 6 Nov 2025 15:45:42 +0100 Subject: [PATCH 12/21] Added helper function for converting a message to jubjub base field. --- mithril-stm/src/schnorr_signature/mod.rs | 38 +++++++++++++++++++ .../src/schnorr_signature/signing_key.rs | 6 ++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/mithril-stm/src/schnorr_signature/mod.rs b/mithril-stm/src/schnorr_signature/mod.rs index 28db9f452f3..ed0ad6ff89b 100644 --- a/mithril-stm/src/schnorr_signature/mod.rs +++ b/mithril-stm/src/schnorr_signature/mod.rs @@ -1,5 +1,43 @@ #![allow(dead_code)] +use midnight_curves::Fq as JubjubBase; +use sha2::{Digest, Sha256}; + mod signature; mod signing_key; mod verification_key; + +/// Convert an arbitrary array of bytes into a Jubjub scalar field element +/// First hash the message to 256 bits use Sha256 then perform the conversion +/// TODO: Handle the unwrap properly +pub fn hash_msg_to_jubjubbase(msg: &[u8]) -> JubjubBase { + let mut hash = Sha256::new(); + hash.update(msg); + let hmsg = hash.finalize(); + let mut output = [0u8; 32]; + output.copy_from_slice(&hmsg); + JubjubBase::from_raw([ + u64::from_le_bytes(output[0..8].try_into().unwrap()), + u64::from_le_bytes(output[8..16].try_into().unwrap()), + u64::from_le_bytes(output[16..24].try_into().unwrap()), + u64::from_le_bytes(output[24..32].try_into().unwrap()), + ]) +} + +#[cfg(test)] +mod tests { + + use super::*; + // Testing conversion from arbitrary message to base field element + #[test] + fn test_hash_msg_to_jubjubbase() { + let msg = vec![0, 0, 0, 1]; + let h = hash_msg_to_jubjubbase(&msg); + let bytes_le = [ + 179, 7, 17, 168, 141, 112, 57, 117, 112, 92, 169, 56, 36, 70, 1, 217, 9, 13, 255, 42, + 100, 207, 166, 110, 188, 47, 35, 211, 35, 168, 100, 25, + ]; + let field_elem = JubjubBase::from_bytes_le(&bytes_le).unwrap(); + assert_eq!(h, field_elem) + } +} diff --git a/mithril-stm/src/schnorr_signature/signing_key.rs b/mithril-stm/src/schnorr_signature/signing_key.rs index f56bc202a75..00db19903c2 100644 --- a/mithril-stm/src/schnorr_signature/signing_key.rs +++ b/mithril-stm/src/schnorr_signature/signing_key.rs @@ -4,7 +4,9 @@ use rand_core::{CryptoRng, RngCore}; use group::Group; -use crate::schnorr_signature::signature::SchnorrSignature; +use crate::schnorr_signature::{ + signature::SchnorrSignature, verification_key::SchnorrVerificationKey, +}; pub(crate) struct SchnorrSigningKey(pub(crate) JubjubScalar); @@ -16,7 +18,7 @@ impl SchnorrSigningKey { fn sign(&self, msg: &[u8], rng: &mut (impl RngCore + CryptoRng)) -> SchnorrSignature { // Use the subgroup generator to compute the curve points let g = JubjubSubgroup::generator(); - let vk = todo!(); + let vk = SchnorrVerificationKey::from(self); // SchnorrSignature { // sigma: 0, From da8c6f11af44256b86d0a8d9f86f0a0ebe1ea5e4 Mon Sep 17 00:00:00 2001 From: Dam Date: Thu, 6 Nov 2025 16:53:50 +0100 Subject: [PATCH 13/21] Added sign function for SchnorrSigningKey and necessary helper functions. --- mithril-stm/src/schnorr_signature/mod.rs | 46 +++++++++++++- .../src/schnorr_signature/signature.rs | 14 +++-- .../src/schnorr_signature/signing_key.rs | 61 ++++++++++++++++--- .../src/schnorr_signature/verification_key.rs | 2 +- 4 files changed, 106 insertions(+), 17 deletions(-) diff --git a/mithril-stm/src/schnorr_signature/mod.rs b/mithril-stm/src/schnorr_signature/mod.rs index ed0ad6ff89b..0bc9d6f24f8 100644 --- a/mithril-stm/src/schnorr_signature/mod.rs +++ b/mithril-stm/src/schnorr_signature/mod.rs @@ -1,16 +1,30 @@ #![allow(dead_code)] -use midnight_curves::Fq as JubjubBase; +use midnight_circuits::{ + ecc::{hash_to_curve::HashToCurveGadget, native::EccChip}, + hash::poseidon::PoseidonChip, + types::AssignedNative, +}; +use midnight_curves::{Fq as JubjubBase, JubjubAffine, JubjubExtended, JubjubSubgroup}; use sha2::{Digest, Sha256}; mod signature; mod signing_key; mod verification_key; +/// Defining a type for the CPU hash to curve gadget +type JubjubHashToCurve = HashToCurveGadget< + JubjubBase, + JubjubExtended, + AssignedNative, + PoseidonChip, + EccChip, +>; + /// Convert an arbitrary array of bytes into a Jubjub scalar field element /// First hash the message to 256 bits use Sha256 then perform the conversion /// TODO: Handle the unwrap properly -pub fn hash_msg_to_jubjubbase(msg: &[u8]) -> JubjubBase { +pub(crate) fn hash_msg_to_jubjubbase(msg: &[u8]) -> JubjubBase { let mut hash = Sha256::new(); hash.update(msg); let hmsg = hash.finalize(); @@ -24,11 +38,26 @@ pub fn hash_msg_to_jubjubbase(msg: &[u8]) -> JubjubBase { ]) } +pub(crate) fn get_coordinates(point: JubjubSubgroup) -> (JubjubBase, JubjubBase) { + let extended = JubjubExtended::from(point); // Convert to JubjubExtended + let affine = JubjubAffine::from(extended); // Convert to JubjubAffine (affine coordinates) + let x = affine.get_u(); // Get x-coordinate + let y = affine.get_v(); // Get y-coordinate + (x, y) +} + #[cfg(test)] mod tests { use super::*; - // Testing conversion from arbitrary message to base field element + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + + use crate::schnorr_signature::{ + signing_key::SchnorrSigningKey, verification_key::SchnorrVerificationKey, + }; + + // Testing conversion from arbitrary message to scalar field element #[test] fn test_hash_msg_to_jubjubbase() { let msg = vec![0, 0, 0, 1]; @@ -40,4 +69,15 @@ mod tests { let field_elem = JubjubBase::from_bytes_le(&bytes_le).unwrap(); assert_eq!(h, field_elem) } + + // TODO: Complete the test once the verification function is implemented + #[test] + fn test_sig() { + let msg = vec![0, 0, 0, 1]; + let seed = [0u8; 32]; + let mut rng = ChaCha20Rng::from_seed(seed); + let sk = SchnorrSigningKey::generate(&mut rng); + let _vk = SchnorrVerificationKey::from(&sk); + let _sig = sk.sign(&msg, &mut rng); + } } diff --git a/mithril-stm/src/schnorr_signature/signature.rs b/mithril-stm/src/schnorr_signature/signature.rs index a1a6a4afaef..59c0f067cc1 100644 --- a/mithril-stm/src/schnorr_signature/signature.rs +++ b/mithril-stm/src/schnorr_signature/signature.rs @@ -1,7 +1,11 @@ -pub use midnight_curves::{Fq as JubjubBase, Fr as JubjubScalar, JubjubSubgroup}; +use midnight_curves::{Fq as JubjubBase, Fr as JubjubScalar, JubjubSubgroup}; -pub struct SchnorrSignature { - sigma: JubjubSubgroup, - s: JubjubScalar, - c: JubjubBase, +/// Structure of the Schnorr signature to use with the SNARK +/// This signature includes a value `sigma` which depends only on +/// the message and the signing key. +/// This value is used in the lottery process to determine the correct indices. +pub(crate) struct SchnorrSignature { + pub(crate) sigma: JubjubSubgroup, + pub(crate) s: JubjubScalar, + pub(crate) c: JubjubBase, } diff --git a/mithril-stm/src/schnorr_signature/signing_key.rs b/mithril-stm/src/schnorr_signature/signing_key.rs index 00db19903c2..b42b8a4eeae 100644 --- a/mithril-stm/src/schnorr_signature/signing_key.rs +++ b/mithril-stm/src/schnorr_signature/signing_key.rs @@ -1,9 +1,14 @@ use ff::Field; -use midnight_curves::{Fr as JubjubScalar, JubjubSubgroup}; +use midnight_circuits::hash::poseidon::PoseidonChip; +use midnight_circuits::instructions::hash::HashCPU; +use midnight_curves::{Fq as JubjubBase, Fr as JubjubScalar, JubjubSubgroup}; use rand_core::{CryptoRng, RngCore}; +use midnight_circuits::instructions::HashToCurveCPU; + use group::Group; +use crate::schnorr_signature::{JubjubHashToCurve, get_coordinates, hash_msg_to_jubjubbase}; use crate::schnorr_signature::{ signature::SchnorrSignature, verification_key::SchnorrVerificationKey, }; @@ -15,17 +20,51 @@ impl SchnorrSigningKey { SchnorrSigningKey(JubjubScalar::random(rng)) } - fn sign(&self, msg: &[u8], rng: &mut (impl RngCore + CryptoRng)) -> SchnorrSignature { + pub(crate) fn sign( + &self, + msg: &[u8], + rng: &mut (impl RngCore + CryptoRng), + ) -> SchnorrSignature { // Use the subgroup generator to compute the curve points let g = JubjubSubgroup::generator(); let vk = SchnorrVerificationKey::from(self); - // SchnorrSignature { - // sigma: 0, - // s: 0, - // c: 0, - // } - todo!() + // First hashing the message to a scalar then hashing it to a curve point + let hash = JubjubHashToCurve::hash_to_curve(&[hash_msg_to_jubjubbase(msg)]); + + // sigma = H(msg) * sk + let sigma = hash * self.0; + + // Compute the random part of the signature with + // r1 = H(msg) * r + // r2 = g * r + let r = JubjubScalar::random(rng); + let r1 = hash * r; + let r2 = g * r; + + // Since the hash function takes as input scalar elements + // We need to convert the EC points to their coordinates + // I use gx and gy for now but maybe we can replace them by a DST? + let (gx, gy) = get_coordinates(g); + let (hashx, hashy) = get_coordinates(hash); + let (vkx, vky) = get_coordinates(vk.0); + let (sigmax, sigmay) = get_coordinates(sigma); + let (r1x, r1y) = get_coordinates(r1); + let (r2x, r2y) = get_coordinates(r2); + + let c = PoseidonChip::::hash(&[ + gx, gy, hashx, hashy, vkx, vky, sigmax, sigmay, r1x, r1y, r2x, r2y, + ]); + + // TODO: remove the unwrap and handle the error + let c_scalar = JubjubScalar::from_bytes(&c.to_bytes_le()).into_option().unwrap(); + let s = r - c_scalar * self.0; + + SchnorrSignature { + sigma, + s, + c, + } } } @@ -40,4 +79,10 @@ mod tests { let mut rng = ChaCha20Rng::from_seed([0u8; 32]); let _sk = SchnorrSigningKey::generate(&mut rng); } + + #[test] + fn test_generate_signature() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let _sk = SchnorrSigningKey::generate(&mut rng); + } } diff --git a/mithril-stm/src/schnorr_signature/verification_key.rs b/mithril-stm/src/schnorr_signature/verification_key.rs index c7df5bd91b4..dd1fee652ed 100644 --- a/mithril-stm/src/schnorr_signature/verification_key.rs +++ b/mithril-stm/src/schnorr_signature/verification_key.rs @@ -3,7 +3,7 @@ pub use midnight_curves::JubjubSubgroup; use crate::schnorr_signature::signing_key::SchnorrSigningKey; -pub struct SchnorrVerificationKey(JubjubSubgroup); +pub struct SchnorrVerificationKey(pub(crate) JubjubSubgroup); impl From<&SchnorrSigningKey> for SchnorrVerificationKey { /// Convert a Shnorr secret key into a verification key From f1bfa8b364551d2dafc859f1828c457acad7eea4 Mon Sep 17 00:00:00 2001 From: Dam Date: Thu, 6 Nov 2025 17:41:32 +0100 Subject: [PATCH 14/21] Added test function for get_coordinates and fix the sign function. --- mithril-stm/src/schnorr_signature/mod.rs | 14 ++++++++++++++ .../src/schnorr_signature/signing_key.rs | 19 ++++++++++++------- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/mithril-stm/src/schnorr_signature/mod.rs b/mithril-stm/src/schnorr_signature/mod.rs index 0bc9d6f24f8..b5a4efe7f41 100644 --- a/mithril-stm/src/schnorr_signature/mod.rs +++ b/mithril-stm/src/schnorr_signature/mod.rs @@ -50,6 +50,7 @@ pub(crate) fn get_coordinates(point: JubjubSubgroup) -> (JubjubBase, JubjubBase) mod tests { use super::*; + use group::Group; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; @@ -62,6 +63,7 @@ mod tests { fn test_hash_msg_to_jubjubbase() { let msg = vec![0, 0, 0, 1]; let h = hash_msg_to_jubjubbase(&msg); + // Correct value corresponding to the message [0,0,0,1] let bytes_le = [ 179, 7, 17, 168, 141, 112, 57, 117, 112, 92, 169, 56, 36, 70, 1, 217, 9, 13, 255, 42, 100, 207, 166, 110, 188, 47, 35, 211, 35, 168, 100, 25, @@ -70,6 +72,18 @@ mod tests { assert_eq!(h, field_elem) } + // Testing conversion from EC point to scalar coordinates + // For now only printing, next step is to try to generate a point + // from x and y values to check if they match with the result of the function + #[test] + fn test_get_coordinates() { + let seed = [0u8; 32]; + let mut rng = ChaCha20Rng::from_seed(seed); + let point = JubjubSubgroup::random(&mut rng); + let (x, y) = get_coordinates(point); + println!("{:?}", (x, y)); + } + // TODO: Complete the test once the verification function is implemented #[test] fn test_sig() { diff --git a/mithril-stm/src/schnorr_signature/signing_key.rs b/mithril-stm/src/schnorr_signature/signing_key.rs index b42b8a4eeae..52914871706 100644 --- a/mithril-stm/src/schnorr_signature/signing_key.rs +++ b/mithril-stm/src/schnorr_signature/signing_key.rs @@ -56,15 +56,20 @@ impl SchnorrSigningKey { gx, gy, hashx, hashy, vkx, vky, sigmax, sigmay, r1x, r1y, r2x, r2y, ]); - // TODO: remove the unwrap and handle the error - let c_scalar = JubjubScalar::from_bytes(&c.to_bytes_le()).into_option().unwrap(); + // We want to use the from_raw function because the result of + // the poseidon hash might not fit into the smaller modulus + // the Fr scalar field + // TODO: Refactor this + let bytes = c.to_bytes_le(); + let c_scalar = JubjubScalar::from_raw([ + u64::from_le_bytes(bytes[0..8].try_into().unwrap()), + u64::from_le_bytes(bytes[8..16].try_into().unwrap()), + u64::from_le_bytes(bytes[16..24].try_into().unwrap()), + u64::from_le_bytes(bytes[24..32].try_into().unwrap()), + ]); let s = r - c_scalar * self.0; - SchnorrSignature { - sigma, - s, - c, - } + SchnorrSignature { sigma, s, c } } } From 9195e0e950e38e79214727fe251146790e9adaf1 Mon Sep 17 00:00:00 2001 From: Dam Date: Thu, 6 Nov 2025 18:12:23 +0100 Subject: [PATCH 15/21] Added verification function and tests to signature. --- mithril-stm/src/schnorr_signature/mod.rs | 39 ++++++++++-- .../src/schnorr_signature/signature.rs | 61 +++++++++++++++++++ 2 files changed, 95 insertions(+), 5 deletions(-) diff --git a/mithril-stm/src/schnorr_signature/mod.rs b/mithril-stm/src/schnorr_signature/mod.rs index b5a4efe7f41..b00db964df2 100644 --- a/mithril-stm/src/schnorr_signature/mod.rs +++ b/mithril-stm/src/schnorr_signature/mod.rs @@ -54,8 +54,11 @@ mod tests { use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; - use crate::schnorr_signature::{ - signing_key::SchnorrSigningKey, verification_key::SchnorrVerificationKey, + use crate::{ + error::MultiSignatureError, + schnorr_signature::{ + signing_key::SchnorrSigningKey, verification_key::SchnorrVerificationKey, + }, }; // Testing conversion from arbitrary message to scalar field element @@ -84,14 +87,40 @@ mod tests { println!("{:?}", (x, y)); } - // TODO: Complete the test once the verification function is implemented #[test] fn test_sig() { let msg = vec![0, 0, 0, 1]; let seed = [0u8; 32]; let mut rng = ChaCha20Rng::from_seed(seed); let sk = SchnorrSigningKey::generate(&mut rng); - let _vk = SchnorrVerificationKey::from(&sk); - let _sig = sk.sign(&msg, &mut rng); + let vk = SchnorrVerificationKey::from(&sk); + let sig = sk.sign(&msg, &mut rng); + sig.verify(&msg, &vk).unwrap(); + } + + // TODO: Change the errors + #[test] + fn test_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); + let vk = SchnorrVerificationKey::from(&sk); + + let sk2 = SchnorrSigningKey::generate(&mut rng); + let vk2 = SchnorrVerificationKey::from(&sk2); + + let sig = sk.sign(&msg, &mut rng); + let sig2 = sk.sign(&msg2, &mut rng); + + // Wrong verification key is used + let result = sig.verify(&msg, &vk2); + assert_eq!(result, Err(MultiSignatureError::BatchInvalid)); + + // Wrong message is verified + let result = sig2.verify(&msg, &vk); + assert_eq!(result, Err(MultiSignatureError::BatchInvalid)); } } diff --git a/mithril-stm/src/schnorr_signature/signature.rs b/mithril-stm/src/schnorr_signature/signature.rs index 59c0f067cc1..2972a1a2533 100644 --- a/mithril-stm/src/schnorr_signature/signature.rs +++ b/mithril-stm/src/schnorr_signature/signature.rs @@ -1,5 +1,18 @@ +use midnight_circuits::hash::poseidon::PoseidonChip; +use midnight_circuits::instructions::HashToCurveCPU; +use midnight_circuits::instructions::hash::HashCPU; use midnight_curves::{Fq as JubjubBase, Fr as JubjubScalar, JubjubSubgroup}; +use group::Group; + +use crate::{ + error::MultiSignatureError, + schnorr_signature::{ + JubjubHashToCurve, get_coordinates, hash_msg_to_jubjubbase, + verification_key::SchnorrVerificationKey, + }, +}; + /// Structure of the Schnorr signature to use with the SNARK /// This signature includes a value `sigma` which depends only on /// the message and the signing key. @@ -9,3 +22,51 @@ pub(crate) struct SchnorrSignature { pub(crate) s: JubjubScalar, pub(crate) c: JubjubBase, } + +impl SchnorrSignature { + pub(crate) fn verify( + &self, + msg: &[u8], + vk: &SchnorrVerificationKey, + ) -> Result<(), MultiSignatureError> { + let g = JubjubSubgroup::generator(); + + // First hashing the message to a scalar then hashing it to a curve point + let hash = JubjubHashToCurve::hash_to_curve(&[hash_msg_to_jubjubbase(msg)]); + + // Computing R1 = H(msg) * s + sigma * c + let c_bytes = self.c.to_bytes_le(); + let c_scalar = JubjubScalar::from_raw([ + u64::from_le_bytes(c_bytes[0..8].try_into().unwrap()), + u64::from_le_bytes(c_bytes[8..16].try_into().unwrap()), + u64::from_le_bytes(c_bytes[16..24].try_into().unwrap()), + u64::from_le_bytes(c_bytes[24..32].try_into().unwrap()), + ]); + let h_s = hash * self.s; + let sigma_c = self.sigma * c_scalar; + let r1_tilde = h_s + sigma_c; + + // Computing R2 = g * s + vk * c + let g_s = g * self.s; + let vk_c = vk.0 * c_scalar; + let r2_tilde = g_s + vk_c; + + let (gx, gy) = get_coordinates(g); + let (hashx, hashy) = get_coordinates(hash); + let (vkx, vky) = get_coordinates(vk.0); + let (sigmax, sigmay) = get_coordinates(self.sigma); + let (r1x, r1y) = get_coordinates(r1_tilde); + let (r2x, r2y) = get_coordinates(r2_tilde); + + let c_tilde = PoseidonChip::::hash(&[ + gx, gy, hashx, hashy, vkx, vky, sigmax, sigmay, r1x, r1y, r2x, r2y, + ]); + + if c_tilde != self.c { + // TODO: Wrong error for now, need to change that once the errors are added + return Err(MultiSignatureError::BatchInvalid); + } + + Ok(()) + } +} From 439344ac9fb12c05a3e73d4b25b5d9c0dc1fbdf9 Mon Sep 17 00:00:00 2001 From: Dam Date: Thu, 6 Nov 2025 18:19:48 +0100 Subject: [PATCH 16/21] Removed unused dependency. --- Cargo.lock | 1 - mithril-stm/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 08712e24166..b9210a9b8aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4387,7 +4387,6 @@ dependencies = [ "serde", "serde_json", "sha2", - "subtle", "thiserror 2.0.17", ] diff --git a/mithril-stm/Cargo.toml b/mithril-stm/Cargo.toml index 7c93e64e105..abca24377be 100644 --- a/mithril-stm/Cargo.toml +++ b/mithril-stm/Cargo.toml @@ -35,7 +35,6 @@ rand_core = { workspace = true } rayon = { workspace = true } serde = { workspace = true } sha2 = "0.10.9" -subtle = "2.6.1" thiserror = { workspace = true } [target.'cfg(any(target_family = "wasm", target_env = "musl", windows))'.dependencies] From 08552eaa17e57c54a98f8365f6eda3488567c0af Mon Sep 17 00:00:00 2001 From: Dam Date: Fri, 7 Nov 2025 12:33:23 +0100 Subject: [PATCH 17/21] Added DST to signature and removed unwraps. --- Cargo.lock | 1 + mithril-stm/Cargo.toml | 1 + mithril-stm/src/schnorr_signature/mod.rs | 50 ++++++++++++------- .../src/schnorr_signature/signature.rs | 41 ++++++++------- .../src/schnorr_signature/signing_key.rs | 32 ++++++++---- 5 files changed, 77 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b9210a9b8aa..2dcdee89f8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4367,6 +4367,7 @@ dependencies = [ name = "mithril-stm" version = "0.5.5" dependencies = [ + "anyhow", "blake2 0.10.6", "blst", "criterion", diff --git a/mithril-stm/Cargo.toml b/mithril-stm/Cargo.toml index abca24377be..84941e03c9b 100644 --- a/mithril-stm/Cargo.toml +++ b/mithril-stm/Cargo.toml @@ -22,6 +22,7 @@ future_proof_system = [] # For activating future proof systems future_snark = [] # For activating snark features [dependencies] +anyhow.workspace = true blake2 = "0.10.6" # Enforce blst portable feature for runtime detection of Intel ADX instruction set. blst = { version = "0.3.16", features = ["portable"] } diff --git a/mithril-stm/src/schnorr_signature/mod.rs b/mithril-stm/src/schnorr_signature/mod.rs index b00db964df2..f8285465f5f 100644 --- a/mithril-stm/src/schnorr_signature/mod.rs +++ b/mithril-stm/src/schnorr_signature/mod.rs @@ -8,10 +8,15 @@ use midnight_circuits::{ use midnight_curves::{Fq as JubjubBase, JubjubAffine, JubjubExtended, JubjubSubgroup}; use sha2::{Digest, Sha256}; +use anyhow::{Result, anyhow}; + mod signature; mod signing_key; mod verification_key; +/// A DST to distinguish between use of Poseidon hash +pub const DST_SIGNATURE: JubjubBase = JubjubBase::from_raw([2u64, 0, 0, 0]); + /// Defining a type for the CPU hash to curve gadget type JubjubHashToCurve = HashToCurveGadget< JubjubBase, @@ -24,18 +29,25 @@ type JubjubHashToCurve = HashToCurveGadget< /// Convert an arbitrary array of bytes into a Jubjub scalar field element /// First hash the message to 256 bits use Sha256 then perform the conversion /// TODO: Handle the unwrap properly -pub(crate) fn hash_msg_to_jubjubbase(msg: &[u8]) -> JubjubBase { +pub(crate) fn hash_msg_to_jubjubbase(msg: &[u8]) -> Result { let mut hash = Sha256::new(); hash.update(msg); let hmsg = hash.finalize(); let mut output = [0u8; 32]; - output.copy_from_slice(&hmsg); - JubjubBase::from_raw([ - u64::from_le_bytes(output[0..8].try_into().unwrap()), - u64::from_le_bytes(output[8..16].try_into().unwrap()), - u64::from_le_bytes(output[16..24].try_into().unwrap()), - u64::from_le_bytes(output[24..32].try_into().unwrap()), - ]) + // Adding a check here but this + if hmsg.len() == output.len() { + output.copy_from_slice(&hmsg); + } else { + return Err(anyhow!( + "Hash of the message does not have the correct lenght." + )); + } + Ok(JubjubBase::from_raw([ + u64::from_le_bytes(output[0..8].try_into()?), + u64::from_le_bytes(output[8..16].try_into()?), + u64::from_le_bytes(output[16..24].try_into()?), + u64::from_le_bytes(output[24..32].try_into()?), + ])) } pub(crate) fn get_coordinates(point: JubjubSubgroup) -> (JubjubBase, JubjubBase) { @@ -54,18 +66,15 @@ mod tests { use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; - use crate::{ - error::MultiSignatureError, - schnorr_signature::{ - signing_key::SchnorrSigningKey, verification_key::SchnorrVerificationKey, - }, + use crate::schnorr_signature::{ + signing_key::SchnorrSigningKey, verification_key::SchnorrVerificationKey, }; // Testing conversion from arbitrary message to scalar field element #[test] fn test_hash_msg_to_jubjubbase() { let msg = vec![0, 0, 0, 1]; - let h = hash_msg_to_jubjubbase(&msg); + let h = hash_msg_to_jubjubbase(&msg).unwrap(); // Correct value corresponding to the message [0,0,0,1] let bytes_le = [ 179, 7, 17, 168, 141, 112, 57, 117, 112, 92, 169, 56, 36, 70, 1, 217, 9, 13, 255, 42, @@ -94,7 +103,7 @@ mod tests { let mut rng = ChaCha20Rng::from_seed(seed); let sk = SchnorrSigningKey::generate(&mut rng); let vk = SchnorrVerificationKey::from(&sk); - let sig = sk.sign(&msg, &mut rng); + let sig = sk.sign(&msg, &mut rng).unwrap(); sig.verify(&msg, &vk).unwrap(); } @@ -112,15 +121,18 @@ mod tests { let sk2 = SchnorrSigningKey::generate(&mut rng); let vk2 = SchnorrVerificationKey::from(&sk2); - let sig = sk.sign(&msg, &mut rng); - let sig2 = sk.sign(&msg2, &mut rng); + let sig = sk.sign(&msg, &mut rng).unwrap(); + let sig2 = sk.sign(&msg2, &mut rng).unwrap(); // Wrong verification key is used let result = sig.verify(&msg, &vk2); - assert_eq!(result, Err(MultiSignatureError::BatchInvalid)); + assert!( + result.is_err(), + "Wrong verfication key used, test should fail." + ); // Wrong message is verified let result = sig2.verify(&msg, &vk); - assert_eq!(result, Err(MultiSignatureError::BatchInvalid)); + assert!(result.is_err(), "Wrong message used, test should fail."); } } diff --git a/mithril-stm/src/schnorr_signature/signature.rs b/mithril-stm/src/schnorr_signature/signature.rs index 2972a1a2533..e01f02482a9 100644 --- a/mithril-stm/src/schnorr_signature/signature.rs +++ b/mithril-stm/src/schnorr_signature/signature.rs @@ -1,3 +1,4 @@ +use anyhow::{Result, anyhow}; use midnight_circuits::hash::poseidon::PoseidonChip; use midnight_circuits::instructions::HashToCurveCPU; use midnight_circuits::instructions::hash::HashCPU; @@ -5,12 +6,9 @@ use midnight_curves::{Fq as JubjubBase, Fr as JubjubScalar, JubjubSubgroup}; use group::Group; -use crate::{ - error::MultiSignatureError, - schnorr_signature::{ - JubjubHashToCurve, get_coordinates, hash_msg_to_jubjubbase, - verification_key::SchnorrVerificationKey, - }, +use crate::schnorr_signature::{ + DST_SIGNATURE, JubjubHashToCurve, get_coordinates, hash_msg_to_jubjubbase, + verification_key::SchnorrVerificationKey, }; /// Structure of the Schnorr signature to use with the SNARK @@ -24,23 +22,19 @@ pub(crate) struct SchnorrSignature { } impl SchnorrSignature { - pub(crate) fn verify( - &self, - msg: &[u8], - vk: &SchnorrVerificationKey, - ) -> Result<(), MultiSignatureError> { + pub(crate) fn verify(&self, msg: &[u8], vk: &SchnorrVerificationKey) -> Result<()> { let g = JubjubSubgroup::generator(); // First hashing the message to a scalar then hashing it to a curve point - let hash = JubjubHashToCurve::hash_to_curve(&[hash_msg_to_jubjubbase(msg)]); + let hash = JubjubHashToCurve::hash_to_curve(&[hash_msg_to_jubjubbase(msg)?]); // Computing R1 = H(msg) * s + sigma * c let c_bytes = self.c.to_bytes_le(); let c_scalar = JubjubScalar::from_raw([ - u64::from_le_bytes(c_bytes[0..8].try_into().unwrap()), - u64::from_le_bytes(c_bytes[8..16].try_into().unwrap()), - u64::from_le_bytes(c_bytes[16..24].try_into().unwrap()), - u64::from_le_bytes(c_bytes[24..32].try_into().unwrap()), + u64::from_le_bytes(c_bytes[0..8].try_into()?), + u64::from_le_bytes(c_bytes[8..16].try_into()?), + u64::from_le_bytes(c_bytes[16..24].try_into()?), + u64::from_le_bytes(c_bytes[24..32].try_into()?), ]); let h_s = hash * self.s; let sigma_c = self.sigma * c_scalar; @@ -51,7 +45,6 @@ impl SchnorrSignature { let vk_c = vk.0 * c_scalar; let r2_tilde = g_s + vk_c; - let (gx, gy) = get_coordinates(g); let (hashx, hashy) = get_coordinates(hash); let (vkx, vky) = get_coordinates(vk.0); let (sigmax, sigmay) = get_coordinates(self.sigma); @@ -59,12 +52,22 @@ impl SchnorrSignature { let (r2x, r2y) = get_coordinates(r2_tilde); let c_tilde = PoseidonChip::::hash(&[ - gx, gy, hashx, hashy, vkx, vky, sigmax, sigmay, r1x, r1y, r2x, r2y, + DST_SIGNATURE, + hashx, + hashy, + vkx, + vky, + sigmax, + sigmay, + r1x, + r1y, + r2x, + r2y, ]); if c_tilde != self.c { // TODO: Wrong error for now, need to change that once the errors are added - return Err(MultiSignatureError::BatchInvalid); + return Err(anyhow!("Signature failed to verify.")); } Ok(()) diff --git a/mithril-stm/src/schnorr_signature/signing_key.rs b/mithril-stm/src/schnorr_signature/signing_key.rs index 52914871706..04277e20cec 100644 --- a/mithril-stm/src/schnorr_signature/signing_key.rs +++ b/mithril-stm/src/schnorr_signature/signing_key.rs @@ -1,3 +1,4 @@ +use anyhow::Result; use ff::Field; use midnight_circuits::hash::poseidon::PoseidonChip; use midnight_circuits::instructions::hash::HashCPU; @@ -8,7 +9,9 @@ use midnight_circuits::instructions::HashToCurveCPU; use group::Group; -use crate::schnorr_signature::{JubjubHashToCurve, get_coordinates, hash_msg_to_jubjubbase}; +use crate::schnorr_signature::{ + DST_SIGNATURE, JubjubHashToCurve, get_coordinates, hash_msg_to_jubjubbase, +}; use crate::schnorr_signature::{ signature::SchnorrSignature, verification_key::SchnorrVerificationKey, }; @@ -24,13 +27,13 @@ impl SchnorrSigningKey { &self, msg: &[u8], rng: &mut (impl RngCore + CryptoRng), - ) -> SchnorrSignature { + ) -> Result { // Use the subgroup generator to compute the curve points let g = JubjubSubgroup::generator(); let vk = SchnorrVerificationKey::from(self); // First hashing the message to a scalar then hashing it to a curve point - let hash = JubjubHashToCurve::hash_to_curve(&[hash_msg_to_jubjubbase(msg)]); + let hash = JubjubHashToCurve::hash_to_curve(&[hash_msg_to_jubjubbase(msg)?]); // sigma = H(msg) * sk let sigma = hash * self.0; @@ -45,7 +48,6 @@ impl SchnorrSigningKey { // Since the hash function takes as input scalar elements // We need to convert the EC points to their coordinates // I use gx and gy for now but maybe we can replace them by a DST? - let (gx, gy) = get_coordinates(g); let (hashx, hashy) = get_coordinates(hash); let (vkx, vky) = get_coordinates(vk.0); let (sigmax, sigmay) = get_coordinates(sigma); @@ -53,7 +55,17 @@ impl SchnorrSigningKey { let (r2x, r2y) = get_coordinates(r2); let c = PoseidonChip::::hash(&[ - gx, gy, hashx, hashy, vkx, vky, sigmax, sigmay, r1x, r1y, r2x, r2y, + DST_SIGNATURE, + hashx, + hashy, + vkx, + vky, + sigmax, + sigmay, + r1x, + r1y, + r2x, + r2y, ]); // We want to use the from_raw function because the result of @@ -62,14 +74,14 @@ impl SchnorrSigningKey { // TODO: Refactor this let bytes = c.to_bytes_le(); let c_scalar = JubjubScalar::from_raw([ - u64::from_le_bytes(bytes[0..8].try_into().unwrap()), - u64::from_le_bytes(bytes[8..16].try_into().unwrap()), - u64::from_le_bytes(bytes[16..24].try_into().unwrap()), - u64::from_le_bytes(bytes[24..32].try_into().unwrap()), + u64::from_le_bytes(bytes[0..8].try_into()?), + u64::from_le_bytes(bytes[8..16].try_into()?), + u64::from_le_bytes(bytes[16..24].try_into()?), + u64::from_le_bytes(bytes[24..32].try_into()?), ]); let s = r - c_scalar * self.0; - SchnorrSignature { sigma, s, c } + Ok(SchnorrSignature { sigma, s, c }) } } From 3a84e72dcfceafdfa15546c44a0cac260205d0e9 Mon Sep 17 00:00:00 2001 From: Dam Date: Fri, 7 Nov 2025 13:12:03 +0100 Subject: [PATCH 18/21] Added conversion function for scalar from BLS12 to Jubjub. --- mithril-stm/src/schnorr_signature/mod.rs | 33 +++++++++++++++++-- .../src/schnorr_signature/signature.rs | 10 ++---- .../src/schnorr_signature/signing_key.rs | 9 ++--- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/mithril-stm/src/schnorr_signature/mod.rs b/mithril-stm/src/schnorr_signature/mod.rs index f8285465f5f..660781884d1 100644 --- a/mithril-stm/src/schnorr_signature/mod.rs +++ b/mithril-stm/src/schnorr_signature/mod.rs @@ -5,7 +5,9 @@ use midnight_circuits::{ hash::poseidon::PoseidonChip, types::AssignedNative, }; -use midnight_curves::{Fq as JubjubBase, JubjubAffine, JubjubExtended, JubjubSubgroup}; +use midnight_curves::{ + Fq as JubjubBase, Fr as JubjubScalar, JubjubAffine, JubjubExtended, JubjubSubgroup, +}; use sha2::{Digest, Sha256}; use anyhow::{Result, anyhow}; @@ -50,6 +52,8 @@ pub(crate) fn hash_msg_to_jubjubbase(msg: &[u8]) -> Result { ])) } +/// Extract the coordinates of a given point +/// This is mainly use to feed the Poseidon hash function pub(crate) fn get_coordinates(point: JubjubSubgroup) -> (JubjubBase, JubjubBase) { let extended = JubjubExtended::from(point); // Convert to JubjubExtended let affine = JubjubAffine::from(extended); // Convert to JubjubAffine (affine coordinates) @@ -58,6 +62,18 @@ pub(crate) fn get_coordinates(point: JubjubSubgroup) -> (JubjubBase, JubjubBase) (x, y) } +/// Convert an element of the BLS12-381 base field to +/// one of the Jubjub base field +pub fn jubjub_base_to_scalar(x: &JubjubBase) -> Result { + let bytes = x.to_bytes_le(); + Ok(JubjubScalar::from_raw([ + u64::from_le_bytes(bytes[0..8].try_into()?), + u64::from_le_bytes(bytes[8..16].try_into()?), + u64::from_le_bytes(bytes[16..24].try_into()?), + u64::from_le_bytes(bytes[24..32].try_into()?), + ])) +} + #[cfg(test)] mod tests { @@ -96,8 +112,21 @@ mod tests { println!("{:?}", (x, y)); } + // Testing conversion from BLS12-381 base field to Jubjub base field + // TODO: Add randomness to val + #[test] + fn test_jubjub_ase_to_scalar() { + let val = vec![0, 0, 0, 1]; + let jjbase = JubjubBase::from_raw(val.clone().try_into().unwrap()); + let jjscalar = JubjubScalar::from_raw(val.try_into().unwrap()); + + let converted_base = jubjub_base_to_scalar(&jjbase).unwrap(); + + assert_eq!(jjscalar, converted_base); + } + #[test] - fn test_sig() { + fn test_sig_and_verify() { let msg = vec![0, 0, 0, 1]; let seed = [0u8; 32]; let mut rng = ChaCha20Rng::from_seed(seed); diff --git a/mithril-stm/src/schnorr_signature/signature.rs b/mithril-stm/src/schnorr_signature/signature.rs index e01f02482a9..7c2831c05d9 100644 --- a/mithril-stm/src/schnorr_signature/signature.rs +++ b/mithril-stm/src/schnorr_signature/signature.rs @@ -8,7 +8,7 @@ use group::Group; use crate::schnorr_signature::{ DST_SIGNATURE, JubjubHashToCurve, get_coordinates, hash_msg_to_jubjubbase, - verification_key::SchnorrVerificationKey, + jubjub_base_to_scalar, verification_key::SchnorrVerificationKey, }; /// Structure of the Schnorr signature to use with the SNARK @@ -29,13 +29,7 @@ impl SchnorrSignature { let hash = JubjubHashToCurve::hash_to_curve(&[hash_msg_to_jubjubbase(msg)?]); // Computing R1 = H(msg) * s + sigma * c - let c_bytes = self.c.to_bytes_le(); - let c_scalar = JubjubScalar::from_raw([ - u64::from_le_bytes(c_bytes[0..8].try_into()?), - u64::from_le_bytes(c_bytes[8..16].try_into()?), - u64::from_le_bytes(c_bytes[16..24].try_into()?), - u64::from_le_bytes(c_bytes[24..32].try_into()?), - ]); + let c_scalar = jubjub_base_to_scalar(&self.c)?; let h_s = hash * self.s; let sigma_c = self.sigma * c_scalar; let r1_tilde = h_s + sigma_c; diff --git a/mithril-stm/src/schnorr_signature/signing_key.rs b/mithril-stm/src/schnorr_signature/signing_key.rs index 04277e20cec..8261e1af967 100644 --- a/mithril-stm/src/schnorr_signature/signing_key.rs +++ b/mithril-stm/src/schnorr_signature/signing_key.rs @@ -11,6 +11,7 @@ use group::Group; use crate::schnorr_signature::{ DST_SIGNATURE, JubjubHashToCurve, get_coordinates, hash_msg_to_jubjubbase, + jubjub_base_to_scalar, }; use crate::schnorr_signature::{ signature::SchnorrSignature, verification_key::SchnorrVerificationKey, @@ -72,13 +73,7 @@ impl SchnorrSigningKey { // the poseidon hash might not fit into the smaller modulus // the Fr scalar field // TODO: Refactor this - let bytes = c.to_bytes_le(); - let c_scalar = JubjubScalar::from_raw([ - u64::from_le_bytes(bytes[0..8].try_into()?), - u64::from_le_bytes(bytes[8..16].try_into()?), - u64::from_le_bytes(bytes[16..24].try_into()?), - u64::from_le_bytes(bytes[24..32].try_into()?), - ]); + let c_scalar = jubjub_base_to_scalar(&c)?; let s = r - c_scalar * self.0; Ok(SchnorrSignature { sigma, s, c }) From 5400577013fdaf7035f670bb4281e493c328cfcf Mon Sep 17 00:00:00 2001 From: Dam Date: Fri, 7 Nov 2025 15:01:21 +0100 Subject: [PATCH 19/21] Added wip to and from bytes for Schnorr signing key. --- .../src/schnorr_signature/signing_key.rs | 60 ++++++++++++++++++- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/mithril-stm/src/schnorr_signature/signing_key.rs b/mithril-stm/src/schnorr_signature/signing_key.rs index 8261e1af967..ca03e57c955 100644 --- a/mithril-stm/src/schnorr_signature/signing_key.rs +++ b/mithril-stm/src/schnorr_signature/signing_key.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{Result, anyhow}; use ff::Field; use midnight_circuits::hash::poseidon::PoseidonChip; use midnight_circuits::instructions::hash::HashCPU; @@ -17,6 +17,8 @@ use crate::schnorr_signature::{ signature::SchnorrSignature, verification_key::SchnorrVerificationKey, }; +/// Schnorr Signing key, it is essentially a random scalar of the Jubjub scalar field +#[derive(Debug, Clone)] pub(crate) struct SchnorrSigningKey(pub(crate) JubjubScalar); impl SchnorrSigningKey { @@ -24,6 +26,7 @@ impl SchnorrSigningKey { SchnorrSigningKey(JubjubScalar::random(rng)) } + // TODO: Check if we want the sign function to handle the randomness by itself pub(crate) fn sign( &self, msg: &[u8], @@ -78,14 +81,45 @@ impl SchnorrSigningKey { Ok(SchnorrSignature { sigma, s, c }) } + + fn to_bytes(&self) -> [u8; 32] { + self.0.to_bytes() + } + + /// Convert a string of bytes into a `SchnorrSigningKey`. + /// The bytes must represent a Jubjub scalar or the conversion will fail + // TODO: Maybe rework this function, do we want to allow any bytes representation + // to be convertible to a sk? + fn from_bytes(bytes: &[u8]) -> Result { + // This is a bit ugly, I'll try to find a better way to do it + let bytes = bytes + .get(..32) + .ok_or(anyhow!("Not enough bytes to create a signing key."))? + .try_into()?; + // Jubjub returs a CtChoice so I convert it to an option that looses the const time property + match JubjubScalar::from_bytes(bytes).into_option() { + Some(sk) => Ok(Self(sk)), + // the error should be updated + None => Err(anyhow!( + "Failed to create a Jubjub scalar from the given bytes." + )), + } + } } +// #[cfg(test)] mod tests { use super::*; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; + impl PartialEq for SchnorrSigningKey { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } + } + #[test] fn test_generate_signing_key() { let mut rng = ChaCha20Rng::from_seed([0u8; 32]); @@ -93,8 +127,28 @@ mod tests { } #[test] - fn test_generate_signature() { + fn test_to_from_bytes() { let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - let _sk = SchnorrSigningKey::generate(&mut rng); + let sk = SchnorrSigningKey::generate(&mut rng); + let bytes = sk.to_bytes(); + let recovered_sk = SchnorrSigningKey::from_bytes(&bytes).unwrap(); + assert_eq!(sk, recovered_sk); + } + + // For now failing test, maybe change it later depending on what + // we want for from_bytes + #[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); + + assert!( + result.is_err(), + "Value is not a proper sk, test should fail." + ); } } From fd1fb451974431e2e529a2fc1ff02802c426ba11 Mon Sep 17 00:00:00 2001 From: Dam Date: Fri, 7 Nov 2025 15:06:28 +0100 Subject: [PATCH 20/21] Added comments and derive for main structs. --- mithril-stm/src/schnorr_signature/signature.rs | 1 + mithril-stm/src/schnorr_signature/verification_key.rs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/mithril-stm/src/schnorr_signature/signature.rs b/mithril-stm/src/schnorr_signature/signature.rs index 7c2831c05d9..2a145f56a03 100644 --- a/mithril-stm/src/schnorr_signature/signature.rs +++ b/mithril-stm/src/schnorr_signature/signature.rs @@ -15,6 +15,7 @@ use crate::schnorr_signature::{ /// This signature includes a value `sigma` 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)] pub(crate) struct SchnorrSignature { pub(crate) sigma: JubjubSubgroup, pub(crate) s: JubjubScalar, diff --git a/mithril-stm/src/schnorr_signature/verification_key.rs b/mithril-stm/src/schnorr_signature/verification_key.rs index dd1fee652ed..911e503c5b7 100644 --- a/mithril-stm/src/schnorr_signature/verification_key.rs +++ b/mithril-stm/src/schnorr_signature/verification_key.rs @@ -3,6 +3,9 @@ pub use midnight_curves::JubjubSubgroup; use crate::schnorr_signature::signing_key::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)] pub struct SchnorrVerificationKey(pub(crate) JubjubSubgroup); impl From<&SchnorrSigningKey> for SchnorrVerificationKey { From cf446ba14440ef36f6b9569bf20e483afc4dc77a Mon Sep 17 00:00:00 2001 From: Dam Date: Fri, 7 Nov 2025 17:32:59 +0100 Subject: [PATCH 21/21] Added eval_dense_mapping function to signature. --- mithril-stm/src/schnorr_signature/mod.rs | 3 ++- .../src/schnorr_signature/signature.rs | 24 ++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/mithril-stm/src/schnorr_signature/mod.rs b/mithril-stm/src/schnorr_signature/mod.rs index 660781884d1..25b0d6e9504 100644 --- a/mithril-stm/src/schnorr_signature/mod.rs +++ b/mithril-stm/src/schnorr_signature/mod.rs @@ -17,7 +17,8 @@ mod signing_key; mod verification_key; /// A DST to distinguish between use of Poseidon hash -pub const DST_SIGNATURE: JubjubBase = JubjubBase::from_raw([2u64, 0, 0, 0]); +pub const DST_SIGNATURE: JubjubBase = JubjubBase::from_raw([0u64, 0, 0, 0]); +pub const DST_LOTTERY: JubjubBase = JubjubBase::from_raw([1u64, 0, 0, 0]); /// Defining a type for the CPU hash to curve gadget type JubjubHashToCurve = HashToCurveGadget< diff --git a/mithril-stm/src/schnorr_signature/signature.rs b/mithril-stm/src/schnorr_signature/signature.rs index 2a145f56a03..deb437c7f20 100644 --- a/mithril-stm/src/schnorr_signature/signature.rs +++ b/mithril-stm/src/schnorr_signature/signature.rs @@ -6,9 +6,12 @@ use midnight_curves::{Fq as JubjubBase, Fr as JubjubScalar, JubjubSubgroup}; use group::Group; -use crate::schnorr_signature::{ - DST_SIGNATURE, JubjubHashToCurve, get_coordinates, hash_msg_to_jubjubbase, - jubjub_base_to_scalar, verification_key::SchnorrVerificationKey, +use crate::{ + Index, + schnorr_signature::{ + DST_LOTTERY, DST_SIGNATURE, JubjubHashToCurve, get_coordinates, hash_msg_to_jubjubbase, + jubjub_base_to_scalar, verification_key::SchnorrVerificationKey, + }, }; /// Structure of the Schnorr signature to use with the SNARK @@ -67,4 +70,19 @@ impl SchnorrSignature { Ok(()) } + + /// Dense mapping function indexed by the index to be evaluated + /// adapted to the Schnorr signature. + /// We need to convert the inputs to fit in a Poseidon hash. + /// The order of the hash input must be the same as the one in the SNARK circuit + /// `ev = H(DST || msg || index || σ) <- MSP.Eval(msg,index,σ)` given in paper. + fn evaluate_dense_mapping(&self, msg: &[u8], index: Index) -> Result<[u8; 32]> { + let msg = JubjubHashToCurve::hash_to_curve(&[hash_msg_to_jubjubbase(msg)?]); + let (msgx, msgy) = get_coordinates(msg); + // TODO: Check if this is the correct way to add the index + let idx = JubjubBase::from_raw([0, 0, 0, index]); + let (sigmax, sigmay) = get_coordinates(self.sigma); + let ev = PoseidonChip::::hash(&[DST_LOTTERY, msgx, msgy, idx, sigmax, sigmay]); + Ok(ev.to_bytes_le()) + } }