From c22eed8245120fd4be3c24bc236749e78d05a361 Mon Sep 17 00:00:00 2001 From: Skalman Date: Wed, 13 May 2026 11:41:41 -0400 Subject: [PATCH 1/7] Addressing [Wildcard match arm defeats exhaustiveness check](https://github.com/paritytech/srlabs_findings/issues/660) --- src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c97a2e6..d839f4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -215,7 +215,8 @@ impl Message { fn cipher_suite(&self) -> Vec { let id = match self.2 { MessageType::ProofOfPossession => PROOF_OF_POSSESSION_ID, - _ => NORMAL_MESSAGE_SIGNATURE_ID, + MessageType::NormalAssumingPoP => NORMAL_MESSAGE_SIGNATURE_ID, + MessageType::NormalBasic => NORMAL_MESSAGE_SIGNATURE_ID, }; let h2c_suite_id = [ @@ -228,7 +229,7 @@ impl Message { let sc_tag = match self.2 { MessageType::ProofOfPossession => POP_MESSAGE, MessageType::NormalAssumingPoP => NORMAL_MESSAGE_SIGNATURE_ASSUMING_POP, - _ => NORMAL_MESSAGE_SIGNATURE_BASIC, + MessageType::NormalBasic => NORMAL_MESSAGE_SIGNATURE_BASIC, }; [id, &h2c_suite_id[..], sc_tag].concat() From a5bd782ce7f8cfaba3023d0803eded9c6ff8ad05 Mon Sep 17 00:00:00 2001 From: Skalman Date: Tue, 19 May 2026 00:07:35 -0400 Subject: [PATCH 2/7] Add ValidateKey Step according to IETF BLS draft and tests. Resolving https://github.com/paritytech/srlabs_findings/issues/661 --- src/engine.rs | 13 ++++- src/experimental/bit.rs | 8 ++- src/nugget_pop.rs | 40 +++++++++++++- src/single.rs | 27 +++++++++- src/verifiers.rs | 113 ++++++++++++++++++++++++++++++++++------ 5 files changed, 179 insertions(+), 22 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 0c7dfa2..73bff45 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -32,7 +32,7 @@ use ark_ec::{ }; use ark_ff::field_hashers::{DefaultFieldHasher, HashToField}; use ark_ff::{Field, PrimeField, UniformRand, Zero}; -use ark_serialize::CanonicalSerialize; +use ark_serialize::{CanonicalSerialize, Valid}; use rand::Rng; use rand_core::RngCore; @@ -219,6 +219,17 @@ pub trait EngineBLS { Self::PublicKeyPrepared::from(g_affine) } + /// Reject public keys that are the identity element or not in the + /// prime-order subgroup. Defends verification against inputs that + /// bypass the deserialization-time check — e.g. direct tuple-struct + /// construction, or aggregates that sum to the identity. + /// + /// Implements `KeyValidate` from + /// . + fn validate_public_key(g: &Self::PublicKeyGroupAffine) -> bool { + !g.is_zero() && g.check().is_ok() + } + /// Process the signature to be use in pairing. This has to be /// implemented by the type of BLS system implementing the engine /// by calling either prepare_g1 or prepare_g2 based on which group diff --git a/src/experimental/bit.rs b/src/experimental/bit.rs index 27c91b5..1f04db7 100644 --- a/src/experimental/bit.rs +++ b/src/experimental/bit.rs @@ -632,7 +632,9 @@ mod tests { .collect::>(); let mut bitsig1 = BitSignedMessage::::new(pop.clone(), &msg1); - assert!(bitsig1.verify()); // verifiers::verify_with_distinct_messages(&dms,true) + // Empty aggregate: the summed public key is the identity element, + // which `validate_public_key` rejects per IETF KeyValidate. + assert!(!bitsig1.verify()); for (i, sig) in sigs1.iter().enumerate().take(2) { assert!(bitsig1.add(sig).is_ok() == (i < 4)); assert!(bitsig1.verify()); // verifiers::verify_with_distinct_messages(&dms,true) @@ -687,7 +689,9 @@ mod tests { let mut countsig = CountSignedMessage::::new(pop.clone(), msg1); assert!(countsig.signers.len() == 1); - assert!(countsig.verify()); // verifiers::verify_with_distinct_messages(&dms,true) + // Empty aggregate: the summed public key is the identity element, + // which `validate_public_key` rejects per IETF KeyValidate. + assert!(!countsig.verify()); assert!(countsig.add_bitsig(&bitsig1).is_ok()); assert!(bitsig1.signature == countsig.signature); assert!(countsig.signers.len() == 1); diff --git a/src/nugget_pop.rs b/src/nugget_pop.rs index c05f2f3..9ec7126 100644 --- a/src/nugget_pop.rs +++ b/src/nugget_pop.rs @@ -92,7 +92,11 @@ impl let mut randomized_pub_in_g1 = public_key_in_signature_group; randomized_pub_in_g1 *= randomization_coefficient; let signature = E::prepare_signature(self.0 + randomized_pub_in_g1); - let prepared_public_key = E::prepare_public_key(public_key_of_prover.1); + let Some(prepared_public_key) = + crate::verifiers::validate_and_prepare_public_key::(public_key_of_prover.1) + else { + return false; + }; let prepared = [ ( prepared_public_key.clone(), @@ -162,12 +166,13 @@ where #[cfg(all(test, feature = "std"))] mod tests { use crate::double_nugget::DoubleNuggetBLS; - use crate::engine::TinyBLS381; + use crate::engine::{EngineBLS, TinyBLS381}; use crate::serialize::SerializableToBytes; use crate::single::Keypair; use crate::{nugget_pop::NuggetBLSPoP, NuggetDoublePublicKey}; use crate::{ProofOfPossession, ProofOfPossessionGenerator}; + use ark_ff::Zero; use rand::thread_rng; use sha2::Sha256; @@ -324,4 +329,35 @@ mod tests { NuggetBLSnCPPoP, >(); } + + /// A keypair with secret scalar zero and identity public key. + /// `SecretKeyVT::sign` returns `sk * H(msg) = identity` for such + /// a keypair, so the PoP produced by `generate_pok` is itself + /// identity. The pairing equation in `NuggetBLSPoP::verify` then + /// reduces to `identity == identity`, so without `validate_public_key` + /// the verifier would accept — rejection here is attributable to + /// the public key check. + #[test] + fn nugget_bls_pop_rejects_identity_pk() { + use crate::single::{PublicKey, SecretKeyVT}; + let mut keypair = Keypair:: { + public: PublicKey::( + ::PublicKeyGroup::zero(), + ), + secret: SecretKeyVT::(::Scalar::zero()) + .into_split(thread_rng()), + }; + let pop = as ProofOfPossessionGenerator< + TinyBLS381, + Sha256, + NuggetDoublePublicKey, + NuggetBLSPoP, + >>::generate_pok(&mut keypair); + let double_pk = DoubleNuggetBLS::into_nugget_double_public_key(&keypair); + assert!(! as ProofOfPossession< + TinyBLS381, + Sha256, + NuggetDoublePublicKey, + >>::verify(&pop, &double_pk)); + } } diff --git a/src/single.rs b/src/single.rs index 7b826b3..0b220f7 100644 --- a/src/single.rs +++ b/src/single.rs @@ -458,7 +458,11 @@ impl Signature { /// Verify a single BLS signature pub fn verify(&self, message: &Message, publickey: &PublicKey) -> bool { - let publickey = E::prepare_public_key(publickey.0); + let Some(publickey) = + crate::verifiers::validate_and_prepare_public_key::(publickey.0) + else { + return false; + }; // TODO: Bentchmark these two variants // Variant 1. Do not batch any normalizations let message = E::prepare_signature(message.hash_to_signature_curve::()); @@ -992,4 +996,25 @@ mod tests { random_seed.as_slice(), ); } + + /// Keypair with secret=0, public=identity produces an identity + /// signature, so the pairing equation reduces to `identity == identity` + /// — an unprotected `Signature::verify` would accept. Rejection here + /// is attributable to `validate_public_key`. + #[test] + fn signature_verify_rejects_identity_pk() { + use ark_ff::Zero; + use rand::{rngs::StdRng, SeedableRng}; + + type EB = UsualBLS; + + let mut keypair = Keypair:: { + public: PublicKey::(::PublicKeyGroup::zero()), + secret: SecretKeyVT::(::Scalar::zero()) + .into_split(StdRng::from_seed([0u8; 32])), + }; + let s = keypair.signed_message(&Message::new(b"ctx", b"test message")); + assert!(!s.signature.verify(&s.message, &s.publickey)); + assert!(!Signed::verify(&s)); + } } diff --git a/src/verifiers.rs b/src/verifiers.rs index 20fbb05..927a410 100644 --- a/src/verifiers.rs +++ b/src/verifiers.rs @@ -37,6 +37,22 @@ pub type SignatureAffine = <::SignatureGroup as CurveGroup>:: // ── Shared helpers ────────────────────────────────────────────────── +/// Validate a public key with `EngineBLS::validate_public_key` and, on +/// success, return the prepared form. Returns `None` if the public key +/// is the identity element or not in the prime-order subgroup. All +/// verifier paths should go through this wrapper so the check cannot +/// be skipped by callers constructing `PublicKey` directly or by +/// aggregates that sum to the identity. +pub fn validate_and_prepare_public_key( + g: impl Into>, +) -> Option { + let g_affine: PublicKeyAffine = g.into(); + if !E::validate_public_key(&g_affine) { + return None; + } + Some(E::prepare_public_key(g_affine)) +} + /// Verify from fully normalized (affine) inputs. /// All public keys, messages, and the signature must already be in affine form. /// This prepares the pairing inputs and calls `verify_prepared`. @@ -46,11 +62,13 @@ fn verify_normalized( affine_signature: SignatureAffine, ) -> bool { let prepared_sig = E::prepare_signature(affine_signature); - let prepared = affine_publickeys - .iter() - .zip(affine_messages) - .map(|(pk, m)| (E::prepare_public_key(*pk), E::prepare_signature(*m))) - .collect::>(); + let mut prepared = Vec::with_capacity(affine_publickeys.len()); + for (pk, m) in affine_publickeys.iter().zip(affine_messages) { + let Some(prepared_pk) = validate_and_prepare_public_key::(*pk) else { + return false; + }; + prepared.push((prepared_pk, E::prepare_signature(*m))); + } E::verify_prepared(prepared_sig, prepared.iter()) } @@ -143,15 +161,18 @@ fn normalize_messages_and_signature( /// Simple unoptimized BLS signature verification. Useful for testing. pub fn verify_unoptimized(s: S) -> bool { let signature = S::E::prepare_signature(s.signature().0); - let prepared = s - .messages_and_publickeys() - .map(|(message, public_key)| { - ( - S::E::prepare_public_key(public_key.borrow().0), - S::E::prepare_signature(message.borrow().hash_to_signature_curve::()), - ) - }) - .collect::>(); + let mut prepared = Vec::new(); + for (message, public_key) in s.messages_and_publickeys() { + let Some(prepared_pk) = + validate_and_prepare_public_key::(public_key.borrow().0) + else { + return false; + }; + prepared.push(( + prepared_pk, + S::E::prepare_signature(message.borrow().hash_to_signature_curve::()), + )); + } S::E::verify_prepared(signature, prepared.iter()) } @@ -365,13 +386,47 @@ fn verify_with_gaussian_elimination(s: S) -> bool { #[cfg(test)] mod tests { use super::*; - use crate::{Keypair, Message, UsualBLS}; - use ark_bls12_381::Bls12_381; + use crate::single::{SecretKeyVT, SignedMessage}; + use crate::{Keypair, Message, PublicKey, UsualBLS}; + use ark_bls12_381::{Bls12_381, Fq, G1Affine}; + use ark_ec::AffineRepr; + use ark_ff::{UniformRand, Zero}; + use ark_serialize::Valid; use rand::rngs::StdRng; use rand::SeedableRng; type EB = UsualBLS; + /// Keypair with secret scalar zero and identity public key. The + /// produced signature is also identity, so every pairing equation + /// reduces to `identity == identity` and an unprotected verifier + /// would accept. Any rejection observed in the verifier tests + /// below is therefore attributable to `validate_public_key`. + fn identity_keypair() -> Keypair { + Keypair { + public: PublicKey::(::PublicKeyGroup::zero()), + secret: SecretKeyVT::(::Scalar::zero()) + .into_split(StdRng::from_seed([0u8; 32])), + } + } + + fn identity_signed() -> SignedMessage { + identity_keypair().signed_message(&Message::new(b"ctx", b"test message")) + } + + /// A G1 point on the curve but outside the prime-order subgroup. + fn non_subgroup_g1() -> G1Affine { + let mut rng = StdRng::from_seed([42u8; 32]); + loop { + let x = Fq::rand(&mut rng); + if let Some(point) = G1Affine::get_point_from_x_unchecked(x, false) { + if point.check().is_err() { + return point; + } + } + } + } + #[test] fn verify_simple_single_signature() { let good = Message::new(b"ctx", b"test message"); @@ -401,4 +456,30 @@ mod tests { let signed = keypair.signed_message(&good); assert!(verify_unoptimized(&signed)); } + + #[test] + fn verify_simple_rejects_identity_pk() { + assert!(!verify_simple(&identity_signed())); + } + + #[test] + fn verify_unoptimized_rejects_identity_pk() { + assert!(!verify_unoptimized(&identity_signed())); + } + + #[test] + fn verify_with_distinct_messages_rejects_identity_pk() { + assert!(!verify_with_distinct_messages(&identity_signed(), true)); + } + + #[test] + fn validate_and_prepare_public_key_rejects_identity() { + let identity = PublicKeyAffine::::zero(); + assert!(validate_and_prepare_public_key::(identity).is_none()); + } + + #[test] + fn validate_and_prepare_public_key_rejects_non_subgroup() { + assert!(validate_and_prepare_public_key::(non_subgroup_g1()).is_none()); + } } From 5c30868de44362a8b2fe24d41766d40bf74447b7 Mon Sep 17 00:00:00 2001 From: Skalman Date: Tue, 19 May 2026 00:20:19 -0400 Subject: [PATCH 3/7] Fail verification instead of panicing when final exponentiation fails addressing https://github.com/paritytech/srlabs_findings/issues/666 --- src/engine.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 73bff45..7079bcd 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -198,8 +198,7 @@ pub trait EngineBLS { signature, )]; Self::final_exponentiation(Self::miller_loop(inputs.into_iter().map(|t| t).chain(&lhs))) - .unwrap() - == (PairingOutput::::zero()) //zero is the target_field::one !! + == Some(PairingOutput::::zero()) //zero is the target_field::one !! } /// Prepared negative of the generator of the public key curve. From 07fdfcff8efc2d2c96766760d70cdc2cf779e16f Mon Sep 17 00:00:00 2001 From: Skalman Date: Tue, 19 May 2026 01:07:48 -0400 Subject: [PATCH 4/7] Add side-channel protection to NuggetBLS::sign resolving https://github.com/paritytech/srlabs_findings/issues/673 --- src/chaum_pedersen_signature.rs | 49 ++++++++++++++++++++++++++++++++- src/nugget.rs | 28 ++++++++++++++++--- src/single.rs | 39 ++++++++++++++------------ 3 files changed, 94 insertions(+), 22 deletions(-) diff --git a/src/chaum_pedersen_signature.rs b/src/chaum_pedersen_signature.rs index ab4709a..6b840a9 100644 --- a/src/chaum_pedersen_signature.rs +++ b/src/chaum_pedersen_signature.rs @@ -13,7 +13,7 @@ use crate::dual_scalar_mul::DualScalarMultiplication; use crate::engine::EngineBLS; use crate::nugget::{NuggetBLS, NuggetPublicKey, NuggetSignature}; use crate::serialize::SerializableToBytes; -use crate::{Message, SecretKeyVT}; +use crate::{Message, SecretKey, SecretKeyVT}; pub type DLEQProof = (::Scalar, ::Scalar); pub type ChaumPedersenSignature = NuggetSignature; @@ -336,6 +336,53 @@ where } } +/// Side-channel-protected variant: BLS signing goes through +/// `SecretKey::seeded_sign` (resplits the key with deterministic +/// randomness), while DLEQ proof generation and witness derivation +/// delegate to the vartime form, which is acceptable because they +/// operate over auxiliary scalars rather than the long-term key. +impl + ChaumPedersenSigner for SecretKey +where + S: PrimeGroup + SerializableToBytes, +{ + fn generate_cp_signature(&mut self, message: &Message) -> ChaumPedersenSignature { + let bls_signature = self.seeded_sign(message); + let dleq = >::generate_dleq_proof( + self, + message, + bls_signature.0, + ); + NuggetSignature(bls_signature.0, dleq) + } + + fn generate_dleq_proof( + &mut self, + message: &Message, + bls_signature: E::SignatureGroup, + ) -> DLEQProof { + as ChaumPedersenSigner>::generate_dleq_proof( + &mut self.into_vartime(), + message, + bls_signature, + ) + } + + fn generate_witness_scaler( + &self, + _message_point_as_bytes: &Vec, + ) -> <::PublicKeyGroup as PrimeGroup>::ScalarField { + // Unreachable: `generate_dleq_proof` for `SecretKey` delegates to + // the `SecretKeyVT` impl, which calls its own `generate_witness_scaler`. + // Trait coherence forces us to provide a body here, but no caller + // ever lands on it. + unimplemented!( + "SecretKey::generate_witness_scaler is never called; \ + dleq generation delegates to SecretKeyVT" + ) + } +} + #[cfg(all(test, feature = "std"))] mod tests { use rand::thread_rng; diff --git a/src/nugget.rs b/src/nugget.rs index 0c7df81..734f36f 100644 --- a/src/nugget.rs +++ b/src/nugget.rs @@ -25,7 +25,7 @@ use crate::chaum_pedersen_signature::{ChaumPedersenSigner, ChaumPedersenVerifier use crate::dual_scalar_mul::DualScalarMultiplication; use crate::chaum_pedersen_signature::DLEQProof; use crate::serialize::SerializableToBytes; -use crate::single::{Keypair, KeypairVT, PublicKey, SecretKeyVT, Signature}; +use crate::single::{Keypair, KeypairVT, PublicKey, SecretKey, SecretKeyVT, Signature}; use crate::{EngineBLS, Message, Signed}; /// Wrapper for a point in the signature group which is supposed to @@ -89,6 +89,26 @@ where } } +/// Side-channel-protected variant: signing goes through the +/// `ChaumPedersenSigner` impl for `SecretKey`, so the resplit happens +/// on the split key (no `into_vartime` conversion is done here). +impl NuggetBLS for SecretKey +where + S: PrimeGroup + SerializableToBytes, +{ + fn into_public_key_in_signature_group(&self) -> PublicKeyInSignatureGroup { + NuggetBLS::::into_public_key_in_signature_group(&self.into_vartime()) + } + + fn into_public_key_in_sister_group(&self) -> PublicKeyInSisterGroup { + self.into_vartime().into_public_key_in_sister_group() + } + + fn sign(&mut self, message: &Message) -> NuggetSignature { + ChaumPedersenSigner::::generate_cp_signature(self, &message) + } +} + impl NuggetBLS for KeypairVT where S: PrimeGroup + SerializableToBytes, @@ -112,16 +132,16 @@ where S: PrimeGroup + SerializableToBytes, { fn into_public_key_in_signature_group(&self) -> PublicKeyInSignatureGroup { - NuggetBLS::::into_public_key_in_signature_group(&self.into_vartime()) + NuggetBLS::::into_public_key_in_signature_group(&self.secret) } fn into_public_key_in_sister_group(&self) -> PublicKeyInSisterGroup { - self.into_vartime().into_public_key_in_sister_group() + NuggetBLS::::into_public_key_in_sister_group(&self.secret) } /// Sign a message using a Seedabale RNG created from a seed derived from the message and key fn sign(&mut self, message: &Message) -> NuggetSignature { - NuggetBLS::::sign(&mut self.into_vartime(), message) + NuggetBLS::::sign(&mut self.secret, message) } } diff --git a/src/single.rs b/src/single.rs index 0b220f7..573bda1 100644 --- a/src/single.rs +++ b/src/single.rs @@ -269,6 +269,27 @@ impl SecretKey { self.sign_once(message) } + /// Sign deterministically, reseeding the resplit RNG from a hash + /// of both key halves and the message. + pub fn seeded_sign(&mut self, message: &Message) -> Signature { + let mut serialized_part1 = [0u8; 32]; + let mut serialized_part2 = [0u8; 32]; + self.key[0] + .serialize_compressed(&mut serialized_part1[..]) + .unwrap(); + self.key[1] + .serialize_compressed(&mut serialized_part2[..]) + .unwrap(); + + let seed_digest = Sha256::new() + .chain_update(serialized_part1) + .chain_update(serialized_part2) + .chain_update(message.0); + + let seed: [u8; 32] = seed_digest.finalize().into(); + self.sign(message, StdRng::from_seed(seed)) + } + /// Derive our public key from our secret key /// /// We do not resplit for side channel protections here since @@ -605,23 +626,7 @@ impl Keypair { /// Sign a message using a Seedabale RNG created from a seed derived from the message and key pub fn sign(&mut self, message: &Message) -> Signature { - let mut serialized_part1 = [0u8; 32]; - let mut serialized_part2 = [0u8; 32]; - self.secret.key[0] - .serialize_compressed(&mut serialized_part1[..]) - .unwrap(); - self.secret.key[1] - .serialize_compressed(&mut serialized_part2[..]) - .unwrap(); - - let seed_digest = Sha256::new() - .chain_update(serialized_part1) - .chain_update(serialized_part2) - .chain_update(message.0); - - let seed: [u8; 32] = seed_digest.finalize().into(); - - self.sign_with_rng::(message, SeedableRng::from_seed(seed)) + self.secret.seeded_sign(message) } #[cfg(feature = "std")] From f614d3ce73a236d9402108eb28b46c5d619ee495 Mon Sep 17 00:00:00 2001 From: Skalman Date: Thu, 21 May 2026 10:06:54 -0400 Subject: [PATCH 5/7] Add zeroize to secret keys and zeroize it serializations for generating cp secrets. Resolves https://github.com/paritytech/srlabs_findings/issues/675 --- src/chaum_pedersen_signature.rs | 6 ++++-- src/experimental/schnorr_pop.rs | 4 +++- src/single.rs | 15 ++++++++++----- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/chaum_pedersen_signature.rs b/src/chaum_pedersen_signature.rs index 6b840a9..e217953 100644 --- a/src/chaum_pedersen_signature.rs +++ b/src/chaum_pedersen_signature.rs @@ -322,10 +322,12 @@ where &self, message_point_as_bytes: &Vec, ) -> <::PublicKeyGroup as PrimeGroup>::ScalarField { - let secret_key_as_bytes = self.to_bytes(); - let mut secret_key_hasher = H::default(); + + let mut secret_key_as_bytes = self.to_bytes(); secret_key_hasher.update(secret_key_as_bytes.as_slice()); + ::zeroize::Zeroize::zeroize(secret_key_as_bytes.as_mut_slice()); + let hashed_secret_key = secret_key_hasher.finalize_fixed_reset().to_vec(); let hasher = as HashToField< diff --git a/src/experimental/schnorr_pop.rs b/src/experimental/schnorr_pop.rs index e06371d..b486f8f 100644 --- a/src/experimental/schnorr_pop.rs +++ b/src/experimental/schnorr_pop.rs @@ -41,11 +41,13 @@ impl BLSSchnorrPo //The pseudo random witness is generated similar to eddsa witness //hash(secret_key|publick_key) fn witness_scalar(&self) -> <::PublicKeyGroup as PrimeGroup>::ScalarField { - let secret_key_as_bytes = self.secret.to_bytes(); + let mut secret_key_as_bytes = self.secret.to_bytes(); let public_key_as_bytes = ::public_key_point_to_byte(&self.public.0); let mut secret_key_hasher = H::default(); secret_key_hasher.update(secret_key_as_bytes.as_slice()); + ::zeroize::Zeroize::zeroize(secret_key_as_bytes.as_mut_slice()); + let hashed_secret_key = secret_key_hasher.finalize_fixed_reset().to_vec(); let hasher = as HashToField< diff --git a/src/single.rs b/src/single.rs index 573bda1..6eeb967 100644 --- a/src/single.rs +++ b/src/single.rs @@ -45,6 +45,7 @@ use sha3::{ }; use digest::Digest; +use zeroize::{Zeroize, ZeroizeOnDrop}; use core::iter::once; @@ -54,8 +55,8 @@ use crate::{EngineBLS, Message, Signed}; /// Secret signing key lacking the side channel protections from /// key splitting. Avoid using directly in production. -#[derive(CanonicalSerialize, CanonicalDeserialize)] -pub struct SecretKeyVT(pub E::Scalar); +#[derive(CanonicalSerialize, CanonicalDeserialize, Zeroize, ZeroizeOnDrop)] +pub struct SecretKeyVT(#[zeroize] pub E::Scalar); impl Clone for SecretKeyVT { fn clone(&self) -> Self { @@ -163,10 +164,11 @@ impl SecretKeyVT { /// Secret signing key including the side channel protections from /// key splitting. +#[derive(ZeroizeOnDrop)] pub struct SecretKey { - key: [E::Scalar; 2], - old_unsigned: E::SignatureGroup, - old_signed: E::SignatureGroup, + #[zeroize] key: [E::Scalar; 2], + #[zeroize] old_unsigned: E::SignatureGroup, + #[zeroize] old_signed: E::SignatureGroup, } impl Clone for SecretKey { @@ -286,6 +288,9 @@ impl SecretKey { .chain_update(serialized_part2) .chain_update(message.0); + ::zeroize::Zeroize::zeroize(&mut serialized_part1); + ::zeroize::Zeroize::zeroize(&mut serialized_part2); + let seed: [u8; 32] = seed_digest.finalize().into(); self.sign(message, StdRng::from_seed(seed)) } From d2eb9dacc6e193111ca0f2c073d4a8a6fba8ac3a Mon Sep 17 00:00:00 2001 From: Skalman Date: Thu, 28 May 2026 17:35:23 -0400 Subject: [PATCH 6/7] - Restrict identity public key check to verifying PoP. - Only perform subgroup checks when verifying signature. - Add test for failure of strirct unforgiblity. --- src/engine.rs | 20 ++- src/experimental/bit.rs | 8 +- src/nugget_pop.rs | 9 +- src/single.rs | 41 ++---- src/verifiers.rs | 270 ++++++++++++++++++++++++++++++---------- 5 files changed, 239 insertions(+), 109 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 7079bcd..943bafb 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -218,15 +218,27 @@ pub trait EngineBLS { Self::PublicKeyPrepared::from(g_affine) } + /// Subgroup-membership check for a public-key-group point. + /// Used during signature verification (identity is allowed there). + fn verify_public_key_in_public_key_subgroup(g: &Self::PublicKeyGroupAffine) -> bool { + g.check().is_ok() + } + + /// Subgroup-membership check for a signature-group point. + /// Used during signature verification. + fn verify_signature_in_signature_subgroup(g: &Self::SignatureGroupAffine) -> bool { + g.check().is_ok() + } + /// Reject public keys that are the identity element or not in the - /// prime-order subgroup. Defends verification against inputs that - /// bypass the deserialization-time check — e.g. direct tuple-struct - /// construction, or aggregates that sum to the identity. + /// prime-order subgroup. Defends PoP verification against inputs + /// that bypass the deserialization-time check — e.g. direct + /// tuple-struct construction, or aggregates that sum to the identity. /// /// Implements `KeyValidate` from /// . fn validate_public_key(g: &Self::PublicKeyGroupAffine) -> bool { - !g.is_zero() && g.check().is_ok() + !g.is_zero() && Self::verify_public_key_in_public_key_subgroup(g) } /// Process the signature to be use in pairing. This has to be diff --git a/src/experimental/bit.rs b/src/experimental/bit.rs index 1f04db7..f53ddde 100644 --- a/src/experimental/bit.rs +++ b/src/experimental/bit.rs @@ -632,9 +632,7 @@ mod tests { .collect::>(); let mut bitsig1 = BitSignedMessage::::new(pop.clone(), &msg1); - // Empty aggregate: the summed public key is the identity element, - // which `validate_public_key` rejects per IETF KeyValidate. - assert!(!bitsig1.verify()); + assert!(bitsig1.verify()); // verifiers::verify_with_distinct_messages(&dms,true) for (i, sig) in sigs1.iter().enumerate().take(2) { assert!(bitsig1.add(sig).is_ok() == (i < 4)); assert!(bitsig1.verify()); // verifiers::verify_with_distinct_messages(&dms,true) @@ -689,9 +687,7 @@ mod tests { let mut countsig = CountSignedMessage::::new(pop.clone(), msg1); assert!(countsig.signers.len() == 1); - // Empty aggregate: the summed public key is the identity element, - // which `validate_public_key` rejects per IETF KeyValidate. - assert!(!countsig.verify()); + assert!(countsig.verify()); assert!(countsig.add_bitsig(&bitsig1).is_ok()); assert!(bitsig1.signature == countsig.signature); assert!(countsig.signers.len() == 1); diff --git a/src/nugget_pop.rs b/src/nugget_pop.rs index 9ec7126..b817d80 100644 --- a/src/nugget_pop.rs +++ b/src/nugget_pop.rs @@ -92,11 +92,12 @@ impl let mut randomized_pub_in_g1 = public_key_in_signature_group; randomized_pub_in_g1 *= randomization_coefficient; let signature = E::prepare_signature(self.0 + randomized_pub_in_g1); - let Some(prepared_public_key) = - crate::verifiers::validate_and_prepare_public_key::(public_key_of_prover.1) - else { + let public_key_affine: ::PublicKeyGroupAffine = + public_key_of_prover.1.into(); + if !E::validate_public_key(&public_key_affine) { return false; - }; + } + let prepared_public_key = E::prepare_public_key(public_key_affine); let prepared = [ ( prepared_public_key.clone(), diff --git a/src/single.rs b/src/single.rs index 6eeb967..ce35a76 100644 --- a/src/single.rs +++ b/src/single.rs @@ -484,21 +484,17 @@ impl Signature { /// Verify a single BLS signature pub fn verify(&self, message: &Message, publickey: &PublicKey) -> bool { - let Some(publickey) = - crate::verifiers::validate_and_prepare_public_key::(publickey.0) - else { + let pk_affine: ::PublicKeyGroupAffine = publickey.0.into(); + if !E::verify_public_key_in_public_key_subgroup(&pk_affine) { return false; - }; - // TODO: Bentchmark these two variants - // Variant 1. Do not batch any normalizations + } + let sig_affine: ::SignatureGroupAffine = self.0.into(); + if !E::verify_signature_in_signature_subgroup(&sig_affine) { + return false; + } + let publickey = E::prepare_public_key(pk_affine); let message = E::prepare_signature(message.hash_to_signature_curve::()); - let signature = E::prepare_signature(self.0); - // Variant 2. Batch signature curve normalizations - // let mut s = [E::hash_to_signature_curve(message), signature.0]; - // E::SignatureCurve::batch_normalization(&s); - // let message = s[0].into_affine().prepare(); - // let signature = s[1].into_affine().prepare(); - // TODO: Compare benchmarks on variants + let signature = E::prepare_signature(sig_affine); E::verify_prepared(signature, &[(publickey, message)]) } } @@ -1007,24 +1003,5 @@ mod tests { ); } - /// Keypair with secret=0, public=identity produces an identity - /// signature, so the pairing equation reduces to `identity == identity` - /// — an unprotected `Signature::verify` would accept. Rejection here - /// is attributable to `validate_public_key`. - #[test] - fn signature_verify_rejects_identity_pk() { - use ark_ff::Zero; - use rand::{rngs::StdRng, SeedableRng}; - - type EB = UsualBLS; - let mut keypair = Keypair:: { - public: PublicKey::(::PublicKeyGroup::zero()), - secret: SecretKeyVT::(::Scalar::zero()) - .into_split(StdRng::from_seed([0u8; 32])), - }; - let s = keypair.signed_message(&Message::new(b"ctx", b"test message")); - assert!(!s.signature.verify(&s.message, &s.publickey)); - assert!(!Signed::verify(&s)); - } } diff --git a/src/verifiers.rs b/src/verifiers.rs index 927a410..3271c69 100644 --- a/src/verifiers.rs +++ b/src/verifiers.rs @@ -8,7 +8,7 @@ use core::borrow::Borrow; // We use BTreeMap instead of HashMap for no_std compatibility. use alloc::collections::BTreeMap; use ark_ec::AffineRepr; -use ark_ff::field_hashers::{DefaultFieldHasher, HashToField}; +use ark_ff::{One, Zero, field_hashers::{DefaultFieldHasher, HashToField}}; use ark_serialize::CanonicalSerialize; use digest::FixedOutputReset; @@ -37,22 +37,6 @@ pub type SignatureAffine = <::SignatureGroup as CurveGroup>:: // ── Shared helpers ────────────────────────────────────────────────── -/// Validate a public key with `EngineBLS::validate_public_key` and, on -/// success, return the prepared form. Returns `None` if the public key -/// is the identity element or not in the prime-order subgroup. All -/// verifier paths should go through this wrapper so the check cannot -/// be skipped by callers constructing `PublicKey` directly or by -/// aggregates that sum to the identity. -pub fn validate_and_prepare_public_key( - g: impl Into>, -) -> Option { - let g_affine: PublicKeyAffine = g.into(); - if !E::validate_public_key(&g_affine) { - return None; - } - Some(E::prepare_public_key(g_affine)) -} - /// Verify from fully normalized (affine) inputs. /// All public keys, messages, and the signature must already be in affine form. /// This prepares the pairing inputs and calls `verify_prepared`. @@ -61,13 +45,16 @@ fn verify_normalized( affine_messages: &[SignatureAffine], affine_signature: SignatureAffine, ) -> bool { + if !E::verify_signature_in_signature_subgroup(&affine_signature) { + return false; + } let prepared_sig = E::prepare_signature(affine_signature); let mut prepared = Vec::with_capacity(affine_publickeys.len()); for (pk, m) in affine_publickeys.iter().zip(affine_messages) { - let Some(prepared_pk) = validate_and_prepare_public_key::(*pk) else { + if !E::verify_public_key_in_public_key_subgroup(pk) { return false; - }; - prepared.push((prepared_pk, E::prepare_signature(*m))); + } + prepared.push((E::prepare_public_key(*pk), E::prepare_signature(*m))); } E::verify_prepared(prepared_sig, prepared.iter()) } @@ -160,16 +147,19 @@ fn normalize_messages_and_signature( /// Simple unoptimized BLS signature verification. Useful for testing. pub fn verify_unoptimized(s: S) -> bool { - let signature = S::E::prepare_signature(s.signature().0); + let affine_signature = s.signature().0.into(); + if !S::E::verify_signature_in_signature_subgroup(&affine_signature) { + return false; + } + let signature = S::E::prepare_signature(affine_signature); let mut prepared = Vec::new(); for (message, public_key) in s.messages_and_publickeys() { - let Some(prepared_pk) = - validate_and_prepare_public_key::(public_key.borrow().0) - else { + let pk_affine: PublicKeyAffine = public_key.borrow().0.into(); + if !S::E::verify_public_key_in_public_key_subgroup(&pk_affine) { return false; - }; + } prepared.push(( - prepared_pk, + S::E::prepare_public_key(pk_affine), S::E::prepare_signature(message.borrow().hash_to_signature_curve::()), )); } @@ -304,6 +294,12 @@ pub fn verify_using_aggregated_auxiliary_public_keys< // And verify the aggregate signature. let (affine_msgs, affine_sig) = normalize_messages_and_signature::(merged_msgs, signature); + // `verify_normalized` runs `verify_public_key_in_public_key_subgroup` + // on every entry of `merged_pks`. That subgroup check is critical for + // this scheme: the auxiliary-key construction binds `aggregated_aux_pub_key` + // to the signers via the pseudo-random scalar, and a public key that + // sits outside the prime-order subgroup would let an attacker forge a + // matching `aggregated_aux_pub_key` and pass verification. Do not drop it. verify_normalized::(&merged_pks, &affine_msgs, affine_sig) } @@ -386,47 +382,145 @@ fn verify_with_gaussian_elimination(s: S) -> bool { #[cfg(test)] mod tests { use super::*; - use crate::single::{SecretKeyVT, SignedMessage}; - use crate::{Keypair, Message, PublicKey, UsualBLS}; - use ark_bls12_381::{Bls12_381, Fq, G1Affine}; - use ark_ec::AffineRepr; - use ark_ff::{UniformRand, Zero}; - use ark_serialize::Valid; + use crate::single::SignedMessage; + use crate::{Keypair, Message, PublicKey, Signature, UsualBLS}; + use ark_bls12_381::{Bls12_381, Fq, Fq2, G1Affine, G2Affine}; + use ark_ec::{AffineRepr, CurveGroup, PrimeGroup}; + use ark_ff::{BitIteratorBE, PrimeField, UniformRand}; use rand::rngs::StdRng; use rand::SeedableRng; type EB = UsualBLS; - /// Keypair with secret scalar zero and identity public key. The - /// produced signature is also identity, so every pairing equation - /// reduces to `identity == identity` and an unprotected verifier - /// would accept. Any rejection observed in the verifier tests - /// below is therefore attributable to `validate_public_key`. - fn identity_keypair() -> Keypair { - Keypair { - public: PublicKey::(::PublicKeyGroup::zero()), - secret: SecretKeyVT::(::Scalar::zero()) - .into_split(StdRng::from_seed([0u8; 32])), + /// Multiply `point` by an integer `scalar` (big-endian limbs) using + /// plain double-and-add over the curve. We deliberately bypass the + /// `Group::mul_bigint` path because arkworks' BLS12-381 curves use a + /// GLV-optimized scalar multiplication that decomposes the scalar + /// modulo `r`. For a point `P` outside the prime-order subgroup the + /// GLV identity `ϕ(P) = λ·P` does not hold, and any scalar that is + /// `0 mod r` would also reduce away, breaking the cofactor-projection + /// arithmetic we rely on below. Bit-by-bit double-and-add computes + /// `scalar·P` as a literal integer multiple on the curve. + fn mul_by_int_no_glv>(point: G, scalar: S) -> G { + let mut result = G::zero(); + for b in BitIteratorBE::without_leading_zeros(scalar) { + result.double_in_place(); + if b { + result += point; + } } + result } - fn identity_signed() -> SignedMessage { - identity_keypair().signed_message(&Message::new(b"ctx", b"test message")) - } - - /// A G1 point on the curve but outside the prime-order subgroup. - fn non_subgroup_g1() -> G1Affine { + /// Sample a random point on E(Fq), then multiply by `r`. This kills + /// the r-torsion component and leaves a point in the cofactor + /// subgroup G1[h₁]. Such a point pairs to 1 with any G2[r] element + /// under the optimal-ate pairing — so attaching it to a public key + /// shifts the key out of the prime-order subgroup *without disturbing + /// the verification equation*. The subgroup check is therefore the + /// only thing that can reject inputs built from it. + fn cofactor_subgroup_g1() -> G1Affine { let mut rng = StdRng::from_seed([42u8; 32]); + let r = ::MODULUS; loop { let x = Fq::rand(&mut rng); - if let Some(point) = G1Affine::get_point_from_x_unchecked(x, false) { - if point.check().is_err() { - return point; - } + let Some(point) = G1Affine::get_point_from_x_unchecked(x, false) else { + continue; + }; + let projected = mul_by_int_no_glv(point.into_group(), r).into_affine(); + if !projected.is_zero() { + return projected; + } + } + } + + /// G2 analogue of `cofactor_subgroup_g1`: a point in `E'(Fq²)[h₂]`, + /// obtained by multiplying a random `E'(Fq²)` point by `r` to clear + /// the r-torsion component. + /// + /// **Asymmetry with the G1 case.** Unlike the G1 cofactor point, + /// this one does **not** pair to 1 with `G1[r]` under optimal-ate pairing. + /// For BLS12-381's sextic twist, `E'(Fq²)` has order exactly `r·h₂` + /// with `gcd(r, h₂) = 1`, so `E'(Fq²)[r]` is cyclic of order `r` and + /// equals G2 itself — there is no Fq²-rational "anti-G2" subspace. + /// The other ψ-eigenspace of `E'[r]` lives in `E'(Fq¹²)` and cannot + /// be represented as a `G2Affine`. Consequently, the cofactor-attack + /// shape `e(g1, q_g2) = 1` simply does not hold on the G2 side of + /// BLS12-381: a non-G2 candidate in `E'(Fq²)` either fails the + /// subgroup check **or** produces a non-trivial Fq¹² residue through + /// the pairing. + /// + /// The point produced here still drives the rejection tests: it sits + /// outside `E'(Fq²)[r]` (because `[r]·random` retains the h₂-cofactor + /// component) and is rejected by `verify_signature_in_signature_subgroup`. + /// The pairing equation would also reject it, so on the G2 side the + /// subgroup check is belt-and-braces, in contrast to the G1 side + /// where it is the sole defence. + fn cofactor_subgroup_g2() -> G2Affine { + let mut rng = StdRng::from_seed([43u8; 32]); + let r = ::MODULUS; + loop { + let x = Fq2::rand(&mut rng); + let Some(point) = G2Affine::get_point_from_x_unchecked(x, false) else { + continue; + }; + let projected = mul_by_int_no_glv(point.into_group(), r).into_affine(); + if !projected.is_zero() { + return projected; } } } + /// Build a `SignedMessage` whose public key carries a G1-cofactor + /// component. Under optimal-ate `e(Q_g1, ·) = 1`, so the pairing + /// equation still balances — an unprotected verifier accepts. The + /// G1 subgroup check is the only thing that catches this. + /// + /// Construction (E = UsualBLS, PK in G1, sig in G2): + /// pk' = sk · (g1 + Q_g1) = pk + sk · Q_g1 with Q_g1 ∈ G1[h₁] + /// sig' = sk · H(m) (unchanged) + fn signed_with_g1_cofactor_pk() -> SignedMessage { + let message = Message::new(b"ctx", b"test message"); + let mut keypair = Keypair::::generate(StdRng::from_seed([0u8; 32])); + let signed = keypair.signed_message(&message); + let sk = keypair.into_vartime().secret.0; + + let q_g1: ::PublicKeyGroup = cofactor_subgroup_g1().into(); + let bad_pk = q_g1 * sk + signed.publickey.0; + + SignedMessage { + message: signed.message, + publickey: PublicKey::(bad_pk), + signature: signed.signature, + } + } + + /// Build a `SignedMessage` whose signature carries a G2-cofactor + /// component. Unlike the G1 case the pairing equation does **not** + /// remain balanced (see `cofactor_subgroup_g2` for why), so on + /// BLS12-381 this attack would be rejected by `verify_prepared` even + /// without the subgroup check. It still drives the rejection tests + /// because the subgroup check trips first. + /// + /// Construction: + /// pk' = sk · g1 (unchanged) + /// sig' = sk · (H(m) + Q_g2) = sig + sk · Q_g2 with Q_g2 ∈ E'(Fq²)[h₂] + fn signed_with_g2_cofactor_sig() -> SignedMessage { + let message = Message::new(b"ctx", b"test message"); + let mut keypair = Keypair::::generate(StdRng::from_seed([0u8; 32])); + let signed = keypair.signed_message(&message); + let sk = keypair.into_vartime().secret.0; + + let q_g2: ::SignatureGroup = cofactor_subgroup_g2().into(); + let bad_sig = q_g2 * sk + signed.signature.0; + + SignedMessage { + message: signed.message, + publickey: signed.publickey, + signature: Signature::(bad_sig), + } + } + #[test] fn verify_simple_single_signature() { let good = Message::new(b"ctx", b"test message"); @@ -457,29 +551,79 @@ mod tests { assert!(verify_unoptimized(&signed)); } + /// Sanity check that `e(Q_g1, g2_gen) = 1`: this is the mathematical + /// basis for the G1-side cofactor attack — without it the G1 + /// rejection tests would have no meaning. + /// + /// We do **not** assert the symmetric `e(g1_gen, Q_g2) = 1`. For + /// BLS12-381's sextic twist no such non-trivial `Q_g2 ∈ E'(Fq²)` + /// exists (see `cofactor_subgroup_g2`); the G2 rejection tests + /// succeed because the subgroup check **and** the pairing equation + /// independently reject the doctored input, not because of a + /// pair-to-1 property. #[test] - fn verify_simple_rejects_identity_pk() { - assert!(!verify_simple(&identity_signed())); + fn cofactor_points_pair_to_one() { + use ark_ec::pairing::Pairing; + let q_g1 = cofactor_subgroup_g1(); + let g2_gen = G2Affine::generator(); + let p1 = Bls12_381::pairing(q_g1, g2_gen); + if !p1.0.is_one() { + eprintln!("e(Q_g1, g2_gen) = {:?} (expected 1)", p1.0); + } + assert!(p1.0.is_one(), "e(Q_g1, g2_gen) != 1"); } + /// With only the G1-cofactor component spliced into the public key, + /// the underlying pairing equation still balances (because + /// `e(Q_g1, ·) = 1`). `verify_prepared`, which performs no subgroup + /// validation, accepts. This proves the high-level verifier + /// rejections below are attributable solely to the subgroup checks, + /// not to broken pairing math. #[test] - fn verify_unoptimized_rejects_identity_pk() { - assert!(!verify_unoptimized(&identity_signed())); + fn verify_prepared_accepts_cofactor_components() { + let bad = signed_with_g1_cofactor_pk(); + let prepared_pk = ::prepare_public_key(bad.publickey.0); + let prepared_msg = ::prepare_signature( + bad.message.hash_to_signature_curve::(), + ); + let prepared_sig = ::prepare_signature(bad.signature.0); + let pairs = [(prepared_pk, prepared_msg)]; + assert!(::verify_prepared(prepared_sig, pairs.iter())); } #[test] - fn verify_with_distinct_messages_rejects_identity_pk() { - assert!(!verify_with_distinct_messages(&identity_signed(), true)); + fn verify_simple_rejects_g1_cofactor_pk() { + assert!(!verify_simple(&signed_with_g1_cofactor_pk())); } #[test] - fn validate_and_prepare_public_key_rejects_identity() { - let identity = PublicKeyAffine::::zero(); - assert!(validate_and_prepare_public_key::(identity).is_none()); + fn verify_unoptimized_rejects_g1_cofactor_pk() { + assert!(!verify_unoptimized(&signed_with_g1_cofactor_pk())); } #[test] - fn validate_and_prepare_public_key_rejects_non_subgroup() { - assert!(validate_and_prepare_public_key::(non_subgroup_g1()).is_none()); + fn verify_with_distinct_messages_rejects_g1_cofactor_pk() { + assert!(!verify_with_distinct_messages( + &signed_with_g1_cofactor_pk(), + true + )); + } + + #[test] + fn verify_simple_rejects_g2_cofactor_sig() { + assert!(!verify_simple(&signed_with_g2_cofactor_sig())); + } + + #[test] + fn verify_unoptimized_rejects_g2_cofactor_sig() { + assert!(!verify_unoptimized(&signed_with_g2_cofactor_sig())); + } + + #[test] + fn verify_with_distinct_messages_rejects_g2_cofactor_sig() { + assert!(!verify_with_distinct_messages( + &signed_with_g2_cofactor_sig(), + true + )); } } From 397b38097a0b0a33b573d61fc523e2cbdea4b1db Mon Sep 17 00:00:00 2001 From: Skalman Date: Tue, 2 Jun 2026 11:30:45 -0400 Subject: [PATCH 7/7] - Remove comments about affine coordinates - Remove redundant imports - fix shifting 6bit instead of 64bits in delinear.rs (srlabs finding 659 --- src/experimental/delinear.rs | 2 +- src/single.rs | 22 +++++----------------- src/verifiers.rs | 2 +- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/src/experimental/delinear.rs b/src/experimental/delinear.rs index 67f1367..ca14ec0 100644 --- a/src/experimental/delinear.rs +++ b/src/experimental/delinear.rs @@ -122,7 +122,7 @@ impl Delinearized { let (x, y) = array_refs!(&b, 8, 8); let mut x: ::BigInt = u64::from_le_bytes(*x).into(); let y: ::BigInt = u64::from_le_bytes(*y).into(); - x <<= 6; //warning: use of deprecated method `ark_ff::BigInteger::muln`: please use the operator `<<` instead : x.muln(64); + x <<= 64; //warning: use of deprecated method `ark_ff::BigInteger::muln`: please use the operator `<<` instead : x.muln(64); x.add_with_carry(&y); ::from_bigint(x).unwrap() } diff --git a/src/single.rs b/src/single.rs index ce35a76..86e2d1b 100644 --- a/src/single.rs +++ b/src/single.rs @@ -28,7 +28,7 @@ use alloc::{vec, vec::Vec}; use ark_ff::field_hashers::{DefaultFieldHasher, HashToField}; use ark_ff::{UniformRand, Zero}; -use ark_ec::{AffineRepr, CurveGroup}; +use ark_ec::{AffineRepr, CurveGroup, PrimeGroup}; use ark_serialize::{ CanonicalDeserialize, CanonicalSerialize, Compress, Read, SerializationError, Valid, Validate, @@ -106,11 +106,7 @@ impl SecretKeyVT { /// Derive our public key from our secret key pub fn into_public(&self) -> PublicKey { - // TODO str4d never decided on projective vs affine here, so benchmark both versions. - PublicKey(::Affine::generator().into_group() * self.0) - // let mut g = ::one(); - // g *= self.0; - // PublicKey(p) + PublicKey(::generator() * self.0) } } @@ -300,19 +296,10 @@ impl SecretKey { /// We do not resplit for side channel protections here since /// this call should be rare. pub fn into_public(&self) -> PublicKey { - let generator = ::Affine::generator(); + let generator = ::generator(); let mut publickey = generator * self.key[0]; - publickey += generator.into_group() * self.key[1]; + publickey += generator * self.key[1]; PublicKey(publickey) - // TODO str4d never decided on projective vs affine here, so benchmark this. - /* - let mut x = ::one(); - x *= self.0; - let y = ::one(); - y *= self.1; - x += &y; - PublicKey(x) - */ } } @@ -485,6 +472,7 @@ impl Signature { /// Verify a single BLS signature pub fn verify(&self, message: &Message, publickey: &PublicKey) -> bool { let pk_affine: ::PublicKeyGroupAffine = publickey.0.into(); + //This is redundant if we have verified public key's PoP which reject out of subgroup keys if !E::verify_public_key_in_public_key_subgroup(&pk_affine) { return false; } diff --git a/src/verifiers.rs b/src/verifiers.rs index 3271c69..158e8d6 100644 --- a/src/verifiers.rs +++ b/src/verifiers.rs @@ -8,7 +8,7 @@ use core::borrow::Borrow; // We use BTreeMap instead of HashMap for no_std compatibility. use alloc::collections::BTreeMap; use ark_ec::AffineRepr; -use ark_ff::{One, Zero, field_hashers::{DefaultFieldHasher, HashToField}}; +use ark_ff::{field_hashers::{DefaultFieldHasher, HashToField}}; use ark_serialize::CanonicalSerialize; use digest::FixedOutputReset;