bls_signatures/
signature.rs

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/// Hash the given message, as used in the signature.
78#[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/// Aggregate signatures by multiplying them together.
89/// Calculated by `signature = \sum_{i = 0}^n signature_i`.
90#[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/// Aggregate signatures by multiplying them together.
108/// Calculated by `signature = \sum_{i = 0}^n signature_i`.
109#[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
124/// Verifies that the signature is the actual aggregated signature of hashes - pubkeys.
125/// Calculated by `e(g1, signature) == \prod_{i = 0}^n e(pk_i, hash_i)`.
126pub 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    // zero key & single hash should fail
138    if n_hashes == 1 && public_keys[0].0.is_identity().into() {
139        return false;
140    }
141
142    // Enforce that messages are distinct as a countermeasure against BLS's rogue-key attack.
143    // See Section 3.1. of the IRTF's BLS signatures spec:
144    // https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02#section-3.1
145    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/// Verifies that the signature is the actual aggregated signature of messages - pubkeys.
195/// Calculated by `e(g1, signature) == \prod_{i = 0}^n e(pk_i, hash_i)`.
196#[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/// Verifies that the signature is the actual aggregated signature of messages - pubkeys.
212/// Calculated by `e(g1, signature) == \prod_{i = 0}^n e(pk_i, hash_i)`.
213#[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    // zero key & single message should fail
230    if n_messages == 1 && public_keys[0].0.is_identity().into() {
231        return false;
232    }
233
234    // Enforce that messages are distinct as a countermeasure against BLS's rogue-key attack.
235    // See Section 3.1. of the IRTF's BLS signatures spec:
236    // https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02#section-3.1
237    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(&gtsig))
280}
281
282/// Verifies that the signature is the actual aggregated signature of messages - pubkeys.
283/// Calculated by `e(g1, signature) == \prod_{i = 0}^n e(pk_i, hash_i)`.
284#[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    // zero key & single message should fail
301    if n_messages == 1 && public_keys[0].0.is_identity().into() {
302        return false;
303    }
304
305    // Enforce that messages are distinct as a countermeasure against BLS's rogue-key attack.
306    // See Section 3.1. of the IRTF's BLS signatures spec:
307    // https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02#section-3.1
308    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(&gtsig))
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        // generate private keys
356        let private_keys: Vec<_> = (0..num_messages)
357            .map(|_| PrivateKey::generate(&mut rng))
358            .collect();
359
360        // generate messages
361        let messages: Vec<Vec<u8>> = (0..num_messages)
362            .map(|_| (0..64).map(|_| rng.gen()).collect())
363            .collect();
364
365        // sign messages
366        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        // generate private keys
403        let private_keys: Vec<_> = (0..num_messages)
404            .map(|_| PrivateKey::generate(&mut rng))
405            .collect();
406
407        // generate messages
408        let message: Vec<u8> = (0..64).map(|_| rng.gen()).collect();
409
410        // sign messages
411        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        // check that equal messages can not be aggreagated
419        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        // In the current iteration we expect the zero key to be valid and work.
442        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        // generate private keys
453        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        // generate messages
460        let messages: Vec<Vec<u8>> = (0..num_messages)
461            .map(|_| (0..64).map(|_| rng.gen()).collect())
462            .collect();
463
464        // sign messages
465        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        // single message is rejected
495        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}