#[cfg(any(test, feature = "proptest-impl"))]
use zcash_protocol::value::Zatoshis;
use zcash_primitives::transaction::{self as zp_tx};
use crate::{
amount::Amount,
block,
serialization::SerializationError,
transaction::{self, LockTime},
transparent::{self, OutPoint, Script},
};
pub fn txin_to_input(
txin: &zcash_transparent::bundle::TxIn<zcash_transparent::bundle::Authorized>,
) -> Result<transparent::Input, SerializationError> {
if *txin.prevout() == zcash_transparent::bundle::OutPoint::NULL {
let script_bytes = txin.script_sig().0 .0.clone();
let (height, data) = if script_bytes.as_slice()
== crate::transparent::serialize::GENESIS_COINBASE_SCRIPT_SIG
{
(
block::Height::MIN,
crate::transparent::serialize::GENESIS_COINBASE_SCRIPT_SIG.to_vec(),
)
} else {
crate::transparent::serialize::parse_coinbase_height(&script_bytes)?
};
Ok(transparent::Input::Coinbase {
height,
data,
sequence: txin.sequence(),
})
} else {
let prevout = txin.prevout();
let hash_bytes: [u8; 32] = *prevout.hash();
Ok(transparent::Input::PrevOut {
outpoint: OutPoint {
hash: transaction::Hash(hash_bytes),
index: prevout.n(),
},
unlock_script: Script::new(&txin.script_sig().0 .0),
sequence: txin.sequence(),
})
}
}
#[cfg(any(test, feature = "proptest-impl"))]
pub fn input_to_txin(
input: &transparent::Input,
) -> zcash_transparent::bundle::TxIn<zcash_transparent::bundle::Authorized> {
match input {
transparent::Input::PrevOut {
outpoint,
unlock_script,
sequence,
} => {
let zp_outpoint =
zcash_transparent::bundle::OutPoint::new(outpoint.hash.0, outpoint.index);
zcash_transparent::bundle::TxIn::from_parts(
zp_outpoint,
zcash_transparent::address::Script(zcash_script::script::Code(
unlock_script.as_raw_bytes().to_vec(),
)),
*sequence,
)
}
transparent::Input::Coinbase { sequence, .. } => {
let script_bytes = input
.coinbase_script()
.expect("coinbase_script reconstructs from a deserialized coinbase input");
zcash_transparent::bundle::TxIn::from_parts(
zcash_transparent::bundle::OutPoint::NULL,
zcash_transparent::address::Script(zcash_script::script::Code(script_bytes)),
*sequence,
)
}
}
}
pub fn txout_to_output(txout: &zcash_transparent::bundle::TxOut) -> transparent::Output {
let value_u64: u64 = txout.value().into();
transparent::Output {
value: Amount::try_from(value_u64 as i64)
.expect("librustzcash Zatoshis is always a valid non-negative Amount"),
lock_script: Script::new(&txout.script_pubkey().0 .0),
}
}
#[cfg(any(test, feature = "proptest-impl"))]
pub fn output_to_txout(output: &transparent::Output) -> zcash_transparent::bundle::TxOut {
let zatoshis = Zatoshis::from_nonnegative_i64(output.value.into())
.expect("Zebra Amount<NonNegative> is always a valid Zatoshis");
zcash_transparent::bundle::TxOut::new(
zatoshis,
zcash_transparent::address::Script(zcash_script::script::Code(
output.lock_script.as_raw_bytes().to_vec(),
)),
)
}
pub fn u32_to_lock_time(lock_time: u32) -> LockTime {
use crate::serialization::ZcashDeserialize;
let bytes = lock_time.to_le_bytes();
LockTime::zcash_deserialize(&bytes[..]).expect("all u32 values are valid LockTimes")
}
#[cfg(any(test, feature = "proptest-impl"))]
pub fn lock_time_to_u32(lock_time: &LockTime) -> u32 {
use crate::serialization::ZcashSerialize;
let mut buf = Vec::with_capacity(4);
lock_time
.zcash_serialize(&mut buf)
.expect("serializing LockTime to vec should not fail");
u32::from_le_bytes(
buf.try_into()
.expect("LockTime serializes to exactly 4 bytes"),
)
}
#[cfg(any(test, feature = "proptest-impl"))]
pub fn height_to_block_height(h: block::Height) -> zcash_protocol::consensus::BlockHeight {
h.into()
}
#[cfg(any(test, feature = "proptest-impl", feature = "elasticsearch"))]
pub fn block_height_to_height(
bh: zcash_protocol::consensus::BlockHeight,
) -> Result<block::Height, SerializationError> {
block::Height::try_from(bh)
.map_err(|_| SerializationError::Parse("block height out of valid range"))
}
pub(crate) fn branch_id_to_network_upgrade(
branch_id: zcash_protocol::consensus::BranchId,
) -> Option<crate::parameters::NetworkUpgrade> {
crate::parameters::NetworkUpgrade::try_from(u32::from(branch_id)).ok()
}
const ZC_NUM_JS_OUTPUTS: usize = 2;
pub const SPROUT_CIPHERTEXT_SIZE: usize = 601;
const GROTH_PROOF_SIZE: usize = 48 + 96 + 48;
const PHGR_PROOF_SIZE: usize = 33 + 33 + 65 + 33 + 33 + 33 + 33 + 33;
const JS_EPHEMERAL_KEY_OFFSET: usize = 8 + 8 + 32 + (2 * 32) + (2 * 32);
pub fn sprout_joinsplit_key_proof_and_ciphertexts(
joinsplit: &zcash_primitives::transaction::components::sprout::JsDescription,
) -> (
[u8; 32],
Vec<u8>,
[[u8; SPROUT_CIPHERTEXT_SIZE]; ZC_NUM_JS_OUTPUTS],
) {
let proof_offset = JS_EPHEMERAL_KEY_OFFSET + 32 + 32 + (2 * 32);
let proof_size = if joinsplit.groth_proof_bytes().is_some() {
GROTH_PROOF_SIZE
} else {
PHGR_PROOF_SIZE
};
let ciphertexts_offset = proof_offset + proof_size;
let joinsplit_size = ciphertexts_offset + (ZC_NUM_JS_OUTPUTS * SPROUT_CIPHERTEXT_SIZE);
let mut bytes = Vec::with_capacity(joinsplit_size);
joinsplit
.write(&mut bytes)
.expect("writing a JoinSplit to a vec cannot fail");
debug_assert_eq!(
bytes.len(),
joinsplit_size,
"the JoinSplit wire layout must match the offsets used here",
);
debug_assert_eq!(
&bytes[proof_offset - 32..proof_offset],
&joinsplit.macs()[1][..],
"the JoinSplit wire layout must match the offsets used here",
);
let mut ephemeral_key = [0u8; 32];
ephemeral_key.copy_from_slice(&bytes[JS_EPHEMERAL_KEY_OFFSET..JS_EPHEMERAL_KEY_OFFSET + 32]);
let proof = bytes[proof_offset..ciphertexts_offset].to_vec();
let mut ciphertexts = [[0u8; SPROUT_CIPHERTEXT_SIZE]; ZC_NUM_JS_OUTPUTS];
for (i, ciphertext) in ciphertexts.iter_mut().enumerate() {
let start = ciphertexts_offset + i * SPROUT_CIPHERTEXT_SIZE;
ciphertext.copy_from_slice(&bytes[start..start + SPROUT_CIPHERTEXT_SIZE]);
}
(ephemeral_key, proof, ciphertexts)
}
type TransparentBundle = zcash_transparent::bundle::Bundle<zcash_transparent::bundle::Authorized>;
type SaplingBundle =
sapling_crypto::Bundle<sapling_crypto::bundle::Authorized, zcash_protocol::value::ZatBalance>;
type OrchardBundle =
orchard::Bundle<orchard::bundle::Authorized, zcash_protocol::value::ZatBalance>;
#[allow(clippy::too_many_arguments)]
pub(crate) fn transaction_data_from_parts(
version: zp_tx::TxVersion,
branch_id: zcash_protocol::consensus::BranchId,
lock_time: u32,
expiry_height: zcash_protocol::consensus::BlockHeight,
transparent_bundle: Option<TransparentBundle>,
sprout_bundle: Option<zp_tx::components::sprout::Bundle>,
sapling_bundle: Option<SaplingBundle>,
orchard_bundle: Option<OrchardBundle>,
ironwood_bundle: Option<OrchardBundle>,
) -> zp_tx::TransactionData<zp_tx::Authorized> {
if version == zp_tx::TxVersion::V6 {
zp_tx::TransactionData::from_parts_v6(
branch_id,
lock_time,
expiry_height,
transparent_bundle,
sapling_bundle,
orchard_bundle,
ironwood_bundle,
)
} else {
debug_assert!(
ironwood_bundle.is_none(),
"only v6 transactions can carry an Ironwood bundle",
);
zp_tx::TransactionData::from_parts(
version,
branch_id,
lock_time,
expiry_height,
transparent_bundle,
sprout_bundle,
sapling_bundle,
orchard_bundle,
)
}
}