commonware_cryptography/bls12381/primitives/
variant.rs

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