tnt_bls/verifiers.rs
1//! ## Algorithms for optimized verification of aggregate and batched BLS signatures.
2//!
3//!
4//!
5
6use core::borrow::Borrow;
7#[cfg(feature = "std")]
8use std::collections::HashMap;
9
10#[cfg(feature = "std")]
11use ark_ec::AffineRepr;
12#[cfg(feature = "std")]
13use ark_ff::field_hashers::{DefaultFieldHasher, HashToField};
14#[cfg(feature = "std")]
15use ark_serialize::CanonicalSerialize;
16#[cfg(feature = "std")]
17use digest::DynDigest;
18#[cfg(feature = "std")]
19use digest::FixedOutputReset;
20
21use ark_ec::CurveGroup;
22
23use alloc::vec::Vec;
24
25use super::*;
26
27// We define these convenience type alias here instead of engine.rs
28// because seemingly only verifier implementations really employ them.
29// And we `pub use engine::*` in lib.rs.
30
31/// Convenience type alias for projective form of `PublicKeyGroup`
32pub type PublicKeyProjective<E> = <E as EngineBLS>::PublicKeyGroup;
33
34/// Convenience type alias for affine form of `PublicKeyGroup`
35pub type PublicKeyAffine<E> = <<E as EngineBLS>::PublicKeyGroup as CurveGroup>::Affine;
36
37/// Convenience type alias for projective form of `SignatureGroup`
38pub type SignatureProjective<E> = <E as EngineBLS>::SignatureGroup;
39
40/// Convenience type alias for affine form of `SignatureGroup`
41pub type SignatureAffine<E> = <<E as EngineBLS>::SignatureGroup as CurveGroup>::Affine;
42
43/// Simple unoptimized BLS signature verification. Useful for testing.
44pub fn verify_unoptimized<S: Signed>(s: S) -> bool {
45 let signature = S::E::prepare_signature(s.signature().0);
46 let prepared = s
47 .messages_and_publickeys()
48 .map(|(message, public_key)| {
49 (
50 S::E::prepare_public_key(public_key.borrow().0),
51 S::E::prepare_signature(message.borrow().hash_to_signature_curve::<S::E>()),
52 )
53 })
54 .collect::<Vec<(_, _)>>();
55 S::E::verify_prepared(signature, prepared.iter())
56}
57
58/// Simple universal BLS signature verification
59///
60/// We support an unstable `Signed::messages_and_publickeys()`
61/// securely by calling it only once and batch normalizing all
62/// points, as do most other verification routines here.
63/// We do no optimizations that reduce the number of pairings
64/// by combining repeated messages or signers.
65pub fn verify_simple<S: Signed>(s: S) -> bool {
66 let signature = s.signature().0;
67 // We could write this more idiomatically using iterator adaptors,
68 // and avoiding an unecessary allocation for publickeys, but only
69 // by calling self.messages_and_publickeys() repeatedly.
70 let itr = s.messages_and_publickeys();
71 let l = {
72 let (lower, upper) = itr.size_hint();
73 upper.unwrap_or(lower)
74 };
75 let mut gpk = Vec::with_capacity(l);
76 let mut gms = Vec::with_capacity(l + 1);
77 for (message, publickey) in itr {
78 gpk.push(publickey.borrow().0.clone());
79 gms.push(message.borrow().hash_to_signature_curve::<S::E>());
80 }
81 let gpk = <<S as Signed>::E as EngineBLS>::PublicKeyGroup::normalize_batch(gpk.as_mut_slice());
82 gms.push(signature);
83 let mut gms =
84 <<S as Signed>::E as EngineBLS>::SignatureGroup::normalize_batch(gms.as_mut_slice());
85 let signature = <<S as Signed>::E as EngineBLS>::prepare_signature(gms.pop().unwrap());
86 let prepared = gpk
87 .iter()
88 .zip(gms)
89 .map(|(pk, m)| {
90 (
91 <<S as Signed>::E as EngineBLS>::prepare_public_key(*pk),
92 <<S as Signed>::E as EngineBLS>::prepare_signature(m),
93 )
94 })
95 .collect::<Vec<(_, _)>>();
96 S::E::verify_prepared(signature, prepared.iter())
97}
98
99/// BLS signature verification optimized for all unique messages
100///
101/// Assuming all messages are distinct, the minimum number of pairings
102/// is the number of unique signers, which we achieve here.
103/// We do not verify message uniqueness here, but leave this to the
104/// aggregate signature type, like `DistinctMessages`.
105///
106/// We merge any messages with identical signers and batch normalize
107/// message points and the signature itself.
108/// We optionally batch normalize the public keys in the event that
109/// they are provided by algerbaic operaations, but this sounds
110/// unlikely given our requirement that messages be distinct.
111#[cfg(feature = "std")]
112pub fn verify_with_distinct_messages<S: Signed>(signed: S, normalize_public_keys: bool) -> bool {
113 let signature = signed.signature().0;
114 // We first hash the messages to the signature curve and
115 // normalize the public keys to operate on them as bytes.
116 // TODO: Assess if we should mutate in place using interior
117 // mutability, maybe using `BorrowMut` support in
118 // `batch_normalization`.
119 let itr = signed.messages_and_publickeys();
120 let l = {
121 let (lower, upper) = itr.size_hint();
122 upper.unwrap_or(lower)
123 };
124 let mut publickeys = Vec::with_capacity(l);
125 let mut messages = Vec::with_capacity(l + 1);
126 for (m, pk) in itr {
127 publickeys.push(pk.borrow().0.clone());
128 messages.push(m.borrow().hash_to_signature_curve::<S::E>());
129 }
130 let mut affine_publickeys = if normalize_public_keys {
131 <<S as Signed>::E as EngineBLS>::PublicKeyGroup::normalize_batch(&publickeys)
132 } else {
133 publickeys
134 .iter()
135 .map(|pk| pk.into_affine())
136 .collect::<Vec<_>>()
137 };
138
139 // We next accumulate message points with the same signer.
140 // We could avoid the allocation here if we sorted both
141 // arrays in parallel. This might mean (a) some sort function
142 // using `ops::IndexMut` instead of slices, and (b) wrapper types
143 // types to make tuples of slices satisfy `ops::IndexMut`.
144 // TODO: Impl PartialEq, Eq, Hash for pairing::EncodedPoint
145 // to avoid struct H(E::PublicKeyGroup::Affine::Uncompressed);
146 type AA<E> = (PublicKeyAffine<E>, SignatureProjective<E>);
147 let mut pks_n_ms = HashMap::with_capacity(l);
148 for (pk, m) in affine_publickeys.drain(..).zip(messages.drain(..)) {
149 let mut pk_uncompressed = vec![0; pk.uncompressed_size()];
150 pk.serialize_uncompressed(&mut pk_uncompressed[..]).unwrap();
151 pks_n_ms
152 .entry(pk_uncompressed)
153 .and_modify(|(_pk0, m0): &mut AA<S::E>| {
154 *m0 += m;
155 })
156 .or_insert((pk, m));
157 }
158
159 let mut publickeys = Vec::with_capacity(l);
160 for (_, (pk, m)) in pks_n_ms.drain() {
161 messages.push(m);
162 publickeys.push(pk.clone());
163 }
164
165 // We finally normalize the messages and signature
166
167 messages.push(signature);
168 let mut affine_messages =
169 <<S as Signed>::E as EngineBLS>::SignatureGroup::normalize_batch(messages.as_mut_slice());
170 let signature =
171 <<S as Signed>::E as EngineBLS>::prepare_signature(affine_messages.pop().unwrap());
172 // TODO: Assess if we could cache normalized message hashes anyplace
173 // using interior mutability, but probably this does not work well
174 // with ur optimization of collecting messages with thesame signer.
175
176 // And verify the aggregate signature.
177 let prepared = publickeys
178 .iter()
179 .zip(messages)
180 .map(|(pk, m)| {
181 (
182 <<S as Signed>::E as EngineBLS>::prepare_public_key(*pk),
183 <<S as Signed>::E as EngineBLS>::prepare_signature(m),
184 )
185 })
186 .collect::<Vec<(_, _)>>();
187
188 S::E::verify_prepared(signature, prepared.iter())
189
190 //let prepared = publickeys.iter().zip(&messages);
191 //S::E::verify_prepared( &signature, prepared )
192}
193
194/// BLS signature verification optimized for all unique messages
195///
196/// Assuming all messages are distinct, the minimum number of pairings
197/// is the number of unique signers, which we achieve here.
198/// We do not verify message uniqueness here, but leave this to the
199/// aggregate signature type, like `DistinctMessages`.
200///
201/// We merge any messages with identical signers and batch normalize
202/// message points and the signature itself.
203/// We optionally batch normalize the public keys in the event that
204/// they are provided by algerbaic operaations, but this sounds
205/// unlikely given our requirement that messages be distinct.
206#[cfg(feature = "std")]
207pub fn verify_using_aggregated_auxiliary_public_keys<
208 E: EngineBLS,
209 H: DynDigest + FixedOutputReset + Default + Clone,
210>(
211 signed: &single_pop_aggregator::SignatureAggregatorAssumingPoP<E>,
212 normalize_public_keys: bool,
213 aggregated_aux_pub_key: <E as EngineBLS>::SignatureGroup,
214) -> bool {
215 let signature = Signed::signature(&signed).0;
216
217 let mut signature_as_bytes = vec![0; signature.compressed_size()];
218 signature
219 .serialize_compressed(&mut signature_as_bytes[..])
220 .expect("compressed size has been alocated");
221
222 let itr = signed.messages_and_publickeys();
223 let l = {
224 let (lower, upper) = itr.size_hint();
225 upper.unwrap_or(lower)
226 };
227 let (first_message, first_public_key) = match signed.messages_and_publickeys().next() {
228 Some((first_message, first_public_key)) => (first_message, first_public_key),
229 None => return false,
230 };
231
232 let mut first_public_key_as_bytes = vec![0; first_public_key.compressed_size()];
233 first_public_key
234 .serialize_compressed(&mut first_public_key_as_bytes[..])
235 .expect("compressed size has been alocated");
236
237 let first_message_point = first_message.hash_to_signature_curve::<E>();
238 let first_message_point_as_bytes = E::signature_point_to_byte(&first_message_point);
239
240 let mut aggregated_aux_pub_key_as_bytes = vec![0; aggregated_aux_pub_key.compressed_size()];
241 aggregated_aux_pub_key
242 .serialize_compressed(&mut aggregated_aux_pub_key_as_bytes[..])
243 .expect("compressed size has been alocated");
244
245 // We first hash the messages to the signature curve and
246 // normalize the public keys to operate on them as bytes.
247 // TODO: Assess if we should mutate in place using interior
248 // mutability, maybe using `BorrowMut` support in
249 // `batch_normalization`.
250
251 // deterministic randomness for adding aggregated auxiliary pub keys
252 //TODO you can't just assume that there is one pubickey you need to stop if they were more or aggregate them
253
254 let pseudo_random_scalar_seed = [
255 first_message_point_as_bytes,
256 first_public_key_as_bytes,
257 aggregated_aux_pub_key_as_bytes,
258 signature_as_bytes,
259 ]
260 .concat();
261
262 let hasher = <DefaultFieldHasher<H> as HashToField<E::Scalar>>::new(&[]);
263 let pseudo_random_scalar: E::Scalar =
264 hasher.hash_to_field::<1>(&pseudo_random_scalar_seed[..])[0];
265
266 let signature = signature + aggregated_aux_pub_key * pseudo_random_scalar;
267
268 let mut publickeys = Vec::with_capacity(l);
269 let mut messages = Vec::with_capacity(l + 1);
270
271 //Simplify from here on.
272 for (m, pk) in itr {
273 publickeys.push(pk.0.clone());
274 messages.push(
275 m.hash_to_signature_curve::<E>()
276 + E::SignatureGroupAffine::generator() * pseudo_random_scalar,
277 );
278 }
279
280 let mut affine_publickeys = if normalize_public_keys {
281 <E as EngineBLS>::PublicKeyGroup::normalize_batch(&publickeys)
282 } else {
283 publickeys
284 .iter()
285 .map(|pk| pk.into_affine())
286 .collect::<Vec<_>>()
287 };
288
289 // We next accumulate message points with the same signer.
290 // We could avoid the allocation here if we sorted both
291 // arrays in parallel. This might mean (a) some sort function
292 // using `ops::IndexMut` instead of slices, and (b) wrapper types
293 // types to make tuples of slices satisfy `ops::IndexMut`.
294 // TODO: Impl PartialEq, Eq, Hash for pairing::EncodedPoint
295 // to avoid struct H(E::PublicKeyGroup::Affine::Uncompressed);
296 type AA<E> = (PublicKeyAffine<E>, SignatureProjective<E>);
297 let mut pks_n_ms = HashMap::with_capacity(l);
298 for (pk, m) in affine_publickeys.drain(..).zip(messages.drain(..)) {
299 let mut pk_uncompressed = vec![0; pk.uncompressed_size()];
300 pk.serialize_uncompressed(&mut pk_uncompressed[..]).unwrap();
301 pks_n_ms
302 .entry(pk_uncompressed)
303 .and_modify(|(_pk0, m0): &mut AA<E>| {
304 *m0 += m;
305 })
306 .or_insert((pk, m));
307 }
308
309 let mut publickeys = Vec::with_capacity(l);
310 for (_, (pk, m)) in pks_n_ms.drain() {
311 messages.push(m);
312 publickeys.push(pk.clone());
313 }
314
315 // We finally normalize the messages and signature
316
317 messages.push(signature);
318 let mut affine_messages =
319 <E as EngineBLS>::SignatureGroup::normalize_batch(messages.as_mut_slice());
320 let signature = <E as EngineBLS>::prepare_signature(affine_messages.pop().unwrap());
321 // TODO: Assess if we could cache normalized message hashes anyplace
322 // using interior mutability, but probably this does not work well
323 // with ur optimization of collecting messages with thesame signer.
324
325 // And verify the aggregate signature.
326 let prepared = publickeys
327 .iter()
328 .zip(messages)
329 .map(|(pk, m)| {
330 (
331 <E as EngineBLS>::prepare_public_key(*pk),
332 <E as EngineBLS>::prepare_signature(m),
333 )
334 })
335 .collect::<Vec<(_, _)>>();
336
337 E::verify_prepared(signature, prepared.iter())
338
339 //let prepared = publickeys.iter().zip(&messages);
340 //S::E::verify_prepared( &signature, prepared )
341}
342
343/*
344/// Excessively optimized BLS signature verification
345///
346/// We minimize the number of pairing operations by doing two
347/// basis change operation using Gaussian elimination, first in the
348/// message space and then in the signer space. As a result, we
349/// do only `1 + min(msg_d,pk_d)` pairings where `msg_d` and `pk_d`
350/// are the numbers of distinct messages and signers, respectively.
351///
352/// We expect this to improve performance dramatically when both
353/// signers and messages are repeated enough, simpler strategies
354/// work as well or better when say messages are distinct.
355///
356/// Explination:
357///
358/// We consider the bipartite graph with vertex sets given by points
359/// on the two curves and edges given by desired pairings between them.
360/// We let $M$ denote the bipartite adjacency matrix for this graph,
361/// so that multiplying $M$ on the the right and left by the vectors
362/// of messages and signers respectively reproduces our original sum
363/// of pairings.
364///
365/// We first use elementary "row" operations to make $M$ upper
366/// triangular, as in Gaussian elimination, but at the cost of also
367/// performing one-sided "change of basis" operations that collect
368/// our original "basis vectors" into sums of curve points.
369/// We next use elementary "column" operations to make $M$ diagonal,
370/// again adjusting the basis with curve point operations.
371///
372/// In this, we regard $M$ as a matrix over the scalar field $F_p$
373/// so we may do row or column swaps and row or column addition
374/// operations with small scalars, but not lone row or column scalar
375/// multiplication because these always involve divisions, which
376/// produces large curve points that slow us down thereafter.
377/// We do not require such divisions because we do not solve any
378/// system of equations and do not need ones on the diagonal.
379///
380/// TODO:
381/// We leave implementing this optimization to near future work
382/// because it benifits from public keys being affine or having
383/// another hashable representation.
384///
385///
386/// As a curiosity, we note one interesting but suboptimal algorithm
387/// that avoids small scalar multiplications when doing this:
388///
389/// If we ignore subtraction, then the minimal number of pairing
390/// operations required to verify aggregated BLS signatures is the
391/// minimal bipartite edge cover, aka bipartite dimension, of the
392/// bipartite graph with vertices given by points on the two curves
393/// and edges given by desired pairings.
394/// In general, this problem is NP-hard even to approximate.
395/// See: https://en.wikipedia.org/wiki/Bipartite_dimension
396///
397/// There are polynomial time algorithms for bipartite edge cover in
398/// special cases, with domino-free graphs being among the widest
399/// known classes. See:
400/// Amilhastre, Jérôme; Janssen, Philippe; Vilarem, Marie-Catherine,
401/// "Computing a minimum biclique cover is polynomial for bipartite domino-free graphs" (1997)
402/// https://core.ac.uk/download/pdf/82546650.pdf
403///
404/// If we now exploit subtraction, then these dominos can be
405/// completed into $K_{3,3}$s, like
406/// $(a,x)+(a,y)+(b,x)+(b,y)+(b,z)+(c,y)+(c,z) = (a+b+c,x+y+z) - (a,z) - (c,z)$
407/// which looks optimal for itself, and likely permits the further
408/// aggregation, and maybe the subtracted terms can be aggregated later.
409///
410/// We could not however find the optimal numbers of pairings by
411/// completing dominos like this because (a+b+c,x+y+z) - (b,y),
412/// which looks optimal for itself, but only has one subtraction.
413fn verify_with_gaussian_elimination<S: Signed>(s: S) -> bool {
414 unimplemented!()
415}
416
417*/