1use std::io;
2use std::sync::atomic::{AtomicBool, Ordering};
3
4#[cfg(feature = "multicore")]
5use rayon::prelude::*;
6
7#[cfg(feature = "pairing")]
8use bls12_381::{
9 hash_to_curve::{ExpandMsgXmd, HashToCurve},
10 Bls12, G1Affine, G2Affine, G2Projective, Gt, MillerLoopResult,
11};
12use pairing_lib::MultiMillerLoop;
13
14#[cfg(feature = "blst")]
15use blstrs::{Bls12, G1Affine, G2Affine, G2Projective, Gt, MillerLoopResult};
16#[cfg(feature = "blst")]
17use group::{prime::PrimeCurveAffine, Group};
18#[cfg(feature = "blst")]
19use pairing_lib::MillerLoopResult as _;
20
21use crate::error::Error;
22use crate::key::*;
23
24const CSUITE: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_";
25const G2_COMPRESSED_SIZE: usize = 96;
26
27#[derive(Debug, Copy, Clone, Eq, PartialEq)]
28pub struct Signature(G2Affine);
29
30impl From<G2Projective> for Signature {
31 fn from(val: G2Projective) -> Self {
32 Signature(val.into())
33 }
34}
35impl From<Signature> for G2Projective {
36 fn from(val: Signature) -> Self {
37 val.0.into()
38 }
39}
40
41impl From<G2Affine> for Signature {
42 fn from(val: G2Affine) -> Self {
43 Signature(val)
44 }
45}
46
47impl From<Signature> for G2Affine {
48 fn from(val: Signature) -> Self {
49 val.0
50 }
51}
52
53impl Serialize for Signature {
54 fn write_bytes(&self, dest: &mut impl io::Write) -> io::Result<()> {
55 dest.write_all(&self.0.to_compressed())?;
56
57 Ok(())
58 }
59
60 fn from_bytes(raw: &[u8]) -> Result<Self, Error> {
61 let g2 = g2_from_slice(raw)?;
62 Ok(g2.into())
63 }
64}
65
66fn g2_from_slice(raw: &[u8]) -> Result<G2Affine, Error> {
67 if raw.len() != G2_COMPRESSED_SIZE {
68 return Err(Error::SizeMismatch);
69 }
70
71 let mut res = [0u8; G2_COMPRESSED_SIZE];
72 res.copy_from_slice(raw);
73
74 Option::from(G2Affine::from_compressed(&res)).ok_or(Error::GroupDecode)
75}
76
77#[cfg(feature = "pairing")]
79pub fn hash(msg: &[u8]) -> G2Projective {
80 <G2Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, CSUITE)
81}
82
83#[cfg(feature = "blst")]
84pub fn hash(msg: &[u8]) -> G2Projective {
85 G2Projective::hash_to_curve(msg, CSUITE, &[])
86}
87
88#[cfg(feature = "multicore")]
91pub fn aggregate(signatures: &[Signature]) -> Result<Signature, Error> {
92 if signatures.is_empty() {
93 return Err(Error::ZeroSizedInput);
94 }
95
96 let res = signatures
97 .into_par_iter()
98 .fold(G2Projective::identity, |mut acc, signature| {
99 acc += &signature.0;
100 acc
101 })
102 .reduce(G2Projective::identity, |acc, val| acc + val);
103
104 Ok(Signature(res.into()))
105}
106
107#[cfg(not(feature = "multicore"))]
110pub fn aggregate(signatures: &[Signature]) -> Result<Signature, Error> {
111 if signatures.is_empty() {
112 return Err(Error::ZeroSizedInput);
113 }
114
115 let res = signatures
116 .into_iter()
117 .fold(G2Projective::identity(), |acc, signature| {
118 acc + &signature.0
119 });
120
121 Ok(Signature(res.into()))
122}
123
124pub fn verify(signature: &Signature, hashes: &[G2Projective], public_keys: &[PublicKey]) -> bool {
127 if hashes.is_empty() || public_keys.is_empty() {
128 return false;
129 }
130
131 let n_hashes = hashes.len();
132
133 if n_hashes != public_keys.len() {
134 return false;
135 }
136
137 if n_hashes == 1 && public_keys[0].0.is_identity().into() {
139 return false;
140 }
141
142 for i in 0..(n_hashes - 1) {
146 for j in (i + 1)..n_hashes {
147 if hashes[i] == hashes[j] {
148 return false;
149 }
150 }
151 }
152
153 let is_valid = AtomicBool::new(true);
154
155 #[cfg(feature = "multicore")]
156 let mut ml = public_keys
157 .par_iter()
158 .zip(hashes.par_iter())
159 .map(|(pk, h)| {
160 if pk.0.is_identity().into() {
161 is_valid.store(false, Ordering::Relaxed);
162 }
163 let pk = pk.as_affine();
164 let h = G2Affine::from(h).into();
165 Bls12::multi_miller_loop(&[(&pk, &h)])
166 })
167 .reduce(MillerLoopResult::default, |acc, cur| acc + cur);
168
169 #[cfg(not(feature = "multicore"))]
170 let mut ml = public_keys
171 .iter()
172 .zip(hashes.iter())
173 .map(|(pk, h)| {
174 if pk.0.is_identity().into() {
175 is_valid.store(false, Ordering::Relaxed);
176 }
177 let pk = pk.as_affine();
178 let h = G2Affine::from(h).into();
179 Bls12::multi_miller_loop(&[(&pk, &h)])
180 })
181 .fold(MillerLoopResult::default(), |acc, cur| acc + cur);
182
183 if !is_valid.load(Ordering::Relaxed) {
184 return false;
185 }
186
187 let g1_neg = -G1Affine::generator();
188
189 ml += Bls12::multi_miller_loop(&[(&g1_neg, &signature.0.into())]);
190
191 ml.final_exponentiation() == Gt::identity()
192}
193
194#[cfg(feature = "pairing")]
197pub fn verify_messages(
198 signature: &Signature,
199 messages: &[&[u8]],
200 public_keys: &[PublicKey],
201) -> bool {
202 #[cfg(feature = "multicore")]
203 let hashes: Vec<_> = messages.par_iter().map(|msg| hash(msg)).collect();
204
205 #[cfg(not(feature = "multicore"))]
206 let hashes: Vec<_> = messages.iter().map(|msg| hash(msg)).collect();
207
208 verify(signature, &hashes, public_keys)
209}
210
211#[cfg(all(feature = "blst", feature = "multicore"))]
214pub fn verify_messages(
215 signature: &Signature,
216 messages: &[&[u8]],
217 public_keys: &[PublicKey],
218) -> bool {
219 if messages.is_empty() || public_keys.is_empty() {
220 return false;
221 }
222
223 let n_messages = messages.len();
224
225 if n_messages != public_keys.len() {
226 return false;
227 }
228
229 if n_messages == 1 && public_keys[0].0.is_identity().into() {
231 return false;
232 }
233
234 if !blstrs::unique_messages(messages) {
238 return false;
239 }
240
241 let valid = AtomicBool::new(true);
242
243 let n_workers = std::cmp::min(rayon::current_num_threads(), n_messages);
244 let mut pairings = messages
245 .par_iter()
246 .zip(public_keys.par_iter())
247 .chunks(n_messages / n_workers)
248 .map(|chunk| {
249 let mut pairing = blstrs::PairingG1G2::new(true, CSUITE);
250
251 for (message, public_key) in chunk {
252 let res = pairing.aggregate(&public_key.0.into(), None, message, &[]);
253 if res.is_err() {
254 valid.store(false, Ordering::Relaxed);
255 break;
256 }
257 }
258 if valid.load(Ordering::Relaxed) {
259 pairing.commit();
260 }
261
262 pairing
263 })
264 .collect::<Vec<_>>();
265
266 let mut gtsig = Gt::default();
267 if valid.load(Ordering::Relaxed) {
268 blstrs::PairingG1G2::aggregated(&mut gtsig, &signature.0);
269 }
270
271 let mut acc = pairings.pop().unwrap();
272 for pairing in &pairings {
273 let res = acc.merge(pairing);
274 if res.is_err() {
275 return false;
276 }
277 }
278
279 valid.load(Ordering::Relaxed) && acc.finalverify(Some(>sig))
280}
281
282#[cfg(all(feature = "blst", not(feature = "multicore")))]
285pub fn verify_messages(
286 signature: &Signature,
287 messages: &[&[u8]],
288 public_keys: &[PublicKey],
289) -> bool {
290 if messages.is_empty() || public_keys.is_empty() {
291 return false;
292 }
293
294 let n_messages = messages.len();
295
296 if n_messages != public_keys.len() {
297 return false;
298 }
299
300 if n_messages == 1 && public_keys[0].0.is_identity().into() {
302 return false;
303 }
304
305 if !blstrs::unique_messages(messages) {
309 return false;
310 }
311
312 let mut valid = true;
313 let mut pairing = blstrs::PairingG1G2::new(true, CSUITE);
314 for (message, public_key) in messages.iter().zip(public_keys.iter()) {
315 let res = pairing.aggregate(&public_key.0.into(), None, message, &[]);
316 if res.is_err() {
317 valid = false;
318 break;
319 }
320
321 pairing.commit();
322 }
323
324 let mut gtsig = Gt::default();
325 if valid {
326 blstrs::PairingG1G2::aggregated(&mut gtsig, &signature.0);
327 }
328
329 valid && pairing.finalverify(Some(>sig))
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335
336 use base64::STANDARD;
337 use ff::Field;
338 use rand::{Rng, SeedableRng};
339 use rand_chacha::ChaCha8Rng;
340 use serde::Deserialize;
341
342 #[cfg(feature = "pairing")]
343 use crate::key::G1_COMPRESSED_SIZE;
344 #[cfg(feature = "pairing")]
345 use bls12_381::{G1Projective, Scalar};
346 #[cfg(feature = "blst")]
347 use blstrs::{G1Projective, Scalar};
348
349 #[test]
350 fn basic_aggregation() {
351 let mut rng = ChaCha8Rng::seed_from_u64(12);
352
353 let num_messages = 10;
354
355 let private_keys: Vec<_> = (0..num_messages)
357 .map(|_| PrivateKey::generate(&mut rng))
358 .collect();
359
360 let messages: Vec<Vec<u8>> = (0..num_messages)
362 .map(|_| (0..64).map(|_| rng.gen()).collect())
363 .collect();
364
365 let sigs = messages
367 .iter()
368 .zip(&private_keys)
369 .map(|(message, pk)| pk.sign(message))
370 .collect::<Vec<Signature>>();
371
372 let aggregated_signature = aggregate(&sigs).expect("failed to aggregate");
373
374 let hashes = messages
375 .iter()
376 .map(|message| hash(message))
377 .collect::<Vec<_>>();
378 let public_keys = private_keys
379 .iter()
380 .map(|pk| pk.public_key())
381 .collect::<Vec<_>>();
382
383 assert!(
384 verify(&aggregated_signature, &hashes, &public_keys),
385 "failed to verify"
386 );
387
388 let messages = messages.iter().map(|r| &r[..]).collect::<Vec<_>>();
389 assert!(verify_messages(
390 &aggregated_signature,
391 &messages[..],
392 &public_keys
393 ));
394 }
395
396 #[test]
397 fn aggregation_same_messages() {
398 let mut rng = ChaCha8Rng::seed_from_u64(12);
399
400 let num_messages = 10;
401
402 let private_keys: Vec<_> = (0..num_messages)
404 .map(|_| PrivateKey::generate(&mut rng))
405 .collect();
406
407 let message: Vec<u8> = (0..64).map(|_| rng.gen()).collect();
409
410 let sigs = private_keys
412 .iter()
413 .map(|pk| pk.sign(&message))
414 .collect::<Vec<Signature>>();
415
416 let aggregated_signature = aggregate(&sigs).expect("failed to aggregate");
417
418 let hashes: Vec<_> = (0..num_messages).map(|_| hash(&message)).collect();
420 let public_keys = private_keys
421 .iter()
422 .map(|pk| pk.public_key())
423 .collect::<Vec<_>>();
424 assert!(
425 !verify(&aggregated_signature, &hashes, &public_keys),
426 "must not verify aggregate with the same messages"
427 );
428 let messages = vec![&message[..]; num_messages];
429
430 assert!(!verify_messages(
431 &aggregated_signature,
432 &messages[..],
433 &public_keys
434 ));
435 }
436
437 #[test]
438 fn test_zero_key() {
439 let mut rng = ChaCha8Rng::seed_from_u64(12);
440
441 let zero_key: PrivateKey = Scalar::ZERO.into();
443 assert!(bool::from(zero_key.public_key().0.is_identity()));
444
445 println!(
446 "{:?}\n{:?}",
447 zero_key.public_key().as_bytes(),
448 zero_key.as_bytes()
449 );
450 let num_messages = 10;
451
452 let mut private_keys: Vec<_> = (0..num_messages - 1)
454 .map(|_| PrivateKey::generate(&mut rng))
455 .collect();
456
457 private_keys.push(zero_key);
458
459 let messages: Vec<Vec<u8>> = (0..num_messages)
461 .map(|_| (0..64).map(|_| rng.gen()).collect())
462 .collect();
463
464 let sigs = messages
466 .iter()
467 .zip(&private_keys)
468 .map(|(message, pk)| pk.sign(message))
469 .collect::<Vec<Signature>>();
470
471 let aggregated_signature = aggregate(&sigs).expect("failed to aggregate");
472
473 let hashes = messages
474 .iter()
475 .map(|message| hash(message))
476 .collect::<Vec<_>>();
477 let public_keys = private_keys
478 .iter()
479 .map(|pk| pk.public_key())
480 .collect::<Vec<_>>();
481
482 assert!(
483 !verify(&aggregated_signature, &hashes, &public_keys),
484 "verified with zero key"
485 );
486
487 let messages = messages.iter().map(|r| &r[..]).collect::<Vec<_>>();
488 assert!(!verify_messages(
489 &aggregated_signature,
490 &messages[..],
491 &public_keys
492 ));
493
494 let signature = zero_key.sign(&messages[0]);
496
497 assert!(!zero_key.public_key().verify(signature, &messages[0]));
498
499 let aggregated_signature = aggregate(&[signature][..]).expect("failed to aggregate");
500 assert!(!verify_messages(
501 &aggregated_signature,
502 &messages[..1],
503 &[zero_key.public_key()][..],
504 ));
505 }
506
507 #[test]
508 fn test_bytes_roundtrip() {
509 let mut rng = ChaCha8Rng::seed_from_u64(12);
510 let sk = PrivateKey::generate(&mut rng);
511
512 let msg = (0..64).map(|_| rng.gen()).collect::<Vec<u8>>();
513 let signature = sk.sign(&msg);
514
515 let signature_bytes = signature.as_bytes();
516 assert_eq!(signature_bytes.len(), 96);
517 assert_eq!(Signature::from_bytes(&signature_bytes).unwrap(), signature);
518 }
519
520 base64_serde_type!(Base64Standard, STANDARD);
521
522 #[derive(Debug, Clone, Deserialize)]
523 struct Case {
524 #[serde(rename = "Msg")]
525 msg: String,
526 #[serde(rename = "Ciphersuite")]
527 ciphersuite: String,
528 #[serde(rename = "G1Compressed", with = "Base64Standard")]
529 g1_compressed: Vec<u8>,
530 #[serde(rename = "G2Compressed", with = "Base64Standard")]
531 g2_compressed: Vec<u8>,
532 #[serde(rename = "BLSPrivKey")]
533 priv_key: Option<String>,
534 #[serde(rename = "BLSPubKey")]
535 pub_key: Option<String>,
536 #[serde(rename = "BLSSigG2")]
537 signature: Option<String>,
538 }
539
540 #[derive(Debug, Clone, Deserialize)]
541 struct Cases {
542 cases: Vec<Case>,
543 }
544
545 fn g1_from_slice(raw: &[u8]) -> Result<G1Affine, Error> {
546 if raw.len() != G1_COMPRESSED_SIZE {
547 return Err(Error::SizeMismatch);
548 }
549
550 let mut res = [0u8; G1_COMPRESSED_SIZE];
551 res.as_mut().copy_from_slice(raw);
552
553 Option::from(G1Affine::from_compressed(&res)).ok_or(Error::GroupDecode)
554 }
555
556 #[cfg(feature = "pairing")]
557 fn hash_to_g1(msg: &[u8], suite: &[u8]) -> G1Projective {
558 <G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, suite)
559 }
560 #[cfg(feature = "blst")]
561 fn hash_to_g1(msg: &[u8], suite: &[u8]) -> G1Projective {
562 G1Projective::hash_to_curve(msg, suite, &[])
563 }
564
565 #[cfg(feature = "pairing")]
566 fn hash_to_g2(msg: &[u8], suite: &[u8]) -> G2Projective {
567 <G2Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, suite)
568 }
569 #[cfg(feature = "blst")]
570 fn hash_to_g2(msg: &[u8], suite: &[u8]) -> G2Projective {
571 G2Projective::hash_to_curve(msg, suite, &[])
572 }
573
574 #[test]
575 fn test_vectors() {
576 let cases: Cases =
577 serde_json::from_slice(&std::fs::read("./tests/data.json").unwrap()).unwrap();
578
579 for case in cases.cases {
580 let g1: G1Projective = g1_from_slice(&case.g1_compressed).unwrap().into();
581
582 assert_eq!(
583 g1,
584 hash_to_g1(case.msg.as_bytes(), case.ciphersuite.as_bytes())
585 );
586
587 let g2: G2Projective = g2_from_slice(&case.g2_compressed).unwrap().into();
588 assert_eq!(
589 g2,
590 hash_to_g2(case.msg.as_bytes(), case.ciphersuite.as_bytes())
591 );
592
593 if case.ciphersuite.as_bytes() == CSUITE {
594 let pub_key =
595 PublicKey::from_bytes(&base64::decode(case.pub_key.as_ref().unwrap()).unwrap())
596 .unwrap();
597 let priv_key = PrivateKey::from_string(case.priv_key.as_ref().unwrap()).unwrap();
598 let signature = Signature::from_bytes(
599 &base64::decode(case.signature.as_ref().unwrap()).unwrap(),
600 )
601 .unwrap();
602
603 let sig2 = priv_key.sign(&case.msg);
604 assert_eq!(signature, sig2, "signatures do not match");
605
606 assert!(pub_key.verify(signature, &case.msg), "failed to verify");
607 }
608 }
609 }
610}