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}