zebra_consensus/primitives/groth16.rs
1//! Async Groth16 batch verifier service
2
3use std::{
4 fmt,
5 future::Future,
6 mem,
7 pin::Pin,
8 task::{Context, Poll},
9};
10
11use bellman::{
12 gadgets::multipack,
13 groth16::{batch, PreparedVerifyingKey, VerifyingKey},
14 VerificationError,
15};
16use bls12_381::Bls12;
17use futures::{future::BoxFuture, FutureExt};
18use once_cell::sync::Lazy;
19use rand::thread_rng;
20
21use tokio::sync::watch;
22use tower::{util::ServiceFn, Service};
23
24use tower_batch_control::{Batch, BatchControl};
25use tower_fallback::{BoxedError, Fallback};
26
27use zebra_chain::{
28 primitives::{
29 ed25519::{self, VerificationKeyBytes},
30 Groth16Proof,
31 },
32 sapling::{Output, PerSpendAnchor, Spend},
33 sprout::{JoinSplit, Nullifier, RandomSeed},
34};
35
36use crate::BoxError;
37
38use super::{spawn_fifo, spawn_fifo_and_convert};
39
40mod params;
41#[cfg(test)]
42mod tests;
43#[cfg(test)]
44mod vectors;
45
46pub use params::{Groth16Parameters, GROTH16_PARAMETERS};
47
48use crate::error::TransactionError;
49
50/// The type of the batch verifier.
51type BatchVerifier = batch::Verifier<Bls12>;
52
53/// The type of verification results.
54type VerifyResult = Result<(), VerificationError>;
55
56/// The type of the batch sender channel.
57type Sender = watch::Sender<Option<VerifyResult>>;
58
59/// The type of the batch item.
60/// This is a Groth16 verification item.
61pub type Item = batch::Item<Bls12>;
62
63/// The type of a raw verifying key.
64/// This is the key used to verify batches.
65pub type BatchVerifyingKey = VerifyingKey<Bls12>;
66
67/// The type of a prepared verifying key.
68/// This is the key used to verify individual items.
69pub type ItemVerifyingKey = PreparedVerifyingKey<Bls12>;
70
71/// Global batch verification context for Groth16 proofs of Spend statements.
72///
73/// This service transparently batches contemporaneous proof verifications,
74/// handling batch failures by falling back to individual verification.
75///
76/// Note that making a `Service` call requires mutable access to the service, so
77/// you should call `.clone()` on the global handle to create a local, mutable
78/// handle.
79pub static SPEND_VERIFIER: Lazy<
80 Fallback<
81 Batch<Verifier, Item>,
82 ServiceFn<fn(Item) -> BoxFuture<'static, Result<(), BoxError>>>,
83 >,
84> = Lazy::new(|| {
85 Fallback::new(
86 Batch::new(
87 Verifier::new(&GROTH16_PARAMETERS.sapling.spend.vk),
88 super::MAX_BATCH_SIZE,
89 None,
90 super::MAX_BATCH_LATENCY,
91 ),
92 // We want to fallback to individual verification if batch verification fails,
93 // so we need a Service to use.
94 //
95 // Because we have to specify the type of a static, we need to be able to
96 // write the type of the closure and its return value. But both closures and
97 // async blocks have unnameable types. So instead we cast the closure to a function
98 // (which is possible because it doesn't capture any state), and use a BoxFuture
99 // to erase the result type.
100 // (We can't use BoxCloneService to erase the service type, because it is !Sync.)
101 tower::service_fn(
102 (|item: Item| {
103 Verifier::verify_single_spawning(
104 item,
105 &GROTH16_PARAMETERS.sapling.spend_prepared_verifying_key,
106 )
107 .boxed()
108 }) as fn(_) -> _,
109 ),
110 )
111});
112
113/// Global batch verification context for Groth16 proofs of Output statements.
114///
115/// This service transparently batches contemporaneous proof verifications,
116/// handling batch failures by falling back to individual verification.
117///
118/// Note that making a `Service` call requires mutable access to the service, so
119/// you should call `.clone()` on the global handle to create a local, mutable
120/// handle.
121pub static OUTPUT_VERIFIER: Lazy<
122 Fallback<
123 Batch<Verifier, Item>,
124 ServiceFn<fn(Item) -> BoxFuture<'static, Result<(), BoxError>>>,
125 >,
126> = Lazy::new(|| {
127 Fallback::new(
128 Batch::new(
129 Verifier::new(&GROTH16_PARAMETERS.sapling.output.vk),
130 super::MAX_BATCH_SIZE,
131 None,
132 super::MAX_BATCH_LATENCY,
133 ),
134 // We want to fallback to individual verification if batch verification
135 // fails, so we need a Service to use.
136 //
137 // See the note on [`SPEND_VERIFIER`] for details.
138 tower::service_fn(
139 (|item: Item| {
140 Verifier::verify_single_spawning(
141 item,
142 &GROTH16_PARAMETERS.sapling.output_prepared_verifying_key,
143 )
144 .boxed()
145 }) as fn(_) -> _,
146 ),
147 )
148});
149
150/// Global batch verification context for Groth16 proofs of JoinSplit statements.
151///
152/// This service does not yet batch verifications, see
153/// <https://github.com/ZcashFoundation/zebra/issues/3127>
154///
155/// Note that making a `Service` call requires mutable access to the service, so
156/// you should call `.clone()` on the global handle to create a local, mutable
157/// handle.
158pub static JOINSPLIT_VERIFIER: Lazy<
159 ServiceFn<fn(Item) -> BoxFuture<'static, Result<(), BoxedError>>>,
160> = Lazy::new(|| {
161 // We just need a Service to use: there is no batch verification for JoinSplits.
162 //
163 // See the note on [`SPEND_VERIFIER`] for details.
164 tower::service_fn(
165 (|item: Item| {
166 Verifier::verify_single_spawning(
167 item,
168 &GROTH16_PARAMETERS.sprout.joinsplit_prepared_verifying_key,
169 )
170 .map(|result| {
171 result
172 .map_err(|e| TransactionError::Groth16(e.to_string()))
173 .map_err(tower_fallback::BoxedError::from)
174 })
175 .boxed()
176 }) as fn(_) -> _,
177 )
178});
179
180/// A Groth16 Description (JoinSplit, Spend, or Output) with a Groth16 proof
181/// and its inputs encoded as scalars.
182pub trait Description {
183 /// The Groth16 proof of this description.
184 fn proof(&self) -> &Groth16Proof;
185 /// The primary inputs for this proof, encoded as [`jubjub::Fq`] scalars.
186 fn primary_inputs(&self) -> Vec<jubjub::Fq>;
187}
188
189impl Description for Spend<PerSpendAnchor> {
190 /// Encodes the primary input for the Sapling Spend proof statement as 7 Bls12_381 base
191 /// field elements, to match [`bellman::groth16::verify_proof`] (the starting fixed element
192 /// `1` is filled in by [`bellman`].
193 ///
194 /// NB: jubjub::Fq is a type alias for bls12_381::Scalar.
195 ///
196 /// <https://zips.z.cash/protocol/protocol.pdf#cctsaplingspend>
197 fn primary_inputs(&self) -> Vec<jubjub::Fq> {
198 let mut inputs = vec![];
199
200 let rk_affine = jubjub::AffinePoint::from_bytes(self.rk.clone().into()).unwrap();
201 inputs.push(rk_affine.get_u());
202 inputs.push(rk_affine.get_v());
203
204 let cv_affine = jubjub::AffinePoint::from(self.cv);
205 inputs.push(cv_affine.get_u());
206 inputs.push(cv_affine.get_v());
207
208 // TODO: V4 only
209 inputs.push(jubjub::Fq::from_bytes(&self.per_spend_anchor.into()).unwrap());
210
211 let nullifier_limbs: [jubjub::Fq; 2] = self.nullifier.into();
212
213 inputs.push(nullifier_limbs[0]);
214 inputs.push(nullifier_limbs[1]);
215
216 inputs
217 }
218
219 fn proof(&self) -> &Groth16Proof {
220 &self.zkproof
221 }
222}
223
224impl Description for Output {
225 /// Encodes the primary input for the Sapling Output proof statement as 5 Bls12_381 base
226 /// field elements, to match [`bellman::groth16::verify_proof`] (the starting fixed element
227 /// `1` is filled in by [`bellman`].
228 ///
229 /// NB: [`jubjub::Fq`] is a type alias for [`bls12_381::Scalar`].
230 ///
231 /// <https://zips.z.cash/protocol/protocol.pdf#cctsaplingoutput>
232 fn primary_inputs(&self) -> Vec<jubjub::Fq> {
233 let mut inputs = vec![];
234
235 let cv_affine = jubjub::AffinePoint::from(self.cv);
236 inputs.push(cv_affine.get_u());
237 inputs.push(cv_affine.get_v());
238
239 let epk_affine = jubjub::AffinePoint::from_bytes(self.ephemeral_key.into()).unwrap();
240 inputs.push(epk_affine.get_u());
241 inputs.push(epk_affine.get_v());
242
243 inputs.push(self.cm_u);
244
245 inputs
246 }
247
248 fn proof(&self) -> &Groth16Proof {
249 &self.zkproof
250 }
251}
252
253/// Compute the [h_{Sig} hash function][1] which is used in JoinSplit descriptions.
254///
255/// `random_seed`: the random seed from the JoinSplit description.
256/// `nf1`: the first nullifier from the JoinSplit description.
257/// `nf2`: the second nullifier from the JoinSplit description.
258/// `joinsplit_pub_key`: the JoinSplit public validation key from the transaction.
259///
260/// [1]: https://zips.z.cash/protocol/protocol.pdf#hsigcrh
261pub(super) fn h_sig(
262 random_seed: &RandomSeed,
263 nf1: &Nullifier,
264 nf2: &Nullifier,
265 joinsplit_pub_key: &VerificationKeyBytes,
266) -> [u8; 32] {
267 let h_sig: [u8; 32] = blake2b_simd::Params::new()
268 .hash_length(32)
269 .personal(b"ZcashComputehSig")
270 .to_state()
271 .update(&(<[u8; 32]>::from(random_seed))[..])
272 .update(&(<[u8; 32]>::from(nf1))[..])
273 .update(&(<[u8; 32]>::from(nf2))[..])
274 .update(joinsplit_pub_key.as_ref())
275 .finalize()
276 .as_bytes()
277 .try_into()
278 .expect("32 byte array");
279 h_sig
280}
281
282impl Description for (&JoinSplit<Groth16Proof>, &ed25519::VerificationKeyBytes) {
283 /// Encodes the primary input for the JoinSplit proof statement as Bls12_381 base
284 /// field elements, to match [`bellman::groth16::verify_proof()`].
285 ///
286 /// NB: [`jubjub::Fq`] is a type alias for [`bls12_381::Scalar`].
287 ///
288 /// `joinsplit_pub_key`: the JoinSplit public validation key for this JoinSplit, from
289 /// the transaction. (All JoinSplits in a transaction share the same validation key.)
290 ///
291 /// This is not yet officially documented; see the reference implementation:
292 /// <https://github.com/zcash/librustzcash/blob/0ec7f97c976d55e1a194a37b27f247e8887fca1d/zcash_proofs/src/sprout.rs#L152-L166>
293 /// <https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc>
294 //
295 // The borrows are actually needed to avoid taking ownership
296 #[allow(clippy::needless_borrow)]
297 fn primary_inputs(&self) -> Vec<jubjub::Fq> {
298 let (joinsplit, joinsplit_pub_key) = self;
299
300 let rt: [u8; 32] = joinsplit.anchor.into();
301 let mac1: [u8; 32] = (&joinsplit.vmacs[0]).into();
302 let mac2: [u8; 32] = (&joinsplit.vmacs[1]).into();
303 let nf1: [u8; 32] = (&joinsplit.nullifiers[0]).into();
304 let nf2: [u8; 32] = (&joinsplit.nullifiers[1]).into();
305 let cm1: [u8; 32] = (&joinsplit.commitments[0]).into();
306 let cm2: [u8; 32] = (&joinsplit.commitments[1]).into();
307 let vpub_old = joinsplit.vpub_old.to_bytes();
308 let vpub_new = joinsplit.vpub_new.to_bytes();
309
310 let h_sig = h_sig(
311 &joinsplit.random_seed,
312 &joinsplit.nullifiers[0],
313 &joinsplit.nullifiers[1],
314 joinsplit_pub_key,
315 );
316
317 // Prepare the public input for the verifier
318 let mut public_input = Vec::with_capacity((32 * 8) + (8 * 2));
319 public_input.extend(rt);
320 public_input.extend(h_sig);
321 public_input.extend(nf1);
322 public_input.extend(mac1);
323 public_input.extend(nf2);
324 public_input.extend(mac2);
325 public_input.extend(cm1);
326 public_input.extend(cm2);
327 public_input.extend(vpub_old);
328 public_input.extend(vpub_new);
329
330 let public_input = multipack::bytes_to_bits(&public_input);
331
332 multipack::compute_multipacking(&public_input)
333 }
334
335 fn proof(&self) -> &Groth16Proof {
336 &self.0.zkproof
337 }
338}
339
340/// A wrapper to allow a TryFrom blanket implementation of the [`Description`]
341/// trait for the [`Item`] struct.
342/// See <https://github.com/rust-lang/rust/issues/50133> for more details.
343pub struct DescriptionWrapper<T>(pub T);
344
345impl<T> TryFrom<DescriptionWrapper<&T>> for Item
346where
347 T: Description,
348{
349 type Error = TransactionError;
350
351 fn try_from(input: DescriptionWrapper<&T>) -> Result<Self, Self::Error> {
352 // # Consensus
353 //
354 // > Elements of a JoinSplit description MUST have the types given above
355 //
356 // https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc
357 //
358 // This validates the 𝜋_{ZKJoinSplit} element. In #3179 we plan to validate
359 // during deserialization, see [`JoinSplit::zcash_deserialize`].
360 Ok(Item::from((
361 bellman::groth16::Proof::read(&input.0.proof().0[..])
362 .map_err(|e| TransactionError::MalformedGroth16(e.to_string()))?,
363 input.0.primary_inputs(),
364 )))
365 }
366}
367
368/// Groth16 signature verifier implementation
369///
370/// This is the core implementation for the batch verification logic of the groth
371/// verifier. It handles batching incoming requests, driving batches to
372/// completion, and reporting results.
373pub struct Verifier {
374 /// A batch verifier for groth16 proofs.
375 batch: BatchVerifier,
376
377 /// The proof verification key.
378 ///
379 /// Making this 'static makes managing lifetimes much easier.
380 vk: &'static BatchVerifyingKey,
381
382 /// A channel for broadcasting the result of a batch to the futures for each batch item.
383 ///
384 /// Each batch gets a newly created channel, so there is only ever one result sent per channel.
385 /// Tokio doesn't have a oneshot multi-consumer channel, so we use a watch channel.
386 tx: Sender,
387}
388
389impl Verifier {
390 /// Create and return a new verifier using the verification key `vk`.
391 fn new(vk: &'static BatchVerifyingKey) -> Self {
392 let batch = BatchVerifier::default();
393 let (tx, _) = watch::channel(None);
394 Self { batch, vk, tx }
395 }
396
397 /// Returns the batch verifier and channel sender from `self`,
398 /// replacing them with a new empty batch.
399 fn take(&mut self) -> (BatchVerifier, &'static BatchVerifyingKey, Sender) {
400 // Use a new verifier and channel for each batch.
401 let batch = mem::take(&mut self.batch);
402
403 let (tx, _) = watch::channel(None);
404 let tx = mem::replace(&mut self.tx, tx);
405
406 (batch, self.vk, tx)
407 }
408
409 /// Synchronously process the batch, and send the result using the channel sender.
410 /// This function blocks until the batch is completed.
411 fn verify(batch: BatchVerifier, vk: &'static BatchVerifyingKey, tx: Sender) {
412 let result = batch.verify(thread_rng(), vk);
413 let _ = tx.send(Some(result));
414 }
415
416 /// Flush the batch using a thread pool, and return the result via the channel.
417 /// This returns immediately, usually before the batch is completed.
418 fn flush_blocking(&mut self) {
419 let (batch, vk, tx) = self.take();
420
421 // Correctness: Do CPU-intensive work on a dedicated thread, to avoid blocking other futures.
422 //
423 // We don't care about execution order here, because this method is only called on drop.
424 tokio::task::block_in_place(|| rayon::spawn_fifo(|| Self::verify(batch, vk, tx)));
425 }
426
427 /// Flush the batch using a thread pool, and return the result via the channel.
428 /// This function returns a future that becomes ready when the batch is completed.
429 async fn flush_spawning(batch: BatchVerifier, vk: &'static BatchVerifyingKey, tx: Sender) {
430 // Correctness: Do CPU-intensive work on a dedicated thread, to avoid blocking other futures.
431 let _ = tx.send(
432 spawn_fifo(move || batch.verify(thread_rng(), vk))
433 .await
434 .ok(),
435 );
436 }
437
438 /// Verify a single item using a thread pool, and return the result.
439 async fn verify_single_spawning(
440 item: Item,
441 pvk: &'static ItemVerifyingKey,
442 ) -> Result<(), BoxError> {
443 // Correctness: Do CPU-intensive work on a dedicated thread, to avoid blocking other futures.
444 spawn_fifo_and_convert(move || item.verify_single(pvk)).await
445 }
446}
447
448impl fmt::Debug for Verifier {
449 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
450 let name = "Verifier";
451 f.debug_struct(name)
452 .field("batch", &"..")
453 .field("vk", &"..")
454 .field("tx", &self.tx)
455 .finish()
456 }
457}
458
459impl Service<BatchControl<Item>> for Verifier {
460 type Response = ();
461 type Error = BoxError;
462 type Future = Pin<Box<dyn Future<Output = Result<(), BoxError>> + Send + 'static>>;
463
464 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
465 Poll::Ready(Ok(()))
466 }
467
468 fn call(&mut self, req: BatchControl<Item>) -> Self::Future {
469 match req {
470 BatchControl::Item(item) => {
471 tracing::trace!("got item");
472 self.batch.queue(item);
473 let mut rx = self.tx.subscribe();
474
475 Box::pin(async move {
476 match rx.changed().await {
477 Ok(()) => {
478 // We use a new channel for each batch,
479 // so we always get the correct batch result here.
480 let result = rx
481 .borrow()
482 .as_ref()
483 .ok_or("threadpool unexpectedly dropped response channel sender. Is Zebra shutting down?")?
484 .clone();
485
486 if result.is_ok() {
487 tracing::trace!(?result, "verified groth16 proof");
488 metrics::counter!("proofs.groth16.verified").increment(1);
489 } else {
490 tracing::trace!(?result, "invalid groth16 proof");
491 metrics::counter!("proofs.groth16.invalid").increment(1);
492 }
493
494 result.map_err(BoxError::from)
495 }
496 Err(_recv_error) => panic!("verifier was dropped without flushing"),
497 }
498 })
499 }
500
501 BatchControl::Flush => {
502 tracing::trace!("got groth16 flush command");
503
504 let (batch, vk, tx) = self.take();
505
506 Box::pin(Self::flush_spawning(batch, vk, tx).map(Ok))
507 }
508 }
509 }
510}
511
512impl Drop for Verifier {
513 fn drop(&mut self) {
514 // We need to flush the current batch in case there are still any pending futures.
515 // This returns immediately, usually before the batch is completed.
516 self.flush_blocking()
517 }
518}