Skip to main content

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