zebra_consensus/primitives/
groth16.rs

1//! Async Groth16 batch verifier service
2
3use std::fmt;
4
5use bellman::{
6    gadgets::multipack,
7    groth16::{batch, PreparedVerifyingKey, VerifyingKey},
8    VerificationError,
9};
10use bls12_381::Bls12;
11use futures::{future::BoxFuture, FutureExt};
12use once_cell::sync::Lazy;
13
14use tokio::sync::watch;
15use tower::util::ServiceFn;
16
17use tower_batch_control::RequestWeight;
18use tower_fallback::BoxedError;
19
20use zebra_chain::{
21    primitives::{
22        ed25519::{self, VerificationKeyBytes},
23        Groth16Proof,
24    },
25    sapling::{Output, PerSpendAnchor, Spend},
26    sprout::{JoinSplit, Nullifier, RandomSeed},
27};
28
29use crate::BoxError;
30
31use super::spawn_fifo_and_convert;
32
33mod params;
34#[cfg(test)]
35mod tests;
36#[cfg(test)]
37mod vectors;
38
39pub use params::{SAPLING, SPROUT};
40
41use crate::error::TransactionError;
42
43/// The type of verification results.
44type VerifyResult = Result<(), VerificationError>;
45
46/// The type of the batch sender channel.
47type Sender = watch::Sender<Option<VerifyResult>>;
48
49/// The type of the batch item.
50/// This is a newtype around a Groth16 verification item.
51#[derive(Clone, Debug)]
52pub struct Item(batch::Item<Bls12>);
53
54impl RequestWeight for Item {}
55
56impl<T: Into<batch::Item<Bls12>>> From<T> for Item {
57    fn from(value: T) -> Self {
58        Self(value.into())
59    }
60}
61
62impl Item {
63    /// Convenience method to call a method on the inner value to perform non-batched verification.
64    pub fn verify_single(self, pvk: &PreparedVerifyingKey<Bls12>) -> VerifyResult {
65        self.0.verify_single(pvk)
66    }
67}
68
69/// The type of a raw verifying key.
70/// This is the key used to verify batches.
71pub type BatchVerifyingKey = VerifyingKey<Bls12>;
72
73/// The type of a prepared verifying key.
74/// This is the key used to verify individual items.
75pub type ItemVerifyingKey = PreparedVerifyingKey<Bls12>;
76
77/// Global batch verification context for Groth16 proofs of JoinSplit statements.
78///
79/// This service does not yet batch verifications, see
80/// <https://github.com/ZcashFoundation/zebra/issues/3127>
81///
82/// Note that making a `Service` call requires mutable access to the service, so
83/// you should call `.clone()` on the global handle to create a local, mutable
84/// handle.
85pub static JOINSPLIT_VERIFIER: Lazy<
86    ServiceFn<fn(Item) -> BoxFuture<'static, Result<(), BoxedError>>>,
87> = Lazy::new(|| {
88    // We just need a Service to use: there is no batch verification for JoinSplits.
89    //
90    // See the note on [`SPEND_VERIFIER`] for details.
91    tower::service_fn(
92        (|item: Item| {
93            // TODO: Simplify the call stack here.
94            Verifier::verify_single_spawning(item, SPROUT.prepared_verifying_key())
95                .map(|result| {
96                    result
97                        .map_err(|e| TransactionError::Groth16(e.to_string()))
98                        .map_err(tower_fallback::BoxedError::from)
99                })
100                .boxed()
101        }) as fn(_) -> _,
102    )
103});
104
105/// A Groth16 Description (JoinSplit, Spend, or Output) with a Groth16 proof
106/// and its inputs encoded as scalars.
107pub trait Description {
108    /// The Groth16 proof of this description.
109    fn proof(&self) -> &Groth16Proof;
110    /// The primary inputs for this proof, encoded as [`jubjub::Fq`] scalars.
111    fn primary_inputs(&self) -> Vec<jubjub::Fq>;
112}
113
114impl Description for Spend<PerSpendAnchor> {
115    /// Encodes the primary input for the Sapling Spend proof statement as 7 Bls12_381 base
116    /// field elements, to match [`bellman::groth16::verify_proof`] (the starting fixed element
117    /// `1` is filled in by [`bellman`].
118    ///
119    /// NB: jubjub::Fq is a type alias for bls12_381::Scalar.
120    ///
121    /// <https://zips.z.cash/protocol/protocol.pdf#cctsaplingspend>
122    fn primary_inputs(&self) -> Vec<jubjub::Fq> {
123        let mut inputs = vec![];
124
125        let rk_affine = jubjub::AffinePoint::from_bytes(self.rk.clone().into()).unwrap();
126        inputs.push(rk_affine.get_u());
127        inputs.push(rk_affine.get_v());
128
129        let cv_affine = jubjub::AffinePoint::from(self.cv);
130        inputs.push(cv_affine.get_u());
131        inputs.push(cv_affine.get_v());
132
133        // TODO: V4 only
134        inputs.push(jubjub::Fq::from_bytes(&self.per_spend_anchor.into()).unwrap());
135
136        let nullifier_limbs: [jubjub::Fq; 2] = self.nullifier.into();
137
138        inputs.push(nullifier_limbs[0]);
139        inputs.push(nullifier_limbs[1]);
140
141        inputs
142    }
143
144    fn proof(&self) -> &Groth16Proof {
145        &self.zkproof
146    }
147}
148
149impl Description for Output {
150    /// Encodes the primary input for the Sapling Output proof statement as 5 Bls12_381 base
151    /// field elements, to match [`bellman::groth16::verify_proof`] (the starting fixed element
152    /// `1` is filled in by [`bellman`].
153    ///
154    /// NB: [`jubjub::Fq`] is a type alias for [`bls12_381::Scalar`].
155    ///
156    /// <https://zips.z.cash/protocol/protocol.pdf#cctsaplingoutput>
157    fn primary_inputs(&self) -> Vec<jubjub::Fq> {
158        let mut inputs = vec![];
159
160        let cv_affine = jubjub::AffinePoint::from(self.cv);
161        inputs.push(cv_affine.get_u());
162        inputs.push(cv_affine.get_v());
163
164        let epk_affine = jubjub::AffinePoint::from_bytes(self.ephemeral_key.into()).unwrap();
165        inputs.push(epk_affine.get_u());
166        inputs.push(epk_affine.get_v());
167
168        inputs.push(self.cm_u);
169
170        inputs
171    }
172
173    fn proof(&self) -> &Groth16Proof {
174        &self.zkproof
175    }
176}
177
178/// Compute the [h_{Sig} hash function][1] which is used in JoinSplit descriptions.
179///
180/// `random_seed`: the random seed from the JoinSplit description.
181/// `nf1`: the first nullifier from the JoinSplit description.
182/// `nf2`: the second nullifier from the JoinSplit description.
183/// `joinsplit_pub_key`: the JoinSplit public validation key from the transaction.
184///
185/// [1]: https://zips.z.cash/protocol/protocol.pdf#hsigcrh
186pub(super) fn h_sig(
187    random_seed: &RandomSeed,
188    nf1: &Nullifier,
189    nf2: &Nullifier,
190    joinsplit_pub_key: &VerificationKeyBytes,
191) -> [u8; 32] {
192    let h_sig: [u8; 32] = blake2b_simd::Params::new()
193        .hash_length(32)
194        .personal(b"ZcashComputehSig")
195        .to_state()
196        .update(&(<[u8; 32]>::from(random_seed))[..])
197        .update(&(<[u8; 32]>::from(nf1))[..])
198        .update(&(<[u8; 32]>::from(nf2))[..])
199        .update(joinsplit_pub_key.as_ref())
200        .finalize()
201        .as_bytes()
202        .try_into()
203        .expect("32 byte array");
204    h_sig
205}
206
207impl Description for (&JoinSplit<Groth16Proof>, &ed25519::VerificationKeyBytes) {
208    /// Encodes the primary input for the JoinSplit proof statement as Bls12_381 base
209    /// field elements, to match [`bellman::groth16::verify_proof()`].
210    ///
211    /// NB: [`jubjub::Fq`] is a type alias for [`bls12_381::Scalar`].
212    ///
213    /// `joinsplit_pub_key`: the JoinSplit public validation key for this JoinSplit, from
214    /// the transaction. (All JoinSplits in a transaction share the same validation key.)
215    ///
216    /// This is not yet officially documented; see the reference implementation:
217    /// <https://github.com/zcash/librustzcash/blob/0ec7f97c976d55e1a194a37b27f247e8887fca1d/zcash_proofs/src/sprout.rs#L152-L166>
218    /// <https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc>
219    //
220    // The borrows are actually needed to avoid taking ownership
221    #[allow(clippy::needless_borrow)]
222    fn primary_inputs(&self) -> Vec<jubjub::Fq> {
223        let (joinsplit, joinsplit_pub_key) = self;
224
225        let rt: [u8; 32] = joinsplit.anchor.into();
226        let mac1: [u8; 32] = (&joinsplit.vmacs[0]).into();
227        let mac2: [u8; 32] = (&joinsplit.vmacs[1]).into();
228        let nf1: [u8; 32] = (&joinsplit.nullifiers[0]).into();
229        let nf2: [u8; 32] = (&joinsplit.nullifiers[1]).into();
230        let cm1: [u8; 32] = (&joinsplit.commitments[0]).into();
231        let cm2: [u8; 32] = (&joinsplit.commitments[1]).into();
232        let vpub_old = joinsplit.vpub_old.to_bytes();
233        let vpub_new = joinsplit.vpub_new.to_bytes();
234
235        let h_sig = h_sig(
236            &joinsplit.random_seed,
237            &joinsplit.nullifiers[0],
238            &joinsplit.nullifiers[1],
239            joinsplit_pub_key,
240        );
241
242        // Prepare the public input for the verifier
243        let mut public_input = Vec::with_capacity((32 * 8) + (8 * 2));
244        public_input.extend(rt);
245        public_input.extend(h_sig);
246        public_input.extend(nf1);
247        public_input.extend(mac1);
248        public_input.extend(nf2);
249        public_input.extend(mac2);
250        public_input.extend(cm1);
251        public_input.extend(cm2);
252        public_input.extend(vpub_old);
253        public_input.extend(vpub_new);
254
255        let public_input = multipack::bytes_to_bits(&public_input);
256
257        multipack::compute_multipacking(&public_input)
258    }
259
260    fn proof(&self) -> &Groth16Proof {
261        &self.0.zkproof
262    }
263}
264
265/// A wrapper to allow a TryFrom blanket implementation of the [`Description`]
266/// trait for the [`Item`] struct.
267/// See <https://github.com/rust-lang/rust/issues/50133> for more details.
268pub struct DescriptionWrapper<T>(pub T);
269
270impl<T> TryFrom<DescriptionWrapper<&T>> for Item
271where
272    T: Description,
273{
274    type Error = TransactionError;
275
276    fn try_from(input: DescriptionWrapper<&T>) -> Result<Self, Self::Error> {
277        // # Consensus
278        //
279        // > Elements of a JoinSplit description MUST have the types given above
280        //
281        // https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc
282        //
283        // This validates the 𝜋_{ZKJoinSplit} element. In #3179 we plan to validate
284        // during deserialization, see [`JoinSplit::zcash_deserialize`].
285        Ok(Item::from((
286            bellman::groth16::Proof::read(&input.0.proof().0[..])
287                .map_err(|e| TransactionError::MalformedGroth16(e.to_string()))?,
288            input.0.primary_inputs(),
289        )))
290    }
291}
292
293/// Groth16 signature verifier implementation
294///
295/// This is the core implementation for the batch verification logic of the groth
296/// verifier. It handles batching incoming requests, driving batches to
297/// completion, and reporting results.
298pub struct Verifier {
299    /// A channel for broadcasting the result of a batch to the futures for each batch item.
300    ///
301    /// Each batch gets a newly created channel, so there is only ever one result sent per channel.
302    /// Tokio doesn't have a oneshot multi-consumer channel, so we use a watch channel.
303    tx: Sender,
304}
305
306impl Verifier {
307    /// Verify a single item using a thread pool, and return the result.
308    async fn verify_single_spawning(
309        item: Item,
310        pvk: &'static ItemVerifyingKey,
311    ) -> Result<(), BoxError> {
312        // Correctness: Do CPU-intensive work on a dedicated thread, to avoid blocking other futures.
313        spawn_fifo_and_convert(move || item.verify_single(pvk)).await
314    }
315}
316
317impl fmt::Debug for Verifier {
318    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319        let name = "Verifier";
320        f.debug_struct(name)
321            .field("batch", &"..")
322            .field("tx", &self.tx)
323            .finish()
324    }
325}