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}