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 crate::BoxError;
21
22use super::spawn_fifo_and_convert;
23
24mod params;
25#[cfg(test)]
26mod tests;
27#[cfg(test)]
28mod vectors;
29
30pub use params::SPROUT;
31
32use crate::error::TransactionError;
33
34/// The type of verification results.
35type VerifyResult = Result<(), VerificationError>;
36
37/// The type of the batch sender channel.
38type Sender = watch::Sender<Option<VerifyResult>>;
39
40/// The type of the batch item.
41/// This is a newtype around a Groth16 verification item.
42#[derive(Clone, Debug)]
43pub struct Item(batch::Item<Bls12>);
44
45impl RequestWeight for Item {}
46
47impl<T: Into<batch::Item<Bls12>>> From<T> for Item {
48    fn from(value: T) -> Self {
49        Self(value.into())
50    }
51}
52
53impl Item {
54    /// Convenience method to call a method on the inner value to perform non-batched verification.
55    pub fn verify_single(self, pvk: &PreparedVerifyingKey<Bls12>) -> VerifyResult {
56        self.0.verify_single(pvk)
57    }
58}
59
60/// The type of a raw verifying key.
61/// This is the key used to verify batches.
62pub type BatchVerifyingKey = VerifyingKey<Bls12>;
63
64/// The type of a prepared verifying key.
65/// This is the key used to verify individual items.
66pub type ItemVerifyingKey = PreparedVerifyingKey<Bls12>;
67
68/// Global batch verification context for Groth16 proofs of JoinSplit statements.
69///
70/// This service does not yet batch verifications, see
71/// <https://github.com/ZcashFoundation/zebra/issues/3127>
72///
73/// Note that making a `Service` call requires mutable access to the service, so
74/// you should call `.clone()` on the global handle to create a local, mutable
75/// handle.
76pub static JOINSPLIT_VERIFIER: Lazy<
77    ServiceFn<fn(Item) -> BoxFuture<'static, Result<(), BoxedError>>>,
78> = Lazy::new(|| {
79    // We just need a Service to use: there is no batch verification for JoinSplits.
80    //
81    // See the note on [`SPEND_VERIFIER`] for details.
82    tower::service_fn(
83        (|item: Item| {
84            // TODO: Simplify the call stack here.
85            Verifier::verify_single_spawning(item, SPROUT.prepared_verifying_key())
86                .map(|result| {
87                    result
88                        .map_err(|e| TransactionError::Groth16(e.to_string()))
89                        .map_err(tower_fallback::BoxedError::from)
90                })
91                .boxed()
92        }) as fn(_) -> _,
93    )
94});
95
96/// Compute the [h_{Sig} hash function][1] which is used in JoinSplit descriptions.
97///
98/// `random_seed`: the random seed from the JoinSplit description.
99/// `nf1`: the first nullifier from the JoinSplit description.
100/// `nf2`: the second nullifier from the JoinSplit description.
101/// `joinsplit_pub_key`: the JoinSplit public validation key from the transaction.
102///
103/// [1]: https://zips.z.cash/protocol/protocol.pdf#hsigcrh
104pub(super) fn h_sig(
105    random_seed: &[u8; 32],
106    nf1: &[u8; 32],
107    nf2: &[u8; 32],
108    joinsplit_pub_key: &[u8; 32],
109) -> [u8; 32] {
110    let h_sig: [u8; 32] = blake2b_simd::Params::new()
111        .hash_length(32)
112        .personal(b"ZcashComputehSig")
113        .to_state()
114        .update(random_seed)
115        .update(nf1)
116        .update(nf2)
117        .update(joinsplit_pub_key)
118        .finalize()
119        .as_bytes()
120        .try_into()
121        .expect("32 byte array");
122    h_sig
123}
124
125/// Create a Groth16 verification [`Item`] from a JoinSplit description and public key.
126pub fn joinsplit_to_item(
127    js: &zcash_primitives::transaction::components::sprout::JsDescription,
128    joinsplit_pub_key: &[u8; 32],
129) -> Result<Item, TransactionError> {
130    let rt = js.anchor();
131    let nf1 = &js.nullifiers()[0];
132    let nf2 = &js.nullifiers()[1];
133    let mac1 = &js.macs()[0];
134    let mac2 = &js.macs()[1];
135    let cm1 = &js.commitments()[0];
136    let cm2 = &js.commitments()[1];
137
138    let h_sig = h_sig(js.random_seed(), nf1, nf2, joinsplit_pub_key);
139
140    let vpub_old = js.vpub_old().to_i64_le_bytes();
141    let vpub_new = js.vpub_new().to_i64_le_bytes();
142
143    let mut public_input = Vec::with_capacity((32 * 8) + (8 * 2));
144    public_input.extend(rt);
145    public_input.extend(h_sig);
146    public_input.extend(nf1);
147    public_input.extend(mac1);
148    public_input.extend(nf2);
149    public_input.extend(mac2);
150    public_input.extend(cm1);
151    public_input.extend(cm2);
152    public_input.extend(vpub_old);
153    public_input.extend(vpub_new);
154
155    let public_input = multipack::bytes_to_bits(&public_input);
156    let primary_inputs = multipack::compute_multipacking(&public_input);
157
158    // V4 transactions always use Groth16 proofs for JoinSplits (V2/V3 use PHGR13).
159    // The proof type is determined at deserialization time by the transaction version
160    // (see zcash_primitives JsDescription::read), so a V4 sprout bundle will always
161    // contain Groth16 proofs. This function must only be called for V4 transactions.
162    let proof_bytes = js.groth_proof_bytes().ok_or_else(|| {
163        TransactionError::MalformedGroth16(
164            "expected Groth16 proof in JoinSplit but found PHGR13 (wrong transaction version?)"
165                .into(),
166        )
167    })?;
168
169    let proof = bellman::groth16::Proof::read(&proof_bytes[..])
170        .map_err(|e| TransactionError::MalformedGroth16(e.to_string()))?;
171
172    Ok(Item::from((proof, primary_inputs)))
173}
174
175/// Groth16 signature verifier implementation
176///
177/// This is the core implementation for the batch verification logic of the groth
178/// verifier. It handles batching incoming requests, driving batches to
179/// completion, and reporting results.
180pub struct Verifier {
181    /// A channel for broadcasting the result of a batch to the futures for each batch item.
182    ///
183    /// Each batch gets a newly created channel, so there is only ever one result sent per channel.
184    /// Tokio doesn't have a oneshot multi-consumer channel, so we use a watch channel.
185    tx: Sender,
186}
187
188impl Verifier {
189    /// Verify a single item using a thread pool, and return the result.
190    async fn verify_single_spawning(
191        item: Item,
192        pvk: &'static ItemVerifyingKey,
193    ) -> Result<(), BoxError> {
194        // Correctness: Do CPU-intensive work on a dedicated thread, to avoid blocking other futures.
195        spawn_fifo_and_convert(move || item.verify_single(pvk)).await
196    }
197}
198
199impl fmt::Debug for Verifier {
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        let name = "Verifier";
202        f.debug_struct(name)
203            .field("batch", &"..")
204            .field("tx", &self.tx)
205            .finish()
206    }
207}