1#[cfg(any(test, feature = "proptest-impl"))]
4use zcash_protocol::value::Zatoshis;
5
6use zcash_primitives::transaction::{self as zp_tx};
7
8use crate::{
9 amount::Amount,
10 block,
11 serialization::SerializationError,
12 transaction::{self, LockTime},
13 transparent::{self, OutPoint, Script},
14};
15
16pub fn txin_to_input(
22 txin: &zcash_transparent::bundle::TxIn<zcash_transparent::bundle::Authorized>,
23) -> Result<transparent::Input, SerializationError> {
24 if *txin.prevout() == zcash_transparent::bundle::OutPoint::NULL {
25 let script_bytes = txin.script_sig().0 .0.clone();
31 let (height, data) = if script_bytes.as_slice()
32 == crate::transparent::serialize::GENESIS_COINBASE_SCRIPT_SIG
33 {
34 (
35 block::Height::MIN,
36 crate::transparent::serialize::GENESIS_COINBASE_SCRIPT_SIG.to_vec(),
37 )
38 } else {
39 crate::transparent::serialize::parse_coinbase_height(&script_bytes)?
40 };
41 Ok(transparent::Input::Coinbase {
42 height,
43 data,
44 sequence: txin.sequence(),
45 })
46 } else {
47 let prevout = txin.prevout();
48 let hash_bytes: [u8; 32] = *prevout.hash();
49 Ok(transparent::Input::PrevOut {
50 outpoint: OutPoint {
51 hash: transaction::Hash(hash_bytes),
52 index: prevout.n(),
53 },
54 unlock_script: Script::new(&txin.script_sig().0 .0),
55 sequence: txin.sequence(),
56 })
57 }
58}
59
60#[cfg(any(test, feature = "proptest-impl"))]
62pub fn input_to_txin(
63 input: &transparent::Input,
64) -> zcash_transparent::bundle::TxIn<zcash_transparent::bundle::Authorized> {
65 match input {
66 transparent::Input::PrevOut {
67 outpoint,
68 unlock_script,
69 sequence,
70 } => {
71 let zp_outpoint =
72 zcash_transparent::bundle::OutPoint::new(outpoint.hash.0, outpoint.index);
73 zcash_transparent::bundle::TxIn::from_parts(
74 zp_outpoint,
75 zcash_transparent::address::Script(zcash_script::script::Code(
76 unlock_script.as_raw_bytes().to_vec(),
77 )),
78 *sequence,
79 )
80 }
81 transparent::Input::Coinbase { sequence, .. } => {
82 let script_bytes = input
87 .coinbase_script()
88 .expect("coinbase_script reconstructs from a deserialized coinbase input");
89
90 zcash_transparent::bundle::TxIn::from_parts(
91 zcash_transparent::bundle::OutPoint::NULL,
92 zcash_transparent::address::Script(zcash_script::script::Code(script_bytes)),
93 *sequence,
94 )
95 }
96 }
97}
98
99pub fn txout_to_output(txout: &zcash_transparent::bundle::TxOut) -> transparent::Output {
103 let value_u64: u64 = txout.value().into();
104 transparent::Output {
105 value: Amount::try_from(value_u64 as i64)
106 .expect("librustzcash Zatoshis is always a valid non-negative Amount"),
107 lock_script: Script::new(&txout.script_pubkey().0 .0),
108 }
109}
110
111#[cfg(any(test, feature = "proptest-impl"))]
113pub fn output_to_txout(output: &transparent::Output) -> zcash_transparent::bundle::TxOut {
114 let zatoshis = Zatoshis::from_nonnegative_i64(output.value.into())
115 .expect("Zebra Amount<NonNegative> is always a valid Zatoshis");
116 zcash_transparent::bundle::TxOut::new(
117 zatoshis,
118 zcash_transparent::address::Script(zcash_script::script::Code(
119 output.lock_script.as_raw_bytes().to_vec(),
120 )),
121 )
122}
123
124pub fn u32_to_lock_time(lock_time: u32) -> LockTime {
131 use crate::serialization::ZcashDeserialize;
133 let bytes = lock_time.to_le_bytes();
134 LockTime::zcash_deserialize(&bytes[..]).expect("all u32 values are valid LockTimes")
135}
136
137#[cfg(any(test, feature = "proptest-impl"))]
139pub fn lock_time_to_u32(lock_time: &LockTime) -> u32 {
140 use crate::serialization::ZcashSerialize;
141 let mut buf = Vec::with_capacity(4);
142 lock_time
143 .zcash_serialize(&mut buf)
144 .expect("serializing LockTime to vec should not fail");
145 u32::from_le_bytes(
146 buf.try_into()
147 .expect("LockTime serializes to exactly 4 bytes"),
148 )
149}
150
151#[cfg(any(test, feature = "proptest-impl"))]
155pub fn height_to_block_height(h: block::Height) -> zcash_protocol::consensus::BlockHeight {
156 h.into()
157}
158
159#[cfg(any(test, feature = "proptest-impl", feature = "elasticsearch"))]
161pub fn block_height_to_height(
162 bh: zcash_protocol::consensus::BlockHeight,
163) -> Result<block::Height, SerializationError> {
164 block::Height::try_from(bh)
165 .map_err(|_| SerializationError::Parse("block height out of valid range"))
166}
167
168pub(crate) fn branch_id_to_network_upgrade(
174 branch_id: zcash_protocol::consensus::BranchId,
175) -> Option<crate::parameters::NetworkUpgrade> {
176 crate::parameters::NetworkUpgrade::try_from(u32::from(branch_id)).ok()
177}
178
179const ZC_NUM_JS_OUTPUTS: usize = 2;
183
184pub const SPROUT_CIPHERTEXT_SIZE: usize = 601;
186
187const GROTH_PROOF_SIZE: usize = 48 + 96 + 48;
189
190const PHGR_PROOF_SIZE: usize = 33 + 33 + 65 + 33 + 33 + 33 + 33 + 33;
192
193const JS_EPHEMERAL_KEY_OFFSET: usize = 8 + 8 + 32 + (2 * 32) + (2 * 32);
196
197pub fn sprout_joinsplit_key_proof_and_ciphertexts(
211 joinsplit: &zcash_primitives::transaction::components::sprout::JsDescription,
212) -> (
213 [u8; 32],
214 Vec<u8>,
215 [[u8; SPROUT_CIPHERTEXT_SIZE]; ZC_NUM_JS_OUTPUTS],
216) {
217 let proof_offset = JS_EPHEMERAL_KEY_OFFSET + 32 + 32 + (2 * 32);
219 let proof_size = if joinsplit.groth_proof_bytes().is_some() {
220 GROTH_PROOF_SIZE
221 } else {
222 PHGR_PROOF_SIZE
223 };
224 let ciphertexts_offset = proof_offset + proof_size;
225 let joinsplit_size = ciphertexts_offset + (ZC_NUM_JS_OUTPUTS * SPROUT_CIPHERTEXT_SIZE);
226
227 let mut bytes = Vec::with_capacity(joinsplit_size);
228 joinsplit
229 .write(&mut bytes)
230 .expect("writing a JoinSplit to a vec cannot fail");
231
232 debug_assert_eq!(
239 bytes.len(),
240 joinsplit_size,
241 "the JoinSplit wire layout must match the offsets used here",
242 );
243 debug_assert_eq!(
244 &bytes[proof_offset - 32..proof_offset],
245 &joinsplit.macs()[1][..],
246 "the JoinSplit wire layout must match the offsets used here",
247 );
248
249 let mut ephemeral_key = [0u8; 32];
250 ephemeral_key.copy_from_slice(&bytes[JS_EPHEMERAL_KEY_OFFSET..JS_EPHEMERAL_KEY_OFFSET + 32]);
251
252 let proof = bytes[proof_offset..ciphertexts_offset].to_vec();
253
254 let mut ciphertexts = [[0u8; SPROUT_CIPHERTEXT_SIZE]; ZC_NUM_JS_OUTPUTS];
255 for (i, ciphertext) in ciphertexts.iter_mut().enumerate() {
256 let start = ciphertexts_offset + i * SPROUT_CIPHERTEXT_SIZE;
257 ciphertext.copy_from_slice(&bytes[start..start + SPROUT_CIPHERTEXT_SIZE]);
258 }
259
260 (ephemeral_key, proof, ciphertexts)
261}
262
263type TransparentBundle = zcash_transparent::bundle::Bundle<zcash_transparent::bundle::Authorized>;
267
268type SaplingBundle =
270 sapling_crypto::Bundle<sapling_crypto::bundle::Authorized, zcash_protocol::value::ZatBalance>;
271
272type OrchardBundle =
275 orchard::Bundle<orchard::bundle::Authorized, zcash_protocol::value::ZatBalance>;
276
277#[allow(clippy::too_many_arguments)]
293pub(crate) fn transaction_data_from_parts(
294 version: zp_tx::TxVersion,
295 branch_id: zcash_protocol::consensus::BranchId,
296 lock_time: u32,
297 expiry_height: zcash_protocol::consensus::BlockHeight,
298 transparent_bundle: Option<TransparentBundle>,
299 sprout_bundle: Option<zp_tx::components::sprout::Bundle>,
300 sapling_bundle: Option<SaplingBundle>,
301 orchard_bundle: Option<OrchardBundle>,
302 ironwood_bundle: Option<OrchardBundle>,
303) -> zp_tx::TransactionData<zp_tx::Authorized> {
304 if version == zp_tx::TxVersion::V6 {
305 zp_tx::TransactionData::from_parts_v6(
307 branch_id,
308 lock_time,
309 expiry_height,
310 transparent_bundle,
311 sapling_bundle,
312 orchard_bundle,
313 ironwood_bundle,
314 )
315 } else {
316 debug_assert!(
317 ironwood_bundle.is_none(),
318 "only v6 transactions can carry an Ironwood bundle",
319 );
320
321 zp_tx::TransactionData::from_parts(
322 version,
323 branch_id,
324 lock_time,
325 expiry_height,
326 transparent_bundle,
327 sprout_bundle,
328 sapling_bundle,
329 orchard_bundle,
330 )
331 }
332}