├── .gitignore ├── .travis.yml ├── Cargo.toml ├── README.md ├── examples ├── aggregate_with_public_key_in_signature_group.rs ├── aggregated.rs ├── aggregated_with_pop.rs ├── nugget_pop.rs └── simple.rs └── src ├── bench.rs ├── bit.rs ├── chaum_pedersen_signature.rs ├── delinear.rs ├── distinct.rs ├── double.rs ├── double_pop.rs ├── engine.rs ├── lib.rs ├── multi_pop_aggregator.rs ├── schnorr_pop.rs ├── serialize.rs ├── single.rs ├── single_pop_aggregator.rs └── verifiers.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | rust: 4 | - stable 5 | - beta 6 | - nightly 7 | 8 | matrix: 9 | allow_failures: 10 | - rust: nightly 11 | 12 | env: 13 | global: 14 | - RUSTFLAGS="-C link-dead-code" 15 | 16 | addons: 17 | apt: 18 | packages: 19 | - libcurl4-openssl-dev 20 | - libelf-dev 21 | - libdw-dev 22 | - cmake 23 | - gcc 24 | - binutils-dev 25 | - libiberty-dev 26 | 27 | after_success: | 28 | wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz && 29 | tar xzf master.tar.gz && 30 | cd kcov-master && 31 | mkdir build && 32 | cd build && 33 | cmake .. && 34 | make && 35 | make install DESTDIR=../../kcov-build && 36 | cd ../.. && 37 | rm -rf kcov-master && 38 | for file in target/debug/bls-*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done && 39 | bash <(curl -s https://codecov.io/bash) && 40 | echo "Uploaded code coverage" 41 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jack Grigg ", "Jeff Burdges ", "Syed Hosseini "] 3 | description = "Aggregate BLS-like signatures" 4 | documentation = "https://docs.rs/w3f-bls" 5 | homepage = "https://github.com/w3f/bls" 6 | license = "MIT/Apache-2.0" 7 | name = "w3f-bls" 8 | repository = "https://github.com/w3f/bls" 9 | version = "0.1.9" 10 | edition = "2018" 11 | 12 | [dependencies] 13 | arrayref = { version = "0.3", default-features = false } 14 | rand = { version = "0.8.5", default-features = false} 15 | rand_core = { version = "0.6", default-features = false } 16 | rand_chacha = { version = "0.3", default-features = false } 17 | sha3 = { version = "0.10", default-features = false } 18 | sha2 = { version = "0.10", default-features = false } 19 | digest = { version = "0.10", default-features = false } 20 | 21 | ark-ff = { version = "0.4.0", default-features = false } 22 | ark-ec = { version = "0.4.0", default-features = false } 23 | ark-serialize = { version = "0.4.0", default-features = false, features = [ "derive" ] } 24 | ark-serialize-derive = { version = "0.4.0", default-features = false } 25 | 26 | ark-bls12-381 = { version = "0.4.0", default-features = false, features = [ "curve" ] } 27 | ark-bls12-377 = { version = "0.4.0", default-features = false, features = [ "curve" ] } 28 | 29 | zeroize = { version = "1.0", default-features = false, features = [ "zeroize_derive" ] } 30 | 31 | [dev-dependencies] 32 | hex-literal = "0.3.4" 33 | 34 | [features] 35 | default = ["std"] 36 | std = ["rand/std"] 37 | experimental = [] 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bls [![Crates.io](https://img.shields.io/crates/v/w3f-bls.svg)](https://crates.io/crates/w3f-bls) # 2 | 3 | Boneh-Lynn-Shacham (BLS) signatures have slow signing, very slow verification, require slow and much less secure pairing friendly curves, and tend towards dangerous malleability. Yet, BLS permits a diverse array of signature aggregation options far beyond any other known signature scheme, which makes BLS a preferred scheme for voting in consensus algorithms and for threshold signatures. 4 | 5 | In this crate, we take a largely unified approach to aggregation techniques and verifier optimisations for BLS signature: We support the [BLS12-381](https://z.cash/blog/new-snark-curve.html) and [BLS12-377](https://eprint.iacr.org/2018/962.pdf) (Barreto-Lynn-Scott) curves via Arkworks traits, but abstract the pairing so that developers can choose their preferred orientation for BLS signatures. We provide aggregation techniques based on messages being distinct, on proofs-of-possession, and on delinearization, although we do not provide all known optimisations for delinearization. 6 | 7 | We provide implementation of generation and verification proof-of-possession based on Schnorr Signature which is faster than using BLS Signature itself for this task. 8 | 9 | We cannot claim these abstractions provide miss-use resistance, but they at least structure the problem, provide some guidlines, and maximize the relevance of warnings present in the documentation. 10 | 11 | ## Documentation 12 | 13 | You first bring the `bls` crate into your project just as you normally would. 14 | 15 | ```rust 16 | use w3f_bls::{Keypair,ZBLS,Message,Signed}; 17 | 18 | let mut keypair = Keypair::::generate(::rand::thread_rng()); 19 | let message = Message::new(b"Some context",b"Some message"); 20 | let sig = keypair.sign(&message); 21 | assert!( sig.verify(&message,&keypair.public) ); 22 | ``` 23 | 24 | In this example, `sig` is a `Signature` which only contains signature. One can use `Keypair::signed_message` method which returns a `SignedMessage` struct that contains the message hash, the signer's public key, and of course the signature, but one should usually detach these constituents for wire formats. 25 | 26 | Aggregated and blind signatures are almost the only reasons anyone would consider using BLS signatures, so we focus on aggregation here. We assume for brevity that `sigs` is an array of `SignedMessage`s, as one might construct like 27 | 28 | As a rule, aggregation that requires distinct messages still requires one miller loop step per message, so aggregate signatures have rather slow verification times. You can nevertheless achieve quite small signature sizes like 29 | 30 | ```rust 31 | #[cfg(feature = "experimental")] 32 | use w3f_bls::{distinct::DistinctMessages, Keypair, Message, Signed, ZBLS}; 33 | 34 | #[cfg(feature = "experimental")] 35 | { 36 | let mut keypairs = [ 37 | Keypair::::generate(::rand::thread_rng()), 38 | Keypair::::generate(::rand::thread_rng()), 39 | ]; 40 | let msgs = [ 41 | "The ships", 42 | "hung in the sky", 43 | "in much the same way", 44 | "that bricks don’t.", 45 | ] 46 | .iter() 47 | .map(|m| Message::new(b"Some context", m.as_bytes())) 48 | .collect::>(); 49 | let sigs = msgs 50 | .iter() 51 | .zip(keypairs.iter_mut()) 52 | .map(|(m, k)| k.signed_message(m)) 53 | .collect::>(); 54 | 55 | let dms = sigs 56 | .iter() 57 | .try_fold(DistinctMessages::::new(), |dm, sig| dm.add(sig)) 58 | .unwrap(); 59 | let signature = <&DistinctMessages as Signed>::signature(&&dms); 60 | 61 | let publickeys = keypairs.iter().map(|k| k.public).collect::>(); 62 | let mut dms = msgs 63 | .into_iter() 64 | .zip(publickeys) 65 | .try_fold( 66 | DistinctMessages::::new(), 67 | |dm, (message, publickey)| dm.add_message_n_publickey(message, publickey), 68 | ) 69 | .unwrap(); 70 | dms.add_signature(&signature); 71 | assert!(dms.verify()) 72 | } 73 | ``` 74 | Anyone who receives the already aggregated signature along with a list of messages and public keys might reconstruct the signature as shown in the above example. 75 | 76 | We recommend distinct message aggregation like this primarily for verifying proofs-of-possession, meaning checking the self certificates for numerous keys. 77 | 78 | Assuming you already have proofs-of-possession, then you'll want to do aggregation with `BitPoPSignedMessage` or some variant tuned to your use case. We recommend more care when using `SignatureAggregatorAssumingPoP` because it provides no mechanism for checking a proof-of-possession table. 79 | 80 | 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. 81 | 82 | ```rust 83 | use w3f_bls::{Keypair,PublicKey,ZBLS,Message,Signed, ProofOfPossessionGenerator, ProofOfPossession, schnorr_pop::{SchnorrPoP}, multi_pop_aggregator::MultiMessageSignatureAggregatorAssumingPoP}; 84 | use sha2::Sha256; 85 | 86 | let mut keypairs = [Keypair::::generate(::rand::thread_rng()), Keypair::::generate(::rand::thread_rng())]; 87 | let msgs = ["The ships", "hung in the sky", "in much the same way", "that bricks don’t."].iter().map(|m| Message::new(b"Some context", m.as_bytes())).collect::>(); 88 | let sigs = msgs.iter().zip(keypairs.iter_mut()).map(|(m,k)| k.sign(m)).collect::>(); 89 | 90 | let publickeys = keypairs.iter().map(|k|k.public.clone()).collect::>(); 91 | let pops = keypairs.iter_mut().map(|k|(ProofOfPossessionGenerator::, SchnorrPoP>::generate_pok(k))).collect::>(); 92 | 93 | //first make sure public keys have valid pop 94 | let publickeys = publickeys.iter().zip(pops.iter()).map(|(publickey, pop) | {assert!(ProofOfPossession::>::verify(pop,publickey)); publickey}).collect::>(); 95 | 96 | let batch_poped = msgs.iter().zip(publickeys).zip(sigs).fold( 97 | MultiMessageSignatureAggregatorAssumingPoP::::new(), 98 | |mut bpop,((message, publickey),sig)| { bpop.add_message_n_publickey(message, &publickey); bpop.add_signature(&sig); bpop } 99 | ); 100 | assert!(batch_poped.verify()) 101 | ``` 102 | 103 | If you lack proofs-of-possesion, then delinearized approaches are provided in the `delinear` module, but such schemes might require a more customised approach. However, note that currently only aggeration assuming proof of possession is maintained and the other strategies are experimental. 104 | 105 | ### Efficient Aggregatable BLS Signatures with Chaum-Pedersen Proofs 106 | 107 | The scheme introduced in [`our recent paper`](https://eprint.iacr.org/2022/1611) is implemented in [`chaum_pederson_signature.rs`](src/chaum_pederson_signature.rs) using `ChaumPedersonSigner` and `ChaumPedersonVerifier` traits and in [`pop.rs`](src/pop.rs) using `add_auxiliary_public_key` and `verify_using_aggregated_auxiliary_public_keys` functions which is demonestrated in the following example: 108 | ```rust 109 | use sha2::Sha256; 110 | use ark_bls12_377::Bls12_377; 111 | use ark_ff::Zero; 112 | use rand::thread_rng; 113 | 114 | use w3f_bls::{ 115 | single_pop_aggregator::SignatureAggregatorAssumingPoP, DoublePublicKeyScheme, EngineBLS, Keypair, Message, PublicKey, PublicKeyInSignatureGroup, Signed, TinyBLS, TinyBLS377, 116 | }; 117 | 118 | 119 | let message = Message::new(b"ctx", b"I'd far rather be happy than right any day."); 120 | let mut keypairs: Vec<_> = (0..3) 121 | .into_iter() 122 | .map(|_| Keypair::>::generate(thread_rng())) 123 | .collect(); 124 | let pub_keys_in_sig_grp: Vec> = keypairs 125 | .iter() 126 | .map(|k| k.into_public_key_in_signature_group()) 127 | .collect(); 128 | 129 | let mut prover_aggregator = 130 | SignatureAggregatorAssumingPoP::::new(message.clone()); 131 | let mut aggregated_public_key = 132 | PublicKey::(::PublicKeyGroup::zero()); 133 | 134 | //sign and aggegate 135 | let _ = keypairs 136 | .iter_mut() 137 | .map(|k| { 138 | prover_aggregator.add_signature(&k.sign(&message)); 139 | aggregated_public_key.0 += k.public.0; 140 | }) 141 | .count(); 142 | 143 | let mut verifier_aggregator = SignatureAggregatorAssumingPoP::::new(message); 144 | 145 | verifier_aggregator.add_signature(&(&prover_aggregator).signature()); 146 | 147 | //aggregate public keys in signature group 148 | verifier_aggregator.add_publickey(&aggregated_public_key); 149 | 150 | pub_keys_in_sig_grp.iter().for_each(|pk| {verifier_aggregator.add_auxiliary_public_key(pk);}); 151 | 152 | assert!( 153 | verifier_aggregator.verify_using_aggregated_auxiliary_public_keys::(), 154 | "verifying with honest auxilary public key should pass" 155 | ); 156 | ``` 157 | 158 | ### Hash to Curve 159 | 160 | In order to sign a message, the library needs to hash the message as a point on the signature curve. While `BLSEngine` trait is agnostic about `MapToSignatureCurve` method, our implementation of BLS12-381 (`ZBLS`) and BLS12-377(`BLS377`) specifically uses Wahby and Boneh hash to curve method described in Section of 6.6.3 of https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/. 161 | 162 | ## Security Warnings 163 | 164 | This library does not make any guarantees about constant-time operations, memory access patterns, or resistance to side-channel attacks. 165 | 166 | ## License 167 | 168 | Licensed under either of 169 | 170 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 171 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 172 | 173 | at your option. 174 | 175 | ### Contribution 176 | 177 | Unless you explicitly state otherwise, any contribution intentionally 178 | submitted for inclusion in the work by you, as defined in the Apache-2.0 179 | license, shall be dual licensed as above, without any additional terms or 180 | conditions. 181 | 182 | -------------------------------------------------------------------------------- /examples/aggregate_with_public_key_in_signature_group.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "std")] 2 | use sha2::Sha256; 3 | #[cfg(feature = "std")] 4 | use w3f_bls::{ 5 | single_pop_aggregator::SignatureAggregatorAssumingPoP, DoublePublicKeyScheme, EngineBLS, 6 | Keypair, Message, PublicKey, PublicKeyInSignatureGroup, Signed, TinyBLS, TinyBLS377, 7 | }; 8 | 9 | #[cfg(feature = "std")] 10 | use ark_bls12_377::Bls12_377; 11 | #[cfg(feature = "std")] 12 | use ark_ff::Zero; 13 | #[cfg(feature = "std")] 14 | use rand::thread_rng; 15 | 16 | /// Run using 17 | /// ```sh 18 | /// cargo run --example aggregated_with_public_key_in_signature_group.rs 19 | /// ``` 20 | fn main() { 21 | #[cfg(feature = "std")] 22 | { 23 | let message = Message::new(b"ctx", b"I'd far rather be happy than right any day."); 24 | let mut keypairs: Vec<_> = (0..3) 25 | .into_iter() 26 | .map(|_| Keypair::>::generate(thread_rng())) 27 | .collect(); 28 | let pub_keys_in_sig_grp: Vec> = keypairs 29 | .iter() 30 | .map(|k| k.into_public_key_in_signature_group()) 31 | .collect(); 32 | let mut prover_aggregator = 33 | SignatureAggregatorAssumingPoP::::new(message.clone()); 34 | let mut aggregated_public_key = 35 | PublicKey::(::PublicKeyGroup::zero()); 36 | 37 | //sign and aggegate 38 | keypairs.iter_mut().for_each(|k| { 39 | prover_aggregator.add_signature(&k.sign(&message)); 40 | aggregated_public_key.0 += k.public.0; 41 | }); 42 | 43 | let mut verifier_aggregator = SignatureAggregatorAssumingPoP::::new(message); 44 | //get the signature and already aggregated public key from the prover 45 | verifier_aggregator.add_signature(&(&prover_aggregator).signature()); 46 | verifier_aggregator.add_publickey(&aggregated_public_key); 47 | 48 | //aggregate public keys in signature group 49 | pub_keys_in_sig_grp.iter().for_each(|pk| { 50 | verifier_aggregator.add_auxiliary_public_key(pk); 51 | }); 52 | 53 | assert!( 54 | verifier_aggregator.verify_using_aggregated_auxiliary_public_keys::(), 55 | "verifying with honest auxilary public key should pass" 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/aggregated.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "experimental")] 2 | use w3f_bls::{distinct::DistinctMessages, Keypair, Message, Signed, ZBLS}; 3 | 4 | /// Run using 5 | /// ```sh 6 | /// cargo run --features "experimental" --example aggregated 7 | /// ``` 8 | fn main() { 9 | #[cfg(feature = "experimental")] 10 | { 11 | let mut keypairs = [ 12 | Keypair::::generate(::rand::thread_rng()), 13 | Keypair::::generate(::rand::thread_rng()), 14 | ]; 15 | let msgs = [ 16 | "The ships", 17 | "hung in the sky", 18 | "in much the same way", 19 | "that bricks don’t.", 20 | ] 21 | .iter() 22 | .map(|m| Message::new(b"Some context", m.as_bytes())) 23 | .collect::>(); 24 | let sigs = msgs 25 | .iter() 26 | .zip(keypairs.iter_mut()) 27 | .map(|(m, k)| k.signed_message(m)) 28 | .collect::>(); 29 | 30 | let dms = sigs 31 | .iter() 32 | .try_fold(DistinctMessages::::new(), |dm, sig| dm.add(sig)) 33 | .unwrap(); 34 | let signature = <&DistinctMessages as Signed>::signature(&&dms); 35 | 36 | let publickeys = keypairs.iter().map(|k| k.public).collect::>(); 37 | let mut dms = msgs 38 | .into_iter() 39 | .zip(publickeys) 40 | .try_fold( 41 | DistinctMessages::::new(), 42 | |dm, (message, publickey)| dm.add_message_n_publickey(message, publickey), 43 | ) 44 | .unwrap(); 45 | dms.add_signature(&signature); 46 | assert!(dms.verify()) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/aggregated_with_pop.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "std")] 2 | use sha2::Sha256; 3 | #[cfg(feature = "std")] 4 | use w3f_bls::{ 5 | multi_pop_aggregator::MultiMessageSignatureAggregatorAssumingPoP, schnorr_pop::SchnorrPoP, 6 | Keypair, Message, ProofOfPossession, ProofOfPossessionGenerator, PublicKey, Signed, ZBLS, 7 | }; 8 | 9 | /// Run using 10 | /// ```sh 11 | /// cargo run --example aggregated_with_pop 12 | /// ``` 13 | fn main() { 14 | #[cfg(feature = "std")] 15 | { 16 | let mut keypairs = [ 17 | Keypair::::generate(::rand::thread_rng()), 18 | Keypair::::generate(::rand::thread_rng()), 19 | ]; 20 | let msgs = [ 21 | "The ships", 22 | "hung in the sky", 23 | "in much the same way", 24 | "that bricks don’t.", 25 | ] 26 | .iter() 27 | .map(|m| Message::new(b"Some context", m.as_bytes())) 28 | .collect::>(); 29 | let sigs = msgs 30 | .iter() 31 | .zip(keypairs.iter_mut()) 32 | .map(|(m, k)| k.sign(m)) 33 | .collect::>(); 34 | 35 | let publickeys = keypairs 36 | .iter() 37 | .map(|k| k.public.clone()) 38 | .collect::>(); 39 | let pops = keypairs.iter_mut().map(|k|(ProofOfPossessionGenerator::, SchnorrPoP>::generate_pok(k))).collect::>(); 40 | 41 | //first make sure public keys have valid pop 42 | let publickeys = publickeys 43 | .iter() 44 | .zip(pops.iter()) 45 | .map(|(publickey, pop)| { 46 | assert!(ProofOfPossession::>::verify( 47 | pop, publickey 48 | )); 49 | publickey 50 | }) 51 | .collect::>(); 52 | 53 | let batch_poped = msgs.iter().zip(publickeys).zip(sigs).fold( 54 | MultiMessageSignatureAggregatorAssumingPoP::::new(), 55 | |mut bpop, ((message, publickey), sig)| { 56 | bpop.add_message_n_publickey(message, &publickey); 57 | bpop.add_signature(&sig); 58 | bpop 59 | }, 60 | ); 61 | assert!(batch_poped.verify()) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /examples/nugget_pop.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "std")] 2 | use sha2::Sha256; 3 | #[cfg(feature = "std")] 4 | use w3f_bls::{ 5 | DoublePublicKey, Keypair, NuggetBLSPoP, NuggetBLSnCPPoP, ProofOfPossessionGenerator, 6 | SerializableToBytes, TinyBLS381, 7 | }; 8 | 9 | #[cfg(feature = "std")] 10 | use rand::thread_rng; 11 | 12 | /// Run using 13 | /// ```sh 14 | /// cargo run --example aggregated_with_public_key_in_signature_group.rs 15 | /// ``` 16 | fn main() { 17 | #[cfg(feature = "std")] 18 | { 19 | let mut keypair = Keypair::::generate(thread_rng()); 20 | 21 | //generate BLS only PoP 22 | let proof_pair = , 26 | NuggetBLSPoP, 27 | >>::generate_pok(&mut keypair); 28 | 29 | println!( 30 | "Nugget BLS Proof of possession of {:?} is {:?}", 31 | keypair.public.to_bytes(), 32 | proof_pair.to_bytes() 33 | ); 34 | 35 | //generate BLS and CP PoP 36 | let proof_pair = , 40 | NuggetBLSnCPPoP, 41 | >>::generate_pok(&mut keypair); 42 | 43 | println!( 44 | "Nugget BLS and CP Proof of possession of {:?} is {:?}", 45 | keypair.public.to_bytes(), 46 | proof_pair.to_bytes() 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "std")] 2 | use w3f_bls::{Keypair, Message, ZBLS}; 3 | 4 | /// Run using 5 | /// ```sh 6 | /// cargo run --example simple 7 | /// ``` 8 | fn main() { 9 | #[cfg(feature = "std")] 10 | { 11 | let mut keypair = Keypair::::generate(::rand::thread_rng()); 12 | let message = Message::new(b"Some context", b"Some message"); 13 | let sig = keypair.sign(&message); 14 | assert!(sig.verify(&message, &keypair.public)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/bench.rs: -------------------------------------------------------------------------------- 1 | extern crate test; 2 | 3 | const NO_OF_MULTI_SIG_SIGNERS : usize = 10000; 4 | use test::{Bencher, black_box}; 5 | 6 | // #[bench] 7 | // fn only_generate_key_pairs(b: &mut Bencher) { 8 | // b.iter(|| { 9 | 10 | // let mut keypairs = generate_many_keypairs(NO_OF_MULTI_SIG_SIGNERS); 11 | // }); 12 | // } 13 | 14 | #[bench] 15 | fn test_many_tiny_aggregate_and_verify_in_g2(b: &mut Bencher) { 16 | let message = Message::new(b"ctx",b"test message"); 17 | let mut keypairs = generate_many_keypairs(NO_OF_MULTI_SIG_SIGNERS); 18 | let mut pub_keys_in_sig_grp : Vec> = keypairs.iter().map(|k| k.into_public_key_in_signature_group()).collect(); 19 | 20 | let mut aggregated_public_key = PublicKey::(::PublicKeyGroup::zero()); 21 | let mut aggregator = MultiMessageSignatureAggregatorAssumingPoP::::new(); 22 | 23 | for k in &mut keypairs { 24 | aggregator.aggregate(&k.signed_message(message)); 25 | aggregated_public_key.0 += k.public.0; 26 | } 27 | 28 | b.iter(|| { 29 | let mut verifier_aggregator = MultiMessageSignatureAggregatorAssumingPoP::::new(); 30 | let mut verifier_aggregated_public_key = PublicKey::(::PublicKeyGroup::zero()); 31 | 32 | verifier_aggregator.add_signature(&aggregator.signature); 33 | 34 | for k in &mut keypairs { 35 | verifier_aggregated_public_key.0 += k.public.0; 36 | } 37 | 38 | verifier_aggregator.add_message_n_publickey(&message, &verifier_aggregated_public_key); 39 | 40 | assert!(verifier_aggregator.verify()); 41 | }); 42 | } 43 | 44 | // #[bench] 45 | // fn test_many_tiny_aggregate_only_no_verify(b: &mut Bencher) { 46 | // let mut keypairs = generate_many_keypairs(NO_OF_MULTI_SIG_SIGNERS); 47 | // let mut pub_keys_in_sig_grp : Vec> = keypairs.iter().map(|k| k.into_public_key_in_signature_group()).collect(); 48 | // let message = Message::new(b"ctx",b"test message"); 49 | 50 | // b.iter(|| { 51 | // let mut aggregator = MultiMessageSignatureAggregatorAssumingPoP::::new(); 52 | // let mut aggregated_public_key = PublicKey::(::PublicKeyGroup::zero()); 53 | 54 | // for k in &mut keypairs { 55 | // aggregator.aggregate(&k.signed_message(message)); 56 | // aggregated_public_key.0 += k.public.0; 57 | 58 | 59 | // } 60 | // }); 61 | // } 62 | 63 | #[bench] 64 | fn test_many_tiny_aggregate_and_verify_in_g1(b: &mut Bencher) { 65 | let message = Message::new(b"ctx",b"test message"); 66 | let mut keypairs = generate_many_keypairs(NO_OF_MULTI_SIG_SIGNERS); 67 | let mut pub_keys_in_sig_grp : Vec> = keypairs.iter().map(|k| k.into_public_key_in_signature_group()).collect(); 68 | 69 | let mut aggregator = MultiMessageSignatureAggregatorAssumingPoP::::new(); 70 | let mut aggregated_public_key = PublicKey::(::PublicKeyGroup::zero()); 71 | 72 | for k in &mut keypairs { 73 | aggregator.aggregate(&k.signed_message(message)); 74 | aggregated_public_key.0 += k.public.0; 75 | } 76 | 77 | b.iter(|| { 78 | let mut verifier_aggregator = MultiMessageSignatureAggregatorAssumingPoP::::new(); 79 | 80 | verifier_aggregator.add_signature(&aggregator.signature); 81 | verifier_aggregator.add_message_n_publickey(&message, &aggregated_public_key); 82 | 83 | for k in &pub_keys_in_sig_grp { 84 | verifier_aggregator.add_auxiliary_public_key(k); 85 | } 86 | 87 | assert!(verifier_aggregator.verify_using_aggregated_auxiliary_public_keys()); 88 | 89 | }); 90 | 91 | } 92 | 93 | const NO_OF_MULTI_SIG_SIGNERS : usize = 1000; 94 | use test::{Bencher, black_box}; 95 | //#[bench] 96 | fn test_bls_verify_many_signatures_simple(b: &mut Bencher) { 97 | let good = Message::new(b"ctx",b"test message"); 98 | 99 | let mut keypair = Keypair::::generate(thread_rng()); 100 | let message = Message::new(b"ctx",b"test message"); 101 | 102 | let sig = keypair.signed_message(message); 103 | 104 | b.iter(|| 105 | for i in 1..NO_OF_MULTI_SIG_SIGNERS { 106 | sig.verify(); 107 | }); 108 | } 109 | 110 | //#[bench] 111 | fn test_bls_verify_many_signatures_chaum_pedersen(b: &mut Bencher) { 112 | let mut keypair = Keypair::::generate(thread_rng()); 113 | let message = Message::new(b"ctx",b"test message"); 114 | 115 | let sig = as ChaumPedersenSigner>::generate_cp_signature(&mut keypair, message); 116 | let public_key_in_sig_group = keypair.into_public_key_in_signature_group(); 117 | 118 | b.iter(|| 119 | for i in 1..NO_OF_MULTI_SIG_SIGNERS { 120 | assert!( as ChaumPedersenVerifier>::verify_cp_signature(&public_key_in_sig_group, message,sig)); 121 | }); 122 | } 123 | 124 | //#[bench] 125 | fn test_pairing(b: &mut Bencher) { 126 | let mut keypair1 = Keypair::::generate(thread_rng()); 127 | 128 | let point_1 = keypair1.into_public_key_in_signature_group().0; 129 | let point_2 = keypair1.public.0; 130 | 131 | b.iter(|| 132 | for i in 0..NO_OF_MULTI_SIG_SIGNERS { 133 | TinyBLS377::pairing(point_2, point_1); 134 | }); 135 | 136 | } 137 | //#[bench] 138 | fn test_scalar_multiplication(b: &mut Bencher) { 139 | let mut keypair1 = Keypair::::generate(thread_rng()); 140 | 141 | let point_1 = keypair1.into_public_key_in_signature_group().0; 142 | let point_2 = keypair1.public.0; 143 | let scalar = keypair1.secret.into_vartime().0; 144 | 145 | b.iter(|| 146 | for i in 0..NO_OF_MULTI_SIG_SIGNERS { 147 | point_1 * scalar; 148 | }); 149 | 150 | } 151 | -------------------------------------------------------------------------------- /src/bit.rs: -------------------------------------------------------------------------------- 1 | //! Aggregate signatures with signers represented by bitfields 2 | //! 3 | //! These tool support both the prefered deliniarized as well as the 4 | //! discuraged proof-of-possession flavors of aggregation. 5 | 6 | use core::borrow::{Borrow, BorrowMut}; 7 | use core::iter::once; 8 | 9 | use ark_ec::Group; 10 | use ark_ff::Zero; 11 | 12 | use super::single::SignedMessage; 13 | use super::verifiers::verify_with_distinct_messages; 14 | use super::*; 15 | 16 | // Slice equality with bytewise equality hack because 17 | // std does not expose `slice::BytewiseEquality` 18 | fn slice_eq_bytewise>(x: &[T], y: &[T]) -> bool { 19 | if x.len() != y.len() { 20 | return false; 21 | } 22 | if ::core::ptr::eq(x, y) { 23 | return true; 24 | } 25 | x == y 26 | } 27 | 28 | /// Signer table required for both delinearization and proofs-of-possession. 29 | /// 30 | /// We explicitly provide a signers bitfield type to support fixed sized 31 | /// variants. 32 | pub trait SignerTable { 33 | /// Returns true if two `SignerTable` databases match exactly. 34 | /// 35 | /// We could employ a PartialEq + Eq bound for this, except 36 | /// those are frequently slow even for small for databases. 37 | fn agreement(&self, other: &Self) -> bool; 38 | 39 | /// Bitfield type used to represent a signers set 40 | /// 41 | /// Must not permit altering length, so `Box<[u8]>` or `[u8; 128]` not `Vec`. 42 | type Signers: Borrow<[u8]> + BorrowMut<[u8]> + Clone + Sized; 43 | /// Create a new signers set bitfield 44 | fn new_signers(&self) -> Self::Signers; 45 | 46 | /// Lookup the public key with a particular bit index. 47 | /// 48 | /// Must succeed if `index < signers.borrow().len()`, but 49 | /// should panic if `index > signers.borrow().len()`. 50 | /// It may return `None` if the position is empty. 51 | /// 52 | /// Must satisfy `self.lookup(i).and_then(|i| self.find(i)) == Some(i)` when `i` is occupied. 53 | fn lookup(&self, index: usize) -> Option>; 54 | 55 | /// Find the bit index for a particular public key. 56 | /// 57 | /// Must succeed if the public key is present, and fail otherwise. 58 | /// 59 | /// Must satisfy `self.find(pk).and_then(|i| self.lookup(i)) == Some(pk)` when `pk` is present. 60 | fn find(&self, publickey: &PublicKey) -> Option; 61 | } 62 | 63 | /// Occupied indices bit mask for `self.signers[offset]` 64 | fn chunk_lookups(signer_table: &ST, offset: usize) -> u8 65 | where 66 | E: EngineBLS, 67 | ST: SignerTable, 68 | { 69 | (0..8).into_iter().fold(0u8, |b, j| { 70 | let i = 8 * offset + j; 71 | let pk = signer_table.lookup(i).filter(|pk| { 72 | // bb = true always due to check in add_points 73 | let bb = Some(i) == signer_table.find(&pk); 74 | debug_assert!( 75 | bb, 76 | "Incorrect SignerTable implementation with duplicate publickeys" 77 | ); 78 | bb 79 | }); 80 | b | pk.map_or(0u8, |_| 1u8 << j) 81 | }) 82 | } 83 | 84 | /// Avoiding duplicate keys inside a slice gets costly. We suggest 85 | /// improving performance by using a customized data type. 86 | /// 87 | /// TODO: Evaluate using Deref vs Borrow in this context 88 | /// TODO: Use specialization here 89 | impl SignerTable for V 90 | where 91 | E: EngineBLS, 92 | V: ::core::ops::Deref]>, 93 | { 94 | fn agreement(&self, other: &Self) -> bool { 95 | slice_eq_bytewise(self.deref(), other.deref()) 96 | } 97 | 98 | type Signers = Box<[u8]>; 99 | fn new_signers(&self) -> Self::Signers { 100 | vec![0u8; (self.deref().len() + 7) / 8].into_boxed_slice() 101 | } 102 | 103 | fn lookup(&self, index: usize) -> Option> { 104 | self.deref().get(index).cloned() 105 | // Checked for duplicates in BitSignedMessage 106 | // .filter(|publickey| { 107 | // Some(index) == self.deref().iter().position(|pk| *pk==publickey); 108 | // debug_assert!(b, "Incorrect SignerTable implementation with duplicate publickeys"); 109 | // b 110 | // }) 111 | } 112 | fn find(&self, publickey: &PublicKey) -> Option { 113 | self.deref().iter().position(|pk| *pk == *publickey) 114 | // Checked for duplicates in BitSignedMessage 115 | // .filter(|index| { 116 | // Some(publickey) == self.deref().get(index); 117 | // debug_assert!(b, "Incorrect SignerTable implementation with duplicate publickeys"); 118 | // b 119 | // }) 120 | } 121 | } 122 | 123 | /// Error type for bitfield-style proof-of-possession aggreggation 124 | /// 125 | /// These do not necessarily represent attacks pr se. We therefore 126 | /// permit users to recover from them, although actual recovery sounds 127 | /// impossible nomrally. 128 | #[derive(Debug)] 129 | pub enum SignerTableError { 130 | /// Attempted to use missmatched proof-of-possession tables. 131 | BadSignerTable(&'static str), 132 | /// Attempted to aggregate distint messages, which requires the 133 | /// the more general SignatureAggregatorAssumingPoP type instead. 134 | MismatchedMessage, 135 | /// Aggregation is impossible due to signers being repeated or 136 | /// repeated too many times in both sets or multi-sets, respectively. 137 | RepeatedSigners, 138 | } 139 | 140 | impl ::core::fmt::Display for SignerTableError { 141 | fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { 142 | use self::SignerTableError::*; 143 | match self { 144 | BadSignerTable(s) => write!(f, "{}", s), 145 | MismatchedMessage => write!( 146 | f, 147 | "Cannot aggregate distinct messages with only a bit field." 148 | ), 149 | RepeatedSigners => write!(f, "Cannot aggregate due to duplicate signers."), 150 | } 151 | } 152 | } 153 | 154 | impl ::std::error::Error for SignerTableError { 155 | fn description(&self) -> &str { 156 | use self::SignerTableError::*; 157 | match self { 158 | BadSignerTable(s) => s, 159 | MismatchedMessage => "Cannot aggregate distinct messages with only a bit field.", 160 | RepeatedSigners => "Cannot aggregate due to duplicate signers", 161 | } 162 | } 163 | } 164 | 165 | /// One individual message with attached aggreggate BLS signatures 166 | /// from signers for whom we previously checked proofs-of-possession, 167 | /// and with the singers presented as a compact bitfield. 168 | /// 169 | /// We may aggregage only one signatures per signer here, but our 170 | /// serialized signature is only one 96 or or 48 bytes compressed 171 | /// curve point, plus the `SignerTable::Signers`, which takes 172 | /// about 1 bit per signer if optimized correctly. 173 | /// 174 | /// You must provide a `SignerTable` for this, likely by 175 | /// implementing it for your own data structures. 176 | // #[derive(Clone)] 177 | pub struct BitSignedMessage> { 178 | proofs_of_possession: POP, 179 | signers: >::Signers, 180 | message: Message, 181 | signature: Signature, 182 | } 183 | 184 | impl<'a, E, POP> Clone for BitSignedMessage 185 | where 186 | E: EngineBLS, 187 | POP: SignerTable + Clone, 188 | { 189 | fn clone(&self) -> BitSignedMessage { 190 | BitSignedMessage { 191 | proofs_of_possession: self.proofs_of_possession.clone(), 192 | signers: self.signers.clone(), 193 | message: self.message.clone(), 194 | signature: self.signature.clone(), 195 | } 196 | } 197 | } 198 | 199 | impl<'a, E, POP> Signed for &'a BitSignedMessage 200 | where 201 | E: EngineBLS, 202 | POP: SignerTable, 203 | { 204 | type E = E; 205 | 206 | type M = Message; 207 | type PKG = PublicKey; 208 | 209 | type PKnM = ::core::iter::Once<(Message, PublicKey)>; 210 | 211 | fn messages_and_publickeys(self) -> Self::PKnM { 212 | let mut publickey = E::PublicKeyGroup::zero(); 213 | for i in 0..8 * self.signers.borrow().len() { 214 | if self.signers.borrow()[i / 8] & (1 << (i % 8)) != 0 { 215 | let pop_pk = self.proofs_of_possession.lookup(i).unwrap(); 216 | if Some(i) != self.proofs_of_possession.find(&pop_pk) { 217 | // unreachable due to check in add points 218 | debug_assert!( 219 | false, 220 | "Incorrect SignerTable implementation with duplicate publickeys" 221 | ); 222 | continue; 223 | } 224 | publickey += &pop_pk.0; 225 | } 226 | } 227 | once((self.message.clone(), PublicKey(publickey))) 228 | } 229 | 230 | fn signature(&self) -> Signature { 231 | self.signature 232 | } 233 | 234 | fn verify(self) -> bool { 235 | // We have already aggregated distinct messages, so our distinct 236 | // message verification code provides reasonable optimizations, 237 | // except the public keys need not be normalized here. 238 | // We foresee verification via gaussian elimination being 239 | // significantly faster, but requiring affine keys. 240 | verify_with_distinct_messages(self, true) 241 | } 242 | } 243 | 244 | impl BitSignedMessage 245 | where 246 | E: EngineBLS, 247 | POP: SignerTable, 248 | { 249 | pub fn new(proofs_of_possession: POP, message: &Message) -> BitSignedMessage { 250 | let signers = proofs_of_possession.new_signers(); 251 | let signature = Signature(E::SignatureGroup::zero()); 252 | BitSignedMessage { 253 | proofs_of_possession, 254 | signers, 255 | message: message.clone(), 256 | signature, 257 | } 258 | } 259 | 260 | fn add_points( 261 | &mut self, 262 | publickey: PublicKey, 263 | signature: Signature, 264 | ) -> Result<(), SignerTableError> { 265 | let i = 266 | self.proofs_of_possession 267 | .find(&publickey) 268 | .ok_or(SignerTableError::BadSignerTable( 269 | "Mismatched proof-of-possession", 270 | ))?; 271 | if self.proofs_of_possession.lookup(i) != Some(publickey) { 272 | return Err(SignerTableError::BadSignerTable( 273 | "Invalid SignerTable implementation with missmatched lookups", 274 | )); 275 | } 276 | let b = 1 << (i % 8); 277 | let s = &mut self.signers.borrow_mut()[i / 8]; 278 | if *s & b != 0 { 279 | return Err(SignerTableError::RepeatedSigners); 280 | } 281 | *s |= b; 282 | self.signature.0 += &signature.0; 283 | Ok(()) 284 | } 285 | 286 | /// Include one signed message, after testing for message and 287 | /// proofs-of-possession table agreement, and disjoint publickeys. 288 | pub fn add(&mut self, signed: &SignedMessage) -> Result<(), SignerTableError> { 289 | if self.message != signed.message { 290 | return Err(SignerTableError::MismatchedMessage); 291 | } 292 | self.add_points(signed.publickey, signed.signature) 293 | } 294 | 295 | /// Merge two `BitSignedMessage`, after testing for message 296 | /// and proofs-of-possession table agreement, and disjoint publickeys. 297 | pub fn merge(&mut self, other: &BitSignedMessage) -> Result<(), SignerTableError> { 298 | if self.message != other.message { 299 | return Err(SignerTableError::MismatchedMessage); 300 | } 301 | if !self 302 | .proofs_of_possession 303 | .agreement(&other.proofs_of_possession) 304 | { 305 | return Err(SignerTableError::BadSignerTable( 306 | "Mismatched proof-of-possession", 307 | )); 308 | } 309 | for (offset, (x, y)) in self 310 | .signers 311 | .borrow() 312 | .iter() 313 | .zip(other.signers.borrow()) 314 | .enumerate() 315 | { 316 | if *x & *y != 0 { 317 | return Err(SignerTableError::RepeatedSigners); 318 | } 319 | if *y & !chunk_lookups(&self.proofs_of_possession, offset) != 0 { 320 | return Err(SignerTableError::BadSignerTable("Absent signer")); 321 | } 322 | } 323 | for (x, y) in self 324 | .signers 325 | .borrow_mut() 326 | .iter_mut() 327 | .zip(other.signers.borrow()) 328 | { 329 | *x |= y; 330 | } 331 | self.signature.0 += &other.signature.0; 332 | Ok(()) 333 | } 334 | } 335 | 336 | /// One individual message with attached aggreggate BLS signatures 337 | /// from signers for whom we previously checked proofs-of-possession, 338 | /// and with the singers presented as a compact bitfield. 339 | /// 340 | /// We may aggregage any number of duplicate signatures per signer here, 341 | /// unlike `CountSignedMessage`, but our serialized signature costs 342 | /// one 96 or or 48 bytes compressed curve point, plus the size of 343 | /// `SignerTable::Signers` times log of the largest duplicate count. 344 | /// 345 | /// You must provide a `SignerTable` for this, perhapos by implementing 346 | /// it for your own fixed size data structures. 347 | // #[derive(Clone)] 348 | pub struct CountSignedMessage> { 349 | proofs_of_possession: POP, 350 | signers: Vec<>::Signers>, 351 | message: Message, 352 | signature: Signature, 353 | /// Errors if a duplicate signature count would exceed this number. 354 | /// We suggest rounding up to the nearest power of two. 355 | pub max_duplicates: usize, 356 | } 357 | 358 | impl<'a, E, POP> Clone for CountSignedMessage 359 | where 360 | E: EngineBLS, 361 | POP: SignerTable + Clone, 362 | { 363 | fn clone(&self) -> CountSignedMessage { 364 | CountSignedMessage { 365 | proofs_of_possession: self.proofs_of_possession.clone(), 366 | signers: self.signers.clone(), 367 | message: self.message.clone(), 368 | signature: self.signature.clone(), 369 | max_duplicates: self.max_duplicates, 370 | } 371 | } 372 | } 373 | 374 | impl<'a, E, POP> Signed for &'a CountSignedMessage 375 | where 376 | E: EngineBLS, 377 | POP: SignerTable, 378 | { 379 | type E = E; 380 | 381 | type M = Message; 382 | type PKG = PublicKey; 383 | 384 | type PKnM = ::core::iter::Once<(Message, PublicKey)>; 385 | 386 | fn messages_and_publickeys(self) -> Self::PKnM { 387 | let mut publickey = E::PublicKeyGroup::zero(); 388 | for signers in self.signers.iter().rev().map(|signers| signers.borrow()) { 389 | publickey.double_in_place(); 390 | for i in 0..8 * signers.len() { 391 | if signers[i / 8] & (1 << (i % 8)) != 0 { 392 | let pop_pk = self.proofs_of_possession.lookup(i).unwrap(); 393 | if Some(i) != self.proofs_of_possession.find(&pop_pk) { 394 | // unreachable due to check in add points 395 | debug_assert!( 396 | false, 397 | "Incorrect SignerTable implementation with duplicate publickeys" 398 | ); 399 | continue; 400 | } 401 | publickey += &pop_pk.0; 402 | } 403 | } 404 | } 405 | once((self.message.clone(), PublicKey(publickey))) 406 | } 407 | 408 | fn signature(&self) -> Signature { 409 | self.signature 410 | } 411 | 412 | fn verify(self) -> bool { 413 | // We have already aggregated distinct messages, so our distinct 414 | // message verification code provides reasonable optimizations, 415 | // except the public keys need not be normalized here. 416 | // We foresee verification via gaussian elimination being 417 | // significantly faster, but requiring affine keys. 418 | verify_with_distinct_messages(self, true) 419 | } 420 | } 421 | 422 | impl CountSignedMessage 423 | where 424 | E: EngineBLS, 425 | POP: SignerTable, 426 | { 427 | pub fn new(proofs_of_possession: POP, message: Message) -> CountSignedMessage { 428 | let signers = vec![proofs_of_possession.new_signers(); 1]; 429 | let signature = Signature(E::SignatureGroup::zero()); 430 | let max_duplicates = 16; 431 | CountSignedMessage { 432 | proofs_of_possession, 433 | signers, 434 | message, 435 | signature, 436 | max_duplicates, 437 | } 438 | } 439 | 440 | /* 441 | fn check_one_lookup(&self, index: usize) -> Result<(),SignerTableError> { 442 | let e = SignerTableError::BadSignerTable("Invalid SignerTable implementation with missmatched lookups"); 443 | self.proofs_of_possession.lookup(index).filter(|pk| { 444 | Some(index) == self.proofs_of_possession.find(&pk) 445 | }).map(|_| ()).ok_or(e) 446 | } 447 | */ 448 | 449 | fn reserve_depth(&mut self, count: usize) { 450 | let l = 0usize.leading_zeros() - count.leading_zeros(); 451 | if l as usize <= self.signers.len() { 452 | return; 453 | } 454 | let l = l as usize - self.signers.len(); 455 | self.signers.reserve(l); 456 | for _i in 0..l { 457 | self.signers.push(self.proofs_of_possession.new_signers()); 458 | } 459 | } 460 | 461 | /* 462 | commented out to rid of unused warning 463 | TODO: add test coverage to trim and uncomment 464 | fn trim(&mut self) { 465 | let empty = |s: &POP::Signers| s.borrow().iter().all(|b| *b == 0u8); 466 | let c = self.signers.len() - self.signers.iter().rev().take_while(|s| empty(&*s)).count(); 467 | self.signers.truncate(c) 468 | }*/ 469 | 470 | fn test_count(&self, count: usize) -> Result<(), SignerTableError> { 471 | if count >= self.max_duplicates || count >= usize::max_value() { 472 | return Err(SignerTableError::RepeatedSigners); 473 | } 474 | Ok(()) 475 | } 476 | 477 | /// Get the count of the number of duplicate signatures by the public key with a given index. 478 | fn get_count(&self, index: usize) -> usize { 479 | let mut count = 0; 480 | for signers in self.signers.iter().rev().map(|signers| signers.borrow()) { 481 | count *= 2; 482 | if signers[index / 8] & (1 << (index % 8)) != 0 { 483 | count += 1; 484 | } 485 | } 486 | count 487 | } 488 | 489 | /// Set the count of the number of duplicate signatures by the public key with a given index. 490 | /// Always call test_count before invoking this method. 491 | fn set_count(&mut self, index: usize, mut count: usize) { 492 | self.reserve_depth(count); 493 | for signers in self.signers.iter_mut().map(|signers| signers.borrow_mut()) { 494 | if count & 1usize != 0 { 495 | signers[index / 8] |= 1 << (index % 8); 496 | } else { 497 | signers[index / 8] &= !(1 << (index % 8)); 498 | } 499 | count /= 2; 500 | } 501 | } 502 | 503 | fn add_points( 504 | &mut self, 505 | publickey: PublicKey, 506 | signature: Signature, 507 | ) -> Result<(), SignerTableError> { 508 | let i = 509 | self.proofs_of_possession 510 | .find(&publickey) 511 | .ok_or(SignerTableError::BadSignerTable( 512 | "Mismatched proof-of-possession", 513 | ))?; 514 | if self.proofs_of_possession.lookup(i) != Some(publickey) { 515 | return Err(SignerTableError::BadSignerTable( 516 | "Invalid SignerTable implementation with missmatched lookups", 517 | )); 518 | } 519 | let count = self.get_count(i) + 1; 520 | self.test_count(count)?; 521 | self.set_count(i, count); 522 | self.signature.0 += &signature.0; 523 | Ok(()) 524 | } 525 | 526 | /// Include one signed message, after testing for message and 527 | /// proofs-of-possession table agreement, and disjoint publickeys. 528 | pub fn add(&mut self, signed: &SignedMessage) -> Result<(), SignerTableError> { 529 | if self.message != signed.message { 530 | return Err(SignerTableError::MismatchedMessage); 531 | } 532 | self.add_points(signed.publickey, signed.signature) 533 | } 534 | 535 | pub fn add_bitsig(&mut self, other: &BitSignedMessage) -> Result<(), SignerTableError> { 536 | if self.message != other.message { 537 | return Err(SignerTableError::MismatchedMessage); 538 | } 539 | if !self 540 | .proofs_of_possession 541 | .agreement(&other.proofs_of_possession) 542 | { 543 | return Err(SignerTableError::BadSignerTable( 544 | "Mismatched proof-of-possession", 545 | )); 546 | } 547 | let os = other.signers.borrow(); 548 | for offset in 0..self.signers[0].borrow().len() { 549 | if self 550 | .signers 551 | .iter() 552 | .fold(os[offset], |b, s| b | s.borrow()[offset]) 553 | & !chunk_lookups(&self.proofs_of_possession, offset) 554 | != 0u8 555 | { 556 | return Err(SignerTableError::BadSignerTable("Absent signer")); 557 | } 558 | for j in 0..8 { 559 | let mut count = self.get_count(8 * offset + j); 560 | if os[offset] & (1 << j) != 0 { 561 | count += 1; 562 | } 563 | self.test_count(count)?; 564 | } 565 | } 566 | for index in 0..8 * self.signers[0].borrow().len() { 567 | let count = self.get_count(index); 568 | if os[index / 8] & (1 << (index % 8)) != 0 { 569 | self.set_count(index, count + 1); 570 | } 571 | } 572 | self.signature.0 += &other.signature.0; 573 | Ok(()) 574 | } 575 | 576 | /// Merge two `CountSignedMessage`, after testing for message 577 | /// and proofs-of-possession table agreement, and disjoint publickeys. 578 | pub fn merge(&mut self, other: &CountSignedMessage) -> Result<(), SignerTableError> { 579 | if self.message != other.message { 580 | return Err(SignerTableError::MismatchedMessage); 581 | } 582 | if !self 583 | .proofs_of_possession 584 | .agreement(&other.proofs_of_possession) 585 | { 586 | return Err(SignerTableError::BadSignerTable( 587 | "Mismatched proof-of-possession", 588 | )); 589 | } 590 | for offset in 0..self.signers[0].borrow().len() { 591 | if self 592 | .signers 593 | .iter() 594 | .chain(&other.signers) 595 | .fold(0u8, |b, s| b | s.borrow()[offset]) 596 | & !chunk_lookups(&self.proofs_of_possession, offset) 597 | != 0u8 598 | { 599 | return Err(SignerTableError::BadSignerTable("Absent signer")); 600 | } 601 | for j in 0..8 { 602 | let index = 8 * offset + j; 603 | self.test_count(self.get_count(index).saturating_add(other.get_count(index)))?; 604 | } 605 | } 606 | for index in 0..8 * self.signers[0].borrow().len() { 607 | self.set_count(index, self.get_count(index) + other.get_count(index)); 608 | } 609 | self.signature.0 += &other.signature.0; 610 | Ok(()) 611 | } 612 | } 613 | 614 | #[cfg(all(test, feature = "std"))] 615 | mod tests { 616 | use rand::thread_rng; // Rng 617 | 618 | use super::*; 619 | 620 | #[test] 621 | fn proofs_of_possession() { 622 | let msg1 = Message::new(b"ctx", b"some message"); 623 | let msg2 = Message::new(b"ctx", b"another message"); 624 | 625 | let k = |_| Keypair::::generate(thread_rng()); 626 | let mut keypairs = (0..4).into_iter().map(k).collect::>(); 627 | let pop = keypairs.iter().map(|k| k.public).collect::>(); 628 | let dup = keypairs[3].clone(); 629 | keypairs.push(dup); 630 | let sigs1 = keypairs 631 | .iter_mut() 632 | .map(|k| k.signed_message(&msg1)) 633 | .collect::>(); 634 | 635 | let mut bitsig1 = BitSignedMessage::::new(pop.clone(), &msg1); 636 | assert!(bitsig1.verify()); // verifiers::verify_with_distinct_messages(&dms,true) 637 | for (i, sig) in sigs1.iter().enumerate().take(2) { 638 | assert!(bitsig1.add(sig).is_ok() == (i < 4)); 639 | assert!(bitsig1.verify()); // verifiers::verify_with_distinct_messages(&dms,true) 640 | } 641 | let mut bitsig1a = BitSignedMessage::::new(pop.clone(), &msg1); 642 | for (i, sig) in sigs1.iter().enumerate().skip(2) { 643 | assert!(bitsig1a.add(sig).is_ok() == (i < 4)); 644 | assert!(bitsig1a.verify()); // verifiers::verify_with_distinct_messages(&dms,true) 645 | } 646 | assert!(bitsig1.merge(&bitsig1a).is_ok()); 647 | assert!(bitsig1.merge(&bitsig1a).is_err()); 648 | assert!(verifiers::verify_unoptimized(&bitsig1)); 649 | assert!(verifiers::verify_simple(&bitsig1)); 650 | assert!(verifiers::verify_with_distinct_messages(&bitsig1, false)); 651 | // assert!( verifiers::verify_with_gaussian_elimination(&dms) ); 652 | 653 | let sigs2 = keypairs 654 | .iter_mut() 655 | .map(|k| k.signed_message(&msg2)) 656 | .collect::>(); 657 | let mut bitsig2 = BitSignedMessage::::new(pop.clone(), &msg2); 658 | for sig in sigs2.iter().take(3) { 659 | assert!(bitsig2.add(sig).is_ok()); 660 | } 661 | assert!(bitsig1.merge(&bitsig2).is_err()); 662 | 663 | let mut multimsg = 664 | multi_pop_aggregator::MultiMessageSignatureAggregatorAssumingPoP::::new(); 665 | multimsg.aggregate(&bitsig1); 666 | multimsg.aggregate(&bitsig2); 667 | assert!(multimsg.verify()); // verifiers::verify_with_distinct_messages(&dms,true) 668 | assert!(verifiers::verify_unoptimized(&multimsg)); 669 | assert!(verifiers::verify_simple(&multimsg)); 670 | assert!(verifiers::verify_with_distinct_messages(&multimsg, false)); 671 | 672 | let oops = Keypair::::generate(thread_rng()).signed_message(&msg2); 673 | assert!(bitsig1.add_points(oops.publickey, oops.signature).is_err()); 674 | /* 675 | TODO: Test that adding signers for an incorrect message fails, but this version angers teh borrow checker. 676 | let mut oops_pop = pop.clone(); 677 | oops_pop.push(oops.publickey); 678 | // We should constriuvt a better test here because this only works 679 | // because pop.len() is not a multiple of 8. 680 | bitsig1.proofs_of_possession = &oops_pop[..]; 681 | bitsig1.add_points(oops.publickey,oops.signature).unwrap(); 682 | assert!( ! bitsig1.verify() ); 683 | assert!( ! verifiers::verify_unoptimized(&bitsig1) ); 684 | assert!( ! verifiers::verify_simple(&bitsig1) ); 685 | assert!( ! verifiers::verify_with_distinct_messages(&bitsig1,false) ); 686 | */ 687 | 688 | let mut countsig = CountSignedMessage::::new(pop.clone(), msg1); 689 | assert!(countsig.signers.len() == 1); 690 | assert!(countsig.verify()); // verifiers::verify_with_distinct_messages(&dms,true) 691 | assert!(countsig.add_bitsig(&bitsig1).is_ok()); 692 | assert!(bitsig1.signature == countsig.signature); 693 | assert!(countsig.signers.len() == 1); 694 | assert!( 695 | bitsig1.messages_and_publickeys().next() == countsig.messages_and_publickeys().next() 696 | ); 697 | assert!(countsig.verify()); 698 | for (i, sig) in sigs1.iter().enumerate().take(3) { 699 | assert!(countsig.add(sig).is_ok() == (i < 4)); 700 | assert!(countsig.verify(), "countsig failed at sig {}", i); // verifiers::verify_with_distinct_messages(&dms,true) 701 | } 702 | assert!(countsig.add_bitsig(&bitsig1a).is_ok()); 703 | assert!(countsig.add_bitsig(&bitsig1a).is_ok()); 704 | assert!(countsig.add_bitsig(&bitsig2).is_err()); 705 | let countpop2 = countsig.clone(); 706 | assert!(countsig.merge(&countpop2).is_ok()); 707 | assert!(verifiers::verify_unoptimized(&countsig)); 708 | assert!(verifiers::verify_simple(&countsig)); 709 | assert!(verifiers::verify_with_distinct_messages(&countsig, false)); 710 | countsig.max_duplicates = 4; 711 | assert!(countsig.merge(&countpop2).is_err()); 712 | } 713 | } 714 | -------------------------------------------------------------------------------- /src/chaum_pedersen_signature.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | 3 | use ark_ec::Group; 4 | use ark_ff::field_hashers::{DefaultFieldHasher, HashToField}; 5 | 6 | use digest::DynDigest; 7 | 8 | use crate::double::{DoublePublicKeyScheme, PublicKeyInSignatureGroup}; 9 | use crate::engine::EngineBLS; 10 | use crate::schnorr_pop::SchnorrProof; 11 | use crate::serialize::SerializableToBytes; 12 | use crate::single::Signature; 13 | use crate::{Message, SecretKeyVT}; 14 | 15 | pub type ChaumPedersenSignature = (Signature, SchnorrProof); 16 | 17 | /// ProofOfPossion trait which should be implemented by secret 18 | pub trait ChaumPedersenSigner { 19 | /// The proof of possession generator is supposed to 20 | /// to produce a schnoor signature of the message using 21 | /// the secret key which it claim to possess. 22 | fn generate_cp_signature(&mut self, message: &Message) -> ChaumPedersenSignature; 23 | 24 | fn generate_witness_scaler( 25 | &self, 26 | message_point_as_bytes: &Vec, 27 | ) -> <::PublicKeyGroup as Group>::ScalarField; 28 | 29 | fn generate_dleq_proof( 30 | &mut self, 31 | message: &Message, 32 | bls_signature: E::SignatureGroup, 33 | ) -> SchnorrProof; 34 | } 35 | 36 | /// This should be implemented by public key 37 | pub trait ChaumPedersenVerifier { 38 | fn verify_cp_signature( 39 | &self, 40 | message: &Message, 41 | signature_proof: ChaumPedersenSignature, 42 | ) -> bool; 43 | } 44 | 45 | impl ChaumPedersenSigner for SecretKeyVT { 46 | fn generate_cp_signature(&mut self, message: &Message) -> ChaumPedersenSignature { 47 | //First we generate a vanila BLS Signature; 48 | let bls_signature = SecretKeyVT::sign(self, message); 49 | ( 50 | bls_signature, 51 | as ChaumPedersenSigner>::generate_dleq_proof( 52 | self, 53 | message, 54 | bls_signature.0, 55 | ), 56 | ) 57 | } 58 | 59 | #[allow(non_snake_case)] 60 | fn generate_dleq_proof( 61 | &mut self, 62 | message: &Message, 63 | bls_signature: E::SignatureGroup, 64 | ) -> SchnorrProof { 65 | let signature_point = bls_signature; 66 | let message_point = message.hash_to_signature_curve::(); 67 | 68 | let signature_point_as_bytes = E::signature_point_to_byte(&signature_point); 69 | let message_point_as_bytes = E::signature_point_to_byte(&message_point); 70 | let public_key_in_signature_group_as_bytes = E::signature_point_to_byte( 71 | &DoublePublicKeyScheme::::into_public_key_in_signature_group(self).0, 72 | ); 73 | 74 | let mut k = as ChaumPedersenSigner>::generate_witness_scaler( 75 | self, 76 | &message_point_as_bytes, 77 | ); 78 | 79 | let A_point = <::SignatureGroup as Group>::generator() * k; 80 | let B_point = message_point * k; 81 | 82 | let A_point_as_bytes = E::signature_point_to_byte(&A_point); 83 | let B_point_as_bytes = E::signature_point_to_byte(&B_point); 84 | 85 | let proof_basis = [ 86 | message_point_as_bytes, 87 | public_key_in_signature_group_as_bytes, 88 | signature_point_as_bytes, 89 | A_point_as_bytes, 90 | B_point_as_bytes, 91 | ] 92 | .concat(); 93 | 94 | let hasher = as HashToField< 95 | <::PublicKeyGroup as Group>::ScalarField, 96 | >>::new(&[]); 97 | 98 | let c = hasher.hash_to_field(proof_basis.as_slice(), 1)[0]; 99 | 100 | let s = k - c * self.0; 101 | 102 | ::zeroize::Zeroize::zeroize(&mut k); //clear secret witness from memory 103 | 104 | (c, s) 105 | } 106 | 107 | fn generate_witness_scaler( 108 | &self, 109 | message_point_as_bytes: &Vec, 110 | ) -> <::PublicKeyGroup as Group>::ScalarField { 111 | let secret_key_as_bytes = self.to_bytes(); 112 | 113 | let mut secret_key_hasher = H::default(); 114 | secret_key_hasher.update(secret_key_as_bytes.as_slice()); 115 | let hashed_secret_key = secret_key_hasher.finalize_reset().to_vec(); 116 | 117 | let hasher = as HashToField< 118 | <::PublicKeyGroup as Group>::ScalarField, 119 | >>::new(&[]); 120 | let scalar_seed = [hashed_secret_key, message_point_as_bytes.clone()].concat(); 121 | hasher.hash_to_field(scalar_seed.as_slice(), 1)[0] 122 | } 123 | } 124 | 125 | /// This should be implemented by public key 126 | #[allow(non_snake_case)] 127 | impl ChaumPedersenVerifier 128 | for PublicKeyInSignatureGroup 129 | { 130 | fn verify_cp_signature( 131 | &self, 132 | message: &Message, 133 | signature_proof: ChaumPedersenSignature, 134 | ) -> bool { 135 | let A_check_point = <::SignatureGroup as Group>::generator() 136 | * signature_proof.1 .1 137 | + self.0 * signature_proof.1 .0; 138 | 139 | let B_check_point = message.hash_to_signature_curve::() * signature_proof.1 .1 140 | + signature_proof.0 .0 * signature_proof.1 .0; 141 | 142 | let A_point_as_bytes = E::signature_point_to_byte(&A_check_point); 143 | let B_point_as_bytes = E::signature_point_to_byte(&B_check_point); 144 | 145 | let signature_point_as_bytes = signature_proof.0.to_bytes(); 146 | let message_point_as_bytes = 147 | E::signature_point_to_byte(&message.hash_to_signature_curve::()); 148 | let public_key_in_signature_group_as_bytes = E::signature_point_to_byte(&self.0); 149 | 150 | let resulting_proof_basis = [ 151 | message_point_as_bytes, 152 | public_key_in_signature_group_as_bytes, 153 | signature_point_as_bytes, 154 | A_point_as_bytes, 155 | B_point_as_bytes, 156 | ] 157 | .concat(); 158 | 159 | let hasher = as HashToField< 160 | <::PublicKeyGroup as Group>::ScalarField, 161 | >>::new(&[]); 162 | let c_check: <::PublicKeyGroup as Group>::ScalarField = 163 | hasher.hash_to_field(resulting_proof_basis.as_slice(), 1)[0]; 164 | 165 | c_check == signature_proof.1 .0 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/delinear.rs: -------------------------------------------------------------------------------- 1 | //! Delinearized batching and aggregation for BLS signatures 2 | //! 3 | //! We handle delinearized flavors of aggregate BLS signatures here, 4 | //! meaning we multiply signatures by an exponent that seems random 5 | //! relative to the signers public key. In this, we support both 6 | //! batching with explicit randomness, and delinearization in which 7 | //! we [treat the hash of all included public keys as a random oracle](https://crypto.stanford.edu/~dabo/pubs/papers/BLSmultisig.html) 8 | //! 9 | //! We caution that delinerized aggregation leans towards slightly 10 | //! different abstractions than linear aggregation. In this module, 11 | //! we select an approach that complements well our linear strategies, 12 | //! but if you need delinearized aggregation then you should consider 13 | //! adding a more finely tuned scheme. 14 | 15 | use ark_ec::CurveGroup; 16 | use ark_ff::BigInteger; 17 | use ark_ff::{PrimeField, Zero}; 18 | use ark_serialize::CanonicalSerialize; 19 | use arrayref::array_refs; 20 | #[cfg(feature = "std")] 21 | use rand::thread_rng; 22 | use rand::Rng; 23 | use sha3::{ 24 | digest::{ExtendableOutput, Update, XofReader}, 25 | Shake128, 26 | }; 27 | 28 | use std::collections::HashMap; 29 | 30 | use super::single::SignedMessage; 31 | use super::verifiers::verify_with_distinct_messages; 32 | use super::*; 33 | 34 | /// Delinearized batched and aggregated BLS signatures. 35 | /// 36 | /// We caution that this type only represents one of several 37 | /// optimizations possible. We believe it fits well when messages 38 | /// are often repeated but signers are rarely repeated. 39 | /// 40 | /// We should create another type for when repeated signers are 41 | /// expected, likely by keying the hash map on the pubkic key. 42 | /// In practice though, if signers are often repeated then you should 43 | /// should consider a proof-of-possession scheme, which requiees all 44 | /// signers register in advance. 45 | pub struct Delinearized { 46 | key: Shake128, 47 | messages_n_publickeys: HashMap>, 48 | signature: Signature, 49 | } 50 | 51 | impl Clone for Delinearized { 52 | fn clone(&self) -> Delinearized { 53 | Delinearized { 54 | key: self.key.clone(), 55 | messages_n_publickeys: self.messages_n_publickeys.clone(), 56 | signature: self.signature.clone(), 57 | } 58 | } 59 | } 60 | 61 | impl<'a, E: EngineBLS> Signed for &'a Delinearized { 62 | type E = E; 63 | 64 | type M = &'a Message; 65 | type PKG = &'a PublicKey; 66 | type PKnM = ::std::collections::hash_map::Iter<'a, Message, PublicKey>; 67 | 68 | fn messages_and_publickeys(self) -> Self::PKnM { 69 | self.messages_n_publickeys.iter() 70 | } 71 | 72 | fn signature(&self) -> Signature { 73 | self.signature 74 | } 75 | 76 | fn verify(self) -> bool { 77 | verify_with_distinct_messages(self, true) 78 | } 79 | } 80 | 81 | impl Delinearized { 82 | pub fn new(key: Shake128) -> Delinearized { 83 | Delinearized { 84 | key, 85 | messages_n_publickeys: HashMap::new(), 86 | signature: Signature(E::SignatureGroup::zero()), 87 | } 88 | } 89 | pub fn new_keyed(key: &[u8]) -> Delinearized { 90 | let mut t = Shake128::default(); 91 | t.update(b"Delinearised BLS with key:"); 92 | let l = key.len() as u64; 93 | t.update(&l.to_le_bytes()); 94 | t.update(key); 95 | Delinearized::new(t) 96 | } 97 | pub fn new_batched_rng(mut rng: R) -> Delinearized { 98 | let r = rng.gen::<[u8; 32]>(); 99 | Delinearized::new_keyed(&r[..]) 100 | } 101 | 102 | #[cfg(feature = "std")] 103 | pub fn new_batched() -> Delinearized { 104 | Delinearized::new_batched_rng(thread_rng()) 105 | } 106 | 107 | /// Return the mask used for a particular public key. 108 | /// 109 | /// TODO: We only want 128 bits here, not a full scalar. We thus 110 | /// need `mul_bits` exposed by the pairing crate, at which point 111 | /// our return type here changes. 112 | pub fn mask(&self, publickey: &PublicKey) -> E::Scalar { 113 | let mut t = self.key.clone(); 114 | let pk_affine = publickey.0.into_affine(); 115 | let mut pk_uncompressed = vec![0; pk_affine.uncompressed_size()]; 116 | pk_affine 117 | .serialize_uncompressed(&mut pk_uncompressed[..]) 118 | .unwrap(); 119 | t.update(&pk_uncompressed); 120 | let mut b = [0u8; 16]; 121 | t.finalize_xof().read(&mut b[..]); 122 | let (x, y) = array_refs!(&b, 8, 8); 123 | let mut x: ::BigInt = u64::from_le_bytes(*x).into(); 124 | let y: ::BigInt = u64::from_le_bytes(*y).into(); 125 | x.muln(64); 126 | x.add_with_carry(&y); 127 | ::from_bigint(x).unwrap() 128 | } 129 | 130 | /// Add only a `Signature` to our internal signature, 131 | /// assumes the signature was previously delinearized elsewhere. 132 | /// 133 | /// Useful for constructing an aggregate signature. 134 | pub fn add_delinearized_signature(&mut self, signature: &Signature) { 135 | self.signature.0 += signature.0; 136 | } 137 | 138 | /// Add only a `Message` and `PublicKey` to our internal data, 139 | /// doing delinearization ourselves. 140 | /// 141 | /// Useful for constructing an aggregate signature, but we 142 | /// recommend instead using a custom types like `BitPoPSignedMessage`. 143 | pub fn add_message_n_publickey( 144 | &mut self, 145 | message: &Message, 146 | mut publickey: PublicKey, 147 | ) -> E::Scalar { 148 | let mask = self.mask(&publickey); 149 | // We must use projective corrdinates here, dispite converting to 150 | // affine just above, because only `CurveGroup::mul_assign` 151 | // skips doubling until a set bit is found. 152 | // In fact, there is no method to do this without abusing variable 153 | // time arithmatic, which might change in future, so we should add 154 | // some `CurveGroup` method `fn mul_128(&self, blinding: u128)`. 155 | // Or even expose the `AffineRepr::mul_bits` method. 156 | // TODO: Is using affine here actually faster? 157 | publickey.0 *= mask; 158 | self.messages_n_publickeys 159 | .entry(message.clone()) 160 | .and_modify(|pk0| pk0.0 += publickey.0) 161 | .or_insert(publickey); 162 | mask 163 | } 164 | 165 | /// Aggregage BLS signatures from singletons using delinearization 166 | pub fn add(&mut self, signed: &SignedMessage) { 167 | let mut signature = signed.signature; 168 | let mask = self.add_message_n_publickey(&signed.message, signed.publickey); 169 | signature.0 *= mask; 170 | self.add_delinearized_signature(&signature); 171 | } 172 | 173 | /// Test that two `Delinearized` use the same key. 174 | /// 175 | /// You should call this before calling `merge`, although 176 | /// we do enforce this because several untestable related 177 | /// conditions suffice too. 178 | // TODO: See https://github.com/dalek-cryptography/merlin/pull/37 179 | pub fn agreement(&self, other: &Delinearized) -> bool { 180 | let mut c = [[0u8; 16]; 2]; 181 | self.key.clone().finalize_xof().read(&mut c[0]); 182 | other.key.clone().finalize_xof().read(&mut c[1]); 183 | c[0] == c[1] 184 | } 185 | 186 | /// Merge another `Delinearized` for simultanious verification. 187 | /// 188 | /// You should only call this if `self.agreement(other)` or some 189 | /// related condition holds, or if you have message disjointness. 190 | // TODO: Feed into disjoint message aggregation. 191 | pub fn merge(&mut self, other: &Delinearized) { 192 | // if ! self.agreement(other) { return Err(()); } 193 | for (message, publickey) in other.messages_n_publickeys.iter() { 194 | self.messages_n_publickeys 195 | .entry(message.clone()) 196 | .and_modify(|pk0| pk0.0 += publickey.0) 197 | .or_insert(*publickey); 198 | } 199 | self.signature.0 += other.signature.0; 200 | // Ok(()) 201 | } 202 | } 203 | 204 | /* 205 | type PublicKeyUncompressed = <<::$group as ProjectiveCurve>::Affine as AffineRepr>::Compressed; 206 | 207 | #[derive(Clone)] 208 | pub struct DelinearizedRepeatedSigners { 209 | key: Shake128, 210 | messages_n_publickeys: HashMap,(Message,PublicKey)>, 211 | signature: Signature, 212 | } 213 | */ 214 | 215 | #[cfg(all(test, feature = "std"))] 216 | mod tests { 217 | use super::*; 218 | 219 | #[test] 220 | fn delinearized() { 221 | let msg1 = Message::new(b"ctx", b"some message"); 222 | 223 | let k = |_| Keypair::::generate(thread_rng()); 224 | let mut keypairs = (0..4).into_iter().map(k).collect::>(); 225 | let dup = keypairs[3].clone(); 226 | keypairs.push(dup); 227 | let sigs1 = keypairs 228 | .iter_mut() 229 | .map(|k| k.signed_message(&msg1)) 230 | .collect::>(); 231 | 232 | let mut dl = Delinearized::::new_batched(); 233 | for sig in sigs1.iter() { 234 | dl.add(sig); 235 | assert!(dl.verify()); // verifiers::verify_with_distinct_messages(&dms,true) 236 | } 237 | assert!(verifiers::verify_unoptimized(&dl)); 238 | assert!(verifiers::verify_simple(&dl)); 239 | assert!(verifiers::verify_with_distinct_messages(&dl, false)); 240 | // assert!( verifiers::verify_with_gaussian_elimination(&dl) ); 241 | 242 | assert!(dl.agreement(&dl)); 243 | let dl_too = dl.clone(); 244 | dl.merge(&dl_too); 245 | assert!(dl.verify()); 246 | // TODO: more more 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/distinct.rs: -------------------------------------------------------------------------------- 1 | //! ## Aggregation for BLS signatures with distinct message. 2 | //! 3 | //! In this module, we provide the linear flavor of aggregate BLS 4 | //! signature in which all messages are required to be distinct. 5 | //! In other words, if all messages are distinct then we cannot add 6 | //! public keys from different pairings anyways. 7 | //! 8 | //! In verification, we can add different message hashes signed by the 9 | //! same public key, ala `e(g1,s*H(m1)+s*H(m2)) = e(s*g1,H(m1)+H(m2))`, 10 | //! assuming we need not worry about a signers "equivocating" in 11 | //! advance by providing signatures that verify only when aggregated. 12 | //! We cannot exploit this before verification however, due to the 13 | //! requirement to enforce distinct messages. 14 | //! 15 | //! We also note that most signature schemes permit support extremely 16 | //! efficent signer side batching, which normally out performs BLS. 17 | //! It's ocasioanlly worth asking if signers can be trusted to such 18 | //! collected signatures. See also: 19 | //! - RSA: https://eprint.iacr.org/2018/082.pdf 20 | //! - Boneh-Boyen: https://crypto.stanford.edu/~dabo/papers/bbsigs.pdf 21 | //! http://sci-gems.math.bas.bg:8080/jspui/bitstream/10525/1569/1/sjc096-vol3-num3-2009.pdf 22 | 23 | use ark_ff::Zero; 24 | use std::collections::HashMap; 25 | 26 | use super::single::SignedMessage; 27 | use super::verifiers::verify_with_distinct_messages; 28 | use super::*; 29 | 30 | /// Error tyoe for non-distinct messages found during distinct 31 | /// message aggregation. 32 | /// 33 | /// There are numerous scenarios that make recovery from such errors 34 | /// impossible. We therefore destroy the aggregate signature struct 35 | /// whenever creating this, so that users cannot respond incorrectly 36 | /// to an error message. 37 | #[derive(Debug)] 38 | pub struct AttackViaDuplicateMessages; 39 | 40 | impl ::std::fmt::Display for AttackViaDuplicateMessages { 41 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { 42 | write!(f, "Attempted to aggregate duplicate messages.") 43 | } 44 | } 45 | 46 | impl ::std::error::Error for AttackViaDuplicateMessages { 47 | fn description(&self) -> &str { 48 | "Attempted to aggregate duplicate messages." 49 | } 50 | } 51 | 52 | /// Distinct messages with attached BLS signature 53 | /// 54 | /// We can aggregate BLS signatures on distinct messages without 55 | /// additional assuptions or delinearization. In this variant, there 56 | /// is obviously no aggregation on the signature curve, so verification 57 | /// still requires one pairing per message. We can however aggregate 58 | /// numerous messages with the same signer, so this works well when 59 | /// a small signer set signs numerous messages, even if the signer set 60 | /// remains unknown. 61 | /// 62 | /// We also of course benifit from running one single Miller loop and 63 | /// final exponentiation when compiuting all these pairings. We note 64 | /// that proofs-of-possession require distinct messages because the 65 | /// message must uniquely single out the signing key, so they may be 66 | /// aggregated or batch verified with distinct message mode, and 67 | /// indeed using distinct messages aggregation is optimal. 68 | /// 69 | /// We recommend using this for either batching or aggregation, but 70 | /// we do yet not provide any serialization scheme for the aggregate 71 | /// version. Instead, you should serialize the aggregated signature 72 | /// seperately, and reconstruct this type using its `add_*` methods. 73 | #[derive(Clone)] 74 | pub struct DistinctMessages { 75 | messages_n_publickeys: HashMap>, 76 | signature: Signature, 77 | } 78 | 79 | impl<'a, E: EngineBLS> Signed for &'a DistinctMessages { 80 | type E = E; 81 | 82 | type M = &'a Message; 83 | type PKG = &'a PublicKey; 84 | type PKnM = ::std::collections::hash_map::Iter<'a, Message, PublicKey>; 85 | 86 | fn messages_and_publickeys(self) -> Self::PKnM { 87 | self.messages_n_publickeys.iter() 88 | } 89 | 90 | fn signature(&self) -> Signature { 91 | self.signature 92 | } 93 | 94 | fn verify(self) -> bool { 95 | verify_with_distinct_messages(self, false) 96 | } 97 | } 98 | 99 | /* 100 | We do not require an abstract aggregation routine here since only 101 | two quite different types work in this case. 102 | pub trait SignedWithDistinctMessages : Signed {} 103 | impl SignedWithDistinctMessages for SignedMessage {} 104 | impl SignedWithDistinctMessages for DistinctMessages {} 105 | */ 106 | 107 | impl DistinctMessages { 108 | pub fn new() -> DistinctMessages { 109 | DistinctMessages { 110 | messages_n_publickeys: HashMap::new(), 111 | signature: Signature(E::SignatureGroup::zero()), 112 | } 113 | } 114 | 115 | /// Add only a `Signature` to our internal signature. 116 | /// 117 | /// Useful in constructing an aggregate signature from this type. 118 | pub fn add_signature(&mut self, signature: &Signature) { 119 | self.signature.0 += &signature.0; 120 | } 121 | 122 | /// Add only a `Message` and `PublicKey` to our internal data. 123 | /// 124 | /// Useful in constructing an aggregate signature from this type. 125 | /// 126 | /// We require that duplicate message halt verification by consuming 127 | /// self by vaule and return it only if no duplicates occur. 128 | pub fn add_message_n_publickey( 129 | mut self, 130 | message: Message, 131 | publickey: PublicKey, 132 | ) -> DistinctMessagesResult { 133 | if let Some(_old_publickey) = self.messages_n_publickeys.insert(message, publickey) { 134 | // We need not recover from this error because the hash map gets erased. 135 | // self.messages_n_publickeys.insert(signed.message,old_publickey); 136 | return Err(AttackViaDuplicateMessages); 137 | } 138 | Ok(self) 139 | } 140 | 141 | /// Aggregage BLS signatures from singletons with distinct messages 142 | /// 143 | /// We require that duplicate message halt verification by consuming 144 | /// self by vaule and return it only if no duplicates occur. 145 | pub fn add(self, signed: &SignedMessage) -> DistinctMessagesResult { 146 | let mut me = self.add_message_n_publickey(signed.message.clone(), signed.publickey)?; 147 | me.add_signature(&signed.signature); 148 | Ok(me) 149 | } 150 | 151 | /// Aggregage BLS signatures from sources with distinct messages 152 | /// 153 | /// We require that duplicate message halt verification by consuming 154 | /// self by vaule and return it only if no duplicates occur. 155 | pub fn merge(mut self, signed: &DistinctMessages) -> DistinctMessagesResult { 156 | // We need not detect duplicates early for recovery because 157 | // duplicates cause our hashmap to be freed anyways. 158 | // for (m,_pk) in signed.messages_n_publickeys.iter() { 159 | // if self.messages_n_publickeys.contains_key(m) { 160 | // return Err(AttackViaDuplicateMessages); 161 | // } 162 | // } 163 | for (m, pk) in signed.messages_n_publickeys.iter() { 164 | // assert!(self.messages_n_publickeys.insert(*m,*pk).is_none()); 165 | if self.messages_n_publickeys.insert(m.clone(), *pk).is_some() { 166 | return Err(AttackViaDuplicateMessages); 167 | } 168 | } 169 | self.add_signature(&signed.signature); 170 | Ok(self) 171 | } 172 | } 173 | 174 | pub type DistinctMessagesResult = Result, AttackViaDuplicateMessages>; 175 | 176 | /* 177 | TODO: Adopt .collect::>() via FromIterator 178 | whenever https://github.com/rust-lang/rfcs/issues/1856 gets resolved. 179 | impl<'a,E: EngineBLS> FromIterator<&'a SignedMessage> for DistinctMessagesResult { 180 | fn from_iter(ii: II) -> Self 181 | where II: IntoIterator>, 182 | { 183 | ii.into_iter().try_fold(DistinctMessages::::new(), |dm,sm| dm.add(sm)) 184 | } 185 | } 186 | */ 187 | 188 | #[cfg(all(test, feature = "std"))] 189 | mod tests { 190 | use rand::thread_rng; // Rng 191 | 192 | use super::*; 193 | 194 | #[test] 195 | fn distinct_messages() { 196 | let msgs = [ 197 | Message::new(b"ctx", b"Message1"), 198 | Message::new(b"ctx", b"Message1"), 199 | Message::new(b"ctx", b"Message2"), 200 | Message::new(b"ctx", b"Message3"), 201 | Message::new(b"ctx", b"Message4"), 202 | ]; 203 | 204 | let k = |_| Keypair::::generate(thread_rng()); 205 | let mut keypairs = (0..4).into_iter().map(k).collect::>(); 206 | let dup = keypairs[3].clone(); 207 | keypairs.push(dup); 208 | 209 | let sigs = msgs 210 | .iter() 211 | .zip(keypairs.iter_mut()) 212 | .map(|(m, k)| k.signed_message(m)) 213 | .collect::>(); 214 | 215 | let dm_new = || DistinctMessages::::new(); 216 | fn dm_add( 217 | dm: DistinctMessages, 218 | sig: &SignedMessage, 219 | ) -> Result, AttackViaDuplicateMessages> { 220 | dm.add(sig) 221 | } 222 | 223 | let mut dms = sigs.iter().skip(1).try_fold(dm_new(), dm_add).unwrap(); 224 | assert!(dms.messages_and_publickeys().len() == 4); 225 | let dms0 = sigs.iter().skip(1).try_fold(dm_new(), dm_add).unwrap(); 226 | assert!(dms0.merge(&dms).is_err()); 227 | assert!(sigs.iter().try_fold(dm_new(), dm_add).is_err()); 228 | assert!(dms.verify()); // verifiers::verify_with_distinct_messages(&dms,false) 229 | assert!(verifiers::verify_unoptimized(&dms)); 230 | assert!(verifiers::verify_simple(&dms)); 231 | assert!(verifiers::verify_with_distinct_messages(&dms, true)); 232 | // assert!( verifiers::verify_with_gaussian_elimination(&dms) ); 233 | 234 | let dms1 = sigs 235 | .iter() 236 | .skip(1) 237 | .take(2) 238 | .try_fold(dm_new(), dm_add) 239 | .unwrap(); 240 | let dms2 = sigs.iter().skip(3).try_fold(dm_new(), dm_add).unwrap(); 241 | assert!(dms1.merge(&dms2).unwrap().signature == dms.signature); 242 | 243 | *(dms.messages_n_publickeys.get_mut(&msgs[1]).unwrap()) = keypairs[0].public.clone(); 244 | assert!(!dms.verify(), "Verification by an incorrect signer passed"); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/double.rs: -------------------------------------------------------------------------------- 1 | //! ## BLS key pair with public key in both G1 and G2 2 | //! ## Unaggreagated BLS signature along side with their DLEQ proof 3 | //! 4 | //! Implements schemes suggested the 5 | //! [paper](https://eprint.iacr.org/2022/1611) 6 | //! 7 | //! The scheme proposes for the public key be represented by doube points, 8 | //! both in G1 and G2 and aggregate keys in G1. 9 | //! 10 | //! It also proposes that each individual BLS signature accompany a DLEQ proof 11 | //! for faster verification 12 | 13 | use alloc::vec::Vec; 14 | use core::iter::once; 15 | 16 | use ark_ec::{AffineRepr, CurveGroup}; 17 | 18 | use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; 19 | 20 | use sha2::Sha256; 21 | 22 | use crate::broken_derives; 23 | use crate::chaum_pedersen_signature::{ChaumPedersenSigner, ChaumPedersenVerifier}; 24 | use crate::schnorr_pop::SchnorrProof; 25 | use crate::serialize::SerializableToBytes; 26 | use crate::single::{Keypair, KeypairVT, PublicKey, SecretKeyVT, Signature}; 27 | use crate::{EngineBLS, Message, Signed}; 28 | 29 | /// Wrapper for a point in the signature group which is supposed to 30 | /// the same logarithm as the public key in the public key group 31 | #[derive(Debug, CanonicalSerialize, CanonicalDeserialize)] 32 | pub struct PublicKeyInSignatureGroup(pub E::SignatureGroup); 33 | broken_derives!(PublicKeyInSignatureGroup); // Actually the derive works for this one, not sure why. 34 | 35 | /// BLS Public Key with sub keys in both groups. 36 | #[derive(Debug, Clone, CanonicalSerialize, CanonicalDeserialize)] 37 | pub struct DoublePublicKey(pub E::SignatureGroup, pub E::PublicKeyGroup); 38 | 39 | impl DoublePublicKey { 40 | pub fn verify(&self, message: &Message, signature: &DoubleSignature) -> bool { 41 | signature.verify(message, self) 42 | } 43 | } 44 | 45 | /// Serialization for DoublePublickey 46 | impl SerializableToBytes for DoublePublicKey { 47 | const SERIALIZED_BYTES_SIZE: usize = 48 | E::SIGNATURE_SERIALIZED_SIZE + E::PUBLICKEY_SERIALIZED_SIZE; 49 | } 50 | 51 | pub trait DoublePublicKeyScheme { 52 | fn into_public_key_in_signature_group(&self) -> PublicKeyInSignatureGroup; 53 | 54 | /// Return a double public object containing public keys both in G1 and G2 55 | fn into_double_public_key(&self) -> DoublePublicKey; 56 | fn sign(&mut self, message: &Message) -> DoubleSignature; 57 | } 58 | 59 | impl DoublePublicKeyScheme for SecretKeyVT { 60 | fn into_public_key_in_signature_group(&self) -> PublicKeyInSignatureGroup { 61 | PublicKeyInSignatureGroup( 62 | ::Affine::generator().into_group() * self.0, 63 | ) 64 | } 65 | 66 | fn into_double_public_key(&self) -> DoublePublicKey { 67 | DoublePublicKey( 68 | self.into_public_key_in_signature_group().0, 69 | self.into_public().0, 70 | ) 71 | } 72 | 73 | /// Sign a message using a Seedabale RNG created from a seed derived from the message and key 74 | fn sign(&mut self, message: &Message) -> DoubleSignature { 75 | let chaum_pedersen_signature = 76 | ChaumPedersenSigner::::generate_cp_signature(self, &message); 77 | DoubleSignature(chaum_pedersen_signature.0 .0, chaum_pedersen_signature.1) 78 | } 79 | } 80 | 81 | impl DoublePublicKeyScheme for KeypairVT { 82 | fn into_public_key_in_signature_group(&self) -> PublicKeyInSignatureGroup { 83 | self.secret.into_public_key_in_signature_group() 84 | } 85 | 86 | fn into_double_public_key(&self) -> DoublePublicKey { 87 | self.secret.into_double_public_key() 88 | } 89 | 90 | /// Sign a message using a Seedabale RNG created from a seed derived from the message and key 91 | fn sign(&mut self, message: &Message) -> DoubleSignature { 92 | DoublePublicKeyScheme::sign(&mut self.secret, message) 93 | } 94 | } 95 | 96 | impl DoublePublicKeyScheme for Keypair { 97 | fn into_public_key_in_signature_group(&self) -> PublicKeyInSignatureGroup { 98 | self.into_vartime().into_public_key_in_signature_group() 99 | } 100 | 101 | fn into_double_public_key(&self) -> DoublePublicKey { 102 | self.into_vartime().into_double_public_key() 103 | } 104 | 105 | /// Sign a message using a Seedabale RNG created from a seed derived from the message and key 106 | fn sign(&mut self, message: &Message) -> DoubleSignature { 107 | DoublePublicKeyScheme::sign(&mut self.into_vartime(), message) 108 | } 109 | } 110 | 111 | /// Detached BLS Signature containing DLEQ 112 | #[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] 113 | pub struct DoubleSignature(pub E::SignatureGroup, SchnorrProof); 114 | 115 | impl DoubleSignature { 116 | //const DESCRIPTION : &'static str = "A BLS signature"; 117 | 118 | /// Verify a single BLS signature using DLEQ proof 119 | pub fn verify(&self, message: &Message, publickey: &DoublePublicKey) -> bool { 120 | as ChaumPedersenVerifier>::verify_cp_signature( 121 | &PublicKeyInSignatureGroup(publickey.0), 122 | &message, 123 | (Signature(self.0), self.1), 124 | ) 125 | } 126 | } 127 | 128 | /// Message with attached BLS signature 129 | /// 130 | /// 131 | #[derive(Debug, Clone)] 132 | pub struct DoubleSignedMessage { 133 | pub message: Message, 134 | pub publickey: DoublePublicKey, 135 | pub signature: DoubleSignature, 136 | } 137 | 138 | impl PartialEq for DoubleSignedMessage { 139 | fn eq(&self, other: &Self) -> bool { 140 | self.message.eq(&other.message) 141 | && self.publickey.0.eq(&other.publickey.0) 142 | && self.publickey.1.eq(&other.publickey.1) 143 | && self.signature.0.eq(&other.signature.0) 144 | } 145 | } 146 | 147 | impl<'a, E: EngineBLS> Signed for &'a DoubleSignedMessage { 148 | type E = E; 149 | 150 | type M = Message; 151 | type PKG = PublicKey; 152 | 153 | type PKnM = ::core::iter::Once<(Message, PublicKey)>; 154 | 155 | fn messages_and_publickeys(self) -> Self::PKnM { 156 | once((self.message.clone(), PublicKey(self.publickey.1))) // TODO: Avoid clone 157 | } 158 | 159 | fn signature(&self) -> Signature { 160 | Signature(self.signature.0) 161 | } 162 | 163 | fn verify(self) -> bool { 164 | //we chaum pederesen verification which is faster 165 | ChaumPedersenVerifier::::verify_cp_signature( 166 | &PublicKeyInSignatureGroup::(self.publickey.0), 167 | &self.message, 168 | (Signature(self.signature.0), self.signature.1), 169 | ) 170 | } 171 | } 172 | 173 | /// Serialization for DoubleSignature 174 | impl SerializableToBytes for DoubleSignature { 175 | const SERIALIZED_BYTES_SIZE: usize = E::SIGNATURE_SERIALIZED_SIZE + 2 * E::SECRET_KEY_SIZE; 176 | } 177 | 178 | #[cfg(all(test, feature = "std"))] 179 | mod tests { 180 | use rand::thread_rng; 181 | 182 | use super::*; 183 | 184 | use ark_bls12_377::Bls12_377; 185 | use ark_bls12_381::Bls12_381; 186 | use ark_ec::bls12::Bls12Config; 187 | use ark_ec::hashing::curve_maps::wb::{WBConfig, WBMap}; 188 | use ark_ec::hashing::map_to_curve_hasher::MapToCurve; 189 | use ark_ec::pairing::Pairing as PairingEngine; 190 | 191 | use crate::{EngineBLS, Message, TinyBLS}; 192 | 193 | fn double_public_serialization_test< 194 | EB: EngineBLS, 195 | E: PairingEngine, 196 | P: Bls12Config, 197 | >( 198 | x: DoubleSignedMessage, 199 | ) -> DoubleSignedMessage 200 | where 201 |

::G2Config: WBConfig, 202 | WBMap<

::G2Config>: MapToCurve<::G2>, 203 | { 204 | let DoubleSignedMessage { 205 | message, 206 | publickey, 207 | signature, 208 | } = x; 209 | 210 | let publickey = DoublePublicKey::::from_bytes(&publickey.to_bytes()).unwrap(); 211 | let signature = DoubleSignature::::from_bytes(&signature.to_bytes()).unwrap(); 212 | 213 | DoubleSignedMessage { 214 | message, 215 | publickey, 216 | signature, 217 | } 218 | } 219 | 220 | fn test_single_bls_message_double_signature_scheme< 221 | EB: EngineBLS, 222 | E: PairingEngine, 223 | P: Bls12Config, 224 | >() 225 | where 226 |

::G2Config: WBConfig, 227 | WBMap<

::G2Config>: MapToCurve<::G2>, 228 | { 229 | let good = Message::new(b"ctx", b"test message"); 230 | 231 | let mut keypair = Keypair::::generate(thread_rng()); 232 | let public_key = DoublePublicKeyScheme::into_double_public_key(&mut keypair); 233 | let good_sig = DoublePublicKeyScheme::sign(&mut keypair, &good); 234 | 235 | assert!( 236 | public_key.verify(&good, &good_sig), 237 | "Verification of a valid signature failed!" 238 | ); 239 | 240 | let bad = Message::new(b"ctx", b"wrong message"); 241 | let bad_sig = DoublePublicKeyScheme::sign(&mut keypair, &bad); 242 | 243 | assert!(bad_sig.verify( 244 | &bad, 245 | &DoublePublicKeyScheme::into_double_public_key(&keypair) 246 | )); 247 | 248 | assert!(good != bad, "good == bad"); 249 | assert!(good_sig.0 != bad_sig.0, "good sig == bad sig"); 250 | 251 | assert!( 252 | !bad_sig.verify( 253 | &good, 254 | &DoublePublicKeyScheme::into_double_public_key(&keypair) 255 | ), 256 | "Verification of a signature on a different message passed!" 257 | ); 258 | assert!( 259 | !good_sig.verify( 260 | &bad, 261 | &DoublePublicKeyScheme::into_double_public_key(&keypair) 262 | ), 263 | "Verification of a signature on a different message passed!" 264 | ); 265 | } 266 | 267 | #[test] 268 | fn test_double_public_key_double_signature_serialization_for_bls12_377() { 269 | let mut keypair = 270 | Keypair::>::generate(thread_rng()); 271 | let message = Message::new(b"ctx", b"test message"); 272 | let good_sig0 = DoublePublicKeyScheme::sign(&mut keypair, &message); 273 | 274 | let signed_message = DoubleSignedMessage { 275 | message: message, 276 | publickey: DoublePublicKey( 277 | keypair.into_public_key_in_signature_group().0, 278 | keypair.public.0, 279 | ), 280 | signature: good_sig0, 281 | }; 282 | 283 | assert!( 284 | signed_message.verify(), 285 | "valid double signed message should verify" 286 | ); 287 | 288 | let deserialized_signed_message = double_public_serialization_test::< 289 | TinyBLS, 290 | Bls12_377, 291 | ark_bls12_377::Config, 292 | >(signed_message); 293 | 294 | assert!( 295 | deserialized_signed_message.verify(), 296 | "deserialized valid double signed message should verify" 297 | ); 298 | } 299 | 300 | #[test] 301 | fn test_double_public_key_double_signature_serialization_for_bls12_381() { 302 | let mut keypair = 303 | Keypair::>::generate(thread_rng()); 304 | let message = Message::new(b"ctx", b"test message"); 305 | let good_sig0 = DoublePublicKeyScheme::sign(&mut keypair, &message); 306 | 307 | let signed_message = DoubleSignedMessage { 308 | message: message, 309 | publickey: DoublePublicKey( 310 | keypair.into_public_key_in_signature_group().0, 311 | keypair.public.0, 312 | ), 313 | signature: good_sig0, 314 | }; 315 | 316 | assert!( 317 | signed_message.verify(), 318 | "valid double signed message should verify" 319 | ); 320 | 321 | let deserialized_signed_message = double_public_serialization_test::< 322 | TinyBLS, 323 | Bls12_381, 324 | ark_bls12_381::Config, 325 | >(signed_message); 326 | 327 | assert!( 328 | deserialized_signed_message.verify(), 329 | "deserialized valid double signed message should verify" 330 | ); 331 | } 332 | 333 | #[test] 334 | fn test_single_bls_message_double_signature_scheme_for_bls12_377() { 335 | test_single_bls_message_double_signature_scheme::< 336 | TinyBLS, 337 | Bls12_377, 338 | ark_bls12_377::Config, 339 | >(); 340 | } 341 | 342 | #[test] 343 | fn test_single_bls_message_double_signature_scheme_for_bls12_381() { 344 | test_single_bls_message_double_signature_scheme::< 345 | TinyBLS, 346 | Bls12_381, 347 | ark_bls12_381::Config, 348 | >(); 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /src/double_pop.rs: -------------------------------------------------------------------------------- 1 | //! ## Implementation of ProofofPossion trait for Double BLS public keys using 2 | //! the scheme described in [https://eprint.iacr.org/2022/1611] which also 3 | //! complies with the proof of possession proposed in 4 | //! [draft-irtf-cfrg-bls-signature-05](https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html) 5 | 6 | use crate::engine::EngineBLS; 7 | use crate::{DoubleSignature, Message, ProofOfPossession, ProofOfPossessionGenerator}; 8 | 9 | use crate::double::{DoublePublicKey, DoublePublicKeyScheme}; 10 | use crate::serialize::SerializableToBytes; 11 | use crate::single::{Keypair, PublicKey}; 12 | 13 | use alloc::vec::Vec; 14 | use digest::DynDigest; 15 | 16 | use ark_ec::Group; 17 | use ark_ff::field_hashers::{DefaultFieldHasher, HashToField}; 18 | use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; 19 | 20 | /// Proof Of Possession of the secret key as the secret scaler genarting both public 21 | /// keys in G1 and G2 by generating a BLS Signature of public key (in G2) 22 | #[derive(CanonicalSerialize, CanonicalDeserialize)] 23 | pub struct NuggetBLSPoP(pub E::SignatureGroup); 24 | 25 | //The bls proof of possession for single or double public key schemes are the same 26 | impl 27 | ProofOfPossessionGenerator, NuggetBLSPoP> for Keypair 28 | { 29 | fn generate_pok(&mut self) -> NuggetBLSPoP { 30 | //We simply classicaly BLS sign public key in G2 based on https://eprint.iacr.org/2022/1611 31 | let sigma_pop = ProofOfPossessionGenerator::, NuggetBLSnCPPoP>::generate_pok(self); 32 | NuggetBLSPoP::(sigma_pop.0 .0) 33 | } 34 | } 35 | 36 | /// Serialization for DoublePublickey 37 | impl SerializableToBytes for NuggetBLSPoP { 38 | const SERIALIZED_BYTES_SIZE: usize = E::SIGNATURE_SERIALIZED_SIZE; 39 | } 40 | 41 | /// The verification process for verifying both possession of one secret key 42 | /// for two public key is different. 43 | impl ProofOfPossession> 44 | for NuggetBLSPoP 45 | { 46 | /// verify the validity of PoP by performing the following Pairing 47 | /// e(H_pop(pk_2) + t.g_1, pk_2) = e(sign(H_pop(pk_2))+ t.pk_1, g_2) 48 | /// we verifying by calling the verify_prepared ⎈function from the 49 | /// engine. 50 | fn verify(&self, public_key_of_prover: &DoublePublicKey) -> bool { 51 | //First we need to generate our randomness in a way that 52 | //prover is unable to predict. We assume g1 and g2 are fixed. 53 | 54 | let public_key_as_bytes = 55 | ::public_key_point_to_byte(&public_key_of_prover.1); 56 | let public_key_in_signature_group = public_key_of_prover.0; 57 | let public_key_in_signature_group_as_bytes = 58 | E::signature_point_to_byte(&public_key_in_signature_group); 59 | 60 | let public_key_hashed_to_signature_group = 61 | Message::new_pop_message(b"", &public_key_as_bytes).hash_to_signature_curve::(); 62 | let public_key_hashed_to_signature_group_as_bytes = 63 | E::signature_point_to_byte(&public_key_hashed_to_signature_group); 64 | let mut random_oracle_seed = Vec::with_capacity( 65 | public_key_hashed_to_signature_group_as_bytes.len() 66 | + public_key_as_bytes.len() 67 | + public_key_in_signature_group_as_bytes.len() 68 | + E::SIGNATURE_SERIALIZED_SIZE, 69 | ); 70 | 71 | random_oracle_seed.extend_from_slice(&public_key_hashed_to_signature_group_as_bytes); 72 | random_oracle_seed.extend_from_slice(&public_key_as_bytes); 73 | random_oracle_seed.extend_from_slice(&public_key_in_signature_group_as_bytes); 74 | random_oracle_seed.extend_from_slice(&E::signature_point_to_byte(&self.0)); 75 | 76 | let hasher = as HashToField< 77 | <::PublicKeyGroup as Group>::ScalarField, 78 | >>::new(&[]); 79 | 80 | let randomization_coefficient: E::Scalar = 81 | hasher.hash_to_field(random_oracle_seed.as_slice(), 1)[0]; 82 | 83 | let mut randomized_pub_in_g1 = public_key_in_signature_group; 84 | randomized_pub_in_g1 *= randomization_coefficient; 85 | let signature = E::prepare_signature(self.0 + randomized_pub_in_g1); 86 | let prepared_public_key = E::prepare_public_key(public_key_of_prover.1); 87 | let prepared = [ 88 | ( 89 | prepared_public_key.clone(), 90 | E::prepare_signature(public_key_hashed_to_signature_group), 91 | ), 92 | ( 93 | prepared_public_key.clone(), 94 | E::prepare_signature(E::generator_of_signature_group() * randomization_coefficient), 95 | ), 96 | ]; 97 | E::verify_prepared(signature, prepared.iter()) 98 | } 99 | } 100 | 101 | /// Proof Of Possession of the secret key as the secret scaler genarting both public 102 | /// keys in G1 and G2 by generating a BLS Signature of public key (in G2) plus proof 103 | /// of knowledge of the secret key of the chaum-pedersen key (samae secret key) 104 | #[derive(CanonicalSerialize, CanonicalDeserialize)] 105 | pub struct NuggetBLSnCPPoP(pub DoubleSignature); 106 | 107 | //The implement the generation of bls proof of possession including chaum-pederesno PoP for double public key schemes 108 | impl 109 | ProofOfPossessionGenerator, NuggetBLSnCPPoP> for Keypair 110 | { 111 | fn generate_pok(&mut self) -> NuggetBLSnCPPoP { 112 | //We simply classicaly BLS sign public key in G2 based on https://eprint.iacr.org/2022/1611 113 | let public_key_as_bytes = self.public.to_bytes(); 114 | let sigma_pop = DoublePublicKeyScheme::::sign( 115 | self, 116 | &Message::new_pop_message(b"", &public_key_as_bytes.as_slice()), 117 | ); 118 | 119 | NuggetBLSnCPPoP::(sigma_pop) 120 | } 121 | } 122 | 123 | /// Serialization for NuggetBLSnCPPoP 124 | impl SerializableToBytes for NuggetBLSnCPPoP { 125 | const SERIALIZED_BYTES_SIZE: usize = 126 | as SerializableToBytes>::SERIALIZED_BYTES_SIZE; 127 | } 128 | 129 | /// The verification process for verifying both nugget BLS and CP 130 | impl ProofOfPossession> 131 | for NuggetBLSnCPPoP 132 | { 133 | /// verify the validity of PoP by verifying nugget PoP and the CP 134 | /// signature 135 | fn verify(&self, public_key_of_prover: &DoublePublicKey) -> bool { 136 | let public_key_in_public_key_group_as_bytes = 137 | PublicKey::(public_key_of_prover.1).to_bytes(); 138 | //verify double pairing && verify cp 139 | as ProofOfPossession>>::verify( 140 | &NuggetBLSPoP::(self.0 .0), 141 | public_key_of_prover, 142 | ) && public_key_of_prover.verify( 143 | &Message::new_pop_message(b"", &public_key_in_public_key_group_as_bytes.as_slice()), 144 | &self.0, 145 | ) 146 | } 147 | } 148 | 149 | #[cfg(all(test, feature = "std"))] 150 | mod tests { 151 | use crate::double::DoublePublicKeyScheme; 152 | use crate::engine::TinyBLS381; 153 | use crate::serialize::SerializableToBytes; 154 | use crate::single::Keypair; 155 | use crate::{double_pop::NuggetBLSPoP, DoublePublicKey}; 156 | use crate::{ProofOfPossession, ProofOfPossessionGenerator}; 157 | 158 | use rand::thread_rng; 159 | use sha2::Sha256; 160 | 161 | use super::NuggetBLSnCPPoP; 162 | 163 | fn double_bls_pop_sign< 164 | PoPFlavor: ProofOfPossession>, 165 | >() 166 | where 167 | Keypair: 168 | ProofOfPossessionGenerator, PoPFlavor>, 169 | { 170 | let mut keypair = Keypair::::generate(thread_rng()); 171 | as ProofOfPossessionGenerator< 172 | TinyBLS381, 173 | Sha256, 174 | DoublePublicKey, 175 | PoPFlavor, 176 | >>::generate_pok(&mut keypair); 177 | } 178 | 179 | #[test] 180 | fn nugget_bls_pop_sign() { 181 | double_bls_pop_sign::>(); 182 | } 183 | 184 | #[test] 185 | fn nugget_bls_and_cp_pop_sign() { 186 | double_bls_pop_sign::>(); 187 | } 188 | 189 | fn double_bls_pop_sign_and_verify< 190 | PoPFlavor: ProofOfPossession>, 191 | >() 192 | where 193 | Keypair: 194 | ProofOfPossessionGenerator, PoPFlavor>, 195 | { 196 | let mut keypair = Keypair::::generate(thread_rng()); 197 | let proof_pair = , 201 | PoPFlavor, 202 | >>::generate_pok(&mut keypair); 203 | assert!( 204 | ProofOfPossession::>::verify( 205 | &proof_pair, 206 | &DoublePublicKeyScheme::into_double_public_key(&keypair) 207 | ), 208 | "valid pok does not verify" 209 | ); 210 | } 211 | 212 | #[test] 213 | fn nugget_bls_pop_sign_and_verify() { 214 | double_bls_pop_sign_and_verify::>(); 215 | } 216 | 217 | #[test] 218 | fn nugget_bls_and_cp_pop_sign_and_verify() { 219 | double_bls_pop_sign_and_verify::>(); 220 | } 221 | 222 | fn double_bls_pop_of_random_public_key_should_fail< 223 | PoPFlavor: ProofOfPossession>, 224 | >() 225 | where 226 | Keypair: 227 | ProofOfPossessionGenerator, PoPFlavor>, 228 | { 229 | let mut keypair_good = Keypair::::generate(thread_rng()); 230 | let proof_pair = , 234 | PoPFlavor, 235 | >>::generate_pok(&mut keypair_good); 236 | let keypair_bad = Keypair::::generate(thread_rng()); 237 | assert!( 238 | !ProofOfPossession::>::verify( 239 | &proof_pair, 240 | &DoublePublicKeyScheme::into_double_public_key(&keypair_bad) 241 | ), 242 | "invalid pok of unrelated public key should not verify" 243 | ); 244 | } 245 | 246 | #[test] 247 | fn nugget_bls_pop_of_random_public_key_should_fail() { 248 | double_bls_pop_of_random_public_key_should_fail::>(); 249 | } 250 | 251 | #[test] 252 | fn nugget_bls_and_cp_pop_of_random_public_key_should_fail() { 253 | double_bls_pop_of_random_public_key_should_fail::>(); 254 | } 255 | 256 | fn pop_of_a_double_public_key_should_serialize_and_deserialize_for_bls12_381< 257 | PoPFlavor: ProofOfPossession> + SerializableToBytes, 258 | >() 259 | where 260 | Keypair: 261 | ProofOfPossessionGenerator, PoPFlavor>, 262 | { 263 | let mut keypair = Keypair::::generate(thread_rng()); 264 | 265 | let proof_pair = , 269 | PoPFlavor, 270 | >>::generate_pok(&mut keypair); 271 | 272 | let serialized_proof = proof_pair.to_bytes(); 273 | let deserialized_proof = PoPFlavor::from_bytes(&serialized_proof).unwrap(); 274 | 275 | assert!( 276 | ProofOfPossession::>::verify( 277 | &deserialized_proof, 278 | &DoublePublicKeyScheme::into_double_public_key(&keypair) 279 | ), 280 | "valid pok does not verify" 281 | ); 282 | } 283 | 284 | #[test] 285 | fn nugget_bls_pop_should_serialize_and_deserialize_for_bls12_381() { 286 | pop_of_a_double_public_key_should_serialize_and_deserialize_for_bls12_381::< 287 | NuggetBLSPoP, 288 | >(); 289 | } 290 | 291 | #[test] 292 | fn nugget_bls_and_cp_pop_should_serialize_and_deserialize_for_bls12_381() { 293 | pop_of_a_double_public_key_should_serialize_and_deserialize_for_bls12_381::< 294 | NuggetBLSnCPPoP, 295 | >(); 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /src/engine.rs: -------------------------------------------------------------------------------- 1 | //! ## Adaptation of `ark_ec::PairingEngine` to BLS-like signatures. 2 | //! 3 | //! We provide an `EngineBLS` trait that adapts `pairing::Engine` 4 | //! to BLS-like signatures by permitting the group roles to be 5 | //! transposed, which involves removing the field of definition, 6 | //! while retaining the correct associations. 7 | //! 8 | //! We support same-message aggregation strategies using wrappers 9 | //! that satisfy `EngineBLS` as well, primarily because these 10 | //! strategies must ocntroll access to the public key type. 11 | //! 12 | //! In future, we should support [Pixel](https://github.com/w3f/bls/issues/4) 13 | //! by adding wrapper that replace `SignatureGroup` with a product 14 | //! of both groups. I think this requires abstracting `CruveAffine` 15 | //! and `CruveProjective` without their base fields and wNAF windows, 16 | //! but still with their affine, projective, and compressed forms, 17 | //! and batch normalization. 18 | 19 | use core::borrow::Borrow; 20 | use core::ops::MulAssign; 21 | 22 | use alloc::{vec, vec::Vec}; 23 | 24 | use ark_ec::hashing::curve_maps::wb::{WBConfig, WBMap}; 25 | use ark_ec::hashing::{ 26 | map_to_curve_hasher::{MapToCurve, MapToCurveBasedHasher}, 27 | HashToCurve, 28 | }; 29 | use ark_ec::{ 30 | pairing::{MillerLoopOutput, Pairing, PairingOutput}, 31 | AffineRepr, CurveGroup, 32 | }; 33 | use ark_ff::field_hashers::{DefaultFieldHasher, HashToField}; 34 | use ark_ff::{Field, PrimeField, UniformRand, Zero}; 35 | use ark_serialize::CanonicalSerialize; 36 | use rand::Rng; 37 | use rand_core::RngCore; 38 | 39 | use core::fmt::Debug; 40 | 41 | use sha2::Sha256; //IETF standard asks for SHA256 42 | 43 | use ark_ec::bls12::Bls12Config; 44 | use core::marker::PhantomData; 45 | 46 | // Expand SHA256 from 256 bits to 1024 bits. 47 | // let output_bits = 1024; 48 | // let output_bytes = 1024 / 8; 49 | // let mut hasher = FullDomainHash::::new(output_bytes).unwrap(); 50 | // hasher.update(b"ATTACK AT DAWN"); 51 | // let result = hasher.finalize_boxed().into_vec(); 52 | 53 | /// A weakening of `pairing::Engine` to permit transposing the groups. 54 | /// 55 | /// You cannot transpose the two groups in a `pairing::Engine` without 56 | /// first providing panicing implementations of `pairing::PrimeField` 57 | /// for `Engine::Fqe`, which is not a prime field, and second, 58 | /// providing wrapper types for the projective and affine group 59 | /// representations, which makes interacting with the original 60 | /// `pairing::Engine` annoying. This trait merely replicates 61 | /// transposable functionality from `pairing::Engine` by removing 62 | /// the fields of definition, but leaves the actual BLS signature 63 | /// scheme to wrapper types. 64 | /// 65 | /// We also extract two functions users may with to override: 66 | /// random scalar generation and hashing to the singature curve. 67 | pub trait EngineBLS { 68 | type Engine: Pairing; //; 69 | type Scalar: PrimeField; // = ::Fr; 70 | /// Group where BLS public keys live 71 | /// 72 | /// You should take this to be the `Engine::G1` curve usually 73 | /// becuase all verifiers perform additions on this curve, or 74 | /// even scalar multiplicaitons with delinearization. 75 | type PublicKeyGroupBaseField: Field; 76 | type PublicKeyGroupAffine: AffineRepr 77 | + From 78 | + Into 79 | + Into; 80 | //+ Into<::Affine>; 81 | 82 | type PublicKeyGroup: CurveGroup< 83 | Affine = Self::PublicKeyGroupAffine, 84 | ScalarField = Self::Scalar, 85 | BaseField = Self::PublicKeyGroupBaseField, 86 | > + From 87 | + Into 88 | + MulAssign; 89 | 90 | type PublicKeyPrepared: Default + Clone + Send + Sync + Debug + From; 91 | 92 | const PUBLICKEY_SERIALIZED_SIZE: usize; 93 | const SECRET_KEY_SIZE: usize; 94 | 95 | // See https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html#name-ciphersuites 96 | const CURVE_NAME: &'static [u8]; 97 | const SIG_GROUP_NAME: &'static [u8]; 98 | const CIPHER_SUIT_DOMAIN_SEPARATION: &'static [u8]; 99 | 100 | /// Group where BLS signatures live 101 | /// 102 | /// You should take this to be the `Engine::G2` curve usually 103 | /// becuase only aggregators perform additions on this curve, or 104 | /// scalar multiplicaitons with delinearization. 105 | type SignatureGroupBaseField: Field; 106 | 107 | type SignatureGroupAffine: AffineRepr 108 | + From 109 | + Into 110 | + Into; 111 | 112 | type SignatureGroup: CurveGroup< 113 | Affine = Self::SignatureGroupAffine, 114 | ScalarField = Self::Scalar, 115 | BaseField = Self::SignatureGroupBaseField, 116 | > + Into 117 | + From 118 | + MulAssign; 119 | 120 | type SignaturePrepared: Default + Clone + Send + Sync + Debug + From; 121 | 122 | const SIGNATURE_SERIALIZED_SIZE: usize; 123 | 124 | type HashToSignatureField: HashToField; 125 | type MapToSignatureCurve: MapToCurve; 126 | 127 | /// Generate a random scalar for use as a secret key. 128 | fn generate(rng: &mut R) -> Self::Scalar { 129 | Self::Scalar::rand(rng) 130 | } 131 | 132 | /// getter function for the hash to curve map 133 | fn hash_to_curve_map() -> MapToCurveBasedHasher< 134 | Self::SignatureGroup, 135 | Self::HashToSignatureField, 136 | Self::MapToSignatureCurve, 137 | >; 138 | 139 | /// Hash one message to the signature curve. 140 | fn hash_to_signature_curve>(message: M) -> Self::SignatureGroup { 141 | Self::hash_to_curve_map() 142 | .hash(message.borrow()) 143 | .unwrap() 144 | .into_group() 145 | } 146 | 147 | /// Run the Miller loop from `Engine` but orients its arguments 148 | /// to be a `SignatureGroup` and `PublicKeyGroup`. 149 | fn miller_loop<'a, I>(i: I) -> MillerLoopOutput 150 | where 151 | Self::PublicKeyPrepared: 'a, 152 | Self::SignaturePrepared: 'a, 153 | I: IntoIterator< 154 | Item = &'a ( 155 | ::PublicKeyPrepared, 156 | Self::SignaturePrepared, 157 | ), 158 | >; 159 | 160 | /// Perform final exponentiation on the result of a Miller loop. 161 | fn final_exponentiation( 162 | e: MillerLoopOutput, 163 | ) -> Option> { 164 | Self::Engine::final_exponentiation(e) 165 | } 166 | 167 | /// Performs a pairing operation `e(p, q)` by calling `Engine::pairing` 168 | /// but orients its arguments to be a `PublicKeyGroup` and `SignatureGroup`. 169 | fn pairing(p: G1, q: G2) -> ::TargetField 170 | where 171 | G1: Into<::Affine>, 172 | G2: Into<::Affine>; 173 | /* 174 | { 175 | Self::final_exponentiation(&Self::miller_loop( 176 | [(&(p.into().prepare()), &(q.into().prepare()))].into_iter(), 177 | )).unwrap() 178 | } 179 | */ 180 | 181 | /// Implement verification equation for aggregate BLS signatures 182 | /// provided as prepared points 183 | /// 184 | /// This low-level routine does no verification of critical security 185 | /// properties like message distinctness. It exists purely to 186 | /// simplify replacing mid-level routines with optimized variants, 187 | /// like versions that cache public key preperation or use fewer pairings. 188 | fn verify_prepared<'a, I>(signature: Self::SignaturePrepared, inputs: I) -> bool 189 | where 190 | Self::PublicKeyPrepared: 'a, 191 | Self::SignaturePrepared: 'a, 192 | I: IntoIterator, 193 | { 194 | let lhs: [_; 1] = [( 195 | Self::minus_generator_of_public_key_group_prepared(), 196 | signature, 197 | )]; 198 | Self::final_exponentiation(Self::miller_loop(inputs.into_iter().map(|t| t).chain(&lhs))) 199 | .unwrap() 200 | == (PairingOutput::::zero()) //zero is the target_field::one !! 201 | } 202 | 203 | /// Prepared negative of the generator of the public key curve. 204 | fn minus_generator_of_public_key_group_prepared() -> Self::PublicKeyPrepared; 205 | 206 | /// return the generator of signature group 207 | fn generator_of_signature_group() -> Self::SignatureGroup { 208 | ::Affine::generator().into() 209 | } 210 | 211 | /// Process the public key to be use in pairing. This has to be 212 | /// implemented by the type of BLS system implementing the engine 213 | /// by calling either prepare_g1 or prepare_g2 based on which group 214 | /// is used by the signature system to host the public key 215 | fn prepare_public_key(g: impl Into) -> Self::PublicKeyPrepared { 216 | let g_affine: Self::PublicKeyGroupAffine = g.into(); 217 | Self::PublicKeyPrepared::from(g_affine) 218 | } 219 | 220 | /// Process the signature to be use in pairing. This has to be 221 | /// implemented by the type of BLS system implementing the engine 222 | /// by calling either prepare_g1 or prepare_g2 based on which group 223 | /// is used by the signature system to host the public key 224 | fn prepare_signature(g: impl Into) -> Self::SignaturePrepared { 225 | let g_affine: Self::SignatureGroupAffine = g.into(); 226 | Self::SignaturePrepared::from(g_affine) 227 | } 228 | 229 | /// Serialization helper for various sigma protocols 230 | fn signature_point_to_byte(point: &Self::SignatureGroup) -> Vec { 231 | let mut point_as_bytes = vec![0; Self::SIGNATURE_SERIALIZED_SIZE]; 232 | let point_affine = point.into_affine(); 233 | point_affine 234 | .serialize_compressed(&mut point_as_bytes[..]) 235 | .unwrap(); 236 | point_as_bytes 237 | } 238 | 239 | fn public_key_point_to_byte(point: &Self::PublicKeyGroup) -> Vec { 240 | let mut point_as_bytes = vec![0; Self::PUBLICKEY_SERIALIZED_SIZE]; 241 | let point_affine = point.into_affine(); 242 | point_affine 243 | .serialize_compressed(&mut point_as_bytes[..]) 244 | .unwrap(); 245 | point_as_bytes 246 | } 247 | } 248 | 249 | /// Usual aggregate BLS signature scheme on ZCash's BLS12-381 curve. 250 | pub type ZBLS = UsualBLS; 251 | pub type BLS377 = UsualBLS; 252 | 253 | /// Usual aggregate BLS signature scheme on ZCash's BLS12-381 curve. 254 | // pub const Z_BLS : ZBLS = UsualBLS(::zexe_algebra::bls12_381::Bls12_381{}); 255 | 256 | /// Usual BLS variant with tiny 48 byte public keys and 96 byte signatures. 257 | /// 258 | /// We favor this variant because verifiers always perform 259 | /// `O(signers)` additions on the `PublicKeyGroup`, or worse 128 bit 260 | /// scalar multiplications with delinearization. 261 | /// We also orient this variant to match zcash's traits. 262 | #[derive(Default)] 263 | pub struct UsualBLS(pub E, PhantomData P>) 264 | where 265 |

::G2Config: WBConfig, 266 | WBMap<

::G2Config>: MapToCurve<::G2>; 267 | 268 | impl EngineBLS for UsualBLS 269 | where 270 |

::G2Config: WBConfig, 271 | WBMap<

::G2Config>: MapToCurve<::G2>, 272 | { 273 | type Engine = E; 274 | type Scalar = ::ScalarField; 275 | 276 | type PublicKeyGroup = E::G1; 277 | type PublicKeyGroupAffine = E::G1Affine; 278 | type PublicKeyPrepared = E::G1Prepared; 279 | type PublicKeyGroupBaseField = <::G1 as CurveGroup>::BaseField; 280 | 281 | const PUBLICKEY_SERIALIZED_SIZE: usize = 48; 282 | const SECRET_KEY_SIZE: usize = 32; 283 | 284 | const CURVE_NAME: &'static [u8] = P::CURVE_NAME; 285 | const SIG_GROUP_NAME: &'static [u8] = b"G2"; 286 | const CIPHER_SUIT_DOMAIN_SEPARATION: &'static [u8] = b"_XMD:SHA-256_SSWU_RO_"; 287 | 288 | type SignatureGroup = E::G2; 289 | type SignatureGroupAffine = E::G2Affine; 290 | type SignaturePrepared = E::G2Prepared; 291 | type SignatureGroupBaseField = <::G2 as CurveGroup>::BaseField; 292 | 293 | const SIGNATURE_SERIALIZED_SIZE: usize = 96; 294 | 295 | type HashToSignatureField = DefaultFieldHasher; 296 | type MapToSignatureCurve = WBMap; 297 | 298 | fn miller_loop<'a, I>(i: I) -> MillerLoopOutput 299 | where 300 | // Self::PublicKeyPrepared: 'a, 301 | // Self::SignaturePrepared: 'a, 302 | I: IntoIterator, 303 | { 304 | let (i_a, i_b): (Vec, Vec) = 305 | i.into_iter().cloned().unzip(); 306 | 307 | E::multi_miller_loop(i_a, i_b) 308 | } 309 | 310 | fn pairing(p: G1, q: G2) -> E::TargetField 311 | where 312 | G1: Into, 313 | G2: Into, 314 | { 315 | E::pairing(p.into(), q.into()).0 316 | } 317 | 318 | /// Prepared negative of the generator of the public key curve. 319 | fn minus_generator_of_public_key_group_prepared() -> Self::PublicKeyPrepared { 320 | let g1_minus_generator = ::Affine::generator(); 321 | >::into( 322 | -g1_minus_generator.into_group(), 323 | ) 324 | } 325 | 326 | fn hash_to_curve_map() -> MapToCurveBasedHasher< 327 | Self::SignatureGroup, 328 | Self::HashToSignatureField, 329 | Self::MapToSignatureCurve, 330 | > { 331 | MapToCurveBasedHasher::< 332 | Self::SignatureGroup, 333 | DefaultFieldHasher, 334 | WBMap, 335 | >::new(&[1]) 336 | .unwrap() 337 | } 338 | } 339 | 340 | /// Infrequently used BLS variant with tiny 48 byte signatures and 96 byte public keys, 341 | /// 342 | /// We recommend gainst this variant by default because verifiers 343 | /// always perform `O(signers)` additions on the `PublicKeyGroup`, 344 | /// or worse 128 bit scalar multiplications with delinearization. 345 | /// Yet, there are specific use cases where this variant performs 346 | /// better. We swapy two group roles relative to zcash here. 347 | #[derive(Default)] 348 | pub struct TinyBLS(pub E, PhantomData P>) 349 | where 350 |

::G1Config: WBConfig, 351 | WBMap<

::G1Config>: MapToCurve<::G1>; 352 | 353 | /// Trait to add extra config for a curve which is not in ArkWorks library 354 | pub trait CurveExtraConfig { 355 | const CURVE_NAME: &'static [u8]; 356 | } 357 | 358 | impl EngineBLS for TinyBLS 359 | where 360 |

::G1Config: WBConfig, 361 | WBMap<

::G1Config>: MapToCurve<::G1>, 362 | { 363 | type Engine = E; 364 | type Scalar = ::ScalarField; 365 | 366 | type SignatureGroup = E::G1; 367 | type SignatureGroupAffine = E::G1Affine; 368 | type SignaturePrepared = E::G1Prepared; 369 | type SignatureGroupBaseField = <::G1 as CurveGroup>::BaseField; 370 | 371 | const SIGNATURE_SERIALIZED_SIZE: usize = 48; 372 | 373 | type PublicKeyGroup = E::G2; 374 | type PublicKeyGroupAffine = E::G2Affine; 375 | type PublicKeyPrepared = E::G2Prepared; 376 | type PublicKeyGroupBaseField = <::G2 as CurveGroup>::BaseField; 377 | 378 | const PUBLICKEY_SERIALIZED_SIZE: usize = 96; 379 | const SECRET_KEY_SIZE: usize = 32; 380 | 381 | const CURVE_NAME: &'static [u8] = P::CURVE_NAME; 382 | const SIG_GROUP_NAME: &'static [u8] = b"G1"; 383 | const CIPHER_SUIT_DOMAIN_SEPARATION: &'static [u8] = b"_XMD:SHA-256_SSWU_RO_"; 384 | 385 | type HashToSignatureField = DefaultFieldHasher; 386 | type MapToSignatureCurve = WBMap; 387 | 388 | fn miller_loop<'a, I>(i: I) -> MillerLoopOutput 389 | where 390 | I: IntoIterator, 391 | { 392 | // We require an ugly unecessary allocation here because 393 | // zcash's pairing library cnsumes an iterator of references 394 | // to tuples of references, which always requires 395 | let (i_a, i_b): (Vec, Vec) = 396 | i.into_iter().cloned().unzip(); 397 | 398 | E::multi_miller_loop(i_b, i_a) //in Tiny BLS signature is in G1 399 | } 400 | 401 | fn pairing(p: G2, q: G1) -> E::TargetField 402 | where 403 | G1: Into, 404 | G2: Into, 405 | { 406 | E::pairing(q.into(), p.into()).0 407 | } 408 | 409 | /// Prepared negative of the generator of the public key curve. 410 | fn minus_generator_of_public_key_group_prepared() -> Self::PublicKeyPrepared { 411 | let g2_minus_generator = ::Affine::generator(); 412 | >::into( 413 | -g2_minus_generator.into_group(), 414 | ) 415 | } 416 | 417 | fn hash_to_curve_map() -> MapToCurveBasedHasher< 418 | Self::SignatureGroup, 419 | Self::HashToSignatureField, 420 | Self::MapToSignatureCurve, 421 | > { 422 | MapToCurveBasedHasher::< 423 | Self::SignatureGroup, 424 | DefaultFieldHasher, 425 | WBMap, 426 | >::new(&[1]) 427 | .unwrap() 428 | } 429 | } 430 | 431 | /// Aggregate BLS signature scheme with Signature in G1 for BLS12-377 curve. 432 | impl CurveExtraConfig for ark_bls12_377::Config { 433 | const CURVE_NAME: &'static [u8] = b"BLS12377"; 434 | } 435 | pub type TinyBLS377 = TinyBLS; 436 | /// Aggregate BLS signature scheme with Signature in G1 for BLS12-381 curve. 437 | impl CurveExtraConfig for ark_bls12_381::Config { 438 | const CURVE_NAME: &'static [u8] = b"BLS12381"; 439 | } 440 | pub type TinyBLS381 = TinyBLS; 441 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Aggregate BLS signature library with extensive tuning options. 2 | //! 3 | //! In short, anyone using BLS signatures should normally choose both 4 | //! an orientation as well as some aggregation and batching strategies 5 | //! These two decissions impact performance dramaticaly, but making 6 | //! the optimal choises requires some attentiom. This crate employs 7 | //! convenient abstraction boundaries between curver arithmatic, 8 | //! verifier routines, and aggregated and/or batched BLS signatures. 9 | //! 10 | //! ### Pairings 11 | //! 12 | //! If we have two elliptic curve with a pairing `e`, then 13 | //! a BLS signature `sigma = s*H(msg)` by a public key `S = s g1` 14 | //! can be verified with the one equation `e(g1,sigma) = e(S,H(msg))`. 15 | //! These simple BLS signatures are very slow to verify however 16 | //! because the pairing map `e` is far slower than many cryptographic 17 | //! primitives. 18 | //! 19 | //! Our pairing `e` maps from a small curve over `F(q)` and a larger 20 | //! curve over `F(q^2)` into some multipliccative group if a field, 21 | //! normally over `F(q^12)`. In principle, this map `e` into `F(q^12)` 22 | //! makes pairing based cryptography like BLS less secure than 23 | //! other elliptic curve based cryptography, which further slows down 24 | //! BLS signatures by requiring larger `q`. 25 | //! 26 | //! ### Arithmatic 27 | //! 28 | //! An almost universally applicable otimization is to seperate the 29 | //! "Miller loop" that computes in `F(q)` and `F(q^2)` from the slow 30 | //! final exponentiation that happens in `F(q^12)`. So our actual 31 | //! verification equation more resembles `e(-g1,sigma) e(S,H(msg)) = 1`. 32 | //! 33 | //! As one curve is smaller and hence faster, the user should choose 34 | //! which orientation of curves they prefer, meaning to which curve 35 | //! they hash, and which curves hold the signatues and public keys. 36 | //! In other words, your desired aggregation techniques and usage 37 | //! characteristics should determine if youp refer the verification 38 | //! equation `e(g1,sigma) = e(S,H(msg))` or the fliped form 39 | //! `e(sigma,g2) = e(H(msg),S)`. See `UsualBLS` and `TinyBLS`. 40 | //! 41 | //! ### Aggregation 42 | //! 43 | //! We consder BLS signatures interesting because they support 44 | //! dramatic optimizations when handling multiple signatures together. 45 | //! In fact, BLS signatures support aggregation by a third party 46 | //! that makes signatures smaller, not merely batch verification. 47 | //! All this stems from the bilinearity of `e`, meaning we reduce 48 | //! the number of pairings, or size of the miller loop, by appling 49 | //! rules like `e(x,z)e(y,z) = e(x+y,z)`, `e(x,y)e(x,z) = e(x,y+z)`, 50 | //! etc. 51 | //! 52 | //! In essence, our aggregation tricks fall into two categories, 53 | //! linear aggregation, in which only addition is used, and 54 | //! delinearized optimiztions, in which we multiply curve points 55 | //! by values unforseeable to the signers. 56 | //! In general, linear techniques provide much better performance, 57 | //! but require stronger invariants be maintained by the caller, 58 | //! like messages being distinct, or limited signer sets with 59 | //! proofs-of-possession. Also, the delinearized techniques remain 60 | //! secure without tricky assumptions, but require more computation. 61 | //! 62 | //! ### Verification 63 | //! 64 | //! We can often further reduce the pairings required in the 65 | //! verification equation, beyond the naieve information tracked 66 | //! by the aggregated signature itself. Aggregated signature must 67 | //! state all the individual messages and/or public keys, but 68 | //! verifiers may collapse anything permitted. 69 | //! We thus encounter aggregation-like decissions that impact 70 | //! verifier performance. 71 | //! 72 | //! We therefore provide an abstract interface that permits 73 | //! doing further aggregation and/or passing any aggregate signature 74 | //! to any verification routine. 75 | //! 76 | //! As a rule, we also attempt to batch normalize different arithmatic 77 | //! outputs, but concievably small signer set sizes might make this 78 | //! a pessimization. 79 | //! 80 | //! 81 | //! 82 | 83 | //#![feature(test)] needed for cargo bench 84 | #![cfg_attr(not(feature = "std"), no_std)] 85 | #[cfg_attr(feature = "std", doc = include_str!("../README.md"))] 86 | #[cfg(doctest)] 87 | pub struct ReadmeDoctests; 88 | 89 | extern crate ark_serialize; 90 | extern crate ark_serialize_derive; 91 | 92 | extern crate ark_ec; 93 | extern crate ark_ff; 94 | extern crate digest; 95 | extern crate rand; 96 | extern crate rand_chacha; 97 | extern crate rand_core; 98 | extern crate sha3; 99 | 100 | extern crate alloc; 101 | 102 | use core::borrow::Borrow; 103 | use digest::DynDigest; 104 | 105 | pub mod chaum_pedersen_signature; 106 | pub mod double; 107 | pub mod double_pop; 108 | pub mod engine; 109 | pub mod schnorr_pop; 110 | pub mod serialize; 111 | pub mod single; 112 | pub mod verifiers; 113 | 114 | #[cfg(feature = "std")] 115 | pub mod multi_pop_aggregator; 116 | #[cfg(feature = "std")] 117 | pub mod single_pop_aggregator; 118 | 119 | #[cfg(feature = "experimental")] 120 | pub mod bit; 121 | #[cfg(feature = "experimental")] 122 | pub mod delinear; 123 | #[cfg(feature = "experimental")] 124 | pub mod distinct; 125 | 126 | pub use engine::*; 127 | 128 | pub use double::{ 129 | DoublePublicKey, DoublePublicKeyScheme, DoubleSignature, PublicKeyInSignatureGroup, 130 | }; 131 | pub use double_pop::{NuggetBLSPoP, NuggetBLSnCPPoP}; 132 | pub use schnorr_pop::SchnorrProof; 133 | pub use serialize::SerializableToBytes; 134 | pub use single::{Keypair, KeypairVT, PublicKey, SecretKey, SecretKeyVT, Signature, SignedMessage}; 135 | 136 | use alloc::vec::Vec; 137 | 138 | /// Internal message hash size. 139 | /// 140 | /// We choose 256 bits here so that birthday bound attacks cannot 141 | /// find messages with the same hash. 142 | const MESSAGE_SIZE: usize = 32; 143 | 144 | /// Ciphersuite standards from BLS signature draft IETF proposal 145 | const PROOF_OF_POSSESSION_ID: &'static [u8] = b"BLS_POP_"; 146 | const NORMAL_MESSAGE_SIGNATURE_ID: &'static [u8] = b"BLS_SIG_"; 147 | 148 | const NORMAL_MESSAGE_SIGNATURE_ASSUMING_POP: &'static [u8] = b"POP_"; 149 | const NORMAL_MESSAGE_SIGNATURE_BASIC: &'static [u8] = b"NUL_"; 150 | const POP_MESSAGE: &'static [u8] = b"POP_"; 151 | 152 | type MessageDigest = [u8; MESSAGE_SIZE]; 153 | /// Internal message hash type. Short for frequent rehashing 154 | /// by `HashMap`, etc. 155 | #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] 156 | pub struct Message(pub MessageDigest, pub alloc::vec::Vec, MessageType); 157 | 158 | #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] 159 | enum MessageType { 160 | ProofOfPossession, 161 | NormalAssumingPoP, 162 | NormalBasic, 163 | } 164 | 165 | impl Message { 166 | pub fn new(context: &[u8], message: &[u8]) -> Message { 167 | let msg_hash = Self::compute_internal_hash(context, message); 168 | Message( 169 | msg_hash, 170 | [context, message].concat(), 171 | MessageType::NormalBasic, 172 | ) 173 | } 174 | 175 | pub fn new_assuming_pop(context: &[u8], message: &[u8]) -> Message { 176 | let msg_hash = Self::compute_internal_hash(context, message); 177 | Message( 178 | msg_hash, 179 | [context, message].concat(), 180 | MessageType::NormalAssumingPoP, 181 | ) 182 | } 183 | 184 | pub fn new_pop_message(context: &[u8], message: &[u8]) -> Message { 185 | let msg_hash = Self::compute_internal_hash(context, message); 186 | Message( 187 | msg_hash, 188 | [context, message].concat(), 189 | MessageType::ProofOfPossession, 190 | ) 191 | } 192 | 193 | fn compute_internal_hash(context: &[u8], message: &[u8]) -> [u8; MESSAGE_SIZE] { 194 | use sha3::{ 195 | digest::{ExtendableOutput, Update, XofReader}, 196 | Shake128, 197 | }; 198 | let mut h = Shake128::default(); 199 | h.update(context); 200 | let l = message.len() as u64; 201 | h.update(&l.to_le_bytes()); 202 | h.update(message); 203 | 204 | let mut msg_hash = [0u8; MESSAGE_SIZE]; 205 | h.finalize_xof().read(&mut msg_hash[..]); 206 | 207 | msg_hash 208 | } 209 | 210 | /// generate ciphersuite string added to the context according to 211 | /// BLS Signature draft proposal to IETF 212 | fn cipher_suite(&self) -> Vec { 213 | let id = match self.2 { 214 | MessageType::ProofOfPossession => PROOF_OF_POSSESSION_ID, 215 | _ => NORMAL_MESSAGE_SIGNATURE_ID, 216 | }; 217 | 218 | let h2c_suite_id = [ 219 | E::CURVE_NAME, 220 | E::SIG_GROUP_NAME, 221 | E::CIPHER_SUIT_DOMAIN_SEPARATION, 222 | ] 223 | .concat(); 224 | 225 | let sc_tag = match self.2 { 226 | MessageType::ProofOfPossession => POP_MESSAGE, 227 | MessageType::NormalAssumingPoP => NORMAL_MESSAGE_SIGNATURE_ASSUMING_POP, 228 | _ => NORMAL_MESSAGE_SIGNATURE_BASIC, 229 | }; 230 | 231 | [id, &h2c_suite_id[..], sc_tag].concat() 232 | } 233 | 234 | pub fn hash_to_signature_curve(&self) -> E::SignatureGroup { 235 | E::hash_to_signature_curve(&[&self.cipher_suite::()[..], &self.1[..]].concat()[..]) 236 | } 237 | } 238 | 239 | impl<'a> From<&'a [u8]> for Message { 240 | fn from(x: &[u8]) -> Message { 241 | Message::new(b"", x) 242 | } 243 | } 244 | 245 | /// Representation of an aggregated BLS signature. 246 | /// 247 | /// We implement this trait only for borrows of appropriate structs 248 | /// because otherwise we'd need extensive lifetime plumbing here, 249 | /// due to the absence of assocaited type constructers (ATCs). 250 | /// We shall make `messages_and_publickeys` take `&sefl` and 251 | /// remove these limitations in the future once ATCs stabalize, 252 | /// thus removing `PKG`. See [Rust RFC 1598](https://github.com/rust-lang/rfcs/blob/master/text/1598-generic_associated_types.md) 253 | /// We shall eventually remove MnPK entirely whenever `-> impl Trait` 254 | /// in traits gets stabalized. See [Rust RFCs 1522, 1951, and 2071](https://github.com/rust-lang/rust/issues/34511 255 | pub trait Signed: Sized { 256 | type E: EngineBLS; 257 | 258 | /// Return the aggregated signature 259 | fn signature(&self) -> Signature; 260 | 261 | type M: Borrow; // = Message; 262 | type PKG: Borrow>; // = PublicKey; 263 | 264 | /// Iterator over, messages and public key reference pairs. 265 | type PKnM: Iterator + ExactSizeIterator; 266 | // type PKnM<'a>: Iterator>::E as EngineBLS>::PublicKeyGroup, 268 | // &'a Self::M, 269 | // )> + DoubleEndedIterator + ExactSizeIterator + 'a; 270 | 271 | /// Returns an iterator over messages and public key reference for 272 | /// pairings, often only partially aggregated. 273 | fn messages_and_publickeys(self) -> Self::PKnM; 274 | // fn messages_and_publickeys<'a>(&'s self) -> PKnM<'a> 275 | // -> impl Iterator + 'a; 276 | 277 | /// Appropriate BLS signature verification for the `Self` type. 278 | /// 279 | /// We use `verify_simple` as a default implementation because 280 | /// it supports unstable `self.messages_and_publickeys()` securely 281 | /// by calling it only once, and does not expect pulic key points 282 | /// to be normalized, but this should usually be replaced by more 283 | /// optimized variants. 284 | fn verify(self) -> bool { 285 | verifiers::verify_simple(self) 286 | } 287 | } 288 | 289 | pub trait ProofOfPossession 290 | where 291 | E: EngineBLS, 292 | H: DynDigest + Default + Clone, 293 | { 294 | fn verify(&self, public_key_of_prover: &PV) -> bool; 295 | } 296 | 297 | /// ProofOfPossion trait which should be implemented by secret 298 | pub trait ProofOfPossessionGenerator< 299 | E: EngineBLS, 300 | H: DynDigest + Default + Clone, 301 | PV, 302 | P: ProofOfPossession, 303 | > 304 | { 305 | /// The proof of possession generator is supposed to 306 | /// to produce a schnorr signature or a bls signature using 307 | /// the secret key which it claims to possess. This proves that 308 | /// that the secret key is known. 309 | fn generate_pok(&mut self) -> P; 310 | } 311 | -------------------------------------------------------------------------------- /src/multi_pop_aggregator.rs: -------------------------------------------------------------------------------- 1 | //! ## Aggregation of BLS signatures using proofs-of-possession 2 | //! 3 | //! In this module, we provide the linear flavor of aggregate 4 | //! BLS signature in which the verifiers has previously checked 5 | //! proofs-of-possession for all public keys. In other words, 6 | //! we simply add up the signatures because the previously checked 7 | //! proofs-of-possession for all signers prevent rogue key attacks. 8 | //! See the security arguments in The Power of Proofs-of-Possession: 9 | //! Securing Multiparty Signatures against Rogue-Key Attacks 10 | //! by Thomas Ristenpart and Scott Yilek at https://eprint.iacr.org/2007/264.pdf 11 | //! 12 | //! These proof-of-possession are simply self-signed certificates, 13 | //! so a BLS signature by each secret key on its own public key. 14 | //! Importantly, the message for this self-signed certificates 15 | //! must uniquely distinguish the public key for which the signature 16 | //! establishes a proof-of-possession. 17 | //! It follows that each proof-of-possession has a unique message, 18 | //! so distinct message aggregation is optimal for verifying them. 19 | //! 20 | //! In this vein, we note that aggregation under proofs-of-possession 21 | //! cannot improve performance when signers sign distinct messages, 22 | //! so proofs-of-possession help with aggregating votes in a concensus 23 | //! protocol, but should never be used for accounts on a block chain. 24 | //! 25 | //! We assume here that users provide their own data structure for 26 | //! proofs-of-poossession. We provide more structure for users who 27 | //! one bit per vote in a concensus protocol: 28 | //! You first verify the proofs-of-possession when building a data 29 | //! structure that holds the voters' keys. You implement the 30 | //! `ProofsOfPossession` trait for this data strtcuture as well, 31 | //! so that the `BitPoPSignedMessage` type provides a signature 32 | //! data type with reasonable sanity checks. 33 | 34 | // Aside about proof-of-possession in the DLOG setting 35 | // https://twitter.com/btcVeg/status/1085490561082183681 36 | 37 | use core::borrow::Borrow; // BorrowMut 38 | use std::collections::HashMap; 39 | 40 | use ark_ff::Zero; 41 | 42 | use super::verifiers::verify_with_distinct_messages; 43 | use super::*; 44 | 45 | /// Batch or aggregate BLS signatures with attached messages and 46 | /// signers, for whom we previously checked proofs-of-possession. 47 | /// 48 | /// In this type, we provide a high-risk low-level batching and 49 | /// aggregation mechanism that merely adds up signatures under the 50 | /// assumption that all required proofs-of-possession were previously 51 | /// checked. 52 | /// 53 | /// We say a signing key has provided a proof-of-possession if the 54 | /// verifier remembers having checked some self-signed certificate 55 | /// by that key. It's insecure to use this aggregation strategy 56 | /// without first cehcking proofs-of-possession. In particular 57 | /// it is insecure to use this aggregation strategy when checking 58 | /// proofs-of-possession, and could not improve performance anyways. 59 | /// Distinct message aggregation is always optimal for checking 60 | /// proofs-of-possession. Please see the module level doumentation 61 | /// for additional discussion and notes on security. 62 | /// 63 | /// We foresee this type primarily being used to batch several 64 | /// `BitPoPSignedMessage`s into one verification. We do not track 65 | /// aggreggated public keys here, instead merging multiples signers 66 | /// public keys anytime they sign the same message, so this type 67 | /// essentially provides only fast batch verificartion. 68 | /// In principle, our `add_*` methods suffice for building an actual 69 | /// aggregate signature type. Yet, normally direct approaches like 70 | /// `BitPoPSignedMessage` work better for aggregation because 71 | /// the `ProofsOfPossession` trait tooling permits both enforce the 72 | /// proofs-of-possession and provide a compact serialization. 73 | /// We see no reason to support serialization for this type as present. 74 | // 75 | /// In principle, one might combine proof-of-possession with distinct 76 | /// message assumptions, or other aggregation strategies, when 77 | /// verifiers have only observed a subset of the proofs-of-possession, 78 | /// but this sounds complex or worse fragile. 79 | /// 80 | // TODO: Implement gaussian elimination verification scheme. 81 | use single::PublicKey; 82 | /// ProofOfPossion trait which should be implemented by secret 83 | 84 | #[derive(Clone)] 85 | pub struct MultiMessageSignatureAggregatorAssumingPoP { 86 | messages_n_publickeys: HashMap>, 87 | signature: Signature, 88 | } 89 | 90 | impl MultiMessageSignatureAggregatorAssumingPoP { 91 | pub fn new() -> MultiMessageSignatureAggregatorAssumingPoP { 92 | MultiMessageSignatureAggregatorAssumingPoP { 93 | messages_n_publickeys: HashMap::new(), 94 | signature: Signature(E::SignatureGroup::zero()), 95 | } 96 | } 97 | 98 | /// Add only a `Signature` to our internal signature. 99 | /// 100 | /// Useful for constructing an aggregate signature, but we 101 | /// recommend instead using a custom types like `BitPoPSignedMessage`. 102 | pub fn add_signature(&mut self, signature: &Signature) { 103 | self.signature.0 += &signature.0; 104 | } 105 | 106 | /// Add only a `Message` and `PublicKey` to our internal data. 107 | /// 108 | /// Useful for constructing an aggregate signature, but we 109 | /// recommend instead using a custom types like `BitPoPSignedMessage`. 110 | pub fn add_message_n_publickey(&mut self, message: &Message, publickey: &PublicKey) { 111 | self.messages_n_publickeys 112 | .entry(message.clone()) 113 | .and_modify(|pk0| pk0.0 += &publickey.0) 114 | .or_insert(*publickey); 115 | } 116 | 117 | /// Aggregage BLS signatures assuming they have proofs-of-possession 118 | pub fn aggregate<'a, S>(&mut self, signed: &'a S) 119 | where 120 | &'a S: Signed, 121 | <&'a S as Signed>::PKG: Borrow>, 122 | { 123 | let signature = signed.signature(); 124 | for (message, pubickey) in signed.messages_and_publickeys() { 125 | self.add_message_n_publickey(message.borrow(), pubickey.borrow()); 126 | } 127 | self.add_signature(&signature); 128 | } 129 | } 130 | 131 | impl<'a, E: EngineBLS> Signed for &'a MultiMessageSignatureAggregatorAssumingPoP { 132 | type E = E; 133 | 134 | type M = &'a Message; 135 | type PKG = &'a PublicKey; 136 | type PKnM = ::std::collections::hash_map::Iter<'a, Message, PublicKey>; 137 | 138 | fn messages_and_publickeys(self) -> Self::PKnM { 139 | self.messages_n_publickeys.iter() 140 | } 141 | 142 | fn signature(&self) -> Signature { 143 | self.signature 144 | } 145 | 146 | fn verify(self) -> bool { 147 | // We have already aggregated distinct messages, so our distinct 148 | // message verification code provides reasonable optimizations, 149 | // except the public keys might not be normalized here. 150 | // We foresee verification via gaussian elimination being faster, 151 | // but requires affine keys or normalization. 152 | verify_with_distinct_messages(self, true) 153 | // TODO: verify_with_gaussian_elimination(self) 154 | } 155 | } 156 | 157 | #[cfg(all(test, feature = "std"))] 158 | mod tests { 159 | 160 | use crate::Keypair; 161 | use crate::Message; 162 | use crate::UsualBLS; 163 | use rand::thread_rng; 164 | 165 | use ark_bls12_381::Bls12_381; 166 | 167 | use super::*; 168 | 169 | #[test] 170 | fn verify_aggregate_single_message_single_signer() { 171 | let good = Message::new(b"ctx", b"test message"); 172 | 173 | let mut keypair = 174 | Keypair::>::generate(thread_rng()); 175 | let good_sig0 = keypair.sign(&good); 176 | assert!(good_sig0.verify(&good, &keypair.public)); 177 | } 178 | 179 | #[test] 180 | fn verify_aggregate_single_message_multi_signers() { 181 | let good = Message::new(b"ctx", b"test message"); 182 | 183 | let mut keypair0 = 184 | Keypair::>::generate(thread_rng()); 185 | let good_sig0 = keypair0.sign(&good); 186 | 187 | let mut keypair1 = 188 | Keypair::>::generate(thread_rng()); 189 | let good_sig1 = keypair1.sign(&good); 190 | 191 | let mut aggregated_sigs = MultiMessageSignatureAggregatorAssumingPoP::< 192 | UsualBLS, 193 | >::new(); 194 | aggregated_sigs.add_signature(&good_sig0); 195 | aggregated_sigs.add_signature(&good_sig1); 196 | 197 | aggregated_sigs.add_message_n_publickey(&good, &keypair0.public); 198 | aggregated_sigs.add_message_n_publickey(&good, &keypair1.public); 199 | 200 | assert!( 201 | aggregated_sigs.verify() == true, 202 | "good aggregated signature of a single message with multiple key does not verify" 203 | ); 204 | } 205 | 206 | #[test] 207 | fn verify_aggregate_multi_messages_single_signer() { 208 | let good0 = Message::new(b"ctx", b"Tab over Space"); 209 | let good1 = Message::new(b"ctx", b"Space over Tab"); 210 | 211 | let mut keypair = 212 | Keypair::>::generate(thread_rng()); 213 | 214 | let good_sig0 = keypair.sign(&good0); 215 | let good_sig1 = keypair.sign(&good1); 216 | 217 | let mut aggregated_sigs = MultiMessageSignatureAggregatorAssumingPoP::< 218 | UsualBLS, 219 | >::new(); 220 | aggregated_sigs.add_signature(&good_sig0); 221 | aggregated_sigs.add_signature(&good_sig1); 222 | 223 | aggregated_sigs.add_message_n_publickey(&good0, &keypair.public); 224 | aggregated_sigs.add_message_n_publickey(&good1, &keypair.public); 225 | 226 | assert!( 227 | aggregated_sigs.verify() == true, 228 | "good aggregated signature of multiple messages with a single key does not verify" 229 | ); 230 | } 231 | 232 | #[test] 233 | fn verify_aggregate_multi_messages_multi_signers() { 234 | let good0 = Message::new(b"ctx", b"in the beginning"); 235 | let good1 = Message::new(b"ctx", b"there was a flying spaghetti monster"); 236 | 237 | let mut keypair0 = 238 | Keypair::>::generate(thread_rng()); 239 | let good_sig0 = keypair0.sign(&good0); 240 | 241 | let mut keypair1 = 242 | Keypair::>::generate(thread_rng()); 243 | let good_sig1 = keypair1.sign(&good1); 244 | 245 | let mut aggregated_sigs = MultiMessageSignatureAggregatorAssumingPoP::< 246 | UsualBLS, 247 | >::new(); 248 | aggregated_sigs.add_signature(&good_sig0); 249 | aggregated_sigs.add_signature(&good_sig1); 250 | 251 | aggregated_sigs.add_message_n_publickey(&good0, &keypair0.public); 252 | aggregated_sigs.add_message_n_publickey(&good1, &keypair1.public); 253 | 254 | assert!( 255 | aggregated_sigs.verify() == true, 256 | "good aggregated signature of multiple messages with multiple keys does not verify" 257 | ); 258 | } 259 | 260 | #[test] 261 | fn verify_aggregate_single_message_repetative_signers() { 262 | let good = Message::new(b"ctx", b"test message"); 263 | 264 | let mut keypair = 265 | Keypair::>::generate(thread_rng()); 266 | let good_sig = keypair.sign(&good); 267 | 268 | let mut aggregated_sigs = MultiMessageSignatureAggregatorAssumingPoP::< 269 | UsualBLS, 270 | >::new(); 271 | aggregated_sigs.add_signature(&good_sig); 272 | aggregated_sigs.add_signature(&good_sig); 273 | 274 | aggregated_sigs.add_message_n_publickey(&good, &keypair.public); 275 | aggregated_sigs.add_message_n_publickey(&good, &keypair.public); 276 | 277 | assert!( 278 | aggregated_sigs.verify() == true, 279 | "good aggregate of a repetitive signature does not verify" 280 | ); 281 | } 282 | 283 | #[test] 284 | fn aggregate_of_signature_of_a_wrong_message_should_not_verify() { 285 | let good0 = Message::new(b"ctx", b"Space over Tab"); 286 | let bad1 = Message::new(b"ctx", b"Tab over Space"); 287 | 288 | let mut keypair0 = 289 | Keypair::>::generate(thread_rng()); 290 | let good_sig0 = keypair0.sign(&good0); 291 | 292 | let mut keypair1 = 293 | Keypair::>::generate(thread_rng()); 294 | let bad_sig1 = keypair1.sign(&bad1); 295 | 296 | let mut aggregated_sigs = MultiMessageSignatureAggregatorAssumingPoP::< 297 | UsualBLS, 298 | >::new(); 299 | aggregated_sigs.add_signature(&good_sig0); 300 | aggregated_sigs.add_signature(&bad_sig1); 301 | 302 | aggregated_sigs.add_message_n_publickey(&good0, &keypair0.public); 303 | aggregated_sigs.add_message_n_publickey(&good0, &keypair1.public); 304 | 305 | assert!( 306 | aggregated_sigs.verify() == false, 307 | "aggregated signature of a wrong message should not verify" 308 | ); 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /src/schnorr_pop.rs: -------------------------------------------------------------------------------- 1 | //! ## Implementation of ProofofPossion trait for BLS keys using schnorr sginature 2 | //! ## TODO: I assume this can also moved to pop.rs but for now I put it separately to help reviews 3 | use ark_ff::field_hashers::{DefaultFieldHasher, HashToField}; 4 | use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; 5 | 6 | use crate::engine::EngineBLS; 7 | use crate::{ProofOfPossession, ProofOfPossessionGenerator}; 8 | 9 | use crate::serialize::SerializableToBytes; 10 | use crate::single::{Keypair, PublicKey}; 11 | 12 | use alloc::vec::Vec; 13 | use digest::DynDigest; 14 | 15 | use ark_ec::Group; 16 | 17 | pub type SchnorrProof = (::Scalar, ::Scalar); 18 | 19 | #[derive(Debug, CanonicalSerialize, CanonicalDeserialize)] 20 | pub struct SchnorrPoP(SchnorrProof); 21 | 22 | impl Clone for SchnorrPoP { 23 | fn clone(&self) -> Self { 24 | SchnorrPoP(self.0) 25 | } 26 | } 27 | 28 | /// Generate Schnorr Signature for an arbitrary message using a key ment to use in BLS scheme 29 | trait BLSSchnorrPoPGenerator: 30 | ProofOfPossessionGenerator, SchnorrPoP> 31 | { 32 | /// Produce a secret witness scalar `k`, aka nonce, from hash of 33 | /// H( H(s) | H(public_key)) because our key does not have the 34 | /// randomness redundacy exists in EdDSA secret key. 35 | fn witness_scalar(&self) -> <::PublicKeyGroup as Group>::ScalarField; 36 | } 37 | 38 | impl BLSSchnorrPoPGenerator for Keypair { 39 | //The pseudo random witness is generated similar to eddsa witness 40 | //hash(secret_key|publick_key) 41 | fn witness_scalar(&self) -> <::PublicKeyGroup as Group>::ScalarField { 42 | let secret_key_as_bytes = self.secret.to_bytes(); 43 | let public_key_as_bytes = ::public_key_point_to_byte(&self.public.0); 44 | 45 | let mut secret_key_hasher = H::default(); 46 | secret_key_hasher.update(secret_key_as_bytes.as_slice()); 47 | let hashed_secret_key = secret_key_hasher.finalize_reset().to_vec(); 48 | 49 | let hasher = as HashToField< 50 | <::PublicKeyGroup as Group>::ScalarField, 51 | >>::new(&[]); 52 | 53 | let scalar_seed = [hashed_secret_key, public_key_as_bytes].concat(); 54 | hasher.hash_to_field(scalar_seed.as_slice(), 1)[0] 55 | } 56 | } 57 | 58 | impl 59 | ProofOfPossessionGenerator, SchnorrPoP> for Keypair 60 | { 61 | //TODO: Message must be equal to public key. 62 | fn generate_pok(&mut self) -> SchnorrPoP { 63 | //First we should figure out the base point in E, I think the secret key trait/struct knows about it. 64 | 65 | //choose random scaler k 66 | //For now we don't we just use a trick similar to Ed25519 67 | //we use hash of concatination of hash the secret key and the public key 68 | 69 | //schnorr equations 70 | 71 | //R = rG. 72 | //k = H(R|M) 73 | //s = k*private_key + r 74 | // publishing (s, R) verifying that (s*G = H(R|M)*Publickey + R => H(R|M)*Publickey + R - s*G = 0) 75 | // so either we need to two into_affine and one curve addition or or two curve additions. 76 | // instead we actually doing H(s*G - H(R|M)*Publickey|M) == H(R|M) == k 77 | // avoiding one curve addition (or two field divisions) in expense of a hash. 78 | let mut r = >::witness_scalar(self); 79 | 80 | let mut r_point = <::PublicKeyGroup as Group>::generator(); 81 | r_point *= r; //todo perhaps we need to mandate E to have a hard coded point 82 | 83 | let r_point_as_bytes = ::public_key_point_to_byte(&r_point); 84 | let public_key_as_bytes = ::public_key_point_to_byte(&self.public.0); //it *must* be the public key (fixed) otherwise secret key can be recovered from the two different proves 85 | 86 | let proof_basis = [r_point_as_bytes, public_key_as_bytes].concat(); 87 | let hasher = as HashToField< 88 | <::PublicKeyGroup as Group>::ScalarField, 89 | >>::new(&[]); 90 | let k = hasher.hash_to_field(proof_basis.as_slice(), 1)[0]; 91 | 92 | let s = (k * self.secret.into_vartime().0) + r; 93 | 94 | ::zeroize::Zeroize::zeroize(&mut r); //clear secret witness from memory 95 | 96 | SchnorrPoP::((s, k)) 97 | } 98 | } 99 | 100 | impl ProofOfPossession> 101 | for SchnorrPoP 102 | { 103 | /// verify the validity of schnoor proof for a given publick key by 104 | /// making sure this is equal to zero 105 | /// H(+s*G - k*Publkey|M) == k 106 | fn verify(&self, public_key_of_prover: &PublicKey) -> bool { 107 | let mut schnorr_point = <::PublicKeyGroup as Group>::generator(); 108 | schnorr_point *= self.0 .0; 109 | let mut k_public_key = public_key_of_prover.0; 110 | k_public_key *= -self.0 .1; 111 | schnorr_point += k_public_key; 112 | 113 | let schnorr_point_as_bytes = ::public_key_point_to_byte(&schnorr_point); 114 | let public_key_as_bytes = 115 | ::public_key_point_to_byte(&public_key_of_prover.0); //it *must* be the public key (fixed) otherwise secret key can be recovered from the two different proves 116 | 117 | let resulting_proof_basis = [schnorr_point_as_bytes, public_key_as_bytes].concat(); 118 | 119 | let hasher = as HashToField< 120 | <::PublicKeyGroup as Group>::ScalarField, 121 | >>::new(&[]); 122 | let random_scalar: E::Scalar = hasher.hash_to_field(resulting_proof_basis.as_slice(), 1)[0]; 123 | random_scalar == self.0 .1 124 | } 125 | } 126 | 127 | #[cfg(all(test, feature = "std"))] 128 | mod tests { 129 | use super::SchnorrPoP; 130 | use crate::engine::ZBLS; 131 | use crate::single::{Keypair, PublicKey}; 132 | use crate::ProofOfPossessionGenerator; 133 | use rand::thread_rng; 134 | use sha2::Sha512; 135 | 136 | #[test] 137 | fn schnorr_bls_pop_sign() { 138 | let mut keypair = Keypair::::generate(thread_rng()); 139 | as ProofOfPossessionGenerator< 140 | ZBLS, 141 | Sha512, 142 | PublicKey, 143 | SchnorrPoP, 144 | >>::generate_pok(&mut keypair); 145 | } 146 | 147 | #[test] 148 | fn schnorr_bls_pop_sign_and_verify() { 149 | use crate::{ProofOfPossession, ProofOfPossessionGenerator}; 150 | 151 | let mut keypair = Keypair::::generate(thread_rng()); 152 | let proof_pair = , 156 | SchnorrPoP, 157 | >>::generate_pok(&mut keypair); 158 | assert!( 159 | ProofOfPossession::>::verify( 160 | &proof_pair, 161 | &keypair.public 162 | ), 163 | "valid pok does not verify" 164 | ); 165 | } 166 | 167 | #[test] 168 | fn schnorr_bls_pop_of_random_public_key_should_fail() { 169 | use crate::{ProofOfPossession, ProofOfPossessionGenerator}; 170 | 171 | let mut keypair_good = Keypair::::generate(thread_rng()); 172 | let proof_pair = , 176 | SchnorrPoP, 177 | >>::generate_pok(&mut keypair_good); 178 | let keypair_bad = Keypair::::generate(thread_rng()); 179 | assert!( 180 | !ProofOfPossession::>::verify( 181 | &proof_pair, 182 | &keypair_bad.public 183 | ), 184 | "invalid pok of unrelated public key should not verify" 185 | ); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/serialize.rs: -------------------------------------------------------------------------------- 1 | use alloc::{vec, vec::Vec}; 2 | use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError}; 3 | 4 | /// Serialization code that is used by multiple modules. 5 | // Note that ark_ff::bytes::ToBytes for projective points export them without converting them to affine 6 | // and so they might leak information about the secret key. 7 | pub trait SerializableToBytes: CanonicalSerialize + CanonicalDeserialize { 8 | const SERIALIZED_BYTES_SIZE: usize; 9 | 10 | fn to_bytes(&self) -> Vec { 11 | let mut serialized_representation: Vec = vec![0; Self::SERIALIZED_BYTES_SIZE]; 12 | self.serialize_compressed(&mut serialized_representation[..]) 13 | .unwrap(); 14 | 15 | return serialized_representation; 16 | } 17 | 18 | fn from_bytes(bytes: &[u8]) -> Result { 19 | Self::deserialize_compressed(bytes) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/single_pop_aggregator.rs: -------------------------------------------------------------------------------- 1 | //! ## Aggregation of BLS signatures using proofs-of-possession 2 | //! 3 | //! In this module, we provide the linear flavor of aggregate 4 | //! BLS signature in which the verifiers has previously checked 5 | //! proofs-of-possession for all public keys. In other words, 6 | //! we simply add up the signatures because the previously checked 7 | //! proofs-of-possession for all signers prevent rogue key attacks. 8 | //! See the security arguments in The Power of Proofs-of-Possession: 9 | //! Securing Multiparty Signatures against Rogue-Key Attacks 10 | //! by Thomas Ristenpart and Scott Yilek at https://eprint.iacr.org/2007/264.pdf 11 | //! 12 | //! These proof-of-possession are simply self-signed certificates, 13 | //! so a BLS signature by each secret key on its own public key. 14 | //! Importantly, the message for this self-signed certificates 15 | //! must uniquely distinguish the public key for which the signature 16 | //! establishes a proof-of-possession. 17 | //! It follows that each proof-of-possession has a unique message, 18 | //! so distinct message aggregation is optimal for verifying them. 19 | //! 20 | //! In this vein, we note that aggregation under proofs-of-possession 21 | //! cannot improve performance when signers sign distinct messages, 22 | //! so proofs-of-possession help with aggregating votes in a concensus 23 | //! protocol, but should never be used for accounts on a block chain. 24 | //! 25 | //! We assume here that users provide their own data structure for 26 | //! proofs-of-poossession. We provide more structure for users who 27 | //! one bit per vote in a concensus protocol: 28 | //! You first verify the proofs-of-possession when building a data 29 | //! structure that holds the voters' keys. You implement the 30 | //! `ProofsOfPossession` trait for this data strtcuture as well, 31 | //! so that the `BitPoPSignedMessage` type provides a signature 32 | //! data type with reasonable sanity checks. 33 | 34 | // Aside about proof-of-possession in the DLOG setting 35 | // https://twitter.com/btcVeg/status/1085490561082183681 36 | 37 | use ark_ff::Zero; 38 | 39 | use super::verifiers::{ 40 | verify_using_aggregated_auxiliary_public_keys, verify_with_distinct_messages, 41 | }; 42 | use super::*; 43 | 44 | use digest::DynDigest; 45 | 46 | /// Batch or aggregate BLS signatures with attached messages and 47 | /// signers, for whom we previously checked proofs-of-possession. 48 | /// 49 | /// In this type, we provide a high-risk low-level batching and 50 | /// aggregation mechanism that merely adds up signatures under the 51 | /// assumption that all required proofs-of-possession were previously 52 | /// checked. 53 | /// 54 | /// We say a signing key has provided a proof-of-possession if the 55 | /// verifier remembers having checked some self-signed certificate 56 | /// by that key. It's insecure to use this aggregation strategy 57 | /// without first cehcking proofs-of-possession. In particular 58 | /// it is insecure to use this aggregation strategy when checking 59 | /// proofs-of-possession, and could not improve performance anyways. 60 | /// Distinct message aggregation is always optimal for checking 61 | /// proofs-of-possession. Please see the module level doumentation 62 | /// for additional discussion and notes on security. 63 | /// 64 | /// We foresee this type primarily being used to batch several 65 | /// `BitPoPSignedMessage`s into one verification. We do not track 66 | /// aggreggated public keys here, instead merging multiples signers 67 | /// public keys anytime they sign the same message, so this type 68 | /// essentially provides only fast batch verificartion. 69 | /// In principle, our `add_*` methods suffice for building an actual 70 | /// aggregate signature type. Yet, normally direct approaches like 71 | /// `BitPoPSignedMessage` work better for aggregation because 72 | /// the `ProofsOfPossession` trait tooling permits both enforce the 73 | /// proofs-of-possession and provide a compact serialization. 74 | /// We see no reason to support serialization for this type as present. 75 | /// message assumptions, or other aggre 76 | /// 77 | /// In principle, one might combine proof-of-possession with distinct 78 | /// message assumptions, or other aggregation strategies, when 79 | /// verifiers have only observed a subset of the proofs-of-possession, 80 | /// but this sounds complex or worse fragile. 81 | /// 82 | /// TODO: Implement gaussian elimination verification scheme. 83 | use core::iter::once; 84 | 85 | use double::PublicKeyInSignatureGroup; 86 | use single::PublicKey; 87 | 88 | #[derive(Clone)] 89 | pub struct SignatureAggregatorAssumingPoP { 90 | message: Message, 91 | aggregated_publickey: PublicKey, 92 | signature: Signature, 93 | aggregated_auxiliary_public_key: PublicKeyInSignatureGroup, 94 | } 95 | 96 | impl SignatureAggregatorAssumingPoP { 97 | pub fn new(message: Message) -> SignatureAggregatorAssumingPoP { 98 | SignatureAggregatorAssumingPoP { 99 | message: message, 100 | aggregated_publickey: PublicKey(E::PublicKeyGroup::zero()), 101 | signature: Signature(E::SignatureGroup::zero()), 102 | aggregated_auxiliary_public_key: PublicKeyInSignatureGroup(E::SignatureGroup::zero()), 103 | } 104 | } 105 | 106 | /// Add only a `Signature` to our internal signature. 107 | /// 108 | /// Useful for constructing an aggregate signature, but we 109 | pub fn add_signature(&mut self, signature: &Signature) { 110 | self.signature.0 += &signature.0; 111 | } 112 | 113 | /// Add only a `PublicKey` to our internal data. 114 | /// 115 | /// Useful for constructing an aggregate signature, but we 116 | /// recommend instead using a custom types like `BitPoPSignedMessage`. 117 | pub fn add_publickey(&mut self, publickey: &PublicKey) { 118 | self.aggregated_publickey.0 += publickey.0; 119 | } 120 | 121 | /// Aggregate the auxiliary public keys in the signature group to be used verification using aux key 122 | pub fn add_auxiliary_public_key( 123 | &mut self, 124 | publickey_in_signature_group: &PublicKeyInSignatureGroup, 125 | ) { 126 | self.aggregated_auxiliary_public_key.0 += publickey_in_signature_group.0; 127 | } 128 | 129 | /// Returns the aggergated public key. 130 | /// 131 | pub fn aggregated_publickey(&self) -> PublicKey { 132 | self.aggregated_publickey 133 | } 134 | 135 | // /// Aggregage BLS signatures assuming they have proofs-of-possession 136 | // /// TODO this function should return Result refusing to aggregate messages 137 | // /// different than the message the aggregator is initiated at 138 | // pub fn aggregate<'a,S>(&mut self, signed: &'a S) 139 | // where 140 | // &'a S: Signed, 141 | // <&'a S as Signed>::PKG: Borrow>, 142 | // { 143 | // let signature = signed.signature(); 144 | // for (message,pubickey) in signed.messages_and_publickeys() { 145 | // self.add_message_n_publickey(message.borrow(),pubickey.borrow()); 146 | // } 147 | // self.add_signature(&signature); 148 | // } 149 | 150 | pub fn verify_using_aggregated_auxiliary_public_keys< 151 | RandomOracle: DynDigest + Default + Clone, 152 | >( 153 | &self, 154 | ) -> bool { 155 | verify_using_aggregated_auxiliary_public_keys::( 156 | self, 157 | true, 158 | self.aggregated_auxiliary_public_key.0, 159 | ) 160 | } 161 | } 162 | 163 | impl<'a, E: EngineBLS> Signed for &'a SignatureAggregatorAssumingPoP { 164 | type E = E; 165 | 166 | type M = Message; 167 | type PKG = PublicKey; 168 | type PKnM = ::core::iter::Once<(Message, PublicKey)>; 169 | 170 | fn messages_and_publickeys(self) -> Self::PKnM { 171 | once((self.message.clone(), self.aggregated_publickey)) // TODO: Avoid clone 172 | } 173 | 174 | fn signature(&self) -> Signature { 175 | self.signature 176 | } 177 | 178 | fn verify(self) -> bool { 179 | // We have already aggregated distinct messages, so our distinct 180 | // message verification code provides reasonable optimizations, 181 | // except the public keys might not be normalized here. 182 | // We foresee verification via gaussian elimination being faster, 183 | // but requires affine keys or normalization. 184 | verify_with_distinct_messages(self, true) 185 | // TODO: verify_with_gaussian_elimination(self) 186 | } 187 | } 188 | 189 | #[cfg(all(test, feature = "std"))] 190 | mod tests { 191 | 192 | use crate::EngineBLS; 193 | use crate::Keypair; 194 | use crate::Message; 195 | use crate::TinyBLS; 196 | use crate::UsualBLS; 197 | use rand::thread_rng; 198 | use sha2::Sha256; 199 | 200 | use ark_bls12_377::Bls12_377; 201 | use ark_bls12_381::Bls12_381; 202 | 203 | use super::*; 204 | 205 | #[test] 206 | fn verify_aggregate_single_message_single_signer() { 207 | let good = Message::new(b"ctx", b"test message"); 208 | 209 | let mut keypair = 210 | Keypair::>::generate(thread_rng()); 211 | let good_sig0 = keypair.sign(&good); 212 | assert!(good_sig0.verify(&good, &keypair.public)); 213 | } 214 | 215 | #[test] 216 | fn verify_aggregate_single_message_multi_signers() { 217 | let good = Message::new(b"ctx", b"test message"); 218 | 219 | let mut keypair0 = 220 | Keypair::>::generate(thread_rng()); 221 | let good_sig0 = keypair0.sign(&good); 222 | 223 | let mut keypair1 = 224 | Keypair::>::generate(thread_rng()); 225 | let good_sig1 = keypair1.sign(&good); 226 | 227 | let mut aggregated_sigs = 228 | SignatureAggregatorAssumingPoP::>::new(good); 229 | aggregated_sigs.add_signature(&good_sig0); 230 | aggregated_sigs.add_signature(&good_sig1); 231 | 232 | aggregated_sigs.add_publickey(&keypair0.public); 233 | aggregated_sigs.add_publickey(&keypair1.public); 234 | 235 | assert!( 236 | aggregated_sigs.verify() == true, 237 | "good aggregated signature of a single message with multiple key does not verify" 238 | ); 239 | } 240 | 241 | #[test] 242 | fn verify_aggregate_single_message_repetative_signers() { 243 | let good = Message::new(b"ctx", b"test message"); 244 | 245 | let mut keypair = 246 | Keypair::>::generate(thread_rng()); 247 | let good_sig = keypair.sign(&good); 248 | 249 | let mut aggregated_sigs = 250 | SignatureAggregatorAssumingPoP::>::new(good); 251 | aggregated_sigs.add_signature(&good_sig); 252 | aggregated_sigs.add_signature(&good_sig); 253 | 254 | aggregated_sigs.add_publickey(&keypair.public); 255 | aggregated_sigs.add_publickey(&keypair.public); 256 | 257 | assert!( 258 | aggregated_sigs.verify() == true, 259 | "good aggregate of a repetitive signature does not verify" 260 | ); 261 | } 262 | 263 | #[test] 264 | fn aggregate_of_signature_of_a_wrong_message_should_not_verify() { 265 | let good0 = Message::new(b"ctx", b"Space over Tab"); 266 | let bad1 = Message::new(b"ctx", b"Tab over Space"); 267 | 268 | let mut keypair0 = 269 | Keypair::>::generate(thread_rng()); 270 | let good_sig0 = keypair0.sign(&good0); 271 | 272 | let mut keypair1 = 273 | Keypair::>::generate(thread_rng()); 274 | let bad_sig1 = keypair1.sign(&bad1); 275 | 276 | let mut aggregated_sigs = SignatureAggregatorAssumingPoP::< 277 | UsualBLS, 278 | >::new(good0); 279 | aggregated_sigs.add_signature(&good_sig0); 280 | aggregated_sigs.add_signature(&bad_sig1); 281 | 282 | aggregated_sigs.add_publickey(&keypair0.public); 283 | aggregated_sigs.add_publickey(&keypair1.public); 284 | 285 | assert!( 286 | aggregated_sigs.verify() == false, 287 | "aggregated signature of a wrong message should not verify" 288 | ); 289 | } 290 | 291 | #[test] 292 | fn test_aggregate_tiny_sigs_and_verify_in_g1() { 293 | let message = Message::new(b"ctx", b"test message"); 294 | let mut keypairs: Vec<_> = (0..3) 295 | .into_iter() 296 | .map(|_| Keypair::>::generate(thread_rng())) 297 | .collect(); 298 | let pub_keys_in_sig_grp: Vec> = keypairs 299 | .iter() 300 | .map(|k| k.into_public_key_in_signature_group()) 301 | .collect(); 302 | 303 | let mut aggregator = SignatureAggregatorAssumingPoP::::new(message.clone()); 304 | let mut aggregated_public_key = 305 | PublicKey::(::PublicKeyGroup::zero()); 306 | 307 | for k in &mut keypairs { 308 | aggregator.add_signature(&k.sign(&message)); 309 | aggregated_public_key.0 += k.public.0; 310 | } 311 | 312 | let mut verifier_aggregator = SignatureAggregatorAssumingPoP::::new(message); 313 | 314 | verifier_aggregator.add_signature(&aggregator.signature); 315 | verifier_aggregator.add_publickey(&aggregated_public_key); 316 | 317 | for k in &pub_keys_in_sig_grp { 318 | verifier_aggregator.add_auxiliary_public_key(k); 319 | } 320 | 321 | assert!( 322 | verifier_aggregator.verify_using_aggregated_auxiliary_public_keys::(), 323 | "verifying with honest auxilary public key should pass" 324 | ); 325 | 326 | //false aggregation in signature group should fails verification. 327 | verifier_aggregator 328 | .add_auxiliary_public_key(&keypairs[0].into_public_key_in_signature_group()); 329 | assert!( 330 | !verifier_aggregator.verify_using_aggregated_auxiliary_public_keys::(), 331 | "verification using non-matching auxilary public key should fail" 332 | ); 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /src/verifiers.rs: -------------------------------------------------------------------------------- 1 | //! ## Algorithms for optimized verification of aggregate and batched BLS signatures. 2 | //! 3 | //! 4 | //! 5 | 6 | use core::borrow::Borrow; 7 | #[cfg(feature = "std")] 8 | use std::collections::HashMap; 9 | 10 | #[cfg(feature = "std")] 11 | use ark_ec::AffineRepr; 12 | #[cfg(feature = "std")] 13 | use ark_ff::field_hashers::{DefaultFieldHasher, HashToField}; 14 | #[cfg(feature = "std")] 15 | use ark_serialize::CanonicalSerialize; 16 | #[cfg(feature = "std")] 17 | use digest::DynDigest; 18 | 19 | use ark_ec::CurveGroup; 20 | 21 | use alloc::vec::Vec; 22 | 23 | use super::*; 24 | 25 | // We define these convenience type alias here instead of engine.rs 26 | // because seemingly only verifier implementations really employ them. 27 | // And we `pub use engine::*` in lib.rs. 28 | 29 | /// Convenience type alias for projective form of `PublicKeyGroup` 30 | pub type PublicKeyProjective = ::PublicKeyGroup; 31 | 32 | /// Convenience type alias for affine form of `PublicKeyGroup` 33 | pub type PublicKeyAffine = <::PublicKeyGroup as CurveGroup>::Affine; 34 | 35 | /// Convenience type alias for projective form of `SignatureGroup` 36 | pub type SignatureProjective = ::SignatureGroup; 37 | 38 | /// Convenience type alias for affine form of `SignatureGroup` 39 | pub type SignatureAffine = <::SignatureGroup as CurveGroup>::Affine; 40 | 41 | /// Simple unoptimized BLS signature verification. Useful for testing. 42 | pub fn verify_unoptimized(s: S) -> bool { 43 | let signature = S::E::prepare_signature(s.signature().0); 44 | let prepared = s 45 | .messages_and_publickeys() 46 | .map(|(message, public_key)| { 47 | ( 48 | S::E::prepare_public_key(public_key.borrow().0), 49 | S::E::prepare_signature(message.borrow().hash_to_signature_curve::()), 50 | ) 51 | }) 52 | .collect::>(); 53 | S::E::verify_prepared(signature, prepared.iter()) 54 | } 55 | 56 | /// Simple universal BLS signature verification 57 | /// 58 | /// We support an unstable `Signed::messages_and_publickeys()` 59 | /// securely by calling it only once and batch normalizing all 60 | /// points, as do most other verification routines here. 61 | /// We do no optimizations that reduce the number of pairings 62 | /// by combining repeated messages or signers. 63 | pub fn verify_simple(s: S) -> bool { 64 | let signature = s.signature().0; 65 | // We could write this more idiomatically using iterator adaptors, 66 | // and avoiding an unecessary allocation for publickeys, but only 67 | // by calling self.messages_and_publickeys() repeatedly. 68 | let itr = s.messages_and_publickeys(); 69 | let l = { 70 | let (lower, upper) = itr.size_hint(); 71 | upper.unwrap_or(lower) 72 | }; 73 | let mut gpk = Vec::with_capacity(l); 74 | let mut gms = Vec::with_capacity(l + 1); 75 | for (message, publickey) in itr { 76 | gpk.push(publickey.borrow().0.clone()); 77 | gms.push(message.borrow().hash_to_signature_curve::()); 78 | } 79 | let gpk = <::E as EngineBLS>::PublicKeyGroup::normalize_batch(gpk.as_mut_slice()); 80 | gms.push(signature); 81 | let mut gms = 82 | <::E as EngineBLS>::SignatureGroup::normalize_batch(gms.as_mut_slice()); 83 | let signature = <::E as EngineBLS>::prepare_signature(gms.pop().unwrap()); 84 | let prepared = gpk 85 | .iter() 86 | .zip(gms) 87 | .map(|(pk, m)| { 88 | ( 89 | <::E as EngineBLS>::prepare_public_key(*pk), 90 | <::E as EngineBLS>::prepare_signature(m), 91 | ) 92 | }) 93 | .collect::>(); 94 | S::E::verify_prepared(signature, prepared.iter()) 95 | } 96 | 97 | /// BLS signature verification optimized for all unique messages 98 | /// 99 | /// Assuming all messages are distinct, the minimum number of pairings 100 | /// is the number of unique signers, which we achieve here. 101 | /// We do not verify message uniqueness here, but leave this to the 102 | /// aggregate signature type, like `DistinctMessages`. 103 | /// 104 | /// We merge any messages with identical signers and batch normalize 105 | /// message points and the signature itself. 106 | /// We optionally batch normalize the public keys in the event that 107 | /// they are provided by algerbaic operaations, but this sounds 108 | /// unlikely given our requirement that messages be distinct. 109 | #[cfg(feature = "std")] 110 | pub fn verify_with_distinct_messages(signed: S, normalize_public_keys: bool) -> bool { 111 | let signature = signed.signature().0; 112 | // We first hash the messages to the signature curve and 113 | // normalize the public keys to operate on them as bytes. 114 | // TODO: Assess if we should mutate in place using interior 115 | // mutability, maybe using `BorrowMut` support in 116 | // `batch_normalization`. 117 | let itr = signed.messages_and_publickeys(); 118 | let l = { 119 | let (lower, upper) = itr.size_hint(); 120 | upper.unwrap_or(lower) 121 | }; 122 | let mut publickeys = Vec::with_capacity(l); 123 | let mut messages = Vec::with_capacity(l + 1); 124 | for (m, pk) in itr { 125 | publickeys.push(pk.borrow().0.clone()); 126 | messages.push(m.borrow().hash_to_signature_curve::()); 127 | } 128 | let mut affine_publickeys = if normalize_public_keys { 129 | <::E as EngineBLS>::PublicKeyGroup::normalize_batch(&publickeys) 130 | } else { 131 | publickeys 132 | .iter() 133 | .map(|pk| pk.into_affine()) 134 | .collect::>() 135 | }; 136 | 137 | // We next accumulate message points with the same signer. 138 | // We could avoid the allocation here if we sorted both 139 | // arrays in parallel. This might mean (a) some sort function 140 | // using `ops::IndexMut` instead of slices, and (b) wrapper types 141 | // types to make tuples of slices satisfy `ops::IndexMut`. 142 | // TODO: Impl PartialEq, Eq, Hash for pairing::EncodedPoint 143 | // to avoid struct H(E::PublicKeyGroup::Affine::Uncompressed); 144 | type AA = (PublicKeyAffine, SignatureProjective); 145 | let mut pks_n_ms = HashMap::with_capacity(l); 146 | for (pk, m) in affine_publickeys.drain(..).zip(messages.drain(..)) { 147 | let mut pk_uncompressed = vec![0; pk.uncompressed_size()]; 148 | pk.serialize_uncompressed(&mut pk_uncompressed[..]).unwrap(); 149 | pks_n_ms 150 | .entry(pk_uncompressed) 151 | .and_modify(|(_pk0, m0): &mut AA| { 152 | *m0 += m; 153 | }) 154 | .or_insert((pk, m)); 155 | } 156 | 157 | let mut publickeys = Vec::with_capacity(l); 158 | for (_, (pk, m)) in pks_n_ms.drain() { 159 | messages.push(m); 160 | publickeys.push(pk.clone()); 161 | } 162 | 163 | // We finally normalize the messages and signature 164 | 165 | messages.push(signature); 166 | let mut affine_messages = 167 | <::E as EngineBLS>::SignatureGroup::normalize_batch(messages.as_mut_slice()); 168 | let signature = 169 | <::E as EngineBLS>::prepare_signature(affine_messages.pop().unwrap()); 170 | // TODO: Assess if we could cache normalized message hashes anyplace 171 | // using interior mutability, but probably this does not work well 172 | // with ur optimization of collecting messages with thesame signer. 173 | 174 | // And verify the aggregate signature. 175 | let prepared = publickeys 176 | .iter() 177 | .zip(messages) 178 | .map(|(pk, m)| { 179 | ( 180 | <::E as EngineBLS>::prepare_public_key(*pk), 181 | <::E as EngineBLS>::prepare_signature(m), 182 | ) 183 | }) 184 | .collect::>(); 185 | 186 | S::E::verify_prepared(signature, prepared.iter()) 187 | 188 | //let prepared = publickeys.iter().zip(&messages); 189 | //S::E::verify_prepared( &signature, prepared ) 190 | } 191 | 192 | /// BLS signature verification optimized for all unique messages 193 | /// 194 | /// Assuming all messages are distinct, the minimum number of pairings 195 | /// is the number of unique signers, which we achieve here. 196 | /// We do not verify message uniqueness here, but leave this to the 197 | /// aggregate signature type, like `DistinctMessages`. 198 | /// 199 | /// We merge any messages with identical signers and batch normalize 200 | /// message points and the signature itself. 201 | /// We optionally batch normalize the public keys in the event that 202 | /// they are provided by algerbaic operaations, but this sounds 203 | /// unlikely given our requirement that messages be distinct. 204 | #[cfg(feature = "std")] 205 | pub fn verify_using_aggregated_auxiliary_public_keys< 206 | E: EngineBLS, 207 | H: DynDigest + Default + Clone, 208 | >( 209 | signed: &single_pop_aggregator::SignatureAggregatorAssumingPoP, 210 | normalize_public_keys: bool, 211 | aggregated_aux_pub_key: ::SignatureGroup, 212 | ) -> bool { 213 | let signature = Signed::signature(&signed).0; 214 | 215 | let mut signature_as_bytes = vec![0; signature.compressed_size()]; 216 | signature 217 | .serialize_compressed(&mut signature_as_bytes[..]) 218 | .expect("compressed size has been alocated"); 219 | 220 | let itr = signed.messages_and_publickeys(); 221 | let l = { 222 | let (lower, upper) = itr.size_hint(); 223 | upper.unwrap_or(lower) 224 | }; 225 | let (first_message, first_public_key) = match signed.messages_and_publickeys().next() { 226 | Some((first_message, first_public_key)) => (first_message, first_public_key), 227 | None => return false, 228 | }; 229 | 230 | let mut first_public_key_as_bytes = vec![0; first_public_key.compressed_size()]; 231 | first_public_key 232 | .serialize_compressed(&mut first_public_key_as_bytes[..]) 233 | .expect("compressed size has been alocated"); 234 | 235 | let first_message_point = first_message.hash_to_signature_curve::(); 236 | let first_message_point_as_bytes = E::signature_point_to_byte(&first_message_point); 237 | 238 | let mut aggregated_aux_pub_key_as_bytes = vec![0; aggregated_aux_pub_key.compressed_size()]; 239 | aggregated_aux_pub_key 240 | .serialize_compressed(&mut aggregated_aux_pub_key_as_bytes[..]) 241 | .expect("compressed size has been alocated"); 242 | 243 | // We first hash the messages to the signature curve and 244 | // normalize the public keys to operate on them as bytes. 245 | // TODO: Assess if we should mutate in place using interior 246 | // mutability, maybe using `BorrowMut` support in 247 | // `batch_normalization`. 248 | 249 | // deterministic randomness for adding aggregated auxiliary pub keys 250 | //TODO you can't just assume that there is one pubickey you need to stop if they were more or aggregate them 251 | 252 | let pseudo_random_scalar_seed = [ 253 | first_message_point_as_bytes, 254 | first_public_key_as_bytes, 255 | aggregated_aux_pub_key_as_bytes, 256 | signature_as_bytes, 257 | ] 258 | .concat(); 259 | 260 | let hasher = as HashToField>::new(&[]); 261 | let pseudo_random_scalar: E::Scalar = 262 | hasher.hash_to_field(&pseudo_random_scalar_seed[..], 1)[0]; 263 | 264 | let signature = signature + aggregated_aux_pub_key * pseudo_random_scalar; 265 | 266 | let mut publickeys = Vec::with_capacity(l); 267 | let mut messages = Vec::with_capacity(l + 1); 268 | 269 | //Simplify from here on. 270 | for (m, pk) in itr { 271 | publickeys.push(pk.0.clone()); 272 | messages.push( 273 | m.hash_to_signature_curve::() 274 | + E::SignatureGroupAffine::generator() * pseudo_random_scalar, 275 | ); 276 | } 277 | 278 | let mut affine_publickeys = if normalize_public_keys { 279 | ::PublicKeyGroup::normalize_batch(&publickeys) 280 | } else { 281 | publickeys 282 | .iter() 283 | .map(|pk| pk.into_affine()) 284 | .collect::>() 285 | }; 286 | 287 | // We next accumulate message points with the same signer. 288 | // We could avoid the allocation here if we sorted both 289 | // arrays in parallel. This might mean (a) some sort function 290 | // using `ops::IndexMut` instead of slices, and (b) wrapper types 291 | // types to make tuples of slices satisfy `ops::IndexMut`. 292 | // TODO: Impl PartialEq, Eq, Hash for pairing::EncodedPoint 293 | // to avoid struct H(E::PublicKeyGroup::Affine::Uncompressed); 294 | type AA = (PublicKeyAffine, SignatureProjective); 295 | let mut pks_n_ms = HashMap::with_capacity(l); 296 | for (pk, m) in affine_publickeys.drain(..).zip(messages.drain(..)) { 297 | let mut pk_uncompressed = vec![0; pk.uncompressed_size()]; 298 | pk.serialize_uncompressed(&mut pk_uncompressed[..]).unwrap(); 299 | pks_n_ms 300 | .entry(pk_uncompressed) 301 | .and_modify(|(_pk0, m0): &mut AA| { 302 | *m0 += m; 303 | }) 304 | .or_insert((pk, m)); 305 | } 306 | 307 | let mut publickeys = Vec::with_capacity(l); 308 | for (_, (pk, m)) in pks_n_ms.drain() { 309 | messages.push(m); 310 | publickeys.push(pk.clone()); 311 | } 312 | 313 | // We finally normalize the messages and signature 314 | 315 | messages.push(signature); 316 | let mut affine_messages = 317 | ::SignatureGroup::normalize_batch(messages.as_mut_slice()); 318 | let signature = ::prepare_signature(affine_messages.pop().unwrap()); 319 | // TODO: Assess if we could cache normalized message hashes anyplace 320 | // using interior mutability, but probably this does not work well 321 | // with ur optimization of collecting messages with thesame signer. 322 | 323 | // And verify the aggregate signature. 324 | let prepared = publickeys 325 | .iter() 326 | .zip(messages) 327 | .map(|(pk, m)| { 328 | ( 329 | ::prepare_public_key(*pk), 330 | ::prepare_signature(m), 331 | ) 332 | }) 333 | .collect::>(); 334 | 335 | E::verify_prepared(signature, prepared.iter()) 336 | 337 | //let prepared = publickeys.iter().zip(&messages); 338 | //S::E::verify_prepared( &signature, prepared ) 339 | } 340 | 341 | /* 342 | /// Excessively optimized BLS signature verification 343 | /// 344 | /// We minimize the number of pairing operations by doing two 345 | /// basis change operation using Gaussian elimination, first in the 346 | /// message space and then in the signer space. As a result, we 347 | /// do only `1 + min(msg_d,pk_d)` pairings where `msg_d` and `pk_d` 348 | /// are the numbers of distinct messages and signers, respectively. 349 | /// 350 | /// We expect this to improve performance dramatically when both 351 | /// signers and messages are repeated enough, simpler strategies 352 | /// work as well or better when say messages are distinct. 353 | /// 354 | /// Explination: 355 | /// 356 | /// We consider the bipartite graph with vertex sets given by points 357 | /// on the two curves and edges given by desired pairings between them. 358 | /// We let $M$ denote the bipartite adjacency matrix for this graph, 359 | /// so that multiplying $M$ on the the right and left by the vectors 360 | /// of messages and signers respectively reproduces our original sum 361 | /// of pairings. 362 | /// 363 | /// We first use elementary "row" operations to make $M$ upper 364 | /// triangular, as in Gaussian elimination, but at the cost of also 365 | /// performing one-sided "change of basis" operations that collect 366 | /// our original "basis vectors" into sums of curve points. 367 | /// We next use elementary "column" operations to make $M$ diagonal, 368 | /// again adjusting the basis with curve point operations. 369 | /// 370 | /// In this, we regard $M$ as a matrix over the scalar field $F_p$ 371 | /// so we may do row or column swaps and row or column addition 372 | /// operations with small scalars, but not lone row or column scalar 373 | /// multiplication because these always involve divisions, which 374 | /// produces large curve points that slow us down thereafter. 375 | /// We do not require such divisions because we do not solve any 376 | /// system of equations and do not need ones on the diagonal. 377 | /// 378 | /// TODO: 379 | /// We leave implementing this optimization to near future work 380 | /// because it benifits from public keys being affine or having 381 | /// another hashable representation. 382 | /// 383 | /// 384 | /// As a curiosity, we note one interesting but suboptimal algorithm 385 | /// that avoids small scalar multiplications when doing this: 386 | /// 387 | /// If we ignore subtraction, then the minimal number of pairing 388 | /// operations required to verify aggregated BLS signatures is the 389 | /// minimal bipartite edge cover, aka bipartite dimension, of the 390 | /// bipartite graph with vertices given by points on the two curves 391 | /// and edges given by desired pairings. 392 | /// In general, this problem is NP-hard even to approximate. 393 | /// See: https://en.wikipedia.org/wiki/Bipartite_dimension 394 | /// 395 | /// There are polynomial time algorithms for bipartite edge cover in 396 | /// special cases, with domino-free graphs being among the widest 397 | /// known classes. See: 398 | /// Amilhastre, Jérôme; Janssen, Philippe; Vilarem, Marie-Catherine, 399 | /// "Computing a minimum biclique cover is polynomial for bipartite domino-free graphs" (1997) 400 | /// https://core.ac.uk/download/pdf/82546650.pdf 401 | /// 402 | /// If we now exploit subtraction, then these dominos can be 403 | /// completed into $K_{3,3}$s, like 404 | /// $(a,x)+(a,y)+(b,x)+(b,y)+(b,z)+(c,y)+(c,z) = (a+b+c,x+y+z) - (a,z) - (c,z)$ 405 | /// which looks optimal for itself, and likely permits the further 406 | /// aggregation, and maybe the subtracted terms can be aggregated later. 407 | /// 408 | /// We could not however find the optimal numbers of pairings by 409 | /// completing dominos like this because (a+b+c,x+y+z) - (b,y), 410 | /// which looks optimal for itself, but only has one subtraction. 411 | fn verify_with_gaussian_elimination(s: S) -> bool { 412 | unimplemented!() 413 | } 414 | 415 | */ 416 | --------------------------------------------------------------------------------