diff --git a/mithril-stm/CHANGELOG.md b/mithril-stm/CHANGELOG.md index f3aa3bcf4eb..bc321b4ed2e 100644 --- a/mithril-stm/CHANGELOG.md +++ b/mithril-stm/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.6.3 (12-03-2025) + +### Added + +- Jubjub wrapper added to schnorr signature module. + ## 0.6.2 (11-27-2025) ### Changed diff --git a/mithril-stm/benches/schnorr_sig.rs b/mithril-stm/benches/schnorr_sig.rs index 4d76badbbb3..50b7680cf46 100644 --- a/mithril-stm/benches/schnorr_sig.rs +++ b/mithril-stm/benches/schnorr_sig.rs @@ -45,8 +45,8 @@ fn sign_and_verify(c: &mut Criterion, nr_sigs: usize) { let mut msks = Vec::new(); let mut sigs = Vec::new(); for _ in 0..nr_sigs { - let sk = SchnorrSigningKey::try_generate(&mut rng).unwrap(); - let vk = SchnorrVerificationKey::from(&sk); + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + let vk = SchnorrVerificationKey::new_from_signing_key(sk.clone()).unwrap(); let sig = sk.sign(&msg, &mut rng_sig).unwrap(); sigs.push(sig); mvks.push(vk); diff --git a/mithril-stm/src/membership_commitment/merkle_tree/leaf.rs b/mithril-stm/src/membership_commitment/merkle_tree/leaf.rs index 0a77e907405..9c1cbd2f558 100644 --- a/mithril-stm/src/membership_commitment/merkle_tree/leaf.rs +++ b/mithril-stm/src/membership_commitment/merkle_tree/leaf.rs @@ -1,6 +1,5 @@ -use std::cmp::Ordering; - use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; use crate::{ MerkleTreeError, Stake, StmResult, VerificationKey, signature_scheme::BlsVerificationKey, diff --git a/mithril-stm/src/membership_commitment/merkle_tree/tree.rs b/mithril-stm/src/membership_commitment/merkle_tree/tree.rs index acd4de54247..98677ee32b5 100644 --- a/mithril-stm/src/membership_commitment/merkle_tree/tree.rs +++ b/mithril-stm/src/membership_commitment/merkle_tree/tree.rs @@ -271,9 +271,8 @@ mod tests { use proptest::{collection::vec, prelude::*}; use rand::{rng, seq::IteratorRandom}; - use crate::signature_scheme::BlsVerificationKey; - use super::*; + use crate::signature_scheme::BlsVerificationKey; fn pow2_plus1(h: usize) -> usize { 1 + 2_usize.pow(h as u32) diff --git a/mithril-stm/src/protocol/aggregate_signature/signature.rs b/mithril-stm/src/protocol/aggregate_signature/signature.rs index f0b4aa53f1c..39df2c00431 100644 --- a/mithril-stm/src/protocol/aggregate_signature/signature.rs +++ b/mithril-stm/src/protocol/aggregate_signature/signature.rs @@ -1,8 +1,7 @@ -use std::{collections::HashMap, fmt::Display, hash::Hash, str::FromStr}; - use anyhow::anyhow; use blake2::digest::{Digest, FixedOutput}; use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, fmt::Display, hash::Hash, str::FromStr}; use super::AggregateVerificationKey; use crate::{ diff --git a/mithril-stm/src/protocol/key_registration.rs b/mithril-stm/src/protocol/key_registration.rs index 14cc7f63d9b..c7baba80c8b 100644 --- a/mithril-stm/src/protocol/key_registration.rs +++ b/mithril-stm/src/protocol/key_registration.rs @@ -1,12 +1,11 @@ //! Key registration functionality. +use anyhow::anyhow; +use blake2::digest::{Digest, FixedOutput}; use std::{ collections::{HashMap, hash_map::Entry}, sync::Arc, }; -use anyhow::anyhow; -use blake2::digest::{Digest, FixedOutput}; - use crate::{ RegisterError, Stake, StmResult, membership_commitment::{MerkleTree, MerkleTreeLeaf}, @@ -95,9 +94,8 @@ mod tests { use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; - use crate::signature_scheme::BlsSigningKey; - use super::*; + use crate::signature_scheme::BlsSigningKey; proptest! { #[test] diff --git a/mithril-stm/src/protocol/single_signature/signature.rs b/mithril-stm/src/protocol/single_signature/signature.rs index 57a9620ebc6..568603ede17 100644 --- a/mithril-stm/src/protocol/single_signature/signature.rs +++ b/mithril-stm/src/protocol/single_signature/signature.rs @@ -1,12 +1,11 @@ +use anyhow::{Context, anyhow}; +use blake2::digest::{Digest, FixedOutput}; +use serde::{Deserialize, Serialize}; use std::{ cmp::Ordering, hash::{Hash, Hasher}, }; -use anyhow::{Context, anyhow}; -use blake2::digest::{Digest, FixedOutput}; -use serde::{Deserialize, Serialize}; - use crate::{ AggregateVerificationKey, Index, Parameters, SignatureError, Stake, StmResult, VerificationKey, is_lottery_won, signature_scheme::BlsSignature, diff --git a/mithril-stm/src/signature_scheme/bls_multi_signature/mod.rs b/mithril-stm/src/signature_scheme/bls_multi_signature/mod.rs index fc3c3bc1646..c4b1427ce6a 100644 --- a/mithril-stm/src/signature_scheme/bls_multi_signature/mod.rs +++ b/mithril-stm/src/signature_scheme/bls_multi_signature/mod.rs @@ -95,9 +95,8 @@ mod tests { use rand_core::{RngCore, SeedableRng}; use super::helper::unsafe_helpers::{p1_affine_to_sig, p2_affine_to_vk}; - use crate::{KeyRegistration, MultiSignatureError, RegisterError}; - use super::*; + use crate::{KeyRegistration, MultiSignatureError, RegisterError}; impl PartialEq for BlsSigningKey { fn eq(&self, other: &Self) -> bool { diff --git a/mithril-stm/src/signature_scheme/bls_multi_signature/verification_key.rs b/mithril-stm/src/signature_scheme/bls_multi_signature/verification_key.rs index 1247115d28f..fbc730dce94 100644 --- a/mithril-stm/src/signature_scheme/bls_multi_signature/verification_key.rs +++ b/mithril-stm/src/signature_scheme/bls_multi_signature/verification_key.rs @@ -1,4 +1,9 @@ use anyhow::anyhow; +use blst::{ + BLST_ERROR, + min_sig::{AggregatePublicKey, PublicKey as BlstVk}, +}; +use serde::{Deserialize, Serialize}; use std::{ cmp::Ordering, fmt::{Display, Formatter}, @@ -6,12 +11,6 @@ use std::{ iter::Sum, }; -use blst::{ - BLST_ERROR, - min_sig::{AggregatePublicKey, PublicKey as BlstVk}, -}; -use serde::{Deserialize, Serialize}; - use super::{BlsProofOfPossession, BlsSigningKey, POP, helper::unsafe_helpers::verify_pairing}; use crate::{MultiSignatureError, StmResult, blst_error_to_stm_error}; diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/error.rs b/mithril-stm/src/signature_scheme/schnorr_signature/error.rs index fa1701a4641..05ba095226b 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/error.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/error.rs @@ -1,5 +1,5 @@ #[cfg(feature = "future_snark")] -use super::{SchnorrSignature, SchnorrVerificationKey}; +use super::{PrimeOrderProjectivePoint, SchnorrSignature}; /// Error types for Schnorr signatures. #[cfg(feature = "future_snark")] @@ -9,19 +9,35 @@ pub enum SchnorrSignatureError { #[error("Invalid Schnorr single signature")] SignatureInvalid(Box), - /// Invalid Verification key - #[error("Invalid Schnorr Verification key")] - VerificationKeyInvalid(Box), - /// This error occurs when the serialization of the raw bytes failed #[error("Invalid bytes")] SerializationError, - /// This error occurs when the signing key fails to generate - #[error("Failed generation of the signing key")] - SigningKeyGenerationError, + /// This error occurs when the serialization of the signing key bytes failed + #[error("Invalid scalar field element bytes")] + ScalarFieldElementSerializationError, + + /// This error occurs when the serialization of the projective point bytes failed + #[error("Invalid projective point bytes")] + ProjectivePointSerializationError, + + /// This error occurs when the serialization of the prime order projective point bytes failed + #[error("Invalid prime order projective point bytes")] + PrimeOrderProjectivePointSerializationError, /// This error occurs when the random scalar fails to generate during the signature #[error("Failed generation of the signature's random scalar")] RandomScalarGenerationError, + + /// This error occurs when signing key is zero or one. + #[error("The signing key is invalid.")] + InvalidSigningKey, + + /// Given point is not on the curve + #[error("Given point is not on the curve")] + PointIsNotOnCurve(Box), + + /// Given point is not prime order + #[error("Given point is not prime order")] + PointIsNotPrimeOrder(Box), } diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/curve_points.rs b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/curve_points.rs new file mode 100644 index 00000000000..09bc0209b32 --- /dev/null +++ b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/curve_points.rs @@ -0,0 +1,175 @@ +use anyhow::anyhow; +use dusk_jubjub::{ + AffinePoint as JubjubAffinePoint, EDWARDS_D, ExtendedPoint as JubjubExtended, + SubgroupPoint as JubjubSubgroup, +}; +use group::{Group, GroupEncoding}; + +use super::{BaseFieldElement, ScalarFieldElement}; +use crate::{StmResult, signature_scheme::SchnorrSignatureError}; + +#[derive(Clone)] +pub(crate) struct AffinePoint(JubjubAffinePoint); + +impl AffinePoint { + pub(crate) fn from_projective_point(projective_point: ProjectivePoint) -> Self { + AffinePoint(JubjubAffinePoint::from(projective_point.0)) + } + + pub(crate) fn from_prime_order_projective_point( + prime_order_projective_point: &PrimeOrderProjectivePoint, + ) -> Self { + AffinePoint(JubjubAffinePoint::from( + ProjectivePoint::from_prime_order_projective_point(*prime_order_projective_point).0, + )) + } + + pub(crate) fn get_u(&self) -> BaseFieldElement { + BaseFieldElement(self.0.get_u()) + } + + pub(crate) fn get_v(&self) -> BaseFieldElement { + BaseFieldElement(self.0.get_v()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct ProjectivePoint(pub(crate) JubjubExtended); + +impl ProjectivePoint { + pub(crate) fn hash_to_projective_point(input: &[u8]) -> Self { + ProjectivePoint(JubjubExtended::hash_to_point(input)) + } + + pub(crate) fn add(&self, other: Self) -> Self { + ProjectivePoint(self.0 + other.0) + } + + pub(crate) fn scalar_multiplication(&self, scalar: &ScalarFieldElement) -> Self { + ProjectivePoint(self.0 * scalar.0) + } + + pub(crate) fn get_coordinates(&self) -> (BaseFieldElement, BaseFieldElement) { + let affine_point = AffinePoint::from_projective_point(*self); + + (affine_point.get_u(), affine_point.get_v()) + } + + pub(crate) fn to_bytes(self) -> [u8; 32] { + self.0.to_bytes() + } + + pub(crate) fn from_bytes(bytes: &[u8]) -> StmResult { + let mut projective_point_bytes = [0u8; 32]; + projective_point_bytes + .copy_from_slice(bytes.get(..32).ok_or(SchnorrSignatureError::SerializationError)?); + + match JubjubExtended::from_bytes(&projective_point_bytes).into_option() { + Some(projective_point) => Ok(Self(projective_point)), + None => Err(anyhow!( + SchnorrSignatureError::ProjectivePointSerializationError + )), + } + } + + pub(crate) fn from_prime_order_projective_point( + prime_order_projective_point: PrimeOrderProjectivePoint, + ) -> Self { + ProjectivePoint(JubjubExtended::from(prime_order_projective_point.0)) + } + + pub(crate) fn is_prime_order(self) -> bool { + self.0.is_prime_order().into() + } +} + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub(crate) struct PrimeOrderProjectivePoint(pub(crate) JubjubSubgroup); + +impl PrimeOrderProjectivePoint { + pub(crate) fn create_generator() -> Self { + PrimeOrderProjectivePoint(JubjubSubgroup::generator()) + } + + pub(crate) fn add(&self, other: Self) -> Self { + PrimeOrderProjectivePoint(self.0 + other.0) + } + + pub(crate) fn scalar_multiplication(&self, scalar: &ScalarFieldElement) -> Self { + PrimeOrderProjectivePoint(self.0 * scalar.0) + } + + /// Check if the given point is on the curve using its coordinates + pub(crate) fn is_on_curve(&self) -> StmResult { + let point_affine_representation = AffinePoint::from_prime_order_projective_point(self); + let (x, y) = ( + point_affine_representation.get_u(), + point_affine_representation.get_v(), + ); + let x_square = x.square(); + let y_square = y.square(); + + let lhs = y_square.sub(&x_square); + let mut rhs = x_square.mul(&y_square); + rhs = rhs.mul(&BaseFieldElement(EDWARDS_D)); + rhs = rhs.add(&BaseFieldElement::get_one()); + + if lhs != rhs { + return Err(anyhow!(SchnorrSignatureError::PointIsNotOnCurve(Box::new( + *self + )))); + } + Ok(*self) + } + + pub(crate) fn to_bytes(self) -> [u8; 32] { + self.0.to_bytes() + } + + pub(crate) fn from_bytes(bytes: &[u8]) -> StmResult { + let mut prime_order_projective_point_bytes = [0u8; 32]; + prime_order_projective_point_bytes + .copy_from_slice(bytes.get(..32).ok_or(SchnorrSignatureError::SerializationError)?); + + match JubjubSubgroup::from_bytes(&prime_order_projective_point_bytes).into_option() { + Some(prime_order_projective_point) => Ok(Self(prime_order_projective_point)), + None => Err(anyhow!( + SchnorrSignatureError::PrimeOrderProjectivePointSerializationError + )), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + mod golden { + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + + use super::*; + + const GOLDEN_JSON: &str = r#"[144, 52, 95, 161, 127, 253, 49, 32, 140, 217, 231, 207, 32, 238, 244, 196, 97, 241, 47, 95, 101, 9, 70, 136, 194, 66, 187, 253, 200, 32, 218, 43]"#; + + fn golden_value() -> PrimeOrderProjectivePoint { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let scalar = ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap(); + let point = PrimeOrderProjectivePoint::create_generator(); + point.scalar_multiplication(&scalar) + } + + #[test] + fn golden_conversions() { + let value = serde_json::from_str(GOLDEN_JSON) + .expect("This JSON deserialization should not fail"); + assert_eq!(golden_value(), value); + + let serialized = + serde_json::to_string(&value).expect("This JSON serialization should not fail"); + let golden_serialized = serde_json::to_string(&golden_value()) + .expect("This JSON serialization should not fail"); + assert_eq!(golden_serialized, serialized); + } + } +} diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/field_elements.rs b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/field_elements.rs new file mode 100644 index 00000000000..8478bb99bb7 --- /dev/null +++ b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/field_elements.rs @@ -0,0 +1,139 @@ +use anyhow::anyhow; +use dusk_jubjub::{Fq as JubjubBase, Fr as JubjubScalar}; +use ff::Field; +use rand_core::{CryptoRng, RngCore}; + +use super::ProjectivePoint; +use crate::{StmResult, signature_scheme::SchnorrSignatureError}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct BaseFieldElement(pub(crate) JubjubBase); + +impl BaseFieldElement { + pub(crate) fn add(&self, other: &Self) -> Self { + BaseFieldElement(self.0 + other.0) + } + + pub(crate) fn sub(&self, other: &Self) -> Self { + BaseFieldElement(self.0 - other.0) + } + + pub(crate) fn mul(&self, other: &Self) -> Self { + BaseFieldElement(self.0 * other.0) + } + + pub(crate) fn square(&self) -> Self { + BaseFieldElement(self.0.square()) + } + + pub(crate) fn get_one() -> Self { + BaseFieldElement(JubjubBase::ONE) + } + + pub(crate) fn collect_coordinates_of_list_of_points( + point_list: &[ProjectivePoint], + ) -> Vec { + let mut coordinates: Vec = Vec::new(); + for point in point_list { + let (u, v) = point.get_coordinates(); + coordinates.push(u); + coordinates.push(v); + } + coordinates + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct ScalarFieldElement(pub(crate) JubjubScalar); + +impl ScalarFieldElement { + pub(crate) fn new_random_scalar(rng: &mut (impl RngCore + CryptoRng)) -> Self { + ScalarFieldElement(JubjubScalar::random(rng)) + } + + pub(crate) fn is_zero(&self) -> bool { + if self.0 == JubjubScalar::zero() { + return true; + } + false + } + + pub(crate) fn is_one(&self) -> bool { + if self.0 == JubjubScalar::one() { + return true; + } + false + } + + pub(crate) fn new_random_nonzero_scalar( + rng: &mut (impl RngCore + CryptoRng), + ) -> StmResult { + for _ in 0..100 { + let random_scalar = Self::new_random_scalar(rng); + if !random_scalar.is_zero() { + return Ok(random_scalar); + } + } + Err(anyhow!(SchnorrSignatureError::RandomScalarGenerationError)) + } + + pub(crate) fn sub(&self, other: &Self) -> Self { + ScalarFieldElement(self.0 - other.0) + } + + pub(crate) fn mul(&self, other: &Self) -> Self { + ScalarFieldElement(self.0 * other.0) + } + + pub(crate) fn to_bytes(self) -> [u8; 32] { + self.0.to_bytes() + } + + pub(crate) fn from_bytes(bytes: &[u8]) -> StmResult { + let mut scalar_bytes = [0u8; 32]; + scalar_bytes.copy_from_slice( + bytes + .get(..32) + .ok_or(SchnorrSignatureError::ScalarFieldElementSerializationError)?, + ); + + match JubjubScalar::from_bytes(&scalar_bytes).into_option() { + Some(scalar_field_element) => Ok(Self(scalar_field_element)), + None => Err(anyhow!( + SchnorrSignatureError::ScalarFieldElementSerializationError + )), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + mod golden { + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + + use super::*; + + const GOLDEN_JSON: &str = r#"[126, 191, 239, 197, 88, 151, 248, 254, 187, 143, 86, 35, 29, 62, 90, 13, 196, 71, 234, 5, 90, 124, 205, 194, 51, 192, 228, 133, 25, 140, 157, 7]"#; + + fn golden_value() -> ScalarFieldElement { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + ScalarFieldElement::new_random_nonzero_scalar(&mut rng).unwrap() + } + + #[test] + fn golden_conversions() { + let value = serde_json::from_str(GOLDEN_JSON) + .expect("This JSON deserialization should not fail"); + assert_eq!(golden_value(), value); + + let serialized = + serde_json::to_string(&value).expect("This JSON serialization should not fail"); + let golden_serialized = serde_json::to_string(&golden_value()) + .expect("This JSON serialization should not fail"); + assert_eq!(golden_serialized, serialized); + } + } +} diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/mod.rs b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/mod.rs new file mode 100644 index 00000000000..72b8c8e5475 --- /dev/null +++ b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/mod.rs @@ -0,0 +1,83 @@ +mod curve_points; +mod field_elements; +mod poseidon_digest; + +pub(crate) use curve_points::*; +pub(crate) use field_elements::*; +pub(crate) use poseidon_digest::*; + +use serde::{ + de::Visitor, + {Deserialize, Deserializer, Serialize, Serializer}, +}; + +// --------------------------------------------------------------------- +// Serde implementation +// --------------------------------------------------------------------- + +macro_rules! impl_serde { + ($st:ty,$visitor:ident,$size:expr) => { + impl Serialize for $st { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + use serde::ser::SerializeTuple; + let mut seq = serializer.serialize_tuple($size)?; + for e in self.to_bytes().iter() { + seq.serialize_element(e)?; + } + seq.end() + } + } + + impl<'de> Deserialize<'de> for $st { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct $visitor; + + impl<'de> Visitor<'de> for $visitor { + type Value = $st; + + fn expecting( + &self, + formatter: &mut ::core::fmt::Formatter, + ) -> ::core::fmt::Result { + formatter.write_str(format!("a signature {}", stringify!($st)).as_str()) + } + + fn visit_seq(self, mut seq: A) -> Result<$st, A::Error> + where + A: serde::de::SeqAccess<'de>, + { + let mut bytes = [0u8; $size]; + for i in 0..$size { + bytes[i] = + seq.next_element()?.ok_or(serde::de::Error::invalid_length( + i, + &format!("expected bytes{}", $size.to_string()).as_str(), + ))?; + } + <$st>::from_bytes(&bytes).map_err(|_| { + serde::de::Error::custom( + &format!("deserialization failed [{}]", stringify!($st)).as_str(), + ) + }) + } + } + + deserializer.deserialize_tuple($size, $visitor) + } + } + }; +} +impl_serde!(ScalarFieldElement, ScalarFieldElementVisitor, 32); +impl_serde!( + PrimeOrderProjectivePoint, + PrimeOrderProjectivePointVisitor, + 32 +); +impl_serde!(ProjectivePoint, ProjectivePointVisitor, 32); +// impl_serde!(BlsSignature, SignatureVisitor, 48); diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/poseidon_digest.rs b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/poseidon_digest.rs new file mode 100644 index 00000000000..73efda7715a --- /dev/null +++ b/mithril-stm/src/signature_scheme/schnorr_signature/jubjub/poseidon_digest.rs @@ -0,0 +1,14 @@ +use dusk_jubjub::Fq as JubjubBase; +use dusk_poseidon::{Domain, Hash}; + +use super::{BaseFieldElement, ScalarFieldElement}; + +/// A DST (Domain Separation Tag) to distinguish between use of Poseidon hash +const DST_SIGNATURE: JubjubBase = JubjubBase::from_raw([0u64, 0, 0, 0]); + +pub(crate) fn compute_truncated_digest(input: &[BaseFieldElement]) -> ScalarFieldElement { + let mut poseidon_input = vec![DST_SIGNATURE]; + poseidon_input.extend(input.iter().map(|i| i.0).collect::>()); + + ScalarFieldElement(Hash::digest_truncated(Domain::Other, &poseidon_input)[0]) +} diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/mod.rs b/mithril-stm/src/signature_scheme/schnorr_signature/mod.rs index 722e38d5df4..0304a789965 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/mod.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/mod.rs @@ -1,16 +1,214 @@ mod error; +mod jubjub; mod signature; mod signing_key; -mod utils; mod verification_key; pub use error::*; +pub(crate) use jubjub::*; pub use signature::*; pub use signing_key::*; -pub(crate) use utils::*; pub use verification_key::*; -use dusk_jubjub::Fq as JubjubBase; +#[cfg(test)] +mod tests { + use proptest::prelude::*; + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; -/// A DST (Domain Separation Tag) to distinguish between use of Poseidon hash -const DST_SIGNATURE: JubjubBase = JubjubBase::from_raw([0u64, 0, 0, 0]); + use crate::signature_scheme::{ + PrimeOrderProjectivePoint, ScalarFieldElement, SchnorrSignature, SchnorrSignatureError, + SchnorrSigningKey, SchnorrVerificationKey, + }; + + proptest! { + #![proptest_config(ProptestConfig::with_cases(50))] + + #[test] + fn verification_key(seed in any::<[u8;32]>()) { + // Valid generation check + let sk = SchnorrSigningKey::generate(&mut ChaCha20Rng::from_seed(seed)).unwrap(); + let g = PrimeOrderProjectivePoint::create_generator(); + let vk = g.scalar_multiplication(&sk.0); + let vk_from_sk = SchnorrVerificationKey::new_from_signing_key(sk).unwrap(); + assert_eq!(vk, vk_from_sk.0); + + // Check if sk is 0 + let mut bytes = [0u8; 32]; + let sk = SchnorrSigningKey(ScalarFieldElement::from_bytes(&bytes).unwrap()); + SchnorrVerificationKey::new_from_signing_key(sk).expect_err("Verification key should not be generated from zero signing key"); + + // Check if sk is 1 + bytes[0] = 1; + let sk = SchnorrSigningKey(ScalarFieldElement::from_bytes(&bytes).unwrap()); + SchnorrVerificationKey::new_from_signing_key(sk).expect_err("Verification key should not be generated from one signing key"); + } + + #[test] + fn valid_signing_verification( + msg in prop::collection::vec(any::(), 1..128), + seed in any::<[u8;32]>(), + ) { + let sk_result = SchnorrSigningKey::generate(&mut ChaCha20Rng::from_seed(seed)); + assert!(sk_result.is_ok(), "Secret ket generation failed"); + let sk = sk_result.unwrap(); + + let vk = SchnorrVerificationKey::new_from_signing_key(sk.clone()).unwrap(); + + let sig_result = sk.sign(&msg, &mut ChaCha20Rng::from_seed(seed)); + assert!(sig_result.is_ok(), "Signature generation failed"); + + let sig = sig_result.unwrap(); + + assert!(sig.verify(&msg, &vk).is_ok(), "Verification failed."); + } + + #[test] + fn invalid_signature(msg in prop::collection::vec(any::(), 1..128), seed in any::<[u8;32]>()) { + let mut rng = ChaCha20Rng::from_seed(seed); + let sk1 = SchnorrSigningKey::generate(&mut rng).unwrap(); + let vk1 = SchnorrVerificationKey::new_from_signing_key(sk1).unwrap(); + let sk2 = SchnorrSigningKey::generate(&mut rng).unwrap(); + let fake_sig = sk2.sign(&msg, &mut rng).unwrap(); + + let error = fake_sig.verify(&msg, &vk1).expect_err("Fake signature should not be verified"); + + assert!( + matches!( + error.downcast_ref::(), + Some(SchnorrSignatureError::SignatureInvalid(_)) + ), + "Unexpected error: {error:?}" + ); + } + + #[test] + fn signing_key_to_from_bytes(seed in any::<[u8;32]>()) { + let mut rng = ChaCha20Rng::from_seed(seed); + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + let mut sk_bytes = sk.to_bytes(); + + // Valid conversion + let recovered_sk = SchnorrSigningKey::from_bytes(&sk_bytes).unwrap(); + assert_eq!(sk.0, recovered_sk.0, "Recovered signing key does not match with the original!"); + + // Not enough bytes + let mut short_bytes = [0u8; 31]; + short_bytes.copy_from_slice(sk_bytes.get(..31).unwrap()); + let result = SchnorrSigningKey::from_bytes(&short_bytes).expect_err("From bytes conversion of signing key should fail"); + assert!( + matches!( + result.downcast_ref::(), + Some(SchnorrSignatureError::SerializationError) + ), + "Unexpected error: {result:?}" + ); + + // Invalid bytes + sk_bytes[31] |= 0xff; + let result = SchnorrSigningKey::from_bytes(&sk_bytes).expect_err("From bytes conversion of signing key should fail"); + assert!( + matches!( + result.downcast_ref::(), + Some(SchnorrSignatureError::ScalarFieldElementSerializationError) + ), + "Unexpected error: {result:?}" + ); + } + + #[test] + fn verification_key_to_from_bytes(seed in any::<[u8;32]>()) { + let mut rng = ChaCha20Rng::from_seed(seed); + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + let vk = SchnorrVerificationKey::new_from_signing_key(sk.clone()).unwrap(); + let mut vk_bytes = vk.to_bytes(); + + // Valid conversion + let recovered_vk = SchnorrVerificationKey::from_bytes(&vk_bytes).unwrap(); + assert_eq!(vk.0, recovered_vk.0, "Recovered verification key does not match with the original!"); + + // Not enough bytes + let mut short_bytes = [0u8; 31]; + short_bytes.copy_from_slice(vk_bytes.get(..31).unwrap()); + let result = SchnorrVerificationKey::from_bytes(&short_bytes).expect_err("From bytes conversion of verification key should fail"); + assert!( + matches!( + result.downcast_ref::(), + Some(SchnorrSignatureError::SerializationError) + ), + "Unexpected error: {result:?}" + ); + + // Invalid bytes + vk_bytes[31] |= 0xff; + let result = SchnorrVerificationKey::from_bytes(&vk_bytes).expect_err("From bytes conversion of verification key should fail"); + assert!( + matches!( + result.downcast_ref::(), + Some(SchnorrSignatureError::PrimeOrderProjectivePointSerializationError) + ), + "Unexpected error: {result:?}" + ); + } + + #[test] + fn signature_to_from_bytes(msg in prop::collection::vec(any::(), 1..128), seed in any::<[u8;32]>()) { + let mut rng = ChaCha20Rng::from_seed(seed); + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + let signature = sk.sign(&msg, &mut ChaCha20Rng::from_seed(seed)).unwrap(); + let signature_bytes = signature.to_bytes(); + + // Valid conversion + let recovered_signature = SchnorrSignature::from_bytes(&signature_bytes).unwrap(); + assert_eq!(signature, recovered_signature, "Recovered signature does not match with the original!"); + + // Test invalid `commitment_point` + let mut corrupted_bytes = signature_bytes; + corrupted_bytes[31] |= 0xff; + let result = SchnorrSignature::from_bytes(&corrupted_bytes).expect_err("From bytes conversion of signature should fail"); + assert!( + matches!( + result.downcast_ref::(), + Some(SchnorrSignatureError::ProjectivePointSerializationError) + ), + "Unexpected error: {result:?}" + ); + + // Test invalid `response` + let mut corrupted_bytes = signature_bytes; + corrupted_bytes[63] |= 0xff; + let result = SchnorrSignature::from_bytes(&corrupted_bytes).expect_err("From bytes conversion should fail"); + assert!( + matches!( + result.downcast_ref::(), + Some(SchnorrSignatureError::ScalarFieldElementSerializationError) + ), + "Unexpected error: {result:?}" + ); + + // Test invalid `challenge` + let mut corrupted_bytes = signature_bytes; + corrupted_bytes[95] |= 0xff; + let result = SchnorrSignature::from_bytes(&corrupted_bytes).expect_err("From bytes conversion should fail"); + assert!( + matches!( + result.downcast_ref::(), + Some(SchnorrSignatureError::ScalarFieldElementSerializationError) + ), + "Unexpected error: {result:?}" + ); + + // Not enough bytes + let mut short_bytes = [0u8; 95]; + short_bytes.copy_from_slice(signature_bytes.get(..95).unwrap()); + let result = SchnorrSignature::from_bytes(&short_bytes).expect_err("From bytes conversion of signature should fail"); + assert!( + matches!( + result.downcast_ref::(), + Some(SchnorrSignatureError::SerializationError) + ), + "Unexpected error: {result:?}" + ); + } + } +} diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs b/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs index 6ddf3bcfba2..1b0455e6510 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/signature.rs @@ -1,30 +1,25 @@ use anyhow::{Context, anyhow}; -use dusk_jubjub::{ - ExtendedPoint as JubjubExtended, Fq as JubjubBase, Fr as JubjubScalar, - SubgroupPoint as JubjubSubgroup, -}; -use dusk_poseidon::{Domain, Hash}; -use group::{Group, GroupEncoding}; +use serde::{Deserialize, Serialize}; use super::{ - DST_SIGNATURE, SchnorrSignatureError, SchnorrVerificationKey, get_coordinates_several_points, - is_on_curve, + BaseFieldElement, PrimeOrderProjectivePoint, ProjectivePoint, ScalarFieldElement, + SchnorrSignatureError, SchnorrVerificationKey, compute_truncated_digest, }; use crate::StmResult; /// Structure of the Schnorr signature to use with the SNARK /// -/// This signature includes a value `sigma` which depends only on +/// This signature includes a value `commitment_point` which depends only on /// the message and the signing key. /// This value is used in the lottery process to determine the correct indices. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct SchnorrSignature { /// Deterministic value depending on the message and secret key - pub(crate) sigma: JubjubExtended, + pub(crate) commitment_point: ProjectivePoint, /// Part of the Schnorr signature depending on the secret key - pub(crate) signature: JubjubScalar, + pub(crate) response: ScalarFieldElement, /// Part of the Schnorr signature NOT depending on the secret key - pub(crate) challenge: JubjubScalar, + pub(crate) challenge: ScalarFieldElement, } impl SchnorrSignature { @@ -39,56 +34,49 @@ impl SchnorrSignature { /// - Ok(()) if the signature verifies and an error if not /// /// The protocol computes: - /// - msg_hash = H(Sha256(msg)) - /// - random_point_1_recomputed = msg_hash * signature + sigma * challenge - /// - random_point_2_recomputed = generator * signature + verification_key * challenge + /// - msg_hash_point = H(Sha256(msg)) + /// - random_point_1_recomputed = response * msg_hash_point + challenge * commitment_point + /// - random_point_2_recomputed = response * prime_order_generator_point + challenge * verification_key /// - challenge_recomputed = Poseidon(DST || H(Sha256(msg)) || verification_key - /// || sigma || random_point_1_recomputed || random_point_2_recomputed) + /// || commitment_point || random_point_1_recomputed || random_point_2_recomputed) /// /// Check: challenge == challenge_recomputed /// pub fn verify(&self, msg: &[u8], verification_key: &SchnorrVerificationKey) -> StmResult<()> { - // Check that the verification key is on the curve - if !is_on_curve(verification_key.0.into()) { - return Err(anyhow!(SchnorrSignatureError::VerificationKeyInvalid( - Box::new(*verification_key) - ))); - } + // Check that the verification key is valid + verification_key + .is_valid() + .with_context(|| "Signature verification failed due to invalid verification key")?; - let generator = JubjubSubgroup::generator(); + let prime_order_generator_point = PrimeOrderProjectivePoint::create_generator(); // First hashing the message to a scalar then hashing it to a curve point - let msg_hash = JubjubExtended::hash_to_point(msg); + let msg_hash_point = ProjectivePoint::hash_to_projective_point(msg); - // Computing R1 = H(msg) * s + sigma * c - let msg_hash_times_signature = msg_hash * self.signature; - let sigma_times_challenge = self.sigma * self.challenge; - let random_point_1_recomputed = msg_hash_times_signature + sigma_times_challenge; + // Computing random_point_1_recomputed = response * H(msg) + challenge * commitment_point + let response_time_msg_hash_point = msg_hash_point.scalar_multiplication(&self.response); + let challenge_times_commitment_point = + self.commitment_point.scalar_multiplication(&self.challenge); + let random_point_1_recomputed = + response_time_msg_hash_point.add(challenge_times_commitment_point); - // Computing R2 = g * s + vk * c - let generator_times_signature = generator * self.signature; - let vk_times_challenge = verification_key.0 * self.challenge; - let random_point_2_recomputed = generator_times_signature + vk_times_challenge; + // Computing random_point_2_recomputed = response * prime_order_generator_point + challenge * vk + let response_times_generator_point = + prime_order_generator_point.scalar_multiplication(&self.response); + let challenge_times_vk = verification_key.0.scalar_multiplication(&self.challenge); + let random_point_2_recomputed = response_times_generator_point.add(challenge_times_vk); // Since the hash function takes as input scalar elements // We need to convert the EC points to their coordinates - let points_coordinates = get_coordinates_several_points(&[ - msg_hash, - verification_key.0.into(), - self.sigma, + let points_coordinates = BaseFieldElement::collect_coordinates_of_list_of_points(&[ + msg_hash_point, + ProjectivePoint::from_prime_order_projective_point(verification_key.0), + self.commitment_point, random_point_1_recomputed, - random_point_2_recomputed.into(), + ProjectivePoint::from_prime_order_projective_point(random_point_2_recomputed), ]); - let mut poseidon_input = vec![DST_SIGNATURE]; - poseidon_input.extend( - points_coordinates - .into_iter() - .flat_map(|(x, y)| [x, y]) - .collect::>(), - ); - - let challenge_recomputed = Hash::digest_truncated(Domain::Other, &poseidon_input)[0]; + let challenge_recomputed = compute_truncated_digest(&points_coordinates); if challenge_recomputed != self.challenge { return Err(anyhow!(SchnorrSignatureError::SignatureInvalid(Box::new( @@ -102,8 +90,8 @@ impl SchnorrSignature { /// Convert an `SchnorrSignature` into bytes. pub fn to_bytes(self) -> [u8; 96] { let mut out = [0; 96]; - out[0..32].copy_from_slice(&self.sigma.to_bytes()); - out[32..64].copy_from_slice(&self.signature.to_bytes()); + out[0..32].copy_from_slice(&self.commitment_point.to_bytes()); + out[32..64].copy_from_slice(&self.response.to_bytes()); out[64..96].copy_from_slice(&self.challenge.to_bytes()); out @@ -116,36 +104,38 @@ impl SchnorrSignature { .with_context(|| "Not enough bytes provided to create a signature."); } - let sigma_bytes = bytes[0..32] - .try_into() - .map_err(|_| anyhow!(SchnorrSignatureError::SerializationError)) - .with_context(|| "Failed to obtain sigma's bytes.")?; - let sigma = JubjubExtended::from_bytes(&sigma_bytes) - .into_option() - .ok_or(anyhow!(SchnorrSignatureError::SerializationError)) - .with_context(|| "Unable to convert bytes into a sigma value.")?; - - let signature_bytes = bytes[32..64] - .try_into() - .map_err(|_| anyhow!(SchnorrSignatureError::SerializationError)) - .with_context(|| "Failed to obtain signature's bytes.")?; - let signature = JubjubScalar::from_bytes(&signature_bytes) - .into_option() - .ok_or(anyhow!(SchnorrSignatureError::SerializationError)) - .with_context(|| "Unable to convert bytes into a signature value.")?; - - let challenge_bytes = bytes[64..96] - .try_into() - .map_err(|_| anyhow!(SchnorrSignatureError::SerializationError)) - .with_context(|| "Failed to obtain challenge's bytes.")?; - let challenge = JubjubScalar::from_bytes(&challenge_bytes) - .into_option() - .ok_or(anyhow!(SchnorrSignatureError::SerializationError)) - .with_context(|| "Unable to convert bytes into a challenge value.")?; + let mut u8bytes = [0u8; 32]; + + u8bytes.copy_from_slice( + bytes + .get(0..32) + .ok_or(SchnorrSignatureError::SerializationError) + .with_context(|| "Could not get the bytes of `commitment_point`")?, + ); + let commitment_point = ProjectivePoint::from_bytes(&u8bytes) + .with_context(|| "Could not convert bytes to `commitment_point`")?; + + u8bytes.copy_from_slice( + bytes + .get(32..64) + .ok_or(SchnorrSignatureError::SerializationError) + .with_context(|| "Could not get the bytes of `response`")?, + ); + let response = ScalarFieldElement::from_bytes(&u8bytes) + .with_context(|| "Could not convert the bytes to `response`")?; + + u8bytes.copy_from_slice( + bytes + .get(64..96) + .ok_or(SchnorrSignatureError::SerializationError) + .with_context(|| "Could not get the bytes of `challenge`")?, + ); + let challenge = ScalarFieldElement::from_bytes(&u8bytes) + .with_context(|| "Could not convert bytes to `challenge`")?; Ok(Self { - sigma, - signature, + commitment_point, + response, challenge, }) } @@ -154,87 +144,37 @@ impl SchnorrSignature { #[cfg(test)] mod tests { - use crate::{SchnorrSignature, SchnorrSigningKey, SchnorrVerificationKey}; - use rand_chacha::ChaCha20Rng; - use rand_core::SeedableRng; - - #[test] - fn invalid_sig() { - let msg = vec![0, 0, 0, 1]; - let msg2 = vec![0, 0, 0, 2]; - let seed = [0u8; 32]; - let mut rng = ChaCha20Rng::from_seed(seed); - let sk = SchnorrSigningKey::try_generate(&mut rng).unwrap(); - let vk = SchnorrVerificationKey::from(&sk); - let sk2 = SchnorrSigningKey::try_generate(&mut rng).unwrap(); - let vk2 = SchnorrVerificationKey::from(&sk2); - - let sig = sk.sign(&msg, &mut rng).unwrap(); - let sig2 = sk.sign(&msg2, &mut rng).unwrap(); - - // Wrong verification key is used - let result1 = sig.verify(&msg, &vk2); - let result2 = sig2.verify(&msg, &vk); - - result1.expect_err("Wrong verification key used, test should fail."); - // Wrong message is verified - result2.expect_err("Wrong message used, test should fail."); - } - - #[test] - fn serialize_deserialize_signature() { - let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - - let msg = vec![0, 0, 0, 1]; - let sk = SchnorrSigningKey::try_generate(&mut rng).unwrap(); - - let sig = sk.sign(&msg, &mut rng).unwrap(); - let sig_bytes: [u8; 96] = sig.to_bytes(); - let sig2 = SchnorrSignature::from_bytes(&sig_bytes).unwrap(); - - assert_eq!(sig, sig2); - } - - #[test] - fn from_bytes_signature_not_enough_bytes() { - let msg = vec![0u8; 95]; - - let result = SchnorrSignature::from_bytes(&msg); - - result.expect_err("Not enough bytes."); - } - mod golden { use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; - use crate::{SchnorrSignature, SchnorrSigningKey}; + use crate::signature_scheme::{SchnorrSignature, SchnorrSigningKey}; - const GOLDEN_BYTES: &[u8; 96] = &[ - 143, 53, 198, 62, 178, 1, 88, 253, 21, 92, 100, 13, 72, 180, 198, 127, 39, 175, 102, - 69, 147, 249, 244, 224, 122, 121, 248, 68, 217, 242, 158, 113, 94, 57, 200, 241, 208, - 145, 251, 8, 92, 119, 163, 38, 81, 85, 54, 36, 193, 221, 254, 242, 21, 129, 110, 161, - 142, 184, 107, 156, 100, 34, 190, 9, 200, 20, 178, 142, 61, 253, 193, 11, 5, 180, 97, - 73, 125, 88, 162, 36, 30, 177, 225, 52, 136, 21, 138, 93, 81, 23, 19, 64, 82, 78, 229, - 3, - ]; + const GOLDEN_JSON: &str = r#" + { + "commitment_point": [143, 53, 198, 62, 178, 1, 88, 253, 21, 92, 100, 13, 72, 180, 198, 127, 39, 175, 102, 69, 147, 249, 244, 224, 122, 121, 248, 68, 217, 242, 158, 113], + "response": [94, 57, 200, 241, 208, 145, 251, 8, 92, 119, 163, 38, 81, 85, 54, 36, 193, 221, 254, 242, 21, 129, 110, 161, 142, 184, 107, 156, 100, 34, 190, 9], + "challenge": [200, 20, 178, 142, 61, 253, 193, 11, 5, 180, 97, 73, 125, 88, 162, 36, 30, 177, 225, 52, 136, 21, 138, 93, 81, 23, 19, 64, 82, 78, 229, 3] + }"#; fn golden_value() -> SchnorrSignature { let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - let sk = SchnorrSigningKey::try_generate(&mut rng).unwrap(); + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); let msg = [0u8; 32]; sk.sign(&msg, &mut rng).unwrap() } #[test] fn golden_conversions() { - let value = SchnorrSignature::from_bytes(GOLDEN_BYTES) - .expect("This from bytes should not fail"); + let value = serde_json::from_str(GOLDEN_JSON) + .expect("This JSON deserialization should not fail"); assert_eq!(golden_value(), value); - let serialized = SchnorrSignature::to_bytes(value); - let golden_serialized = SchnorrSignature::to_bytes(golden_value()); + let serialized = + serde_json::to_string(&value).expect("This JSON serialization should not fail"); + let golden_serialized = serde_json::to_string(&golden_value()) + .expect("This JSON serialization should not fail"); assert_eq!(golden_serialized, serialized); } } diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/signing_key.rs b/mithril-stm/src/signature_scheme/schnorr_signature/signing_key.rs index ef2148f39d1..2f1913e55ed 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/signing_key.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/signing_key.rs @@ -1,54 +1,46 @@ use anyhow::{Context, anyhow}; -use dusk_jubjub::{ - ExtendedPoint as JubjubExtended, Fq as JubjubBase, Fr as JubjubScalar, - SubgroupPoint as JubjubSubgroup, -}; -use dusk_poseidon::{Domain, Hash}; -use group::Group; use rand_core::{CryptoRng, RngCore}; +use serde::{Deserialize, Serialize}; use super::{ - DST_SIGNATURE, SchnorrSignature, SchnorrSignatureError, SchnorrVerificationKey, - generate_non_zero_scalar, get_coordinates_several_points, + BaseFieldElement, PrimeOrderProjectivePoint, ProjectivePoint, ScalarFieldElement, + SchnorrSignature, SchnorrSignatureError, SchnorrVerificationKey, compute_truncated_digest, }; use crate::StmResult; /// Schnorr Signing key, it is essentially a random scalar of the Jubjub scalar field -#[derive(Debug, Clone)] -pub struct SchnorrSigningKey(pub(crate) JubjubScalar); +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct SchnorrSigningKey(pub(crate) ScalarFieldElement); impl SchnorrSigningKey { /// Generate a random scalar value to use as signing key - pub fn try_generate(rng: &mut R) -> StmResult { - let r = generate_non_zero_scalar(rng); - match r { - Ok(s) => Ok(SchnorrSigningKey(s)), - Err(_) => Err(anyhow!(SchnorrSignatureError::SigningKeyGenerationError)) - .with_context(|| "Failed to generate a non zero schnorr signing key."), - } + pub fn generate(rng: &mut R) -> StmResult { + let scalar = ScalarFieldElement::new_random_nonzero_scalar(rng) + .with_context(|| "Failed to generate a non zero Schnorr signing key.")?; + Ok(SchnorrSigningKey(scalar)) } /// This function is an adapted version of the Schnorr signature scheme that includes - /// the computation of a deterministic value (called sigma) based on the message and the signing key + /// the computation of a deterministic value (called commitment_point) based on the message and the signing key /// and works with the Jubjub elliptic curve and the Poseidon hash function. /// /// Input: /// - a message: some bytes /// - a secret key: an element of the scalar field of the Jubjub curve /// Output: - /// - a signature of the form (sigma, signature, challenge): - /// - sigma is deterministic depending only on the message and secret key - /// - the signature and challenge depends on a random value generated during the signature + /// - a signature of the form (commitment_point, response, challenge): + /// - commitment_point is deterministic depending only on the message and secret key + /// - the response and challenge depends on a random value generated during the signature /// /// The protocol computes: - /// - sigma = H(Sha256(msg)) * secret_key + /// - commitment_point = secret_key * H(Sha256(msg)) /// - random_scalar, a random value - /// - random_point_1 = H(Sha256(msg)) * random_scalar - /// - random_point_2 = generator * random_scalar, where generator is a generator of the prime-order subgroup of Jubjub - /// - challenge = Poseidon(DST || H(Sha256(msg)) || verification_key || sigma || random_point_1 || random_point_2) - /// - signature = random_scalar - challenge * signing_key + /// - random_point_1 = random_scalar * H(Sha256(msg)) + /// - random_point_2 = random_scalar * prime_order_generator_point, where generator is a generator of the prime-order subgroup of Jubjub + /// - challenge = Poseidon(DST || H(Sha256(msg)) || verification_key || commitment_point || random_point_1 || random_point_2) + /// - response = random_scalar - challenge * signing_key /// - /// Output the signature (sigma, signature, challenge) + /// Output the signature (commitment_point, response, challenge) /// pub fn sign( &self, @@ -56,45 +48,39 @@ impl SchnorrSigningKey { rng: &mut R, ) -> StmResult { // Use the subgroup generator to compute the curve points - let generator = JubjubSubgroup::generator(); - let verification_key = SchnorrVerificationKey::from(self); + let prime_order_generator_point = PrimeOrderProjectivePoint::create_generator(); + let verification_key = SchnorrVerificationKey::new_from_signing_key(self.clone()) + .with_context(|| "Could not generate verification key from signing key.")?; // First hashing the message to a scalar then hashing it to a curve point - let msg_hash = JubjubExtended::hash_to_point(msg); + let msg_hash_point = ProjectivePoint::hash_to_projective_point(msg); - let sigma = msg_hash * self.0; + let commitment_point = msg_hash_point.scalar_multiplication(&self.0); - // r1 = H(msg) * r, r2 = g * r - let random_scalar = generate_non_zero_scalar(rng) - .with_context(|| "Failed to generate a non zero scalar for the signature.")?; + let random_scalar = ScalarFieldElement::new_random_nonzero_scalar(rng) + .with_context(|| "Random scalar generation failed during signing.")?; - let random_point_1 = msg_hash * random_scalar; - let random_point_2 = generator * random_scalar; + let random_point_1 = msg_hash_point.scalar_multiplication(&random_scalar); + let random_point_2 = prime_order_generator_point.scalar_multiplication(&random_scalar); // Since the hash function takes as input scalar elements // We need to convert the EC points to their coordinates // The order must be preserved - let points_coordinates = get_coordinates_several_points(&[ - msg_hash, - verification_key.0.into(), - sigma, + let points_coordinates = BaseFieldElement::collect_coordinates_of_list_of_points(&[ + msg_hash_point, + ProjectivePoint::from_prime_order_projective_point(verification_key.0), + commitment_point, random_point_1, - random_point_2.into(), + ProjectivePoint::from_prime_order_projective_point(random_point_2), ]); - let mut poseidon_input = vec![DST_SIGNATURE]; - poseidon_input.extend( - points_coordinates - .into_iter() - .flat_map(|(x, y)| [x, y]) - .collect::>(), - ); - let challenge = Hash::digest_truncated(Domain::Other, &poseidon_input)[0]; - let signature = random_scalar - challenge * self.0; + let challenge = compute_truncated_digest(&points_coordinates); + let mut response = challenge.mul(&self.0); + response = random_scalar.sub(&response); Ok(SchnorrSignature { - sigma, - signature, + commitment_point, + response, challenge, }) } @@ -109,108 +95,43 @@ impl SchnorrSigningKey { /// The bytes must represent a Jubjub scalar or the conversion will fail pub fn from_bytes(bytes: &[u8]) -> StmResult { if bytes.len() < 32 { - return Err(anyhow!(SchnorrSignatureError::SerializationError)) - .with_context(|| "Not enough bytes provided to create a Schnorr signing key."); - } - - let signing_key_bytes = bytes[0..32] - .try_into() - .map_err(|_| anyhow!(SchnorrSignatureError::SerializationError)) - .with_context(|| "Failed to obtain the Schnorr signing key's bytes.")?; - - match JubjubScalar::from_bytes(&signing_key_bytes).into_option() { - Some(signing_key) => Ok(Self(signing_key)), - None => Err(anyhow!(SchnorrSignatureError::SerializationError)) - .with_context(|| "Failed to generate a Jubjub scalar from the given bytes."), + return Err(anyhow!(SchnorrSignatureError::SerializationError)).with_context( + || "Not enough bytes provided to re-construct a Schnorr signing key.", + ); } + let scalar_field_element = ScalarFieldElement::from_bytes(bytes) + .with_context(|| "Could not construct Schnorr signing key from given bytes.")?; + Ok(SchnorrSigningKey(scalar_field_element)) } } #[cfg(test)] mod tests { - pub(crate) use super::*; - use rand_chacha::ChaCha20Rng; - use rand_core::SeedableRng; - - #[test] - fn generate_signing_key() { - let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - let _sk = SchnorrSigningKey::try_generate(&mut rng); - } - - #[test] - fn sign_and_verify() { - let msg = vec![0, 0, 0, 1]; - let seed = [0u8; 32]; - let mut rng = ChaCha20Rng::from_seed(seed); - let sk = SchnorrSigningKey::try_generate(&mut rng).unwrap(); - let vk = SchnorrVerificationKey::from(&sk); - - let sig = sk.sign(&msg, &mut rng).unwrap(); - - sig.verify(&msg, &vk).unwrap(); - } - - #[test] - fn to_from_bytes() { - let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - let sk = SchnorrSigningKey::try_generate(&mut rng).unwrap(); - - let sk_bytes = sk.to_bytes(); - let recovered_sk = SchnorrSigningKey::from_bytes(&sk_bytes).unwrap(); - - assert_eq!(sk.0, recovered_sk.0); - } - - // Failing test as the generated bytes represent a value too big to be converted - // in this manner (greater than the jubjub modulus) - #[test] - fn failing_test_from_bytes() { - let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - let mut sk = [0; 32]; - rng.fill_bytes(&mut sk); - // Setting the msb to 1 to make sk bigger than the modulus - sk[0] |= 0xff; - - let result = SchnorrSigningKey::from_bytes(&sk); - - result.expect_err("Value is not a proper sk, test should fail."); - } - - #[test] - fn from_bytes_signing_key_not_enough_bytes() { - let msg = vec![0u8; 31]; - - let result = SchnorrSigningKey::from_bytes(&msg); - - result.expect_err("Not enough bytes."); - } mod golden { use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; - use crate::SchnorrSigningKey; + use crate::signature_scheme::SchnorrSigningKey; - const GOLDEN_BYTES: &[u8; 32] = &[ - 126, 191, 239, 197, 88, 151, 248, 254, 187, 143, 86, 35, 29, 62, 90, 13, 196, 71, 234, - 5, 90, 124, 205, 194, 51, 192, 228, 133, 25, 140, 157, 7, - ]; + const GOLDEN_JSON: &str = r#"[126, 191, 239, 197, 88, 151, 248, 254, 187, 143, 86, 35, 29, 62, 90, 13, 196, 71, 234, 5, 90, 124, 205, 194, 51, 192, 228, 133, 25, 140, 157, 7]"#; fn golden_value() -> SchnorrSigningKey { let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - SchnorrSigningKey::try_generate(&mut rng).unwrap() + SchnorrSigningKey::generate(&mut rng).unwrap() } #[test] fn golden_conversions() { - let value = SchnorrSigningKey::from_bytes(GOLDEN_BYTES) - .expect("This from bytes should not fail"); - assert_eq!(golden_value().0, value.0); - - let serialized = SchnorrSigningKey::to_bytes(&value); - let golden_serialized = SchnorrSigningKey::to_bytes(&golden_value()); + let value = serde_json::from_str(GOLDEN_JSON) + .expect("This JSON deserialization should not fail"); + assert_eq!(golden_value(), value); + + let serialized = + serde_json::to_string(&value).expect("This JSON serialization should not fail"); + let golden_serialized = serde_json::to_string(&golden_value()) + .expect("This JSON serialization should not fail"); assert_eq!(golden_serialized, serialized); } } diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/utils.rs b/mithril-stm/src/signature_scheme/schnorr_signature/utils.rs deleted file mode 100644 index 44b53f83181..00000000000 --- a/mithril-stm/src/signature_scheme/schnorr_signature/utils.rs +++ /dev/null @@ -1,97 +0,0 @@ -use anyhow::{Context, anyhow}; -use dusk_jubjub::{ - AffinePoint as JubjubAffine, EDWARDS_D, ExtendedPoint as JubjubExtended, Fq as JubjubBase, - Fr as JubjubScalar, -}; -use ff::Field; -use group::Curve; -use rand_core::{CryptoRng, RngCore}; - -use super::SchnorrSignatureError; -use crate::StmResult; - -/// Check if the given point is on the curve using its coordinates -pub fn is_on_curve(point: JubjubExtended) -> bool { - let point_affine_representation = JubjubAffine::from(point); - let (x, y) = ( - point_affine_representation.get_u(), - point_affine_representation.get_v(), - ); - let x_square = x.square(); - let y_square = y.square(); - - let lhs = y_square - x_square; - let rhs = JubjubBase::ONE + EDWARDS_D * x_square * y_square; - - lhs == rhs -} - -/// Extract the coordinates of given points in an Extended form -/// -/// This is mainly used to feed the Poseidon hash function, the order is maintained -/// from input to output which is important for the hash function -pub fn get_coordinates_several_points(points: &[JubjubExtended]) -> Vec<(JubjubBase, JubjubBase)> { - let mut points_affine = - vec![JubjubAffine::from_raw_unchecked(JubjubBase::ZERO, JubjubBase::ZERO); points.len()]; - JubjubExtended::batch_normalize(points, &mut points_affine); - - points_affine - .into_iter() - .map(|p| (p.get_u(), p.get_v())) - .collect::>() -} - -/// Generate a random non zero value from the scalar field of Jubjub -/// -/// Tries to generate 100 times a non zero value and returns an error if it fails to do so -pub fn generate_non_zero_scalar(rng: &mut R) -> StmResult { - for _ in 0..100 { - let random_scalar = JubjubScalar::random(&mut *rng); - if random_scalar != JubjubScalar::ZERO { - return Ok(random_scalar); - } - } - Err(anyhow!(SchnorrSignatureError::RandomScalarGenerationError)) - .with_context(|| "Failed to generate a non zero signing key after 100 attempts.") -} - -#[cfg(test)] -mod tests { - - use super::*; - use dusk_jubjub::{AffinePoint as JubjubAffine, ExtendedPoint as JubjubExtended}; - use group::Group; - use rand_chacha::ChaCha20Rng; - use rand_core::SeedableRng; - - #[test] - fn get_coordinates_from_several_points() { - let seed = [0u8; 32]; - let mut rng = ChaCha20Rng::from_seed(seed); - let points = vec![ - JubjubExtended::random(&mut rng), - JubjubExtended::random(&mut rng), - JubjubExtended::random(&mut rng), - ]; - - let coordinates = get_coordinates_several_points(&points); - - let mut coordinates_iter = coordinates.iter(); - for p in points.iter().take(3) { - let (x, y) = coordinates_iter.next().unwrap(); - let point_affine = JubjubAffine::from_raw_unchecked(*x, *y); - let point_extended = JubjubExtended::from_affine(point_affine); - assert_eq!(*p, point_extended); - } - } - - #[test] - fn generation_non_zero_scalar() { - let seed = [0u8; 32]; - let mut rng = ChaCha20Rng::from_seed(seed); - - let scalar = generate_non_zero_scalar(&mut rng).unwrap(); - - assert_ne!(scalar, JubjubScalar::ZERO); - } -} diff --git a/mithril-stm/src/signature_scheme/schnorr_signature/verification_key.rs b/mithril-stm/src/signature_scheme/schnorr_signature/verification_key.rs index a2b671fd7fc..b323ff5e0b9 100644 --- a/mithril-stm/src/signature_scheme/schnorr_signature/verification_key.rs +++ b/mithril-stm/src/signature_scheme/schnorr_signature/verification_key.rs @@ -1,16 +1,43 @@ -use anyhow::{Context, anyhow}; -use dusk_jubjub::SubgroupPoint as JubjubSubgroup; -use group::{Group, GroupEncoding}; +use anyhow::{Context, Ok, anyhow}; +use serde::{Deserialize, Serialize}; -use super::SchnorrSignatureError; +use super::{PrimeOrderProjectivePoint, ProjectivePoint, SchnorrSignatureError, SchnorrSigningKey}; use crate::StmResult; /// Schnorr verification key, it consists of a point on the Jubjub curve /// vk = g * sk, where g is a generator -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] -pub struct SchnorrVerificationKey(pub(crate) JubjubSubgroup); +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct SchnorrVerificationKey(pub(crate) PrimeOrderProjectivePoint); impl SchnorrVerificationKey { + /// Convert a Schnorr secret key into a verification key + /// + /// This is done by computing `vk = g * sk` where g is the generator + /// of the subgroup and sk is the schnorr secret key + pub fn new_from_signing_key(signing_key: SchnorrSigningKey) -> StmResult { + if signing_key.0.is_zero() | signing_key.0.is_one() { + return Err(anyhow!(SchnorrSignatureError::InvalidSigningKey)) + .with_context(|| "Verification key generation failed."); + } + let generator = PrimeOrderProjectivePoint::create_generator(); + + Ok(SchnorrVerificationKey( + generator.scalar_multiplication(&signing_key.0), + )) + } + + pub fn is_valid(&self) -> StmResult { + let projective_point = ProjectivePoint::from_prime_order_projective_point(self.0); + if !projective_point.is_prime_order() { + return Err(anyhow!(SchnorrSignatureError::PointIsNotPrimeOrder( + Box::new(self.0) + ))); + } + self.0.is_on_curve()?; + + Ok(*self) + } + /// Convert a `SchnorrVerificationKey` into bytes. pub fn to_bytes(self) -> [u8; 32] { self.0.to_bytes() @@ -22,92 +49,18 @@ impl SchnorrVerificationKey { pub fn from_bytes(bytes: &[u8]) -> StmResult { if bytes.len() < 32 { return Err(anyhow!(SchnorrSignatureError::SerializationError)).with_context( - || "Not enough bytes provided to create a Schnorr verification key.", + || "Not enough bytes provided to construct a Schnorr verification key.", ); } - let verification_key_bytes = bytes[0..32] - .try_into() - .map_err(|_| anyhow!(SchnorrSignatureError::SerializationError)) - .with_context(|| "Failed to obtain the Schnorr verification key's bytes.")?; - let point = JubjubSubgroup::from_bytes(&verification_key_bytes) - .into_option() - .ok_or(anyhow!(SchnorrSignatureError::SerializationError)) - .with_context(|| "Failed to create a JubjubSubgroup point from the given bytes.")?; - - Ok(SchnorrVerificationKey(point)) - } -} - -impl From<&crate::SchnorrSigningKey> for SchnorrVerificationKey { - /// Convert a Schnorr secret key into a verification key - /// - /// This is done by computing `vk = g * sk` where g is the generator - /// of the subgroup and sk is the schnorr secret key - fn from(signing_key: &crate::SchnorrSigningKey) -> Self { - let generator = JubjubSubgroup::generator(); + let prime_order_projective_point = PrimeOrderProjectivePoint::from_bytes(bytes) + .with_context(|| "Cannot construct Schnorr verification key from given bytes.")?; - SchnorrVerificationKey(generator * signing_key.0) + Ok(SchnorrVerificationKey(prime_order_projective_point)) } } #[cfg(test)] mod tests { - - use dusk_jubjub::Fq as JubjubBase; - use dusk_jubjub::SubgroupPoint as JubjubSubgroup; - use ff::Field; - use group::Group; - use rand_chacha::ChaCha20Rng; - use rand_core::SeedableRng; - - use crate::signature_scheme::{SchnorrSigningKey, SchnorrVerificationKey}; - - #[test] - fn generate_verification_key() { - let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - let sk = SchnorrSigningKey::try_generate(&mut rng).unwrap(); - let g = JubjubSubgroup::generator(); - let vk = g * sk.0; - - let vk_from_sk = SchnorrVerificationKey::from(&sk); - - assert_eq!(vk, vk_from_sk.0); - } - - #[test] - fn verify_fail_verification_key_not_on_curve() { - let msg = vec![0, 0, 0, 1]; - let seed = [0u8; 32]; - let mut rng = ChaCha20Rng::from_seed(seed); - let sk = SchnorrSigningKey::try_generate(&mut rng).unwrap(); - let vk1 = SchnorrVerificationKey::from(&sk); - let sig = sk.sign(&msg, &mut rng).unwrap(); - let vk2 = SchnorrVerificationKey(JubjubSubgroup::from_raw_unchecked( - JubjubBase::ONE, - JubjubBase::ONE, - )); - - let result1 = sig.verify(&msg, &vk1); - let result2 = sig.verify(&msg, &vk2); - - result1.expect("Correct verification key used, test should pass."); - - result2.expect_err("Invalid verification key used, test should fail."); - } - - #[test] - fn serialize_deserialize_vk() { - let seed = 0; - let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(seed); - let sk = SchnorrSigningKey::try_generate(&mut rng).unwrap(); - let vk = SchnorrVerificationKey::from(&sk); - - let vk_bytes = vk.to_bytes(); - let vk2 = SchnorrVerificationKey::from_bytes(&vk_bytes).unwrap(); - - assert_eq!(vk.0, vk2.0); - } - mod golden { use rand_chacha::ChaCha20Rng; @@ -115,25 +68,24 @@ mod tests { use crate::signature_scheme::{SchnorrSigningKey, SchnorrVerificationKey}; - const GOLDEN_BYTES: &[u8; 32] = &[ - 144, 52, 95, 161, 127, 253, 49, 32, 140, 217, 231, 207, 32, 238, 244, 196, 97, 241, 47, - 95, 101, 9, 70, 136, 194, 66, 187, 253, 200, 32, 218, 43, - ]; + const GOLDEN_JSON: &str = r#"[144, 52, 95, 161, 127, 253, 49, 32, 140, 217, 231, 207, 32, 238, 244, 196, 97, 241, 47, 95, 101, 9, 70, 136, 194, 66, 187, 253, 200, 32, 218, 43]"#; fn golden_value() -> SchnorrVerificationKey { let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - let sk = SchnorrSigningKey::try_generate(&mut rng).unwrap(); - SchnorrVerificationKey::from(&sk) + let sk = SchnorrSigningKey::generate(&mut rng).unwrap(); + SchnorrVerificationKey::new_from_signing_key(sk).unwrap() } #[test] fn golden_conversions() { - let value = SchnorrVerificationKey::from_bytes(GOLDEN_BYTES) - .expect("This from bytes should not fail"); + let value = serde_json::from_str(GOLDEN_JSON) + .expect("This JSON deserialization should not fail"); assert_eq!(golden_value(), value); - let serialized = SchnorrVerificationKey::to_bytes(value); - let golden_serialized = SchnorrVerificationKey::to_bytes(golden_value()); + let serialized = + serde_json::to_string(&value).expect("This JSON serialization should not fail"); + let golden_serialized = serde_json::to_string(&golden_value()) + .expect("This JSON serialization should not fail"); assert_eq!(golden_serialized, serialized); } }