1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// Copyright 2019-2024 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

mod public_key_impls;
mod signature_impls;

use bls_signatures::Error;
use blstrs::{G1Affine, G1Projective, G2Affine, G2Projective};
use group::{prime::PrimeCurveAffine, Curve};
use rayon::prelude::*;

// re-exports
pub use bls_signatures::{PublicKey as PublicKeyOnG1, Signature as SignatureOnG2};

// See <https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html#name-basic>
const CSUITE_G1: &[u8] = b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_";
const CSUITE_G2: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_";

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct PublicKeyOnG2(pub(crate) G2Projective);

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct SignatureOnG1(pub(crate) G1Affine);

/// Ported from <https://docs.rs/bls-signatures/0.15.0/src/bls_signatures/signature.rs.html#214>
pub fn verify_messages_unchained(
    public_key: &PublicKeyOnG2,
    messages: &[&[u8]],
    signatures: &[&SignatureOnG1],
) -> bool {
    let n_messages = messages.len();
    if n_messages != signatures.len() {
        return false;
    }
    if n_messages == 0 {
        return true;
    }

    let public_key: G2Affine = public_key.as_affine();
    // zero key & single message should fail
    if n_messages == 1 && public_key.is_identity().into() {
        return false;
    }

    // Enforce that messages are distinct as a countermeasure against BLS's rogue-key attack.
    // See Section 3.1. of the IRTF's BLS signatures spec:
    // https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02#section-3.1
    if !blstrs::unique_messages(messages) {
        return false;
    }

    let n_workers = std::cmp::min(rayon::current_num_threads(), n_messages);
    let Some(Ok(acc)) = messages
        .par_iter()
        .zip(signatures.par_iter())
        .chunks(n_messages / n_workers)
        .map(|chunk| {
            let mut pairing = blstrs::PairingG2G1::new(true, CSUITE_G1);
            for (message, signature) in chunk {
                pairing
                    .aggregate(&public_key, Some(&signature.0), message, &[])
                    .map_err(map_blst_error)?;
            }
            pairing.commit();
            anyhow::Ok(pairing)
        })
        .try_reduce_with(|mut acc, pairing| {
            acc.merge(&pairing).map_err(map_blst_error)?;
            anyhow::Ok(acc)
        })
    else {
        return false;
    };

    acc.finalverify(None)
}

/// Ported from <https://docs.rs/bls-signatures/0.15.0/src/bls_signatures/signature.rs.html#214>
pub fn verify_messages_chained(
    public_key: &PublicKeyOnG1,
    messages: &[&[u8]],
    signatures: &[SignatureOnG2],
) -> bool {
    let n_messages = messages.len();
    if n_messages != signatures.len() {
        return false;
    }
    if n_messages == 0 {
        return true;
    }

    let public_key: G1Affine = public_key.as_affine();
    // zero key & single message should fail
    if n_messages == 1 && public_key.is_identity().into() {
        return false;
    }

    // Enforce that messages are distinct as a countermeasure against BLS's rogue-key attack.
    // See Section 3.1. of the IRTF's BLS signatures spec:
    // https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02#section-3.1
    if !blstrs::unique_messages(messages) {
        return false;
    }

    let n_workers = std::cmp::min(rayon::current_num_threads(), n_messages);
    let Some(Ok(acc)) = messages
        .par_iter()
        .zip(signatures.par_iter())
        .chunks(n_messages / n_workers)
        .map(|chunk| {
            let mut pairing = blstrs::PairingG1G2::new(true, CSUITE_G2);
            for (message, signature) in chunk {
                pairing
                    .aggregate(&public_key, Some(&(*signature).into()), message, &[])
                    .map_err(map_blst_error)?;
            }
            pairing.commit();
            anyhow::Ok(pairing)
        })
        .try_reduce_with(|mut acc, pairing| {
            acc.merge(&pairing).map_err(map_blst_error)?;
            anyhow::Ok(acc)
        })
    else {
        return false;
    };

    acc.finalverify(None)
}

fn map_blst_error(e: impl std::fmt::Debug) -> anyhow::Error {
    anyhow::anyhow!("{e:?}")
}

#[cfg(test)]
mod tests;