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