1use 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#[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
32impl 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
59struct 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 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
150impl 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
166impl TryFrom<transparent::Output> for zcash_transparent::bundle::TxOut {
168 type Error = io::Error;
169
170 #[allow(clippy::needless_borrow)]
172 fn try_from(output: transparent::Output) -> Result<Self, Self::Error> {
173 (&output).try_into()
174 }
175}
176
177impl 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
194impl 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
201impl From<Script> for zcash_transparent::address::Script {
203 #[allow(clippy::needless_borrow)]
205 fn from(script: Script) -> Self {
206 (&script).into()
207 }
208}
209
210#[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 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 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 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#[derive(Debug)]
307enum SighashError {
308 InputIndexOutOfBounds {
311 input_index: usize,
312 input_count: usize,
313 },
314 NoTransparentBundle,
319 BundleInputCountMismatch {
326 input_index: usize,
327 bundle_vin_len: usize,
328 all_prev_outputs_len: usize,
329 },
330 InvalidPreviousOutputAmount,
334}
335
336impl std::fmt::Display for SighashError {
337 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
338 match self {
339 Self::InputIndexOutOfBounds {
340 input_index,
341 input_count,
342 } => write!(
343 f,
344 "input_index {input_index} is out of bounds (transaction has \
345 {input_count} transparent inputs)"
346 ),
347 Self::NoTransparentBundle => f.write_str(
348 "transparent sighash requested for a transaction with no \
349 transparent bundle (vin and vout both empty)",
350 ),
351 Self::BundleInputCountMismatch {
352 input_index,
353 bundle_vin_len,
354 all_prev_outputs_len,
355 } => write!(
356 f,
357 "input_index {input_index} valid for all_previous_outputs (len \
358 {all_prev_outputs_len}) but out of bounds for the parsed \
359 transparent bundle (vin len {bundle_vin_len}); this indicates \
360 a serialize/deserialize round-trip inconsistency"
361 ),
362 Self::InvalidPreviousOutputAmount => f.write_str(
363 "previous output amount could not be converted to Zatoshis; \
364 the amount should have been validated before sighash \
365 computation",
366 ),
367 }
368 }
369}
370
371impl std::error::Error for SighashError {}
372
373pub(crate) fn sighash(
393 precomputed_tx_data: &PrecomputedTxData,
394 hash_type: HashType,
395 input_index_script_code: Option<(usize, Vec<u8>)>,
396) -> SigHash {
397 sighash_inner(
398 precomputed_tx_data,
399 hash_type.try_into().expect("hash type should be canonical"),
400 input_index_script_code,
401 )
402 .expect(
403 "sighash precondition violated: callers must pass an in-bounds \
404 input_index when computing a transparent sighash, and the transaction \
405 must contain the transparent input being signed",
406 )
407}
408
409pub(crate) fn sighash_v4_raw(
419 precomputed_tx_data: &PrecomputedTxData,
420 raw_hash_type: u8,
421 input_index_script_code: Option<(usize, Vec<u8>)>,
422) -> SigHash {
423 sighash_inner(
424 precomputed_tx_data,
425 zcash_transparent::sighash::SighashType::from_raw(raw_hash_type),
426 input_index_script_code,
427 )
428 .expect(
429 "sighash precondition violated: callers must pass an in-bounds \
430 input_index when computing a transparent sighash, and the transaction \
431 must contain the transparent input being signed",
432 )
433}
434
435fn sighash_inner(
442 precomputed_tx_data: &PrecomputedTxData,
443 sighash_type: zcash_transparent::sighash::SighashType,
444 input_index_script_code: Option<(usize, Vec<u8>)>,
445) -> Result<SigHash, SighashError> {
446 let lock_script: zcash_transparent::address::Script;
447 let unlock_script: zcash_transparent::address::Script;
448 let signable_input = match input_index_script_code {
449 Some((input_index, script_code)) => {
450 let all_prev_outputs_len = precomputed_tx_data.all_previous_outputs.len();
457 let output = precomputed_tx_data
458 .all_previous_outputs
459 .get(input_index)
460 .ok_or(SighashError::InputIndexOutOfBounds {
461 input_index,
462 input_count: all_prev_outputs_len,
463 })?;
464 let bundle = precomputed_tx_data
471 .tx_data
472 .transparent_bundle()
473 .ok_or(SighashError::NoTransparentBundle)?;
474 lock_script = output.lock_script.clone().into();
475 unlock_script = zcash_transparent::address::Script(script::Code(script_code));
476 let value = output
477 .value
478 .try_into()
479 .map_err(|_| SighashError::InvalidPreviousOutputAmount)?;
480 let from_parts = zcash_transparent::sighash::SignableInput::from_parts(
481 bundle,
482 sighash_type,
483 input_index,
484 &unlock_script,
485 &lock_script,
486 value,
487 )
488 .map_err(|_| SighashError::BundleInputCountMismatch {
489 input_index,
490 bundle_vin_len: bundle.vin.len(),
491 all_prev_outputs_len,
492 })?;
493 zp_tx::sighash::SignableInput::Transparent(from_parts)
494 }
495 None => zp_tx::sighash::SignableInput::Shielded,
496 };
497
498 Ok(SigHash(
499 *zp_tx::sighash::signature_hash(
500 &precomputed_tx_data.tx_data,
501 &signable_input,
502 &precomputed_tx_data.txid_parts,
503 )
504 .as_ref(),
505 ))
506}
507
508pub(crate) fn auth_digest(tx: &Transaction) -> AuthDigest {
516 let nu = tx.network_upgrade().expect("V5 tx has a network upgrade");
517
518 AuthDigest(
519 tx.to_librustzcash(nu)
520 .expect("V5 tx is convertible to its `zcash_params` equivalent")
521 .auth_commitment()
522 .as_ref()
523 .try_into()
524 .expect("digest has the correct size"),
525 )
526}