Skip to main content

zebra_consensus/primitives/
groth16.rs

1//! Async Groth16 verifier service for Sprout JoinSplit proofs
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::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/// Compute the [h_{Sig} hash function][1] which is used in JoinSplit descriptions.
105///
106/// `random_seed`: the random seed from the JoinSplit description.
107/// `nf1`: the first nullifier from the JoinSplit description.
108/// `nf2`: the second nullifier from the JoinSplit description.
109/// `joinsplit_pub_key`: the JoinSplit public validation key from the transaction.
110///
111/// [1]: https://zips.z.cash/protocol/protocol.pdf#hsigcrh
112pub(super) fn h_sig(
113    random_seed: &RandomSeed,
114    nf1: &Nullifier,
115    nf2: &Nullifier,
116    joinsplit_pub_key: &VerificationKeyBytes,
117) -> [u8; 32] {
118    let h_sig: [u8; 32] = blake2b_simd::Params::new()
119        .hash_length(32)
120        .personal(b"ZcashComputehSig")
121        .to_state()
122        .update(&(<[u8; 32]>::from(random_seed))[..])
123        .update(&(<[u8; 32]>::from(nf1))[..])
124        .update(&(<[u8; 32]>::from(nf2))[..])
125        .update(joinsplit_pub_key.as_ref())
126        .finalize()
127        .as_bytes()
128        .try_into()
129        .expect("32 byte array");
130    h_sig
131}
132
133impl Item {
134    /// Converts a Sprout JoinSplit description and its associated public key into a
135    /// Groth16 verification [`Item`].
136    ///
137    /// Encodes the primary input for the JoinSplit proof statement as Bls12_381 base
138    /// field elements, to match [`bellman::groth16::verify_proof()`].
139    ///
140    /// NB: [`jubjub::Fq`] is a type alias for [`bls12_381::Scalar`].
141    ///
142    /// `joinsplit_pub_key`: the JoinSplit public validation key for this JoinSplit, from
143    /// the transaction. (All JoinSplits in a transaction share the same validation key.)
144    ///
145    /// This is not yet officially documented; see the reference implementation:
146    /// <https://github.com/zcash/librustzcash/blob/0ec7f97c976d55e1a194a37b27f247e8887fca1d/zcash_proofs/src/sprout.rs#L152-L166>
147    /// <https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc>
148    // The borrows are actually needed to avoid taking ownership
149    #[allow(clippy::needless_borrow)]
150    pub fn from_joinsplit(
151        joinsplit: &JoinSplit<Groth16Proof>,
152        joinsplit_pub_key: &ed25519::VerificationKeyBytes,
153    ) -> Result<Self, TransactionError> {
154        let rt: [u8; 32] = joinsplit.anchor.into();
155        let mac1: [u8; 32] = (&joinsplit.vmacs[0]).into();
156        let mac2: [u8; 32] = (&joinsplit.vmacs[1]).into();
157        let nf1: [u8; 32] = (&joinsplit.nullifiers[0]).into();
158        let nf2: [u8; 32] = (&joinsplit.nullifiers[1]).into();
159        let cm1: [u8; 32] = (&joinsplit.commitments[0]).into();
160        let cm2: [u8; 32] = (&joinsplit.commitments[1]).into();
161        let vpub_old = joinsplit.vpub_old.to_bytes();
162        let vpub_new = joinsplit.vpub_new.to_bytes();
163
164        let h_sig = h_sig(
165            &joinsplit.random_seed,
166            &joinsplit.nullifiers[0],
167            &joinsplit.nullifiers[1],
168            joinsplit_pub_key,
169        );
170
171        // Prepare the public input for the verifier
172        let mut public_input = Vec::with_capacity((32 * 8) + (8 * 2));
173        public_input.extend(rt);
174        public_input.extend(h_sig);
175        public_input.extend(nf1);
176        public_input.extend(mac1);
177        public_input.extend(nf2);
178        public_input.extend(mac2);
179        public_input.extend(cm1);
180        public_input.extend(cm2);
181        public_input.extend(vpub_old);
182        public_input.extend(vpub_new);
183
184        let public_input = multipack::bytes_to_bits(&public_input);
185        let primary_inputs = multipack::compute_multipacking(&public_input);
186
187        // # Consensus
188        //
189        // > Elements of a JoinSplit description MUST have the types given above
190        //
191        // https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc
192        //
193        // This validates the 𝜋_{ZKJoinSplit} element. In #3179 we plan to validate
194        // during deserialization, see [`JoinSplit::zcash_deserialize`].
195        let proof = bellman::groth16::Proof::read(&joinsplit.zkproof.0[..])
196            .map_err(|e| TransactionError::MalformedGroth16(e.to_string()))?;
197
198        Ok(Item::from((proof, primary_inputs)))
199    }
200}
201
202/// Groth16 signature verifier implementation
203///
204/// This is the core implementation for the batch verification logic of the groth
205/// verifier. It handles batching incoming requests, driving batches to
206/// completion, and reporting results.
207pub struct Verifier {
208    /// A channel for broadcasting the result of a batch to the futures for each batch item.
209    ///
210    /// Each batch gets a newly created channel, so there is only ever one result sent per channel.
211    /// Tokio doesn't have a oneshot multi-consumer channel, so we use a watch channel.
212    tx: Sender,
213}
214
215impl Verifier {
216    /// Verify a single item using a thread pool, and return the result.
217    async fn verify_single_spawning(
218        item: Item,
219        pvk: &'static ItemVerifyingKey,
220    ) -> Result<(), BoxError> {
221        // Correctness: Do CPU-intensive work on a dedicated thread, to avoid blocking other futures.
222        spawn_fifo_and_convert(move || item.verify_single(pvk)).await
223    }
224}
225
226impl fmt::Debug for Verifier {
227    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228        let name = "Verifier";
229        f.debug_struct(name)
230            .field("batch", &"..")
231            .field("tx", &self.tx)
232            .finish()
233    }
234}