zebra_chain/primitives/
zcash_primitives.rs

1//! Contains code that interfaces with the zcash_primitives crate from
2//! librustzcash.
3
4use std::{io, ops::Deref, sync::Arc};
5
6use zcash_primitives::transaction::{self as zp_tx, TxDigests};
7use zcash_protocol::value::{BalanceError, ZatBalance, Zatoshis};
8use zcash_script::script;
9
10use crate::{
11    amount::{Amount, NonNegative},
12    parameters::NetworkUpgrade,
13    serialization::ZcashSerialize,
14    transaction::{AuthDigest, HashType, SigHash, Transaction},
15    transparent::{self, Script},
16    Error,
17};
18
19// TODO: move copied and modified code to a separate module.
20//
21// Used by boilerplate code below.
22
23#[derive(Clone, Debug)]
24struct TransparentAuth {
25    all_prev_outputs: Arc<Vec<transparent::Output>>,
26}
27
28impl zcash_transparent::bundle::Authorization for TransparentAuth {
29    type ScriptSig = zcash_transparent::address::Script;
30}
31
32// In this block we convert our Output to a librustzcash to TxOut.
33// (We could do the serialize/deserialize route but it's simple enough to convert manually)
34impl zcash_transparent::sighash::TransparentAuthorizingContext for TransparentAuth {
35    fn input_amounts(&self) -> Vec<Zatoshis> {
36        self.all_prev_outputs
37            .iter()
38            .map(|prevout| {
39                prevout
40                    .value
41                    .try_into()
42                    .expect("will not fail since it was previously validated")
43            })
44            .collect()
45    }
46
47    fn input_scriptpubkeys(&self) -> Vec<zcash_transparent::address::Script> {
48        self.all_prev_outputs
49            .iter()
50            .map(|prevout| {
51                zcash_transparent::address::Script(script::Code(
52                    prevout.lock_script.as_raw_bytes().into(),
53                ))
54            })
55            .collect()
56    }
57}
58
59// Boilerplate mostly copied from `zcash/src/rust/src/transaction_ffi.rs` which is required
60// to compute sighash.
61// TODO: remove/change if they improve the API to not require this.
62
63struct MapTransparent {
64    auth: TransparentAuth,
65}
66
67impl zcash_transparent::bundle::MapAuth<zcash_transparent::bundle::Authorized, TransparentAuth>
68    for MapTransparent
69{
70    fn map_script_sig(
71        &self,
72        s: <zcash_transparent::bundle::Authorized as zcash_transparent::bundle::Authorization>::ScriptSig,
73    ) -> <TransparentAuth as zcash_transparent::bundle::Authorization>::ScriptSig {
74        s
75    }
76
77    fn map_authorization(&self, _: zcash_transparent::bundle::Authorized) -> TransparentAuth {
78        // TODO: This map should consume self, so we can move self.auth
79        self.auth.clone()
80    }
81}
82
83struct IdentityMap;
84
85impl
86    zp_tx::components::sapling::MapAuth<
87        sapling_crypto::bundle::Authorized,
88        sapling_crypto::bundle::Authorized,
89    > for IdentityMap
90{
91    fn map_spend_proof(
92        &mut self,
93        p: <sapling_crypto::bundle::Authorized as sapling_crypto::bundle::Authorization>::SpendProof,
94    ) -> <sapling_crypto::bundle::Authorized as sapling_crypto::bundle::Authorization>::SpendProof
95    {
96        p
97    }
98
99    fn map_output_proof(
100        &mut self,
101        p: <sapling_crypto::bundle::Authorized as sapling_crypto::bundle::Authorization>::OutputProof,
102    ) -> <sapling_crypto::bundle::Authorized as sapling_crypto::bundle::Authorization>::OutputProof
103    {
104        p
105    }
106
107    fn map_auth_sig(
108        &mut self,
109        s: <sapling_crypto::bundle::Authorized as sapling_crypto::bundle::Authorization>::AuthSig,
110    ) -> <sapling_crypto::bundle::Authorized as sapling_crypto::bundle::Authorization>::AuthSig
111    {
112        s
113    }
114
115    fn map_authorization(
116        &mut self,
117        a: sapling_crypto::bundle::Authorized,
118    ) -> sapling_crypto::bundle::Authorized {
119        a
120    }
121}
122
123impl zp_tx::components::orchard::MapAuth<orchard::bundle::Authorized, orchard::bundle::Authorized>
124    for IdentityMap
125{
126    fn map_spend_auth(
127        &self,
128        s: <orchard::bundle::Authorized as orchard::bundle::Authorization>::SpendAuth,
129    ) -> <orchard::bundle::Authorized as orchard::bundle::Authorization>::SpendAuth {
130        s
131    }
132
133    fn map_authorization(&self, a: orchard::bundle::Authorized) -> orchard::bundle::Authorized {
134        a
135    }
136}
137
138#[derive(Debug)]
139struct PrecomputedAuth {}
140
141impl zp_tx::Authorization for PrecomputedAuth {
142    type TransparentAuth = TransparentAuth;
143    type SaplingAuth = sapling_crypto::bundle::Authorized;
144    type OrchardAuth = orchard::bundle::Authorized;
145
146    #[cfg(zcash_unstable = "zfuture")]
147    type TzeAuth = zp_tx::components::tze::Authorized;
148}
149
150// End of (mostly) copied code
151
152/// Convert a Zebra transparent::Output into a librustzcash one.
153impl TryFrom<&transparent::Output> for zcash_transparent::bundle::TxOut {
154    type Error = io::Error;
155
156    #[allow(clippy::unwrap_in_result)]
157    fn try_from(output: &transparent::Output) -> Result<Self, Self::Error> {
158        let serialized_output_bytes = output
159            .zcash_serialize_to_vec()
160            .expect("zcash_primitives and Zebra transparent output formats must be compatible");
161
162        zcash_transparent::bundle::TxOut::read(&mut serialized_output_bytes.as_slice())
163    }
164}
165
166/// Convert a Zebra transparent::Output into a librustzcash one.
167impl TryFrom<transparent::Output> for zcash_transparent::bundle::TxOut {
168    type Error = io::Error;
169
170    // The borrow is actually needed to use TryFrom<&transparent::Output>
171    #[allow(clippy::needless_borrow)]
172    fn try_from(output: transparent::Output) -> Result<Self, Self::Error> {
173        (&output).try_into()
174    }
175}
176
177/// Convert a Zebra non-negative Amount into a librustzcash one.
178impl TryFrom<Amount<NonNegative>> for zcash_protocol::value::Zatoshis {
179    type Error = BalanceError;
180
181    fn try_from(amount: Amount<NonNegative>) -> Result<Self, Self::Error> {
182        zcash_protocol::value::Zatoshis::from_nonnegative_i64(amount.into())
183    }
184}
185
186impl TryFrom<Amount> for ZatBalance {
187    type Error = BalanceError;
188
189    fn try_from(amount: Amount) -> Result<Self, Self::Error> {
190        ZatBalance::from_i64(amount.into())
191    }
192}
193
194/// Convert a Zebra Script into a librustzcash one.
195impl From<&Script> for zcash_transparent::address::Script {
196    fn from(script: &Script) -> Self {
197        zcash_transparent::address::Script(script::Code(script.as_raw_bytes().to_vec()))
198    }
199}
200
201/// Convert a Zebra Script into a librustzcash one.
202impl From<Script> for zcash_transparent::address::Script {
203    // The borrow is actually needed to use From<&Script>
204    #[allow(clippy::needless_borrow)]
205    fn from(script: Script) -> Self {
206        (&script).into()
207    }
208}
209
210/// Precomputed data used for sighash or txid computation.
211#[derive(Debug)]
212pub(crate) struct PrecomputedTxData {
213    tx_data: zp_tx::TransactionData<PrecomputedAuth>,
214    txid_parts: TxDigests<blake2b_simd::Hash>,
215    all_previous_outputs: Arc<Vec<transparent::Output>>,
216}
217
218impl PrecomputedTxData {
219    /// Computes the data used for sighash or txid computation.
220    ///
221    /// # Inputs
222    ///
223    /// - `tx`: the relevant transaction.
224    /// - `nu`: the network upgrade to which the transaction belongs.
225    /// - `all_previous_outputs`: the transparent Output matching each transparent input in `tx`.
226    ///
227    /// # Errors
228    ///
229    /// - If `tx` can't be converted to its `librustzcash` equivalent.
230    /// - If `nu` doesn't contain a consensus branch id convertible to its `librustzcash`
231    ///   equivalent.
232    ///
233    /// # Consensus
234    ///
235    /// > [NU5 only, pre-NU6] All transactions MUST use the NU5 consensus branch ID `0xF919A198` as
236    /// > defined in [ZIP-252].
237    ///
238    /// > [NU6 only] All transactions MUST use the NU6 consensus branch ID `0xC8E71055` as defined
239    /// > in  [ZIP-253].
240    ///
241    /// # Notes
242    ///
243    /// The check that ensures compliance with the two consensus rules stated above takes place in
244    /// the [`Transaction::to_librustzcash`] method. If the check fails, the tx can't be converted
245    /// to its `librustzcash` equivalent, which leads to an error. The check relies on the passed
246    /// `nu` parameter, which uniquely represents a consensus branch id and can, therefore, be used
247    /// as an equivalent to a consensus branch id. The desired `nu` is set either by the script or
248    /// tx verifier in `zebra-consensus`.
249    ///
250    /// [ZIP-252]: <https://zips.z.cash/zip-0252>
251    /// [ZIP-253]: <https://zips.z.cash/zip-0253>
252    pub(crate) fn new(
253        tx: &Transaction,
254        nu: NetworkUpgrade,
255        all_previous_outputs: Arc<Vec<transparent::Output>>,
256    ) -> Result<PrecomputedTxData, Error> {
257        let tx = tx.to_librustzcash(nu)?;
258
259        let txid_parts = tx.deref().digest(zp_tx::txid::TxIdDigester);
260
261        let f_transparent = MapTransparent {
262            auth: TransparentAuth {
263                all_prev_outputs: all_previous_outputs.clone(),
264            },
265        };
266
267        let tx_data: zp_tx::TransactionData<PrecomputedAuth> = tx.into_data().map_authorization(
268            f_transparent,
269            IdentityMap,
270            IdentityMap,
271            #[cfg(zcash_unstable = "zfuture")]
272            (),
273        );
274
275        Ok(PrecomputedTxData {
276            tx_data,
277            txid_parts,
278            all_previous_outputs,
279        })
280    }
281
282    /// Returns the Orchard bundle in `tx_data`.
283    pub fn orchard_bundle(
284        &self,
285    ) -> Option<orchard::bundle::Bundle<orchard::bundle::Authorized, ZatBalance>> {
286        self.tx_data.orchard_bundle().cloned()
287    }
288
289    /// Returns the Sapling bundle in `tx_data`.
290    pub fn sapling_bundle(
291        &self,
292    ) -> Option<sapling_crypto::Bundle<sapling_crypto::bundle::Authorized, ZatBalance>> {
293        self.tx_data.sapling_bundle().cloned()
294    }
295}
296
297/// Compute a signature hash using librustzcash.
298///
299/// # Inputs
300///
301/// - `precomputed_tx_data`: precomputed data for the transaction whose
302///   signature hash is being computed.
303/// - `hash_type`: the type of hash (SIGHASH) being used.
304/// - `input_index_script_code`: a tuple with the index of the transparent Input
305///   for which we are producing a sighash and the respective script code being
306///   validated, or None if it's a shielded input.
307pub(crate) fn sighash(
308    precomputed_tx_data: &PrecomputedTxData,
309    hash_type: HashType,
310    input_index_script_code: Option<(usize, Vec<u8>)>,
311) -> SigHash {
312    let lock_script: zcash_transparent::address::Script;
313    let unlock_script: zcash_transparent::address::Script;
314    let signable_input = match input_index_script_code {
315        Some((input_index, script_code)) => {
316            let output = &precomputed_tx_data.all_previous_outputs[input_index];
317            lock_script = output.lock_script.clone().into();
318            unlock_script = zcash_transparent::address::Script(script::Code(script_code));
319            zp_tx::sighash::SignableInput::Transparent(
320                zcash_transparent::sighash::SignableInput::from_parts(
321                    hash_type.try_into().expect("hash type should be ALL"),
322                    input_index,
323                    &unlock_script,
324                    &lock_script,
325                    output
326                        .value
327                        .try_into()
328                        .expect("amount was previously validated"),
329                ),
330            )
331        }
332        None => zp_tx::sighash::SignableInput::Shielded,
333    };
334
335    SigHash(
336        *zp_tx::sighash::signature_hash(
337            &precomputed_tx_data.tx_data,
338            &signable_input,
339            &precomputed_tx_data.txid_parts,
340        )
341        .as_ref(),
342    )
343}
344
345/// Compute the authorizing data commitment of this transaction as specified in [ZIP-244].
346///
347/// # Panics
348///
349/// If passed a pre-v5 transaction.
350///
351/// [ZIP-244]: https://zips.z.cash/zip-0244
352pub(crate) fn auth_digest(tx: &Transaction) -> AuthDigest {
353    let nu = tx.network_upgrade().expect("V5 tx has a network upgrade");
354
355    AuthDigest(
356        tx.to_librustzcash(nu)
357            .expect("V5 tx is convertible to its `zcash_params` equivalent")
358            .auth_commitment()
359            .as_ref()
360            .try_into()
361            .expect("digest has the correct size"),
362    )
363}