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 serialization::ZcashSerialize,
13 transaction::{HashType, SigHash},
14 transparent::{self, Script},
15 Error,
16};
17
18use crate::{parameters::NetworkUpgrade, transaction::Transaction};
19
20#[derive(Clone, Debug)]
25struct TransparentAuth {
26 all_prev_outputs: Arc<Vec<transparent::Output>>,
27}
28
29impl zcash_transparent::bundle::Authorization for TransparentAuth {
30 type ScriptSig = zcash_transparent::address::Script;
31}
32
33impl zcash_transparent::sighash::TransparentAuthorizingContext for TransparentAuth {
36 fn input_amounts(&self) -> Vec<Zatoshis> {
37 self.all_prev_outputs
38 .iter()
39 .map(|prevout| {
40 prevout
41 .value
42 .try_into()
43 .expect("will not fail since it was previously validated")
44 })
45 .collect()
46 }
47
48 fn input_scriptpubkeys(&self) -> Vec<zcash_transparent::address::Script> {
49 self.all_prev_outputs
50 .iter()
51 .map(|prevout| {
52 zcash_transparent::address::Script(script::Code(
53 prevout.lock_script.as_raw_bytes().into(),
54 ))
55 })
56 .collect()
57 }
58}
59
60struct MapTransparent {
65 auth: TransparentAuth,
66}
67
68impl zcash_transparent::bundle::MapAuth<zcash_transparent::bundle::Authorized, TransparentAuth>
69 for MapTransparent
70{
71 fn map_script_sig(
72 &self,
73 s: <zcash_transparent::bundle::Authorized as zcash_transparent::bundle::Authorization>::ScriptSig,
74 ) -> <TransparentAuth as zcash_transparent::bundle::Authorization>::ScriptSig {
75 s
76 }
77
78 fn map_authorization(&self, _: zcash_transparent::bundle::Authorized) -> TransparentAuth {
79 self.auth.clone()
81 }
82}
83
84struct IdentityMap;
85
86impl
87 zp_tx::components::sapling::MapAuth<
88 sapling_crypto::bundle::Authorized,
89 sapling_crypto::bundle::Authorized,
90 > for IdentityMap
91{
92 fn map_spend_proof(
93 &mut self,
94 p: <sapling_crypto::bundle::Authorized as sapling_crypto::bundle::Authorization>::SpendProof,
95 ) -> <sapling_crypto::bundle::Authorized as sapling_crypto::bundle::Authorization>::SpendProof
96 {
97 p
98 }
99
100 fn map_output_proof(
101 &mut self,
102 p: <sapling_crypto::bundle::Authorized as sapling_crypto::bundle::Authorization>::OutputProof,
103 ) -> <sapling_crypto::bundle::Authorized as sapling_crypto::bundle::Authorization>::OutputProof
104 {
105 p
106 }
107
108 fn map_auth_sig(
109 &mut self,
110 s: <sapling_crypto::bundle::Authorized as sapling_crypto::bundle::Authorization>::AuthSig,
111 ) -> <sapling_crypto::bundle::Authorized as sapling_crypto::bundle::Authorization>::AuthSig
112 {
113 s
114 }
115
116 fn map_authorization(
117 &mut self,
118 a: sapling_crypto::bundle::Authorized,
119 ) -> sapling_crypto::bundle::Authorized {
120 a
121 }
122}
123
124impl zp_tx::components::orchard::MapAuth<orchard::bundle::Authorized, orchard::bundle::Authorized>
125 for IdentityMap
126{
127 fn map_spend_auth(
128 &self,
129 s: <orchard::bundle::Authorized as orchard::bundle::Authorization>::SpendAuth,
130 ) -> <orchard::bundle::Authorized as orchard::bundle::Authorization>::SpendAuth {
131 s
132 }
133
134 fn map_authorization(&self, a: orchard::bundle::Authorized) -> orchard::bundle::Authorized {
135 a
136 }
137}
138
139#[derive(Debug)]
140struct PrecomputedAuth {}
141
142impl zp_tx::Authorization for PrecomputedAuth {
143 type TransparentAuth = TransparentAuth;
144 type SaplingAuth = sapling_crypto::bundle::Authorized;
145 type OrchardAuth = orchard::bundle::Authorized;
146
147 #[cfg(zcash_unstable = "zfuture")]
148 type TzeAuth = zp_tx::components::tze::Authorized;
149}
150
151impl TryFrom<&transparent::Output> for zcash_transparent::bundle::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 zcash_transparent::bundle::TxOut::read(&mut serialized_output_bytes.as_slice())
164 }
165}
166
167impl TryFrom<transparent::Output> for zcash_transparent::bundle::TxOut {
169 type Error = io::Error;
170
171 #[allow(clippy::needless_borrow)]
173 fn try_from(output: transparent::Output) -> Result<Self, Self::Error> {
174 (&output).try_into()
175 }
176}
177
178impl TryFrom<Amount<NonNegative>> for zcash_protocol::value::Zatoshis {
180 type Error = BalanceError;
181
182 fn try_from(amount: Amount<NonNegative>) -> Result<Self, Self::Error> {
183 zcash_protocol::value::Zatoshis::from_nonnegative_i64(amount.into())
184 }
185}
186
187impl TryFrom<Amount> for ZatBalance {
188 type Error = BalanceError;
189
190 fn try_from(amount: Amount) -> Result<Self, Self::Error> {
191 ZatBalance::from_i64(amount.into())
192 }
193}
194
195impl From<&Script> for zcash_transparent::address::Script {
197 fn from(script: &Script) -> Self {
198 zcash_transparent::address::Script(script::Code(script.as_raw_bytes().to_vec()))
199 }
200}
201
202impl From<Script> for zcash_transparent::address::Script {
204 #[allow(clippy::needless_borrow)]
206 fn from(script: Script) -> Self {
207 (&script).into()
208 }
209}
210
211#[derive(Debug)]
213pub(crate) struct PrecomputedTxData {
214 tx_data: zp_tx::TransactionData<PrecomputedAuth>,
215 txid_parts: TxDigests<blake2b_simd::Hash>,
216 all_previous_outputs: Arc<Vec<transparent::Output>>,
217}
218
219impl PrecomputedTxData {
220 pub(crate) fn new(
226 tx: &Transaction,
227 nu: NetworkUpgrade,
228 all_previous_outputs: Arc<Vec<transparent::Output>>,
229 ) -> Result<PrecomputedTxData, Error> {
230 let branch_id = nu
231 .branch_id()
232 .and_then(|cbid| zcash_protocol::consensus::BranchId::try_from(cbid).ok())
233 .ok_or(Error::InvalidConsensusBranchId)?;
234
235 let tx_branch_id = tx.inner().deref().consensus_branch_id();
238 if tx.version() >= 5 && tx_branch_id != branch_id {
239 return Err(Error::InvalidConsensusBranchId);
240 }
241
242 Self::from_transaction_with_branch_id(tx, branch_id, all_previous_outputs)
243 }
244
245 fn from_transaction_with_branch_id(
250 tx: &crate::transaction::Transaction,
251 branch_id: zcash_protocol::consensus::BranchId,
252 all_previous_outputs: Arc<Vec<transparent::Output>>,
253 ) -> Result<PrecomputedTxData, Error> {
254 let inner = tx.inner();
255 let txid_parts = inner.deref().digest(zp_tx::txid::TxIdDigester);
256
257 let data = inner.clone().into_data();
265 let data_with_branch_id = crate::transaction::compat::transaction_data_from_parts(
266 data.version(),
267 branch_id,
268 data.lock_time(),
269 data.expiry_height(),
270 data.transparent_bundle().cloned(),
271 data.sprout_bundle().cloned(),
272 data.sapling_bundle().cloned(),
273 data.orchard_bundle().cloned(),
274 data.ironwood_bundle().cloned(),
275 );
276
277 let tx_data: zp_tx::TransactionData<PrecomputedAuth> = data_with_branch_id
278 .map_authorization(
279 MapTransparent {
280 auth: TransparentAuth {
281 all_prev_outputs: all_previous_outputs.clone(),
282 },
283 },
284 IdentityMap,
285 IdentityMap,
286 #[cfg(zcash_unstable = "zfuture")]
287 (),
288 );
289
290 Ok(PrecomputedTxData {
291 tx_data,
292 txid_parts,
293 all_previous_outputs,
294 })
295 }
296
297 pub fn orchard_bundle(
299 &self,
300 ) -> Option<orchard::bundle::Bundle<orchard::bundle::Authorized, ZatBalance>> {
301 self.tx_data.orchard_bundle().cloned()
302 }
303
304 pub fn ironwood_bundle(
309 &self,
310 ) -> Option<orchard::bundle::Bundle<orchard::bundle::Authorized, ZatBalance>> {
311 self.tx_data.ironwood_bundle().cloned()
312 }
313
314 pub fn sapling_bundle(
316 &self,
317 ) -> Option<sapling_crypto::Bundle<sapling_crypto::bundle::Authorized, ZatBalance>> {
318 self.tx_data.sapling_bundle().cloned()
319 }
320}
321
322#[derive(Debug)]
332enum SighashError {
333 InputIndexOutOfBounds {
336 input_index: usize,
337 input_count: usize,
338 },
339 NoTransparentBundle,
344 BundleInputCountMismatch {
351 input_index: usize,
352 bundle_vin_len: usize,
353 all_prev_outputs_len: usize,
354 },
355 InvalidPreviousOutputAmount,
359}
360
361impl std::fmt::Display for SighashError {
362 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
363 match self {
364 Self::InputIndexOutOfBounds {
365 input_index,
366 input_count,
367 } => write!(
368 f,
369 "input_index {input_index} is out of bounds (transaction has \
370 {input_count} transparent inputs)"
371 ),
372 Self::NoTransparentBundle => f.write_str(
373 "transparent sighash requested for a transaction with no \
374 transparent bundle (vin and vout both empty)",
375 ),
376 Self::BundleInputCountMismatch {
377 input_index,
378 bundle_vin_len,
379 all_prev_outputs_len,
380 } => write!(
381 f,
382 "input_index {input_index} valid for all_previous_outputs (len \
383 {all_prev_outputs_len}) but out of bounds for the parsed \
384 transparent bundle (vin len {bundle_vin_len}); this indicates \
385 a serialize/deserialize round-trip inconsistency"
386 ),
387 Self::InvalidPreviousOutputAmount => f.write_str(
388 "previous output amount could not be converted to Zatoshis; \
389 the amount should have been validated before sighash \
390 computation",
391 ),
392 }
393 }
394}
395
396impl std::error::Error for SighashError {}
397
398pub(crate) fn sighash(
418 precomputed_tx_data: &PrecomputedTxData,
419 hash_type: HashType,
420 input_index_script_code: Option<(usize, Vec<u8>)>,
421) -> SigHash {
422 sighash_inner(
423 precomputed_tx_data,
424 hash_type.try_into().expect("hash type should be canonical"),
425 input_index_script_code,
426 )
427 .expect(
428 "sighash precondition violated: callers must pass an in-bounds \
429 input_index when computing a transparent sighash, and the transaction \
430 must contain the transparent input being signed",
431 )
432}
433
434pub(crate) fn sighash_v4_raw(
444 precomputed_tx_data: &PrecomputedTxData,
445 raw_hash_type: u8,
446 input_index_script_code: Option<(usize, Vec<u8>)>,
447) -> SigHash {
448 sighash_inner(
449 precomputed_tx_data,
450 zcash_transparent::sighash::SighashType::from_raw(raw_hash_type),
451 input_index_script_code,
452 )
453 .expect(
454 "sighash precondition violated: callers must pass an in-bounds \
455 input_index when computing a transparent sighash, and the transaction \
456 must contain the transparent input being signed",
457 )
458}
459
460fn sighash_inner(
467 precomputed_tx_data: &PrecomputedTxData,
468 sighash_type: zcash_transparent::sighash::SighashType,
469 input_index_script_code: Option<(usize, Vec<u8>)>,
470) -> Result<SigHash, SighashError> {
471 let lock_script: zcash_transparent::address::Script;
472 let unlock_script: zcash_transparent::address::Script;
473 let signable_input = match input_index_script_code {
474 Some((input_index, script_code)) => {
475 let all_prev_outputs_len = precomputed_tx_data.all_previous_outputs.len();
482 let output = precomputed_tx_data
483 .all_previous_outputs
484 .get(input_index)
485 .ok_or(SighashError::InputIndexOutOfBounds {
486 input_index,
487 input_count: all_prev_outputs_len,
488 })?;
489 let bundle = precomputed_tx_data
496 .tx_data
497 .transparent_bundle()
498 .ok_or(SighashError::NoTransparentBundle)?;
499 lock_script = output.lock_script.clone().into();
500 unlock_script = zcash_transparent::address::Script(script::Code(script_code));
501 let value = output
502 .value
503 .try_into()
504 .map_err(|_| SighashError::InvalidPreviousOutputAmount)?;
505 let from_parts = zcash_transparent::sighash::SignableInput::from_parts(
506 bundle,
507 sighash_type,
508 input_index,
509 &unlock_script,
510 &lock_script,
511 value,
512 )
513 .map_err(|_| SighashError::BundleInputCountMismatch {
514 input_index,
515 bundle_vin_len: bundle.vin.len(),
516 all_prev_outputs_len,
517 })?;
518 zp_tx::sighash::SignableInput::Transparent(from_parts)
519 }
520 None => zp_tx::sighash::SignableInput::Shielded,
521 };
522
523 Ok(SigHash(
524 *zp_tx::sighash::signature_hash(
525 &precomputed_tx_data.tx_data,
526 &signable_input,
527 &precomputed_tx_data.txid_parts,
528 )
529 .as_ref(),
530 ))
531}