commonware_cryptography/bls12381/primitives/
variant.rs

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