diff --git a/README.md b/README.md index 68be7ed..fb242f8 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ Assuming you already have proofs-of-possession, then you'll want to do aggregati The library offers method for generating and verifying proof of positions both based on BLS and [Schnorr Signature](https://en.wikipedia.org/wiki/Schnorr_signature) which is faster to verify than when using BLS signature itself as proof of position. The following example demonstrate how to generate and verify proof of positions and then using `SignatureAggregatorAssumingPoP` to batch and verify multiple BLS signatures. ```rust -use w3f_bls::{Keypair,PublicKey,ZBLS,Message,Signed, ProofOfPossessionGenerator, ProofOfPossession, experimental::schnorr_pop::{SchnorrPoP}, multi_pop_aggregator::MultiMessageSignatureAggregatorAssumingPoP}; +use w3f_bls::{Keypair,PublicKey,ZBLS,Message,Signed, ProofOfPossessionGenerator, ProofOfPossession, experimental::schnorr_pop::{SchnorrPoP}, pop_aggregator::SignatureAggregatorAssumingPoP}; use sha2::Sha256; let mut keypairs = [Keypair::::generate(::rand::thread_rng()), Keypair::::generate(::rand::thread_rng())]; @@ -92,7 +92,7 @@ let pops = keypairs.iter_mut().map(|k|(ProofOfPossessionGenerator::>::verify(pop,publickey)); publickey}).collect::>(); let batch_poped = msgs.iter().zip(publickeys).zip(sigs).fold( - MultiMessageSignatureAggregatorAssumingPoP::::new(), + SignatureAggregatorAssumingPoP::::new(), |mut bpop,((message, publickey),sig)| { bpop.add_message_n_publickey(message, &publickey); bpop.add_signature(&sig); bpop } ); assert!(batch_poped.verify()) @@ -106,12 +106,11 @@ The scheme introduced in [`our recent paper`](https://eprint.iacr.org/2022/1611) ```rust use sha2::Sha256; use ark_bls12_377::Bls12_377; -use ark_ff::Zero; use rand::thread_rng; use w3f_bls::{ - single_pop_aggregator::SignatureAggregatorAssumingPoP, DoubleNuggetBLS, EngineBLS, Keypair, - Message, NuggetPublicKey, PublicKey, PublicKeyInSignatureGroup, Signed, TinyBLS, TinyBLS377, + pop_aggregator::SignatureAggregatorAssumingPoP, DoubleNuggetBLS, EngineBLS, Keypair, + Message, NuggetPublicKey, PublicKeyInSignatureGroup, TinyBLS, TinyBLS377, }; @@ -125,28 +124,13 @@ let pub_keys_in_sig_grp: Vec> = keypairs .map(|k| DoubleNuggetBLS::::into_nugget_double_public_key(k).into_public_key_in_signature_group()) .collect(); -let mut prover_aggregator = - SignatureAggregatorAssumingPoP::::new(message.clone()); -let mut aggregated_public_key = - PublicKey::(::PublicKeyGroup::zero()); +let mut verifier_aggregator = SignatureAggregatorAssumingPoP::::new(); -//sign and aggegate -let _ = keypairs - .iter_mut() - .map(|k| { - prover_aggregator.add_signature(&k.sign(&message)); - aggregated_public_key.0 += k.public.0; - }) - .count(); - -let mut verifier_aggregator = SignatureAggregatorAssumingPoP::::new(message); - -verifier_aggregator.add_signature(&(&prover_aggregator).signature()); - -//aggregate public keys in signature group -verifier_aggregator.add_publickey(&aggregated_public_key); - -pub_keys_in_sig_grp.iter().for_each(|pk| {verifier_aggregator.add_auxiliary_public_key(pk);}); +//sign, aggregate, and add (publickey, aux) pairs +for (k, aux) in keypairs.iter_mut().zip(pub_keys_in_sig_grp.iter()) { + verifier_aggregator.add_signature(&k.sign(&message)); + verifier_aggregator.add_message_n_publickey(&message, &(k.public, *aux)); +} assert!( verifier_aggregator.verify_using_aggregated_auxiliary_public_keys::(), diff --git a/examples/aggregate_with_public_key_in_signature_group.rs b/examples/aggregate_with_public_key_in_signature_group.rs index 9b49124..dff7d2d 100644 --- a/examples/aggregate_with_public_key_in_signature_group.rs +++ b/examples/aggregate_with_public_key_in_signature_group.rs @@ -2,15 +2,13 @@ use sha2::Sha256; #[cfg(feature = "std")] use w3f_bls::{ - single_pop_aggregator::SignatureAggregatorAssumingPoP, EngineBLS, Keypair, Message, NuggetBLS, - PublicKey, PublicKeyInSignatureGroup, Signed, TinyBLS, TinyBLS377, + pop_aggregator::SignatureAggregatorAssumingPoP, EngineBLS, Keypair, Message, NuggetBLS, + PublicKeyInSignatureGroup, TinyBLS, TinyBLS377, }; #[cfg(feature = "std")] use ark_bls12_377::Bls12_377; #[cfg(feature = "std")] -use ark_ff::Zero; -#[cfg(feature = "std")] use rand::thread_rng; /// Run using @@ -31,26 +29,13 @@ fn main() { .iter() .map(|k| NuggetBLS::<_, ::SignatureGroup>::into_public_key_in_signature_group(k)) .collect(); - let mut prover_aggregator = - SignatureAggregatorAssumingPoP::::new(message.clone()); - let mut aggregated_public_key = - PublicKey::(::PublicKeyGroup::zero()); - - //sign and aggegate - keypairs.iter_mut().for_each(|k| { - prover_aggregator.add_signature(&k.sign(&message)); - aggregated_public_key.0 += k.public.0; - }); - - let mut verifier_aggregator = SignatureAggregatorAssumingPoP::::new(message); - //get the signature and already aggregated public key from the prover - verifier_aggregator.add_signature(&(&prover_aggregator).signature()); - verifier_aggregator.add_publickey(&aggregated_public_key); + let mut verifier_aggregator = SignatureAggregatorAssumingPoP::::new(); - //aggregate public keys in signature group - pub_keys_in_sig_grp.iter().for_each(|pk| { - verifier_aggregator.add_auxiliary_public_key(pk); - }); + //sign, aggregate, and add (publickey, aux) pairs + for (k, aux) in keypairs.iter_mut().zip(pub_keys_in_sig_grp.iter()) { + verifier_aggregator.add_signature(&k.sign(&message)); + verifier_aggregator.add_message_n_publickey(&message, &(k.public, *aux)); + } assert!( verifier_aggregator.verify_using_aggregated_auxiliary_public_keys::(), diff --git a/examples/experimental/aggregated_with_pop.rs b/examples/experimental/aggregated_with_pop.rs index f2f29f4..ab4ef0b 100644 --- a/examples/experimental/aggregated_with_pop.rs +++ b/examples/experimental/aggregated_with_pop.rs @@ -3,7 +3,7 @@ use sha2::Sha256; #[cfg(feature = "std")] use w3f_bls::{ experimental::schnorr_pop::SchnorrPoP, - multi_pop_aggregator::MultiMessageSignatureAggregatorAssumingPoP, + pop_aggregator::SignatureAggregatorAssumingPoP, Keypair, Message, ProofOfPossession, ProofOfPossessionGenerator, PublicKey, Signed, ZBLS, }; @@ -53,7 +53,7 @@ fn main() { //now that we have confidence in keys we can verify the batched signature let batch_poped = msgs.iter().zip(publickeys).zip(sigs).fold( - MultiMessageSignatureAggregatorAssumingPoP::::new(), + SignatureAggregatorAssumingPoP::::new(), |mut bpop, ((message, publickey), sig)| { bpop.add_message_n_publickey(message, &publickey); bpop.add_signature(&sig); diff --git a/src/experimental/bench.rs b/src/experimental/bench.rs index 97ab102..5ba6470 100644 --- a/src/experimental/bench.rs +++ b/src/experimental/bench.rs @@ -5,7 +5,7 @@ extern crate test; const NO_OF_MULTI_SIG_SIGNERS: usize = 100; use crate::chaum_pedersen_signature::ChaumPedersenSigner; use crate::chaum_pedersen_signature::ChaumPedersenVerifier; -use crate::multi_pop_aggregator::MultiMessageSignatureAggregatorAssumingPoP; +use crate::pop_aggregator::SignatureAggregatorAssumingPoP; use crate::Keypair; use crate::Message; use crate::Signature as BLSSignature; @@ -55,7 +55,7 @@ use crate::PublicKeyInSignatureGroup; // let mut pub_keys_in_sig_grp : Vec> = keypairs.iter().map(|k| k.into_public_key_in_signature_group()).collect(); // let mut aggregated_public_key = PublicKey::(::PublicKeyGroup::zero()); -// let mut aggregator = MultiMessageSignatureAggregatorAssumingPoP::::new(); +// let mut aggregator = SignatureAggregatorAssumingPoP::::new(); // for k in &mut keypairs { // aggregator.aggregate(&k.signed_message(message)); @@ -63,7 +63,7 @@ use crate::PublicKeyInSignatureGroup; // } // b.iter(|| { -// let mut verifier_aggregator = MultiMessageSignatureAggregatorAssumingPoP::::new(); +// let mut verifier_aggregator = SignatureAggregatorAssumingPoP::::new(); // let mut verifier_aggregated_public_key = PublicKey::(::PublicKeyGroup::zero()); // verifier_aggregator.add_signature(&aggregator.signature); @@ -85,7 +85,7 @@ use crate::PublicKeyInSignatureGroup; // let message = Message::new(b"ctx",b"test message"); // b.iter(|| { -// let mut aggregator = MultiMessageSignatureAggregatorAssumingPoP::::new(); +// let mut aggregator = SignatureAggregatorAssumingPoP::::new(); // let mut aggregated_public_key = PublicKey::(::PublicKeyGroup::zero()); // for k in &mut keypairs { @@ -102,7 +102,7 @@ use crate::PublicKeyInSignatureGroup; // let mut keypairs = generate_many_keypairs(NO_OF_MULTI_SIG_SIGNERS); // let mut pub_keys_in_sig_grp : Vec> = keypairs.iter().map(|k| k.into_public_key_in_signature_group()).collect(); -// let mut aggregator = MultiMessageSignatureAggregatorAssumingPoP::::new(); +// let mut aggregator = SignatureAggregatorAssumingPoP::::new(); // let mut aggregated_public_key = PublicKey::(::PublicKeyGroup::zero()); // for k in &mut keypairs { @@ -111,7 +111,7 @@ use crate::PublicKeyInSignatureGroup; // } // b.iter(|| { -// let mut verifier_aggregator = MultiMessageSignatureAggregatorAssumingPoP::::new(); +// let mut verifier_aggregator = SignatureAggregatorAssumingPoP::::new(); // verifier_aggregator.add_signature(&aggregator.signature); // verifier_aggregator.add_message_n_publickey(&message, &aggregated_public_key); diff --git a/src/experimental/bit.rs b/src/experimental/bit.rs index 27c91b5..0fbf0ae 100644 --- a/src/experimental/bit.rs +++ b/src/experimental/bit.rs @@ -660,8 +660,7 @@ mod tests { assert!(bitsig1.merge(&bitsig2).is_err()); let mut multimsg = - crate::multi_pop_aggregator::MultiMessageSignatureAggregatorAssumingPoP::::new( - ); + crate::pop_aggregator::SignatureAggregatorAssumingPoP::::new(); multimsg.aggregate(&bitsig1); multimsg.aggregate(&bitsig2); assert!(multimsg.verify()); // verifiers::verify_with_distinct_messages(&dms,true) diff --git a/src/lib.rs b/src/lib.rs index c97a2e6..51c0927 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -105,6 +105,7 @@ extern crate sha3; extern crate alloc; +use ark_ff::Zero; use core::borrow::Borrow; use digest::DynDigest; @@ -119,8 +120,7 @@ pub mod serialize; pub mod single; pub mod verifiers; -pub mod multi_pop_aggregator; -pub mod single_pop_aggregator; +pub mod pop_aggregator; #[cfg(feature = "experimental")] pub mod experimental; @@ -138,7 +138,48 @@ pub use single::{Keypair, KeypairVT, PublicKey, SecretKey, SecretKeyVT, Signatur use alloc::vec::Vec; -/// Internal message hash size. +/// Public key types usable in the [`Signed`] trait. +/// +/// Standard BLS uses [`PublicKey`] which carries only the key in the +/// public-key group. Nugget-style schemes use [`NuggetDoublePublicKey`] +/// (or similar) which carries keys in **both** curve groups and thus +/// supports auxiliary-key verification. +pub trait GeneralizedBLSPublicKey { + /// The public key in the public-key group. + fn public_key(&self) -> PublicKey; + + /// The auxiliary public key in the signature group. + /// Returns zero by default (no auxiliary key). + fn public_key_in_signature_group(&self) -> nugget::PublicKeyInSignatureGroup { + nugget::PublicKeyInSignatureGroup(E::SignatureGroup::zero()) + } +} + +impl GeneralizedBLSPublicKey for PublicKey { + fn public_key(&self) -> PublicKey { + *self + } +} + +impl GeneralizedBLSPublicKey for (PublicKey, nugget::PublicKeyInSignatureGroup) { + fn public_key(&self) -> PublicKey { + self.0 + } + fn public_key_in_signature_group(&self) -> nugget::PublicKeyInSignatureGroup { + self.1 + } +} + +impl<'a, E: EngineBLS, T: GeneralizedBLSPublicKey> GeneralizedBLSPublicKey for &'a T { + fn public_key(&self) -> PublicKey { + (*self).public_key() + } + fn public_key_in_signature_group(&self) -> nugget::PublicKeyInSignatureGroup { + (*self).public_key_in_signature_group() + } +} + +/// Internal message hash size. /// /// We choose 256 bits here so that birthday bound attacks cannot /// find messages with the same hash. @@ -262,7 +303,7 @@ pub trait Signed: Sized { fn signature(&self) -> Signature; type M: Borrow; // = Message; - type PKG: Borrow>; // = PublicKey; + type PKG: GeneralizedBLSPublicKey; // = PublicKey; /// Iterator over, messages and public key reference pairs. type PKnM: Iterator + ExactSizeIterator; diff --git a/src/multi_pop_aggregator.rs b/src/multi_pop_aggregator.rs deleted file mode 100644 index 81a07b2..0000000 --- a/src/multi_pop_aggregator.rs +++ /dev/null @@ -1,312 +0,0 @@ -//! ## Aggregation of BLS signatures using proofs-of-possession -//! -//! In this module, we provide the linear flavor of aggregate -//! BLS signature in which the verifiers has previously checked -//! proofs-of-possession for all public keys. In other words, -//! we simply add up the signatures because the previously checked -//! proofs-of-possession for all signers prevent rogue key attacks. -//! See the security arguments in The Power of Proofs-of-Possession: -//! Securing Multiparty Signatures against Rogue-Key Attacks -//! by Thomas Ristenpart and Scott Yilek at https://eprint.iacr.org/2007/264.pdf -//! -//! These proof-of-possession are simply self-signed certificates, -//! so a BLS signature by each secret key on its own public key. -//! Importantly, the message for this self-signed certificates -//! must uniquely distinguish the public key for which the signature -//! establishes a proof-of-possession. -//! It follows that each proof-of-possession has a unique message, -//! so distinct message aggregation is optimal for verifying them. -//! -//! In this vein, we note that aggregation under proofs-of-possession -//! cannot improve performance when signers sign distinct messages, -//! so proofs-of-possession help with aggregating votes in a concensus -//! protocol, but should never be used for accounts on a block chain. -//! -//! We assume here that users provide their own data structure for -//! proofs-of-poossession. We provide more structure for users who -//! one bit per vote in a concensus protocol: -//! You first verify the proofs-of-possession when building a data -//! structure that holds the voters' keys. You implement the -//! `ProofsOfPossession` trait for this data strtcuture as well, -//! so that the `BitPoPSignedMessage` type provides a signature -//! data type with reasonable sanity checks. - -// Aside about proof-of-possession in the DLOG setting -// https://twitter.com/btcVeg/status/1085490561082183681 - -use core::borrow::Borrow; // BorrowMut -// We use BTreeMap instead of BTreeMap for no_std compatibility. -use alloc::collections::BTreeMap; - -use ark_ff::Zero; - -use super::verifiers::verify_with_distinct_messages; -use super::*; - -/// Batch or aggregate BLS signatures with attached messages and -/// signers, for whom we previously checked proofs-of-possession. -/// -/// In this type, we provide a high-risk low-level batching and -/// aggregation mechanism that merely adds up signatures under the -/// assumption that all required proofs-of-possession were previously -/// checked. -/// -/// We say a signing key has provided a proof-of-possession if the -/// verifier remembers having checked some self-signed certificate -/// by that key. It's insecure to use this aggregation strategy -/// without first cehcking proofs-of-possession. In particular -/// it is insecure to use this aggregation strategy when checking -/// proofs-of-possession, and could not improve performance anyways. -/// Distinct message aggregation is always optimal for checking -/// proofs-of-possession. Please see the module level doumentation -/// for additional discussion and notes on security. -/// -/// We foresee this type primarily being used to batch several -/// `BitPoPSignedMessage`s into one verification. We do not track -/// aggreggated public keys here, instead merging multiples signers -/// public keys anytime they sign the same message, so this type -/// essentially provides only fast batch verificartion. -/// In principle, our `add_*` methods suffice for building an actual -/// aggregate signature type. Yet, normally direct approaches like -/// `BitPoPSignedMessage` work better for aggregation because -/// the `ProofsOfPossession` trait tooling permits both enforce the -/// proofs-of-possession and provide a compact serialization. -/// We see no reason to support serialization for this type as present. -// -/// In principle, one might combine proof-of-possession with distinct -/// message assumptions, or other aggregation strategies, when -/// verifiers have only observed a subset of the proofs-of-possession, -/// but this sounds complex or worse fragile. -/// -// TODO: Implement gaussian elimination verification scheme. -use single::PublicKey; -/// ProofOfPossion trait which should be implemented by secret - -#[derive(Clone)] -pub struct MultiMessageSignatureAggregatorAssumingPoP { - messages_n_publickeys: BTreeMap>, - signature: Signature, -} - -impl MultiMessageSignatureAggregatorAssumingPoP { - pub fn new() -> MultiMessageSignatureAggregatorAssumingPoP { - MultiMessageSignatureAggregatorAssumingPoP { - messages_n_publickeys: BTreeMap::new(), - signature: Signature(E::SignatureGroup::zero()), - } - } - - /// Add only a `Signature` to our internal signature. - /// - /// Useful for constructing an aggregate signature, but we - /// recommend instead using a custom types like `BitPoPSignedMessage`. - pub fn add_signature(&mut self, signature: &Signature) { - self.signature.0 += &signature.0; - } - - /// Add only a `Message` and `PublicKey` to our internal data. - /// - /// Useful for constructing an aggregate signature, but we - /// recommend instead using a custom types like `BitPoPSignedMessage`. - pub fn add_message_n_publickey(&mut self, message: &Message, publickey: &PublicKey) { - self.messages_n_publickeys - .entry(message.clone()) - .and_modify(|pk0| pk0.0 += &publickey.0) - .or_insert(*publickey); - } - - /// Aggregage BLS signatures assuming they have proofs-of-possession - pub fn aggregate<'a, S>(&mut self, signed: &'a S) - where - &'a S: Signed, - <&'a S as Signed>::PKG: Borrow>, - { - let signature = signed.signature(); - for (message, pubickey) in signed.messages_and_publickeys() { - self.add_message_n_publickey(message.borrow(), pubickey.borrow()); - } - self.add_signature(&signature); - } -} - -impl<'a, E: EngineBLS> Signed for &'a MultiMessageSignatureAggregatorAssumingPoP { - type E = E; - - type M = &'a Message; - type PKG = &'a PublicKey; - type PKnM = alloc::collections::btree_map::Iter<'a, Message, PublicKey>; - - fn messages_and_publickeys(self) -> Self::PKnM { - self.messages_n_publickeys.iter() - } - - fn signature(&self) -> Signature { - self.signature - } - - fn verify(self) -> bool { - // We have already aggregated distinct messages, so our distinct - // message verification code provides reasonable optimizations, - // except the public keys might not be normalized here. - // We foresee verification via gaussian elimination being faster, - // but requires affine keys or normalization. - verify_with_distinct_messages(self, true) - // TODO: verify_with_gaussian_elimination(self) - } -} - -#[cfg(test)] -mod tests { - - use crate::Keypair; - use crate::Message; - use crate::UsualBLS; - use rand::SeedableRng; - use rand::rngs::StdRng; - - use ark_bls12_381::Bls12_381; - - use super::*; - - #[test] - fn verify_aggregate_single_message_single_signer() { - let good = Message::new(b"ctx", b"test message"); - - let mut keypair = - Keypair::>::generate(StdRng::from_seed([0u8; 32])); - let good_sig0 = keypair.sign(&good); - assert!(good_sig0.verify(&good, &keypair.public)); - } - - #[test] - fn verify_aggregate_single_message_multi_signers() { - let good = Message::new(b"ctx", b"test message"); - - let mut keypair0 = - Keypair::>::generate(StdRng::from_seed([0u8; 32])); - let good_sig0 = keypair0.sign(&good); - - let mut keypair1 = - Keypair::>::generate(StdRng::from_seed([1u8; 32])); - let good_sig1 = keypair1.sign(&good); - - let mut aggregated_sigs = MultiMessageSignatureAggregatorAssumingPoP::< - UsualBLS, - >::new(); - aggregated_sigs.add_signature(&good_sig0); - aggregated_sigs.add_signature(&good_sig1); - - aggregated_sigs.add_message_n_publickey(&good, &keypair0.public); - aggregated_sigs.add_message_n_publickey(&good, &keypair1.public); - - assert!( - aggregated_sigs.verify() == true, - "good aggregated signature of a single message with multiple key does not verify" - ); - } - - #[test] - fn verify_aggregate_multi_messages_single_signer() { - let good0 = Message::new(b"ctx", b"Tab over Space"); - let good1 = Message::new(b"ctx", b"Space over Tab"); - - let mut keypair = - Keypair::>::generate(StdRng::from_seed([0u8; 32])); - - let good_sig0 = keypair.sign(&good0); - let good_sig1 = keypair.sign(&good1); - - let mut aggregated_sigs = MultiMessageSignatureAggregatorAssumingPoP::< - UsualBLS, - >::new(); - aggregated_sigs.add_signature(&good_sig0); - aggregated_sigs.add_signature(&good_sig1); - - aggregated_sigs.add_message_n_publickey(&good0, &keypair.public); - aggregated_sigs.add_message_n_publickey(&good1, &keypair.public); - - assert!( - aggregated_sigs.verify() == true, - "good aggregated signature of multiple messages with a single key does not verify" - ); - } - - #[test] - fn verify_aggregate_multi_messages_multi_signers() { - let good0 = Message::new(b"ctx", b"in the beginning"); - let good1 = Message::new(b"ctx", b"there was a flying spaghetti monster"); - - let mut keypair0 = - Keypair::>::generate(StdRng::from_seed([0u8; 32])); - let good_sig0 = keypair0.sign(&good0); - - let mut keypair1 = - Keypair::>::generate(StdRng::from_seed([1u8; 32])); - let good_sig1 = keypair1.sign(&good1); - - let mut aggregated_sigs = MultiMessageSignatureAggregatorAssumingPoP::< - UsualBLS, - >::new(); - aggregated_sigs.add_signature(&good_sig0); - aggregated_sigs.add_signature(&good_sig1); - - aggregated_sigs.add_message_n_publickey(&good0, &keypair0.public); - aggregated_sigs.add_message_n_publickey(&good1, &keypair1.public); - - assert!( - aggregated_sigs.verify() == true, - "good aggregated signature of multiple messages with multiple keys does not verify" - ); - } - - #[test] - fn verify_aggregate_single_message_repetative_signers() { - let good = Message::new(b"ctx", b"test message"); - - let mut keypair = - Keypair::>::generate(StdRng::from_seed([0u8; 32])); - let good_sig = keypair.sign(&good); - - let mut aggregated_sigs = MultiMessageSignatureAggregatorAssumingPoP::< - UsualBLS, - >::new(); - aggregated_sigs.add_signature(&good_sig); - aggregated_sigs.add_signature(&good_sig); - - aggregated_sigs.add_message_n_publickey(&good, &keypair.public); - aggregated_sigs.add_message_n_publickey(&good, &keypair.public); - - assert!( - aggregated_sigs.verify() == true, - "good aggregate of a repetitive signature does not verify" - ); - } - - #[test] - fn aggregate_of_signature_of_a_wrong_message_should_not_verify() { - let good0 = Message::new(b"ctx", b"Space over Tab"); - let bad1 = Message::new(b"ctx", b"Tab over Space"); - - let mut keypair0 = - Keypair::>::generate(StdRng::from_seed([0u8; 32])); - let good_sig0 = keypair0.sign(&good0); - - let mut keypair1 = - Keypair::>::generate(StdRng::from_seed([1u8; 32])); - let bad_sig1 = keypair1.sign(&bad1); - - let mut aggregated_sigs = MultiMessageSignatureAggregatorAssumingPoP::< - UsualBLS, - >::new(); - aggregated_sigs.add_signature(&good_sig0); - aggregated_sigs.add_signature(&bad_sig1); - - aggregated_sigs.add_message_n_publickey(&good0, &keypair0.public); - aggregated_sigs.add_message_n_publickey(&good0, &keypair1.public); - - assert!( - aggregated_sigs.verify() == false, - "aggregated signature of a wrong message should not verify" - ); - } -} diff --git a/src/pop_aggregator.rs b/src/pop_aggregator.rs new file mode 100644 index 0000000..a222d80 --- /dev/null +++ b/src/pop_aggregator.rs @@ -0,0 +1,594 @@ +//! ## Aggregation of BLS signatures using proofs-of-possession +//! +//! In this module, we provide the linear flavor of aggregate +//! BLS signature in which the verifiers has previously checked +//! proofs-of-possession for all public keys. In other words, +//! we simply add up the signatures because the previously checked +//! proofs-of-possession for all signers prevent rogue key attacks. +//! See the security arguments in The Power of Proofs-of-Possession: +//! Securing Multiparty Signatures against Rogue-Key Attacks +//! by Thomas Ristenpart and Scott Yilek at https://eprint.iacr.org/2007/264.pdf +//! +//! These proof-of-possession are simply self-signed certificates, +//! so a BLS signature by each secret key on its own public key. +//! Importantly, the message for this self-signed certificates +//! must uniquely distinguish the public key for which the signature +//! establishes a proof-of-possession. +//! It follows that each proof-of-possession has a unique message, +//! so distinct message aggregation is optimal for verifying them. +//! +//! In this vein, we note that aggregation under proofs-of-possession +//! cannot improve performance when signers sign distinct messages, +//! so proofs-of-possession help with aggregating votes in a concensus +//! protocol, but should never be used for accounts on a block chain. +//! +//! We assume here that users provide their own data structure for +//! proofs-of-poossession. We provide more structure for users who +//! one bit per vote in a concensus protocol: +//! You first verify the proofs-of-possession when building a data +//! structure that holds the voters' keys. You implement the +//! `ProofsOfPossession` trait for this data strtcuture as well, +//! so that the `BitPoPSignedMessage` type provides a signature +//! data type with reasonable sanity checks. + +// Aside about proof-of-possession in the DLOG setting +// https://twitter.com/btcVeg/status/1085490561082183681 + +use core::borrow::Borrow; +// We use BTreeMap instead of HashMap for no_std compatibility. +use alloc::collections::BTreeMap; + +use ark_ff::Zero; + +use super::verifiers::{ + verify_using_aggregated_auxiliary_public_keys, verify_with_distinct_messages, +}; +use super::*; + +use digest::FixedOutputReset; + +/// Batch or aggregate BLS signatures with attached messages and +/// signers, for whom we previously checked proofs-of-possession. +/// +/// In this type, we provide a high-risk low-level batching and +/// aggregation mechanism that merely adds up signatures under the +/// assumption that all required proofs-of-possession were previously +/// checked. +/// +/// We say a signing key has provided a proof-of-possession if the +/// verifier remembers having checked some self-signed certificate +/// by that key. It's insecure to use this aggregation strategy +/// without first cehcking proofs-of-possession. In particular +/// it is insecure to use this aggregation strategy when checking +/// proofs-of-possession, and could not improve performance anyways. +/// Distinct message aggregation is always optimal for checking +/// proofs-of-possession. Please see the module level doumentation +/// for additional discussion and notes on security. +/// +/// We foresee this type primarily being used to batch several +/// `BitPoPSignedMessage`s into one verification. We do not track +/// aggreggated public keys here, instead merging multiples signers +/// public keys anytime they sign the same message, so this type +/// essentially provides only fast batch verificartion. +/// In principle, our `add_*` methods suffice for building an actual +/// aggregate signature type. Yet, normally direct approaches like +/// `BitPoPSignedMessage` work better for aggregation because +/// the `ProofsOfPossession` trait tooling permits both enforce the +/// proofs-of-possession and provide a compact serialization. +/// We see no reason to support serialization for this type as present. +/// message assumptions, or other aggre +/// +/// In principle, one might combine proof-of-possession with distinct +/// message assumptions, or other aggregation strategies, when +/// verifiers have only observed a subset of the proofs-of-possession, +/// but this sounds complex or worse fragile. +/// +/// TODO: Implement gaussian elimination verification scheme. +use nugget::PublicKeyInSignatureGroup; +use single::PublicKey; + +#[derive(Clone)] +pub struct SignatureAggregatorAssumingPoP { + messages_n_publickeys: BTreeMap, PublicKeyInSignatureGroup)>, + signature: Signature, +} + +impl SignatureAggregatorAssumingPoP { + pub fn new() -> SignatureAggregatorAssumingPoP { + SignatureAggregatorAssumingPoP { + messages_n_publickeys: BTreeMap::new(), + signature: Signature(E::SignatureGroup::zero()), + } + } + + /// Add only a `Signature` to our internal signature. + pub fn add_signature(&mut self, signature: &Signature) { + self.signature.0 += &signature.0; + } + + /// Add a `Message` and public key to our internal data. + /// + /// Public keys signing the same message are merged so that each + /// distinct message ends up paired with a single aggregated key. + /// If the public key carries an auxiliary key in the signature group, + /// it is automatically aggregated as well. + pub fn add_message_n_publickey(&mut self, message: &Message, publickey: &impl GeneralizedBLSPublicKey) { + let pk = publickey.public_key(); + let aux = publickey.public_key_in_signature_group(); + self.messages_n_publickeys + .entry(message.clone()) + .and_modify(|(pk0, aux0)| { + pk0.0 += &pk.0; + aux0.0 += &aux.0; + }) + .or_insert((pk, aux)); + } + + /// Add an auxiliary public key for an existing `(message, publickey)` entry. + /// Used by the verifier to aggregate a public key in the signature group + /// for a message signed by an already aggregated public key. + /// + /// If the message already exists with the same public key, the auxiliary + /// key is aggregated into the existing entry. If the message does not + /// exist yet, inserts a new entry. + /// + /// Returns an error if the message already exists with a different + /// public key — the main public key for each message should have been + /// completely aggregated before calling this function. + pub fn aggregate_aux_publickey_for_message_n_publickey( + &mut self, + message: &Message, + publickey: &PublicKey, + aux: &PublicKeyInSignatureGroup, + ) -> Result<(), &'static str> { + match self.messages_n_publickeys.get_mut(message) { + Some((existing_pk, existing_aux)) if existing_pk.0 == publickey.0 => { + existing_aux.0 += &aux.0; + Ok(()) + } + Some(_) => { + Err("message already exists with a different public key") + } + None => { + self.messages_n_publickeys + .insert(message.clone(), (*publickey, *aux)); + Ok(()) + } + } + } + + /// Aggregate BLS signatures assuming they have proofs-of-possession. + /// + /// Folds in every `(message, publickey)` pair carried by `signed` + /// and adds its signature to our running total. Public keys signing + /// the same message are merged together. + pub fn aggregate<'a, S>(&mut self, signed: &'a S) + where + &'a S: Signed, + { + let signature = signed.signature(); + for (message, publickey) in signed.messages_and_publickeys() { + self.add_message_n_publickey(message.borrow(), &publickey); + } + self.add_signature(&signature); + } + + pub fn verify_using_aggregated_auxiliary_public_keys< + RandomOracle: FixedOutputReset + Default + Clone, + >( + &self, + ) -> bool { + verify_using_aggregated_auxiliary_public_keys::( + self, + true, + ) + } +} + +impl<'a, E: EngineBLS> Signed for &'a SignatureAggregatorAssumingPoP { + type E = E; + + type M = &'a Message; + type PKG = &'a (PublicKey, PublicKeyInSignatureGroup); + type PKnM = alloc::collections::btree_map::Iter<'a, Message, (PublicKey, PublicKeyInSignatureGroup)>; + + fn messages_and_publickeys(self) -> Self::PKnM { + self.messages_n_publickeys.iter() + } + + fn signature(&self) -> Signature { + self.signature + } + + fn verify(self) -> bool { + // We have already aggregated distinct messages, so our distinct + // message verification code provides reasonable optimizations, + // except the public keys might not be normalized here. + // We foresee verification via gaussian elimination being faster, + // but requires affine keys or normalization. + verify_with_distinct_messages(self, true) + // TODO: verify_with_gaussian_elimination(self) + } +} + +#[cfg(test)] +mod tests { + + use crate::EngineBLS; + use crate::Keypair; + use crate::Message; + use crate::TinyBLS; + use crate::UsualBLS; + use rand::SeedableRng; + use rand::rngs::StdRng; + use sha2::Sha256; + + use ark_bls12_377::Bls12_377; + use ark_bls12_381::Bls12_381; + + use super::*; + + #[test] + fn verify_aggregate_single_message_single_signer() { + let good = Message::new(b"ctx", b"test message"); + + let mut keypair = + Keypair::>::generate(StdRng::from_seed([0u8; 32])); + let good_sig0 = keypair.sign(&good); + assert!(good_sig0.verify(&good, &keypair.public)); + } + + #[test] + fn verify_aggregate_single_message_multi_signers() { + let good = Message::new(b"ctx", b"test message"); + + let mut keypair0 = + Keypair::>::generate(StdRng::from_seed([0u8; 32])); + let good_sig0 = keypair0.sign(&good); + + let mut keypair1 = + Keypair::>::generate(StdRng::from_seed([1u8; 32])); + let good_sig1 = keypair1.sign(&good); + + let mut aggregated_sigs = SignatureAggregatorAssumingPoP::< + UsualBLS, + >::new(); + aggregated_sigs.add_signature(&good_sig0); + aggregated_sigs.add_signature(&good_sig1); + + aggregated_sigs.add_message_n_publickey(&good, &keypair0.public); + aggregated_sigs.add_message_n_publickey(&good, &keypair1.public); + + assert!( + aggregated_sigs.verify() == true, + "good aggregated signature of a single message with multiple key does not verify" + ); + } + + #[test] + fn verify_aggregate_multi_messages_single_signer() { + let good0 = Message::new(b"ctx", b"Tab over Space"); + let good1 = Message::new(b"ctx", b"Space over Tab"); + + let mut keypair = + Keypair::>::generate(StdRng::from_seed([0u8; 32])); + + let good_sig0 = keypair.sign(&good0); + let good_sig1 = keypair.sign(&good1); + + let mut aggregated_sigs = SignatureAggregatorAssumingPoP::< + UsualBLS, + >::new(); + aggregated_sigs.add_signature(&good_sig0); + aggregated_sigs.add_signature(&good_sig1); + + aggregated_sigs.add_message_n_publickey(&good0, &keypair.public); + aggregated_sigs.add_message_n_publickey(&good1, &keypair.public); + + assert!( + aggregated_sigs.verify() == true, + "good aggregated signature of multiple messages with a single key does not verify" + ); + } + + #[test] + fn verify_aggregate_multi_messages_multi_signers() { + let good0 = Message::new(b"ctx", b"in the beginning"); + let good1 = Message::new(b"ctx", b"there was a flying spaghetti monster"); + + let mut keypair0 = + Keypair::>::generate(StdRng::from_seed([0u8; 32])); + let good_sig0 = keypair0.sign(&good0); + + let mut keypair1 = + Keypair::>::generate(StdRng::from_seed([1u8; 32])); + let good_sig1 = keypair1.sign(&good1); + + let mut aggregated_sigs = SignatureAggregatorAssumingPoP::< + UsualBLS, + >::new(); + aggregated_sigs.add_signature(&good_sig0); + aggregated_sigs.add_signature(&good_sig1); + + aggregated_sigs.add_message_n_publickey(&good0, &keypair0.public); + aggregated_sigs.add_message_n_publickey(&good1, &keypair1.public); + + assert!( + aggregated_sigs.verify() == true, + "good aggregated signature of multiple messages with multiple keys does not verify" + ); + } + + #[test] + fn verify_aggregate_single_message_repetative_signers() { + let good = Message::new(b"ctx", b"test message"); + + let mut keypair = + Keypair::>::generate(StdRng::from_seed([0u8; 32])); + let good_sig = keypair.sign(&good); + + let mut aggregated_sigs = SignatureAggregatorAssumingPoP::< + UsualBLS, + >::new(); + aggregated_sigs.add_signature(&good_sig); + aggregated_sigs.add_signature(&good_sig); + + aggregated_sigs.add_message_n_publickey(&good, &keypair.public); + aggregated_sigs.add_message_n_publickey(&good, &keypair.public); + + assert!( + aggregated_sigs.verify() == true, + "good aggregate of a repetitive signature does not verify" + ); + } + + #[test] + fn aggregate_of_signature_of_a_wrong_message_should_not_verify() { + let good0 = Message::new(b"ctx", b"Space over Tab"); + let bad1 = Message::new(b"ctx", b"Tab over Space"); + + let mut keypair0 = + Keypair::>::generate(StdRng::from_seed([0u8; 32])); + let good_sig0 = keypair0.sign(&good0); + + let mut keypair1 = + Keypair::>::generate(StdRng::from_seed([1u8; 32])); + let bad_sig1 = keypair1.sign(&bad1); + + let mut aggregated_sigs = SignatureAggregatorAssumingPoP::< + UsualBLS, + >::new(); + aggregated_sigs.add_signature(&good_sig0); + aggregated_sigs.add_signature(&bad_sig1); + + aggregated_sigs.add_message_n_publickey(&good0, &keypair0.public); + aggregated_sigs.add_message_n_publickey(&good0, &keypair1.public); + + assert!( + aggregated_sigs.verify() == false, + "aggregated signature of a wrong message should not verify" + ); + } + + #[test] + fn test_aggregate_tiny_sigs_and_verify_in_g1() { + let message = Message::new(b"ctx", b"test message"); + let mut keypairs: Vec<_> = (0..3) + .into_iter() + .map(|i| Keypair::>::generate(StdRng::from_seed([i; 32]))) + .collect(); + let pub_keys_in_sig_grp: Vec> = keypairs + .iter() + .map(|k| { + nugget::NuggetBLS::< + TinyBLS, + as EngineBLS>::SignatureGroup, + >::into_public_key_in_signature_group(k) + }) + .collect(); + + // Prover: knows individual keys, aggregates signatures and (pk, aux) pairs. + let mut prover_aggregator = SignatureAggregatorAssumingPoP::::new(); + + for (k, aux) in keypairs.iter_mut().zip(pub_keys_in_sig_grp.iter()) { + prover_aggregator.add_signature(&k.sign(&message)); + prover_aggregator.add_message_n_publickey(&message, &(k.public, *aux)); + } + + assert!( + prover_aggregator.verify_using_aggregated_auxiliary_public_keys::(), + "prover: verifying with honest auxilary public key should pass" + ); + + // Verifier: receives aggregated signature + per-message aggregated pk + // from the prover, then adds individual aux keys separately. + let mut verifier_aggregator = SignatureAggregatorAssumingPoP::::new(); + verifier_aggregator.add_signature(&(&prover_aggregator).signature()); + + for (msg, (pk, _aux)) in (&prover_aggregator).messages_and_publickeys() { + verifier_aggregator.add_message_n_publickey(msg, pk); + } + + let aggregated_pk = (&prover_aggregator).messages_and_publickeys().next().unwrap().1.0; + for aux in &pub_keys_in_sig_grp { + verifier_aggregator + .aggregate_aux_publickey_for_message_n_publickey(&message, &aggregated_pk, aux) + .expect("public key should match"); + } + + assert!( + verifier_aggregator.verify_using_aggregated_auxiliary_public_keys::(), + "verifier: verifying with honest auxilary public key should pass" + ); + + // Verifier with wrong aux: signer 1's aux used in place of signer 0's. + let mut bad_verifier = SignatureAggregatorAssumingPoP::::new(); + bad_verifier.add_signature(&(&prover_aggregator).signature()); + for (msg, (pk, _aux)) in (&prover_aggregator).messages_and_publickeys() { + bad_verifier.add_message_n_publickey(msg, pk); + } + bad_verifier + .aggregate_aux_publickey_for_message_n_publickey(&message, &aggregated_pk, &pub_keys_in_sig_grp[1]) + .unwrap(); + bad_verifier + .aggregate_aux_publickey_for_message_n_publickey(&message, &aggregated_pk, &pub_keys_in_sig_grp[1]) + .unwrap(); + bad_verifier + .aggregate_aux_publickey_for_message_n_publickey(&message, &aggregated_pk, &pub_keys_in_sig_grp[2]) + .unwrap(); + + assert!( + !bad_verifier.verify_using_aggregated_auxiliary_public_keys::(), + "verifier: non-matching auxilary public key should fail" + ); + + // Verifier tries to add aux with an individual signer's pk instead of + // the aggregated pk — should error because the message already has a + // different (aggregated) public key. + let mut mismatched_verifier = SignatureAggregatorAssumingPoP::::new(); + mismatched_verifier.add_signature(&(&prover_aggregator).signature()); + mismatched_verifier.add_message_n_publickey(&message, &aggregated_pk); + + let result = mismatched_verifier.aggregate_aux_publickey_for_message_n_publickey( + &message, + &keypairs[0].public, // individual pk, not the aggregated one + &pub_keys_in_sig_grp[0], + ); + assert!( + result.is_err(), + "aggregate_aux should error when public key does not match existing entry" + ); + } + + #[test] + fn test_aggregate_tiny_sigs_multi_messages_and_verify_in_g1() { + let messages: Vec = (0..3) + .map(|i| Message::new(b"ctx", &[b'm', b'0' + i as u8])) + .collect(); + let mut keypairs: Vec<_> = (0..3) + .map(|i| Keypair::>::generate(StdRng::from_seed([i; 32]))) + .collect(); + let pub_keys_in_sig_grp: Vec> = keypairs + .iter() + .map(|k| { + nugget::NuggetBLS::< + TinyBLS, + as EngineBLS>::SignatureGroup, + >::into_public_key_in_signature_group(k) + }) + .collect(); + + // Prover: each signer signs their own distinct message. + let mut prover_aggregator = SignatureAggregatorAssumingPoP::::new(); + + for ((k, m), aux) in keypairs.iter_mut().zip(messages.iter()).zip(pub_keys_in_sig_grp.iter()) { + prover_aggregator.add_signature(&k.sign(m)); + prover_aggregator.add_message_n_publickey(m, &(k.public, *aux)); + } + + assert!( + prover_aggregator.verify_using_aggregated_auxiliary_public_keys::(), + "prover: multi-message aggregate with honest auxilary public keys should pass" + ); + + // Verifier: receives aggregated data from prover, adds aux keys separately. + // Collect per-message (msg, pk, aux) from the prover to preserve association. + let prover_entries: Vec<_> = (&prover_aggregator) + .messages_and_publickeys() + .map(|(m, (pk, aux))| (m.clone(), *pk, *aux)) + .collect(); + + let mut verifier_aggregator = SignatureAggregatorAssumingPoP::::new(); + verifier_aggregator.add_signature(&(&prover_aggregator).signature()); + + for (msg, pk, _) in &prover_entries { + verifier_aggregator.add_message_n_publickey(msg, pk); + } + + for (msg, pk, aux) in &prover_entries { + verifier_aggregator + .aggregate_aux_publickey_for_message_n_publickey(msg, pk, aux) + .expect("public key should match"); + } + + assert!( + verifier_aggregator.verify_using_aggregated_auxiliary_public_keys::(), + "verifier: multi-message aggregate with honest auxilary public keys should pass" + ); + + // Verifier with wrong aux: rotate aux keys so each entry gets the wrong one. + let mut bad_verifier = SignatureAggregatorAssumingPoP::::new(); + bad_verifier.add_signature(&(&prover_aggregator).signature()); + for (msg, pk, _) in &prover_entries { + bad_verifier.add_message_n_publickey(msg, pk); + } + for (i, (msg, pk, _)) in prover_entries.iter().enumerate() { + let wrong_aux = &prover_entries[(i + 1) % prover_entries.len()].2; + bad_verifier + .aggregate_aux_publickey_for_message_n_publickey(msg, pk, wrong_aux) + .unwrap(); + } + + assert!( + !bad_verifier.verify_using_aggregated_auxiliary_public_keys::(), + "verifier: non-matching auxilary public key should fail" + ); + } + + #[test] + fn test_aggregate_tiny_sigs_with_mislabeled_message_fails_verification_in_g1() { + // Each signer signs its real message, but in the verifier we + // deliberately pair every signer's public key with the *wrong* + // message (rotated by one). The aggregated signature is honest, + // yet the (message, publickey) bookkeeping the verifier consumes + // is a lie, so verification must fail. + let real_messages: Vec = (0..3) + .map(|i| Message::new(b"ctx", &[b'm', b'0' + i as u8])) + .collect(); + let mut keypairs: Vec<_> = (0..3) + .map(|i| Keypair::>::generate(StdRng::from_seed([i; 32]))) + .collect(); + let pub_keys_in_sig_grp: Vec> = keypairs + .iter() + .map(|k| { + nugget::NuggetBLS::< + TinyBLS, + as EngineBLS>::SignatureGroup, + >::into_public_key_in_signature_group(k) + }) + .collect(); + + // Prover: signs real messages honestly. + let mut prover_aggregator = SignatureAggregatorAssumingPoP::::new(); + for ((i, k), aux) in keypairs.iter_mut().enumerate().zip(pub_keys_in_sig_grp.iter()) { + prover_aggregator.add_signature(&k.sign(&real_messages[i])); + prover_aggregator.add_message_n_publickey(&real_messages[i], &(k.public, *aux)); + } + + // Verifier: receives aggregated data from prover but pairs keys with wrong messages. + let prover_entries: Vec<_> = (&prover_aggregator) + .messages_and_publickeys() + .map(|(m, (pk, _))| (m.clone(), *pk)) + .collect(); + + let mut verifier_aggregator = SignatureAggregatorAssumingPoP::::new(); + verifier_aggregator.add_signature(&(&prover_aggregator).signature()); + + // Deliberately rotate messages so each pk is paired with the wrong one. + for (i, (_msg, pk)) in prover_entries.iter().enumerate() { + let wrong_message = &real_messages[(i + 1) % real_messages.len()]; + verifier_aggregator.add_message_n_publickey(wrong_message, pk); + } + for (i, (_msg, pk)) in prover_entries.iter().enumerate() { + let wrong_message = &real_messages[(i + 1) % real_messages.len()]; + verifier_aggregator + .aggregate_aux_publickey_for_message_n_publickey(wrong_message, pk, &pub_keys_in_sig_grp[i]) + .expect("public key should match"); + } + + assert!( + !verifier_aggregator.verify_using_aggregated_auxiliary_public_keys::(), + "verification must fail when public keys are paired with the wrong messages" + ); + } +} diff --git a/src/single_pop_aggregator.rs b/src/single_pop_aggregator.rs deleted file mode 100644 index 344e51c..0000000 --- a/src/single_pop_aggregator.rs +++ /dev/null @@ -1,344 +0,0 @@ -//! ## Aggregation of BLS signatures using proofs-of-possession -//! -//! In this module, we provide the linear flavor of aggregate -//! BLS signature in which the verifiers has previously checked -//! proofs-of-possession for all public keys. In other words, -//! we simply add up the signatures because the previously checked -//! proofs-of-possession for all signers prevent rogue key attacks. -//! See the security arguments in The Power of Proofs-of-Possession: -//! Securing Multiparty Signatures against Rogue-Key Attacks -//! by Thomas Ristenpart and Scott Yilek at https://eprint.iacr.org/2007/264.pdf -//! -//! These proof-of-possession are simply self-signed certificates, -//! so a BLS signature by each secret key on its own public key. -//! Importantly, the message for this self-signed certificates -//! must uniquely distinguish the public key for which the signature -//! establishes a proof-of-possession. -//! It follows that each proof-of-possession has a unique message, -//! so distinct message aggregation is optimal for verifying them. -//! -//! In this vein, we note that aggregation under proofs-of-possession -//! cannot improve performance when signers sign distinct messages, -//! so proofs-of-possession help with aggregating votes in a concensus -//! protocol, but should never be used for accounts on a block chain. -//! -//! We assume here that users provide their own data structure for -//! proofs-of-poossession. We provide more structure for users who -//! one bit per vote in a concensus protocol: -//! You first verify the proofs-of-possession when building a data -//! structure that holds the voters' keys. You implement the -//! `ProofsOfPossession` trait for this data strtcuture as well, -//! so that the `BitPoPSignedMessage` type provides a signature -//! data type with reasonable sanity checks. - -// Aside about proof-of-possession in the DLOG setting -// https://twitter.com/btcVeg/status/1085490561082183681 - -use ark_ff::Zero; - -use super::verifiers::{ - verify_using_aggregated_auxiliary_public_keys, verify_with_distinct_messages, -}; -use super::*; - -use digest::FixedOutputReset; - -/// Batch or aggregate BLS signatures with attached messages and -/// signers, for whom we previously checked proofs-of-possession. -/// -/// In this type, we provide a high-risk low-level batching and -/// aggregation mechanism that merely adds up signatures under the -/// assumption that all required proofs-of-possession were previously -/// checked. -/// -/// We say a signing key has provided a proof-of-possession if the -/// verifier remembers having checked some self-signed certificate -/// by that key. It's insecure to use this aggregation strategy -/// without first cehcking proofs-of-possession. In particular -/// it is insecure to use this aggregation strategy when checking -/// proofs-of-possession, and could not improve performance anyways. -/// Distinct message aggregation is always optimal for checking -/// proofs-of-possession. Please see the module level doumentation -/// for additional discussion and notes on security. -/// -/// We foresee this type primarily being used to batch several -/// `BitPoPSignedMessage`s into one verification. We do not track -/// aggreggated public keys here, instead merging multiples signers -/// public keys anytime they sign the same message, so this type -/// essentially provides only fast batch verificartion. -/// In principle, our `add_*` methods suffice for building an actual -/// aggregate signature type. Yet, normally direct approaches like -/// `BitPoPSignedMessage` work better for aggregation because -/// the `ProofsOfPossession` trait tooling permits both enforce the -/// proofs-of-possession and provide a compact serialization. -/// We see no reason to support serialization for this type as present. -/// message assumptions, or other aggre -/// -/// In principle, one might combine proof-of-possession with distinct -/// message assumptions, or other aggregation strategies, when -/// verifiers have only observed a subset of the proofs-of-possession, -/// but this sounds complex or worse fragile. -/// -/// TODO: Implement gaussian elimination verification scheme. -use core::iter::once; - -use nugget::PublicKeyInSignatureGroup; -use single::PublicKey; - -#[derive(Clone)] -pub struct SignatureAggregatorAssumingPoP { - message: Message, - aggregated_publickey: PublicKey, - signature: Signature, - aggregated_auxiliary_public_key: PublicKeyInSignatureGroup, -} - -impl SignatureAggregatorAssumingPoP { - pub fn new(message: Message) -> SignatureAggregatorAssumingPoP { - SignatureAggregatorAssumingPoP { - message: message, - aggregated_publickey: PublicKey(E::PublicKeyGroup::zero()), - signature: Signature(E::SignatureGroup::zero()), - aggregated_auxiliary_public_key: PublicKeyInSignatureGroup(E::SignatureGroup::zero()), - } - } - - /// Add only a `Signature` to our internal signature. - /// - /// Useful for constructing an aggregate signature, but we - pub fn add_signature(&mut self, signature: &Signature) { - self.signature.0 += &signature.0; - } - - /// Add only a `PublicKey` to our internal data. - /// - /// Useful for constructing an aggregate signature, but we - /// recommend instead using a custom types like `BitPoPSignedMessage`. - pub fn add_publickey(&mut self, publickey: &PublicKey) { - self.aggregated_publickey.0 += publickey.0; - } - - /// Aggregate the auxiliary public keys in the signature group to be used verification using aux key - pub fn add_auxiliary_public_key( - &mut self, - publickey_in_signature_group: &PublicKeyInSignatureGroup, - ) { - self.aggregated_auxiliary_public_key.0 += publickey_in_signature_group.0; - } - - /// Returns the aggergated public key. - /// - pub fn aggregated_publickey(&self) -> PublicKey { - self.aggregated_publickey - } - - // /// Aggregage BLS signatures assuming they have proofs-of-possession - // /// TODO this function should return Result refusing to aggregate messages - // /// different than the message the aggregator is initiated at - // pub fn aggregate<'a,S>(&mut self, signed: &'a S) - // where - // &'a S: Signed, - // <&'a S as Signed>::PKG: Borrow>, - // { - // let signature = signed.signature(); - // for (message,pubickey) in signed.messages_and_publickeys() { - // self.add_message_n_publickey(message.borrow(),pubickey.borrow()); - // } - // self.add_signature(&signature); - // } - - pub fn verify_using_aggregated_auxiliary_public_keys< - RandomOracle: FixedOutputReset + Default + Clone, - >( - &self, - ) -> bool { - verify_using_aggregated_auxiliary_public_keys::( - self, - true, - self.aggregated_auxiliary_public_key.0, - ) - } -} - -impl<'a, E: EngineBLS> Signed for &'a SignatureAggregatorAssumingPoP { - type E = E; - - type M = Message; - type PKG = PublicKey; - type PKnM = ::core::iter::Once<(Message, PublicKey)>; - - fn messages_and_publickeys(self) -> Self::PKnM { - once((self.message.clone(), self.aggregated_publickey)) // TODO: Avoid clone - } - - fn signature(&self) -> Signature { - self.signature - } - - fn verify(self) -> bool { - // We have already aggregated distinct messages, so our distinct - // message verification code provides reasonable optimizations, - // except the public keys might not be normalized here. - // We foresee verification via gaussian elimination being faster, - // but requires affine keys or normalization. - verify_with_distinct_messages(self, true) - // TODO: verify_with_gaussian_elimination(self) - } -} - -#[cfg(test)] -mod tests { - - use crate::EngineBLS; - use crate::Keypair; - use crate::Message; - use crate::TinyBLS; - use crate::UsualBLS; - use rand::SeedableRng; - use rand::rngs::StdRng; - use sha2::Sha256; - - use ark_bls12_377::Bls12_377; - use ark_bls12_381::Bls12_381; - - use super::*; - - #[test] - fn verify_aggregate_single_message_single_signer() { - let good = Message::new(b"ctx", b"test message"); - - let mut keypair = - Keypair::>::generate(StdRng::from_seed([0u8; 32])); - let good_sig0 = keypair.sign(&good); - assert!(good_sig0.verify(&good, &keypair.public)); - } - - #[test] - fn verify_aggregate_single_message_multi_signers() { - let good = Message::new(b"ctx", b"test message"); - - let mut keypair0 = - Keypair::>::generate(StdRng::from_seed([0u8; 32])); - let good_sig0 = keypair0.sign(&good); - - let mut keypair1 = - Keypair::>::generate(StdRng::from_seed([1u8; 32])); - let good_sig1 = keypair1.sign(&good); - - let mut aggregated_sigs = - SignatureAggregatorAssumingPoP::>::new(good); - aggregated_sigs.add_signature(&good_sig0); - aggregated_sigs.add_signature(&good_sig1); - - aggregated_sigs.add_publickey(&keypair0.public); - aggregated_sigs.add_publickey(&keypair1.public); - - assert!( - aggregated_sigs.verify() == true, - "good aggregated signature of a single message with multiple key does not verify" - ); - } - - #[test] - fn verify_aggregate_single_message_repetative_signers() { - let good = Message::new(b"ctx", b"test message"); - - let mut keypair = - Keypair::>::generate(StdRng::from_seed([0u8; 32])); - let good_sig = keypair.sign(&good); - - let mut aggregated_sigs = - SignatureAggregatorAssumingPoP::>::new(good); - aggregated_sigs.add_signature(&good_sig); - aggregated_sigs.add_signature(&good_sig); - - aggregated_sigs.add_publickey(&keypair.public); - aggregated_sigs.add_publickey(&keypair.public); - - assert!( - aggregated_sigs.verify() == true, - "good aggregate of a repetitive signature does not verify" - ); - } - - #[test] - fn aggregate_of_signature_of_a_wrong_message_should_not_verify() { - let good0 = Message::new(b"ctx", b"Space over Tab"); - let bad1 = Message::new(b"ctx", b"Tab over Space"); - - let mut keypair0 = - Keypair::>::generate(StdRng::from_seed([0u8; 32])); - let good_sig0 = keypair0.sign(&good0); - - let mut keypair1 = - Keypair::>::generate(StdRng::from_seed([1u8; 32])); - let bad_sig1 = keypair1.sign(&bad1); - - let mut aggregated_sigs = SignatureAggregatorAssumingPoP::< - UsualBLS, - >::new(good0); - aggregated_sigs.add_signature(&good_sig0); - aggregated_sigs.add_signature(&bad_sig1); - - aggregated_sigs.add_publickey(&keypair0.public); - aggregated_sigs.add_publickey(&keypair1.public); - - assert!( - aggregated_sigs.verify() == false, - "aggregated signature of a wrong message should not verify" - ); - } - - #[test] - fn test_aggregate_tiny_sigs_and_verify_in_g1() { - let message = Message::new(b"ctx", b"test message"); - let mut keypairs: Vec<_> = (0..3) - .into_iter() - .map(|i| Keypair::>::generate(StdRng::from_seed([i; 32]))) - .collect(); - let pub_keys_in_sig_grp: Vec> = keypairs - .iter() - .map(|k| { - nugget::NuggetBLS::< - TinyBLS, - as EngineBLS>::SignatureGroup, - >::into_public_key_in_signature_group(k) - }) - .collect(); - - let mut aggregator = SignatureAggregatorAssumingPoP::::new(message.clone()); - let mut aggregated_public_key = - PublicKey::(::PublicKeyGroup::zero()); - - for k in &mut keypairs { - aggregator.add_signature(&k.sign(&message)); - aggregated_public_key.0 += k.public.0; - } - - let mut verifier_aggregator = SignatureAggregatorAssumingPoP::::new(message); - - verifier_aggregator.add_signature(&aggregator.signature); - verifier_aggregator.add_publickey(&aggregated_public_key); - - for k in &pub_keys_in_sig_grp { - verifier_aggregator.add_auxiliary_public_key(k); - } - - assert!( - verifier_aggregator.verify_using_aggregated_auxiliary_public_keys::(), - "verifying with honest auxilary public key should pass" - ); - - //false aggregation in signature group should fails verification. - verifier_aggregator.add_auxiliary_public_key(&nugget::NuggetBLS::< - TinyBLS, - as EngineBLS>::SignatureGroup, - >::into_public_key_in_signature_group( - &keypairs[0] - )); - assert!( - !verifier_aggregator.verify_using_aggregated_auxiliary_public_keys::(), - "verification using non-matching auxilary public key should fail" - ); - } -} diff --git a/src/verifiers.rs b/src/verifiers.rs index 20fbb05..50b9eb0 100644 --- a/src/verifiers.rs +++ b/src/verifiers.rs @@ -77,7 +77,7 @@ fn collect_messages_and_publickeys( let mut publickeys = Vec::with_capacity(l); let mut messages = Vec::with_capacity(l); for (message, publickey) in itr { - publickeys.push(publickey.borrow().0); + publickeys.push(publickey.public_key().0); messages.push(message.borrow().hash_to_signature_curve::()); } (signature, publickeys, messages) @@ -109,6 +109,58 @@ fn merge_by_signer( pks_n_ms.into_values().unzip() } +/// Like `merge_by_signer` but keyed on `(public_key, aux_public_key)`. +/// Only message points are merged for entries sharing the same signer pair. +/// Like `merge_by_signer` but also carries auxiliary public keys. +/// Keyed on the public key; returns `None` if the same public key +/// appears with conflicting auxiliary keys. +fn merge_by_signer_with_aux( + affine_publickeys: Vec>, + aux_keys: Vec>, + messages: Vec>, +) -> Option<( + Vec>, + Vec>, + Vec>, +)> { + type PkAuxMsg = ( + PublicKeyAffine, + SignatureAffine, + SignatureProjective, + ); + let mut map: BTreeMap, PkAuxMsg> = BTreeMap::new(); + for ((pk, aux), m) in affine_publickeys + .into_iter() + .zip(aux_keys) + .zip(messages) + { + let aux_affine = aux.into_affine(); + let mut pk_bytes = vec![0; pk.uncompressed_size()]; + pk.serialize_uncompressed(&mut pk_bytes[..]).unwrap(); + match map.entry(pk_bytes) { + alloc::collections::btree_map::Entry::Occupied(mut e) => { + let (_, existing_aux, existing_msg) = e.get_mut(); + if *existing_aux != aux_affine { + return None; + } + *existing_msg += m; + } + alloc::collections::btree_map::Entry::Vacant(e) => { + e.insert((pk, aux_affine, m)); + } + } + } + let mut pks = Vec::with_capacity(map.len()); + let mut auxs = Vec::with_capacity(map.len()); + let mut msgs = Vec::with_capacity(map.len()); + for (pk, aux, m) in map.into_values() { + pks.push(pk); + auxs.push(aux); + msgs.push(m); + } + Some((pks, auxs, msgs)) +} + /// Batch-normalize projective public keys, or convert to affine individually /// when they are already expected to be normalized. fn normalize_publickeys( @@ -147,7 +199,7 @@ pub fn verify_unoptimized(s: S) -> bool { .messages_and_publickeys() .map(|(message, public_key)| { ( - S::E::prepare_public_key(public_key.borrow().0), + S::E::prepare_public_key(public_key.public_key().0), S::E::prepare_signature(message.borrow().hash_to_signature_curve::()), ) }) @@ -198,20 +250,22 @@ pub fn verify_with_distinct_messages(signed: S, normalize_public_keys } /// BLS signature verification optimized for all unique messages -/// with aggregated auxiliary public keys. +/// with auxiliary public keys. /// -/// Similar to `verify_with_distinct_messages` but adds a randomized -/// auxiliary public key component to each message point and the -/// signature, using deterministic randomness derived from the inputs. +/// Similar to `verify_with_distinct_messages` but for each merged +/// (signer, message) entry, derives a per-entry pseudo-random scalar +/// and folds the auxiliary key contribution into the message point +/// and signature. +// e(asig + \sum_i t_i apk_i,1 , g_2) = \sum_i e (H(m_i) + t_i g_1,apk_i,2) + pub fn verify_using_aggregated_auxiliary_public_keys< E: EngineBLS, H: FixedOutputReset + Default + Clone, >( - signed: &single_pop_aggregator::SignatureAggregatorAssumingPoP, + signed: &pop_aggregator::SignatureAggregatorAssumingPoP, normalize_public_keys: bool, - aggregated_aux_pub_key: ::SignatureGroup, ) -> bool { - let signature = Signed::signature(&signed).0; + let mut signature = Signed::signature(&signed).0; let mut signature_as_bytes = vec![0; signature.compressed_size()]; signature @@ -223,62 +277,54 @@ pub fn verify_using_aggregated_auxiliary_public_keys< let (lower, upper) = itr.size_hint(); upper.unwrap_or(lower) }; - let (first_message, first_public_key) = match signed.messages_and_publickeys().next() { - Some((first_message, first_public_key)) => (first_message, first_public_key), - None => return false, - }; - - let mut first_public_key_as_bytes = vec![0; first_public_key.compressed_size()]; - first_public_key - .serialize_compressed(&mut first_public_key_as_bytes[..]) - .expect("compressed size has been alocated"); - let first_message_point = first_message.hash_to_signature_curve::(); - let first_message_point_as_bytes = E::signature_point_to_byte(&first_message_point); + // Collect public keys, auxiliary keys, and message points. + let mut publickeys = Vec::with_capacity(l); + let mut aux_keys = Vec::with_capacity(l); + let mut messages = Vec::with_capacity(l); + for (m, pk) in itr { + publickeys.push(pk.0.0); + aux_keys.push(pk.1.0); + messages.push(m.hash_to_signature_curve::()); + } - let mut aggregated_aux_pub_key_as_bytes = vec![0; aggregated_aux_pub_key.compressed_size()]; - aggregated_aux_pub_key - .serialize_compressed(&mut aggregated_aux_pub_key_as_bytes[..]) - .expect("compressed size has been alocated"); + let affine_publickeys = normalize_publickeys::(&publickeys, normalize_public_keys); - // We first hash the messages to the signature curve and - // normalize the public keys to operate on them as bytes. - // TODO: Assess if we should mutate in place using interior - // mutability, maybe using `BorrowMut` support in - // `batch_normalization`. + // Merge message points that share the same signer. + // Returns None if same public key appears with conflicting aux keys. + let (merged_pks, merged_aux, mut merged_msgs) = match + merge_by_signer_with_aux::(affine_publickeys, aux_keys, messages) + { + Some(v) => v, + None => return false, + }; - // deterministic randomness for adding aggregated auxiliary pub keys - //TODO you can't just assume that there is one pubickey you need to stop if they were more or aggregate them + // For each merged entry, compute a per-entry pseudo-random scalar + // and fold the auxiliary key contribution into message and signature. + let hasher = as HashToField>::new(&[]); - let pseudo_random_scalar_seed = [ - first_message_point_as_bytes, - first_public_key_as_bytes, - aggregated_aux_pub_key_as_bytes, - signature_as_bytes, - ] - .concat(); + for i in 0..merged_pks.len() { + let mut pk_bytes = vec![0; merged_pks[i].compressed_size()]; + merged_pks[i] + .serialize_compressed(&mut pk_bytes[..]) + .expect("compressed size has been alocated"); - let hasher = as HashToField>::new(&[]); - let pseudo_random_scalar: E::Scalar = - hasher.hash_to_field::<1>(&pseudo_random_scalar_seed[..])[0]; + let mut aux_pk_bytes = vec![0; merged_aux[i].compressed_size()]; + merged_aux[i] + .serialize_compressed(&mut aux_pk_bytes[..]) + .expect("compressed size has been alocated"); - let signature = signature + aggregated_aux_pub_key * pseudo_random_scalar; + let msg_bytes = E::signature_point_to_byte(&merged_msgs[i]); - //Simplify from here on. - let mut publickeys = Vec::with_capacity(l); - let mut messages = Vec::with_capacity(l); - for (m, pk) in itr { - publickeys.push(pk.0); - messages.push( - m.hash_to_signature_curve::() - + E::SignatureGroupAffine::generator() * pseudo_random_scalar, - ); - } + let pseudo_random_scalar_seed = + [msg_bytes, pk_bytes, aux_pk_bytes, signature_as_bytes.clone()].concat(); - let affine_publickeys = normalize_publickeys::(&publickeys, normalize_public_keys); + let pseudo_random_scalar: E::Scalar = + hasher.hash_to_field::<1>(&pseudo_random_scalar_seed[..])[0]; - // We next accumulate message points with the same signer. - let (merged_pks, merged_msgs) = merge_by_signer::(affine_publickeys, messages); + signature += merged_aux[i] * pseudo_random_scalar; + merged_msgs[i] += E::SignatureGroupAffine::generator() * pseudo_random_scalar; + } // And verify the aggregate signature. let (affine_msgs, affine_sig) =