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