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}