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 ironwood_bundle(
294 &self,
295 ) -> Option<orchard::bundle::Bundle<orchard::bundle::Authorized, ZatBalance>> {
296 self.tx_data.ironwood_bundle().cloned()
297 }
298
299 pub fn sapling_bundle(
301 &self,
302 ) -> Option<sapling_crypto::Bundle<sapling_crypto::bundle::Authorized, ZatBalance>> {
303 self.tx_data.sapling_bundle().cloned()
304 }
305}
306
307#[derive(Debug)]
317enum SighashError {
318 InputIndexOutOfBounds {
321 input_index: usize,
322 input_count: usize,
323 },
324 NoTransparentBundle,
329 BundleInputCountMismatch {
336 input_index: usize,
337 bundle_vin_len: usize,
338 all_prev_outputs_len: usize,
339 },
340 InvalidPreviousOutputAmount,
344}
345
346impl std::fmt::Display for SighashError {
347 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
348 match self {
349 Self::InputIndexOutOfBounds {
350 input_index,
351 input_count,
352 } => write!(
353 f,
354 "input_index {input_index} is out of bounds (transaction has \
355 {input_count} transparent inputs)"
356 ),
357 Self::NoTransparentBundle => f.write_str(
358 "transparent sighash requested for a transaction with no \
359 transparent bundle (vin and vout both empty)",
360 ),
361 Self::BundleInputCountMismatch {
362 input_index,
363 bundle_vin_len,
364 all_prev_outputs_len,
365 } => write!(
366 f,
367 "input_index {input_index} valid for all_previous_outputs (len \
368 {all_prev_outputs_len}) but out of bounds for the parsed \
369 transparent bundle (vin len {bundle_vin_len}); this indicates \
370 a serialize/deserialize round-trip inconsistency"
371 ),
372 Self::InvalidPreviousOutputAmount => f.write_str(
373 "previous output amount could not be converted to Zatoshis; \
374 the amount should have been validated before sighash \
375 computation",
376 ),
377 }
378 }
379}
380
381impl std::error::Error for SighashError {}
382
383pub(crate) fn sighash(
403 precomputed_tx_data: &PrecomputedTxData,
404 hash_type: HashType,
405 input_index_script_code: Option<(usize, Vec<u8>)>,
406) -> SigHash {
407 sighash_inner(
408 precomputed_tx_data,
409 hash_type.try_into().expect("hash type should be canonical"),
410 input_index_script_code,
411 )
412 .expect(
413 "sighash precondition violated: callers must pass an in-bounds \
414 input_index when computing a transparent sighash, and the transaction \
415 must contain the transparent input being signed",
416 )
417}
418
419pub(crate) fn sighash_v4_raw(
429 precomputed_tx_data: &PrecomputedTxData,
430 raw_hash_type: u8,
431 input_index_script_code: Option<(usize, Vec<u8>)>,
432) -> SigHash {
433 sighash_inner(
434 precomputed_tx_data,
435 zcash_transparent::sighash::SighashType::from_raw(raw_hash_type),
436 input_index_script_code,
437 )
438 .expect(
439 "sighash precondition violated: callers must pass an in-bounds \
440 input_index when computing a transparent sighash, and the transaction \
441 must contain the transparent input being signed",
442 )
443}
444
445fn sighash_inner(
452 precomputed_tx_data: &PrecomputedTxData,
453 sighash_type: zcash_transparent::sighash::SighashType,
454 input_index_script_code: Option<(usize, Vec<u8>)>,
455) -> Result<SigHash, SighashError> {
456 let lock_script: zcash_transparent::address::Script;
457 let unlock_script: zcash_transparent::address::Script;
458 let signable_input = match input_index_script_code {
459 Some((input_index, script_code)) => {
460 let all_prev_outputs_len = precomputed_tx_data.all_previous_outputs.len();
467 let output = precomputed_tx_data
468 .all_previous_outputs
469 .get(input_index)
470 .ok_or(SighashError::InputIndexOutOfBounds {
471 input_index,
472 input_count: all_prev_outputs_len,
473 })?;
474 let bundle = precomputed_tx_data
481 .tx_data
482 .transparent_bundle()
483 .ok_or(SighashError::NoTransparentBundle)?;
484 lock_script = output.lock_script.clone().into();
485 unlock_script = zcash_transparent::address::Script(script::Code(script_code));
486 let value = output
487 .value
488 .try_into()
489 .map_err(|_| SighashError::InvalidPreviousOutputAmount)?;
490 let from_parts = zcash_transparent::sighash::SignableInput::from_parts(
491 bundle,
492 sighash_type,
493 input_index,
494 &unlock_script,
495 &lock_script,
496 value,
497 )
498 .map_err(|_| SighashError::BundleInputCountMismatch {
499 input_index,
500 bundle_vin_len: bundle.vin.len(),
501 all_prev_outputs_len,
502 })?;
503 zp_tx::sighash::SignableInput::Transparent(from_parts)
504 }
505 None => zp_tx::sighash::SignableInput::Shielded,
506 };
507
508 Ok(SigHash(
509 *zp_tx::sighash::signature_hash(
510 &precomputed_tx_data.tx_data,
511 &signable_input,
512 &precomputed_tx_data.txid_parts,
513 )
514 .as_ref(),
515 ))
516}
517
518pub(crate) fn auth_digest(tx: &Transaction) -> AuthDigest {
526 let nu = tx.network_upgrade().expect("V5 tx has a network upgrade");
527
528 AuthDigest(
529 tx.to_librustzcash(nu)
530 .expect("V5 tx is convertible to its `zcash_params` equivalent")
531 .auth_commitment()
532 .as_ref()
533 .try_into()
534 .expect("digest has the correct size"),
535 )
536}