├── .gitignore ├── .travis.yml ├── Cargo.toml ├── README.md └── src └── lib.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 "] 3 | description = "BLS signatures" 4 | documentation = "https://github.com/str4d/bls" 5 | homepage = "https://github.com/str4d/bls" 6 | license = "MIT/Apache-2.0" 7 | name = "bls" 8 | repository = "https://github.com/str4d/bls" 9 | version = "0.0.1" 10 | edition = "2021" 11 | rust-version = "1.56" 12 | 13 | [dependencies] 14 | digest = "0.9" 15 | pairing = "0.23" 16 | rand_core = "0.6" 17 | 18 | [dev-dependencies] 19 | blake3 = { version = "1, <1.3", features = ["traits-preview"] } 20 | bls12_381 = { version = "0.8", features = ["experimental"] } 21 | rand = "0.8" 22 | rand_chacha = "0.3" 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bls [![Crates.io](https://img.shields.io/crates/v/bls.svg)](https://crates.io/crates/bls) # 2 | 3 | This is a Rust crate for making BLS (Boneh-Lynn-Shacham) signatures. It currently supports the [BLS12-381](https://z.cash/blog/new-snark-curve.html) (Barreto-Lynn-Scott) (yes, I know) construction. 4 | 5 | ## Documentation 6 | 7 | Bring the `bls` crate into your project just as you normally would. 8 | 9 | ```rust 10 | use bls::Keypair; 11 | use pairing::bls12_381::Bls12; 12 | 13 | let keypair = Keypair::::generate(&mut rng); 14 | let message = "Some message"; 15 | let sig = keypair.sign(&message.as_bytes()); 16 | assert_eq!(keypair.verify(&message.as_bytes(), &sig), true); 17 | ``` 18 | 19 | ### Aggregate signatures 20 | 21 | ```rust 22 | use bls::{AggregateSignature, Keypair}; 23 | use pairing::bls12_381::Bls12; 24 | 25 | let mut inputs = Vec::new(); 26 | let mut asig = AggregateSignature::new(); 27 | 28 | let keypair1 = Keypair::::generate(&mut rng); 29 | let message1 = "Some unique message"; 30 | let sig1 = keypair1.sign(&message1.as_bytes()); 31 | inputs.push((keypair1.public, message1)); 32 | asig.aggregate(&sig1); 33 | 34 | let keypair2 = Keypair::::generate(&mut rng); 35 | let message2 = "Some other unique message"; 36 | let sig2 = keypair2.sign(&message2.as_bytes()); 37 | inputs.push((keypair2.public, message2)); 38 | asig.aggregate(&sig2); 39 | 40 | assert_eq!( 41 | asig.verify(&inputs.iter() 42 | .map(|&(ref pk, ref m)| (pk, m.as_bytes())) 43 | .collect()), 44 | true 45 | ); 46 | ``` 47 | 48 | ## Security Warnings 49 | 50 | This library does not make any guarantees about constant-time operations, memory access patterns, or resistance to side-channel attacks. 51 | 52 | ## License 53 | 54 | Licensed under either of 55 | 56 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 57 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 58 | 59 | at your option. 60 | 61 | ### Contribution 62 | 63 | Unless you explicitly state otherwise, any contribution intentionally 64 | submitted for inclusion in the work by you, as defined in the Apache-2.0 65 | license, shall be dual licensed as above, without any additional terms or 66 | conditions. 67 | 68 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use pairing::{ 2 | group::{ff::Field, prime::PrimeCurveAffine, Group}, 3 | Engine, 4 | }; 5 | use rand_core::RngCore; 6 | use std::collections::HashSet; 7 | 8 | pub trait BlsEngine: Engine { 9 | fn hash_message(message: &[u8]) -> Self::G1Affine; 10 | } 11 | 12 | pub struct Signature { 13 | s: E::G1Affine, 14 | } 15 | 16 | pub struct SecretKey { 17 | x: E::Fr, 18 | } 19 | 20 | impl SecretKey { 21 | pub fn generate(csprng: &mut R) -> Self { 22 | SecretKey { 23 | x: E::Fr::random(csprng), 24 | } 25 | } 26 | 27 | pub fn sign(&self, message: &[u8]) -> Signature { 28 | let h = E::hash_message(message); 29 | Signature { 30 | s: (h * self.x).into(), 31 | } 32 | } 33 | } 34 | 35 | pub struct PublicKey { 36 | p_pub: E::G2Affine, 37 | } 38 | 39 | impl PublicKey { 40 | pub fn from_secret(secret: &SecretKey) -> Self { 41 | // TODO Decide on projective vs affine 42 | PublicKey { 43 | p_pub: (E::G2Affine::generator() * secret.x).into(), 44 | } 45 | } 46 | 47 | pub fn verify(&self, message: &[u8], signature: &Signature) -> bool { 48 | let h = E::hash_message(message); 49 | let lhs = E::pairing(&signature.s, &E::G2Affine::generator()); 50 | let rhs = E::pairing(&h, &self.p_pub); 51 | lhs == rhs 52 | } 53 | } 54 | 55 | pub struct Keypair { 56 | pub secret: SecretKey, 57 | pub public: PublicKey, 58 | } 59 | 60 | impl Keypair { 61 | pub fn generate(csprng: &mut R) -> Self { 62 | let secret = SecretKey::generate(csprng); 63 | let public = PublicKey::from_secret(&secret); 64 | Keypair { secret, public } 65 | } 66 | 67 | pub fn sign(&self, message: &[u8]) -> Signature { 68 | self.secret.sign(message) 69 | } 70 | 71 | pub fn verify(&self, message: &[u8], signature: &Signature) -> bool { 72 | self.public.verify(message, signature) 73 | } 74 | } 75 | 76 | pub struct AggregateSignature(Signature); 77 | 78 | impl AggregateSignature { 79 | pub fn new() -> Self { 80 | AggregateSignature(Signature { 81 | s: E::G1Affine::identity(), 82 | }) 83 | } 84 | 85 | pub fn from_signatures(sigs: &Vec>) -> Self { 86 | let mut s = Self::new(); 87 | for sig in sigs { 88 | s.aggregate(sig); 89 | } 90 | s 91 | } 92 | 93 | pub fn aggregate(&mut self, sig: &Signature) { 94 | self.0.s = (self.0.s.to_curve() + sig.s).into(); 95 | } 96 | 97 | pub fn verify(&self, inputs: &Vec<(&PublicKey, &[u8])>) -> bool { 98 | // Messages must be distinct 99 | let messages: HashSet<&[u8]> = inputs.iter().map(|&(_, m)| m).collect(); 100 | if messages.len() != inputs.len() { 101 | return false; 102 | } 103 | // Check pairings 104 | let lhs = E::pairing(&self.0.s, &E::G2Affine::generator()); 105 | let mut rhs = E::Gt::identity(); 106 | for input in inputs { 107 | let h = E::hash_message(input.1); 108 | rhs += E::pairing(&h, &input.0.p_pub); 109 | } 110 | lhs == rhs 111 | } 112 | } 113 | 114 | #[cfg(test)] 115 | mod tests { 116 | use super::*; 117 | 118 | use bls12_381::{ 119 | hash_to_curve::{ExpandMsgXmd, HashToCurve}, 120 | Bls12, 121 | }; 122 | use rand::SeedableRng; 123 | use rand_chacha::ChaChaRng; 124 | 125 | impl BlsEngine for Bls12 { 126 | fn hash_message(message: &[u8]) -> Self::G1Affine { 127 | >>::hash_to_curve( 128 | message, 129 | b"BLSSignatureSeed", 130 | ) 131 | .into() 132 | } 133 | } 134 | 135 | #[test] 136 | fn sign_verify() { 137 | let mut rng = ChaChaRng::from_seed([ 138 | 0x4f, 0x6d, 0x44, 0xbc, 0x2f, 0x27, 0x6c, 0xd6, 0x63, 0xaf, 0xd0, 0xb9, 0x55, 0x86, 139 | 0x3d, 0x54, 0x4f, 0x6d, 0x44, 0xbc, 0x2f, 0x27, 0x6c, 0xd6, 0x63, 0xaf, 0xd0, 0xb9, 140 | 0x55, 0x86, 0x3d, 0x54, 141 | ]); 142 | 143 | for i in 0..500 { 144 | let keypair = Keypair::::generate(&mut rng); 145 | let message = format!("Message {}", i); 146 | let sig = keypair.sign(&message.as_bytes()); 147 | assert_eq!(keypair.verify(&message.as_bytes(), &sig), true); 148 | } 149 | } 150 | 151 | #[test] 152 | fn aggregate_signatures() { 153 | let mut rng = ChaChaRng::from_seed([ 154 | 0x4f, 0x6d, 0x44, 0xbc, 0x2f, 0x27, 0x6c, 0xd6, 0x63, 0xaf, 0xd0, 0xb9, 0x55, 0x86, 155 | 0x3d, 0x54, 0x4f, 0x6d, 0x44, 0xbc, 0x2f, 0x27, 0x6c, 0xd6, 0x63, 0xaf, 0xd0, 0xb9, 156 | 0x55, 0x86, 0x3d, 0x54, 157 | ]); 158 | 159 | let mut inputs = Vec::with_capacity(1000); 160 | let mut signatures = Vec::with_capacity(1000); 161 | for i in 0..500 { 162 | let keypair = Keypair::::generate(&mut rng); 163 | let message = format!("Message {}", i); 164 | let signature = keypair.sign(&message.as_bytes()); 165 | inputs.push((keypair.public, message)); 166 | signatures.push(signature); 167 | 168 | // Only test near the beginning and the end, to reduce test runtime 169 | if i < 10 || i > 495 { 170 | let asig = AggregateSignature::from_signatures(&signatures); 171 | assert_eq!( 172 | asig.verify( 173 | &inputs 174 | .iter() 175 | .map(|&(ref pk, ref m)| (pk, m.as_bytes())) 176 | .collect() 177 | ), 178 | true 179 | ); 180 | } 181 | } 182 | } 183 | 184 | #[test] 185 | fn aggregate_signatures_duplicated_messages() { 186 | let mut rng = ChaChaRng::from_seed([ 187 | 0x4f, 0x6d, 0x44, 0xbc, 0x2f, 0x27, 0x6c, 0xd6, 0x63, 0xaf, 0xd0, 0xb9, 0x55, 0x86, 188 | 0x3d, 0x54, 0x4f, 0x6d, 0x44, 0xbc, 0x2f, 0x27, 0x6c, 0xd6, 0x63, 0xaf, 0xd0, 0xb9, 189 | 0x55, 0x86, 0x3d, 0x54, 190 | ]); 191 | 192 | let mut inputs = Vec::new(); 193 | let mut asig = AggregateSignature::new(); 194 | 195 | // Create the first signature 196 | let keypair = Keypair::::generate(&mut rng); 197 | let message = "First message"; 198 | let signature = keypair.sign(&message.as_bytes()); 199 | inputs.push((keypair.public, message)); 200 | asig.aggregate(&signature); 201 | 202 | // The first "aggregate" signature should pass 203 | assert_eq!( 204 | asig.verify( 205 | &inputs 206 | .iter() 207 | .map(|&(ref pk, ref m)| (pk, m.as_bytes())) 208 | .collect() 209 | ), 210 | true 211 | ); 212 | 213 | // Create the second signature 214 | let keypair = Keypair::::generate(&mut rng); 215 | let message = "Second message"; 216 | let signature = keypair.sign(&message.as_bytes()); 217 | inputs.push((keypair.public, message)); 218 | asig.aggregate(&signature); 219 | 220 | // The second (now-)aggregate signature should pass 221 | assert_eq!( 222 | asig.verify( 223 | &inputs 224 | .iter() 225 | .map(|&(ref pk, ref m)| (pk, m.as_bytes())) 226 | .collect() 227 | ), 228 | true 229 | ); 230 | 231 | // Create the third signature, reusing the second message 232 | let keypair = Keypair::::generate(&mut rng); 233 | let signature = keypair.sign(&message.as_bytes()); 234 | inputs.push((keypair.public, message)); 235 | asig.aggregate(&signature); 236 | 237 | // The third aggregate signature should fail 238 | assert_eq!( 239 | asig.verify( 240 | &inputs 241 | .iter() 242 | .map(|&(ref pk, ref m)| (pk, m.as_bytes())) 243 | .collect() 244 | ), 245 | false 246 | ); 247 | } 248 | } 249 | --------------------------------------------------------------------------------