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};
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<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    #[cfg(zcash_unstable = "zfuture")]
144    type TzeAuth = zp_tx::components::tze::Authorized;
145}
146
147// End of (mostly) copied code
148
149/// Convert a Zebra transparent::Output into a librustzcash one.
150impl TryFrom<&transparent::Output> for zcash_transparent::bundle::TxOut {
151    type Error = io::Error;
152
153    #[allow(clippy::unwrap_in_result)]
154    fn try_from(output: &transparent::Output) -> Result<Self, Self::Error> {
155        let serialized_output_bytes = output
156            .zcash_serialize_to_vec()
157            .expect("zcash_primitives and Zebra transparent output formats must be compatible");
158
159        zcash_transparent::bundle::TxOut::read(&mut serialized_output_bytes.as_slice())
160    }
161}
162
163/// Convert a Zebra transparent::Output into a librustzcash one.
164impl TryFrom<transparent::Output> for zcash_transparent::bundle::TxOut {
165    type Error = io::Error;
166
167    // The borrow is actually needed to use TryFrom<&transparent::Output>
168    #[allow(clippy::needless_borrow)]
169    fn try_from(output: transparent::Output) -> Result<Self, Self::Error> {
170        (&output).try_into()
171    }
172}
173
174/// Convert a Zebra non-negative Amount into a librustzcash one.
175impl TryFrom<Amount<NonNegative>> for zcash_protocol::value::Zatoshis {
176    type Error = BalanceError;
177
178    fn try_from(amount: Amount<NonNegative>) -> Result<Self, Self::Error> {
179        zcash_protocol::value::Zatoshis::from_nonnegative_i64(amount.into())
180    }
181}
182
183impl TryFrom<Amount> for ZatBalance {
184    type Error = BalanceError;
185
186    fn try_from(amount: Amount) -> Result<Self, Self::Error> {
187        ZatBalance::from_i64(amount.into())
188    }
189}
190
191/// Convert a Zebra Script into a librustzcash one.
192impl From<&Script> for zcash_primitives::legacy::Script {
193    fn from(script: &Script) -> Self {
194        zcash_primitives::legacy::Script(script.as_raw_bytes().to_vec())
195    }
196}
197
198/// Convert a Zebra Script into a librustzcash one.
199impl From<Script> for zcash_primitives::legacy::Script {
200    // The borrow is actually needed to use From<&Script>
201    #[allow(clippy::needless_borrow)]
202    fn from(script: Script) -> Self {
203        (&script).into()
204    }
205}
206
207/// Precomputed data used for sighash or txid computation.
208#[derive(Debug)]
209pub(crate) struct PrecomputedTxData {
210    tx_data: zp_tx::TransactionData<PrecomputedAuth>,
211    txid_parts: TxDigests<blake2b_simd::Hash>,
212    all_previous_outputs: Arc<Vec<transparent::Output>>,
213}
214
215impl PrecomputedTxData {
216    /// Computes the data used for sighash or txid computation.
217    ///
218    /// # Inputs
219    ///
220    /// - `tx`: the relevant transaction.
221    /// - `nu`: the network upgrade to which the transaction belongs.
222    /// - `all_previous_outputs`: the transparent Output matching each transparent input in `tx`.
223    ///
224    /// # Errors
225    ///
226    /// - If `tx` can't be converted to its `librustzcash` equivalent.
227    /// - If `nu` doesn't contain a consensus branch id convertible to its `librustzcash`
228    ///   equivalent.
229    ///
230    /// # Consensus
231    ///
232    /// > [NU5 only, pre-NU6] All transactions MUST use the NU5 consensus branch ID `0xF919A198` as
233    /// > defined in [ZIP-252].
234    ///
235    /// > [NU6 only] All transactions MUST use the NU6 consensus branch ID `0xC8E71055` as defined
236    /// > in  [ZIP-253].
237    ///
238    /// # Notes
239    ///
240    /// The check that ensures compliance with the two consensus rules stated above takes place in
241    /// the [`Transaction::to_librustzcash`] method. If the check fails, the tx can't be converted
242    /// to its `librustzcash` equivalent, which leads to an error. The check relies on the passed
243    /// `nu` parameter, which uniquely represents a consensus branch id and can, therefore, be used
244    /// as an equivalent to a consensus branch id. The desired `nu` is set either by the script or
245    /// tx verifier in `zebra-consensus`.
246    ///
247    /// [ZIP-252]: <https://zips.z.cash/zip-0252>
248    /// [ZIP-253]: <https://zips.z.cash/zip-0253>
249    pub(crate) fn new(
250        tx: &Transaction,
251        nu: NetworkUpgrade,
252        all_previous_outputs: Arc<Vec<transparent::Output>>,
253    ) -> Result<PrecomputedTxData, Error> {
254        let tx = tx.to_librustzcash(nu)?;
255
256        let txid_parts = tx.deref().digest(zp_tx::txid::TxIdDigester);
257
258        let f_transparent = MapTransparent {
259            auth: TransparentAuth {
260                all_prev_outputs: all_previous_outputs.clone(),
261            },
262        };
263
264        let tx_data: zp_tx::TransactionData<PrecomputedAuth> = tx.into_data().map_authorization(
265            f_transparent,
266            IdentityMap,
267            IdentityMap,
268            #[cfg(zcash_unstable = "zfuture")]
269            (),
270        );
271
272        Ok(PrecomputedTxData {
273            tx_data,
274            txid_parts,
275            all_previous_outputs,
276        })
277    }
278
279    /// Returns the Orchard bundle in `tx_data`.
280    pub fn orchard_bundle(
281        &self,
282    ) -> Option<orchard::bundle::Bundle<orchard::bundle::Authorized, ZatBalance>> {
283        self.tx_data.orchard_bundle().cloned()
284    }
285
286    /// Returns the Sapling bundle in `tx_data`.
287    pub fn sapling_bundle(
288        &self,
289    ) -> Option<sapling_crypto::Bundle<sapling_crypto::bundle::Authorized, ZatBalance>> {
290        self.tx_data.sapling_bundle().cloned()
291    }
292}
293
294/// Compute a signature hash using librustzcash.
295///
296/// # Inputs
297///
298/// - `precomputed_tx_data`: precomputed data for the transaction whose
299///   signature hash is being computed.
300/// - `hash_type`: the type of hash (SIGHASH) being used.
301/// - `input_index_script_code`: a tuple with the index of the transparent Input
302///   for which we are producing a sighash and the respective script code being
303///   validated, or None if it's a shielded input.
304pub(crate) fn sighash(
305    precomputed_tx_data: &PrecomputedTxData,
306    hash_type: HashType,
307    input_index_script_code: Option<(usize, Vec<u8>)>,
308) -> SigHash {
309    let lock_script: zcash_primitives::legacy::Script;
310    let unlock_script: zcash_primitives::legacy::Script;
311    let signable_input = match input_index_script_code {
312        Some((input_index, script_code)) => {
313            let output = &precomputed_tx_data.all_previous_outputs[input_index];
314            lock_script = output.lock_script.clone().into();
315            unlock_script = zcash_primitives::legacy::Script(script_code);
316            zp_tx::sighash::SignableInput::Transparent(
317                zcash_transparent::sighash::SignableInput::from_parts(
318                    hash_type.try_into().expect("hash type should be ALL"),
319                    input_index,
320                    &unlock_script,
321                    &lock_script,
322                    output
323                        .value
324                        .try_into()
325                        .expect("amount was previously validated"),
326                ),
327            )
328        }
329        None => zp_tx::sighash::SignableInput::Shielded,
330    };
331
332    SigHash(
333        *zp_tx::sighash::signature_hash(
334            &precomputed_tx_data.tx_data,
335            &signable_input,
336            &precomputed_tx_data.txid_parts,
337        )
338        .as_ref(),
339    )
340}
341
342/// Compute the authorizing data commitment of this transaction as specified in [ZIP-244].
343///
344/// # Panics
345///
346/// If passed a pre-v5 transaction.
347///
348/// [ZIP-244]: https://zips.z.cash/zip-0244
349pub(crate) fn auth_digest(tx: &Transaction) -> AuthDigest {
350    let nu = tx.network_upgrade().expect("V5 tx has a network upgrade");
351
352    AuthDigest(
353        tx.to_librustzcash(nu)
354            .expect("V5 tx is convertible to its `zcash_params` equivalent")
355            .auth_commitment()
356            .as_ref()
357            .try_into()
358            .expect("digest has the correct size"),
359    )
360}