commonware_cryptography/bls12381/primitives/
variant.rs

1//! Different variants of the BLS signature scheme.
2
3use super::group::{
4    Point, DST, G1, G1_MESSAGE, G1_PROOF_OF_POSSESSION, G2, G2_MESSAGE, G2_PROOF_OF_POSSESSION,
5};
6use super::Error;
7use crate::bls12381::primitives::group::{Element, Scalar};
8use blst::{Pairing as blst_pairing, BLS12_381_NEG_G1, BLS12_381_NEG_G2};
9use commonware_codec::FixedSize;
10use rand::{CryptoRng, RngCore};
11use std::fmt::Debug;
12use std::hash::Hash;
13
14/// A specific instance of a signature scheme.
15pub trait Variant: Clone + Send + Sync + Hash + Eq + Debug + 'static {
16    /// The public key type.
17    type Public: Point + FixedSize + Debug + Hash + Copy + AsRef<Self::Public>;
18
19    /// The signature type.
20    type Signature: Point + FixedSize + Debug + Hash + Copy + AsRef<Self::Signature>;
21
22    /// The domain separator tag (DST) for a proof of possession.
23    const PROOF_OF_POSSESSION: DST;
24
25    /// The domain separator tag (DST) for a message.
26    const MESSAGE: DST;
27
28    /// Verify the signature from the provided public key and pre-hashed message.
29    fn verify(
30        public: &Self::Public,
31        hm: &Self::Signature,
32        signature: &Self::Signature,
33    ) -> Result<(), Error>;
34
35    /// Verify a batch of signatures from the provided public keys and pre-hashed messages.
36    fn batch_verify<R: RngCore + CryptoRng>(
37        rng: &mut R,
38        publics: &[Self::Public],
39        hms: &[Self::Signature],
40        signatures: &[Self::Signature],
41    ) -> Result<(), Error>;
42}
43
44/// A [Variant] with a public key of type [G1] and a signature of type [G2].
45#[derive(Clone, Hash, PartialEq, Eq)]
46pub struct MinPk {}
47
48impl Variant for MinPk {
49    type Public = G1;
50    type Signature = G2;
51
52    const PROOF_OF_POSSESSION: DST = G2_PROOF_OF_POSSESSION;
53    const MESSAGE: DST = G2_MESSAGE;
54
55    /// Verifies that `e(hm,pk)` is equal to `e(sig,G1::one())` using a single product check with
56    /// a negated G1 generator (`e(hm,pk) * e(sig,-G1::one()) == 1`).
57    fn verify(
58        public: &Self::Public,
59        hm: &Self::Signature,
60        signature: &Self::Signature,
61    ) -> Result<(), Error> {
62        // Create a pairing context
63        //
64        // We only handle pre-hashed messages, so we leave the domain separator tag (`DST`) empty.
65        let mut pairing = blst_pairing::new(false, &[]);
66
67        // Convert `sig` into affine and aggregate `e(sig,-G1::one())`
68        let q = signature.as_blst_p2_affine();
69        unsafe {
70            pairing.raw_aggregate(&q, &BLS12_381_NEG_G1);
71        }
72
73        // Convert `pk` and `hm` into affine
74        let p = public.as_blst_p1_affine();
75        let q = hm.as_blst_p2_affine();
76
77        // Aggregate `e(hm,pk)`
78        pairing.raw_aggregate(&q, &p);
79
80        // Finalize the pairing accumulation and verify the result
81        //
82        // If `finalverify()` returns `true`, it means `e(hm,pk) * e(sig,-G1::one()) == 1`. This
83        // is equivalent to `e(hm,pk) == e(sig,G1::one())`.
84        pairing.commit();
85        if !pairing.finalverify(None) {
86            return Err(Error::InvalidSignature);
87        }
88        Ok(())
89    }
90
91    /// Verifies a set of signatures against their respective public keys and pre-hashed messages.
92    ///
93    /// This method is outperforms individual signature verification (`2` pairings per signature) by
94    /// verifying a random linear combination of the public keys and signatures (`n+1` pairings and
95    /// `2n` multiplications for `n` signatures).
96    ///
97    /// The verification equation for each signature `i` is:
98    /// `e(hm_i,pk_i) == e(sig_i,G1::one())`,
99    /// which is equivalent to checking if `e(hm_i,pk_i) * e(sig_i,-G1::one()) == 1`.
100    ///
101    /// To batch verify `n` such equations, we introduce random non-zero scalars `r_i` (for `i=1..n`).
102    /// The batch verification checks if the product of these individual equations, each raised to the power
103    /// of its respective `r_i`, equals one:
104    /// `prod_i((e(hm_i,pk_i) * e(sig_i,-G1::one()))^{r_i}) == 1`
105    ///
106    /// Using the bilinearity of pairings, this can be rewritten (by moving `r_i` inside the pairings):
107    /// `prod_i(e(hm_i,r_i * pk_i) * e(r_i * sig_i,-G1::one())) == 1`
108    ///
109    /// The second term `e(r_i * sig_i,-G1::one())` can be computed efficiently with Multi-Scalar Multiplication:
110    /// `e(sum_i(r_i * sig_i),-G1::one())`
111    ///
112    /// Finally, we aggregate all pairings `e(hm_i,r_i * pk_i)` (`n`) and `e(sum_i(r_i * sig_i),-G1::one())` (`1`)
113    /// into a single product in the target group `G_T`. If the result is the identity element in `G_T`,
114    /// the batch verification succeeds.
115    ///
116    /// Source: <https://ethresear.ch/t/security-of-bls-batch-verification/10748>
117    fn batch_verify<R: RngCore + CryptoRng>(
118        rng: &mut R,
119        publics: &[Self::Public],
120        hms: &[Self::Signature],
121        signatures: &[Self::Signature],
122    ) -> Result<(), Error> {
123        // Ensure there is an equal number of public keys, messages, and signatures.
124        assert_eq!(publics.len(), hms.len());
125        assert_eq!(publics.len(), signatures.len());
126        if publics.is_empty() {
127            return Ok(());
128        }
129
130        // Generate random non-zero scalars.
131        let scalars: Vec<Scalar> = (0..publics.len())
132            .map(|_| loop {
133                let scalar = Scalar::rand(rng);
134                if scalar != Scalar::zero() {
135                    return scalar;
136                }
137            })
138            .collect();
139
140        // Compute S_agg = sum(r_i * sig_i) using Multi-Scalar Multiplication (MSM).
141        let s_agg = G2::msm(signatures, &scalars);
142
143        // Initialize pairing context. DST is empty as we use pre-hashed messages.
144        let mut pairing = blst_pairing::new(false, &[]);
145
146        // Aggregate the single term corresponding to signatures: e(-G1::one(),S_agg)
147        let s_agg_affine = s_agg.as_blst_p2_affine();
148        unsafe {
149            pairing.raw_aggregate(&s_agg_affine, &BLS12_381_NEG_G1);
150        }
151
152        // Aggregate the `n` terms corresponding to public keys and messages: e(r_i * pk_i,hm_i)
153        for i in 0..publics.len() {
154            let mut scaled_pk = publics[i];
155            scaled_pk.mul(&scalars[i]);
156            let pk_affine = scaled_pk.as_blst_p1_affine();
157            let hm_affine = hms[i].as_blst_p2_affine();
158            pairing.raw_aggregate(&hm_affine, &pk_affine);
159        }
160
161        // Perform the final verification on the product of (n+1) pairing terms.
162        pairing.commit();
163        if !pairing.finalverify(None) {
164            return Err(Error::InvalidSignature);
165        }
166        Ok(())
167    }
168}
169
170impl Debug for MinPk {
171    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172        f.debug_struct("MinPk").finish()
173    }
174}
175
176/// A [Variant] with a public key of type [G2] and a signature of type [G1].
177#[derive(Clone, Hash, PartialEq, Eq)]
178pub struct MinSig {}
179
180impl Variant for MinSig {
181    type Public = G2;
182    type Signature = G1;
183
184    const PROOF_OF_POSSESSION: DST = G1_PROOF_OF_POSSESSION;
185    const MESSAGE: DST = G1_MESSAGE;
186
187    /// Verifies that `e(pk,hm)` is equal to `e(G2::one(),sig)` using a single product check with
188    /// a negated G2 generator (`e(pk,hm) * e(-G2::one(),sig) == 1`).
189    fn verify(
190        public: &Self::Public,
191        hm: &Self::Signature,
192        signature: &Self::Signature,
193    ) -> Result<(), Error> {
194        // Create a pairing context
195        //
196        // We only handle pre-hashed messages, so we leave the domain separator tag (`DST`) empty.
197        let mut pairing = blst_pairing::new(false, &[]);
198
199        // Convert `sig` into affine and aggregate `e(-G2::one(), sig)`
200        let q = signature.as_blst_p1_affine();
201        unsafe {
202            pairing.raw_aggregate(&BLS12_381_NEG_G2, &q);
203        }
204
205        // Convert `pk` and `hm` into affine
206        let p = public.as_blst_p2_affine();
207        let q = hm.as_blst_p1_affine();
208
209        // Aggregate `e(pk,hm)`
210        pairing.raw_aggregate(&p, &q);
211
212        // Finalize the pairing accumulation and verify the result
213        //
214        // If `finalverify()` returns `true`, it means `e(pk,hm) * e(-G2::one(),sig) == 1`. This
215        // is equivalent to `e(pk,hm) == e(G2::one(),sig)`.
216        pairing.commit();
217        if !pairing.finalverify(None) {
218            return Err(Error::InvalidSignature);
219        }
220        Ok(())
221    }
222
223    /// Verifies a set of signatures against their respective public keys and pre-hashed messages.
224    ///
225    /// This method outperforms individual signature verification (`2` pairings per signature) by
226    /// verifying a random linear combination of the public keys and signatures (`n+1` pairings and
227    /// `2n` multiplications for `n` signatures).
228    ///
229    /// The verification equation for each signature `i` is:
230    /// `e(pk_i,hm_i) == e(G2::one(),sig_i)`,
231    /// which is equivalent to checking if `e(pk_i,hm_i) * e(-G2::one(),sig_i) == 1`.
232    ///
233    /// To batch verify `n` such equations, we introduce random non-zero scalars `r_i` (for `i=1..n`).
234    /// The batch verification checks if the product of these individual equations, each effectively
235    /// raised to the power of its respective `r_i`, equals one:
236    /// `prod_i((e(pk_i,hm_i) * e(-G2::one(),sig_i))^{r_i}) == 1`
237    ///
238    /// Using the bilinearity of pairings, this can be rewritten (by moving `r_i` inside the pairings):
239    /// `prod_i(e(r_i * pk_i,hm_i) * e(-G2::one(),r_i * sig_i)) == 1`
240    ///
241    /// The second term `e(-G2::one(),r_i * sig_i)` can be computed efficiently with Multi-Scalar Multiplication:
242    /// `e(-G2::one(),sum_i(r_i * sig_i))`
243    ///
244    /// Finally, we aggregate all pairings `e(r_i * pk_i,hm_i)` (`n`) and `e(-G2::one(),sum_i(r_i * sig_i))` (`1`)
245    /// into a single product in the target group `G_T`. If the result is the identity element in `G_T`,
246    /// the batch verification succeeds.
247    ///
248    /// Source: <https://ethresear.ch/t/security-of-bls-batch-verification/10748>
249    fn batch_verify<R: RngCore + CryptoRng>(
250        rng: &mut R,
251        publics: &[Self::Public],
252        hms: &[Self::Signature],
253        signatures: &[Self::Signature],
254    ) -> Result<(), Error> {
255        // Ensure there is an equal number of public keys, messages, and signatures.
256        assert_eq!(publics.len(), hms.len());
257        assert_eq!(publics.len(), signatures.len());
258        if publics.is_empty() {
259            return Ok(());
260        }
261
262        // Generate random non-zero scalars.
263        let scalars: Vec<Scalar> = (0..publics.len())
264            .map(|_| loop {
265                let scalar = Scalar::rand(rng);
266                if scalar != Scalar::zero() {
267                    return scalar;
268                }
269            })
270            .collect();
271
272        // Compute S_agg = sum(r_i * sig_i) using Multi-Scalar Multiplication (MSM).
273        let s_agg = G1::msm(signatures, &scalars);
274
275        // Initialize pairing context. DST is empty as we use pre-hashed messages.
276        let mut pairing = blst_pairing::new(false, &[]);
277
278        // Aggregate the single term corresponding to signatures: e(S_agg,-G2::one())
279        let s_agg_affine = s_agg.as_blst_p1_affine();
280        unsafe {
281            pairing.raw_aggregate(&BLS12_381_NEG_G2, &s_agg_affine);
282        }
283
284        // Aggregate the `n` terms corresponding to public keys and messages: e(hm_i, r_i * pk_i)
285        for i in 0..publics.len() {
286            let mut scaled_pk = publics[i];
287            scaled_pk.mul(&scalars[i]);
288            let pk_affine = scaled_pk.as_blst_p2_affine();
289            let hm_affine = hms[i].as_blst_p1_affine();
290            pairing.raw_aggregate(&pk_affine, &hm_affine);
291        }
292
293        // Perform the final verification on the product of (n+1) pairing terms.
294        pairing.commit();
295        if !pairing.finalverify(None) {
296            return Err(Error::InvalidSignature);
297        }
298        Ok(())
299    }
300}
301
302impl Debug for MinSig {
303    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
304        f.debug_struct("MinSig").finish()
305    }
306}