zebra/zebra-chain/src/transaction/tests/vectors.rs

994 lines
31 KiB
Rust

//! Fixed test vectors for transactions.
use chrono::{DateTime, NaiveDateTime, Utc};
use color_eyre::eyre::Result;
use lazy_static::lazy_static;
use crate::{
amount::Amount,
block::{Block, Height, MAX_BLOCK_BYTES},
parameters::{Network, NetworkUpgrade},
serialization::{SerializationError, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize},
transaction::{hash::WtxId, sighash::SigHasher, txid::TxIdBuilder, Transaction},
transparent::Script,
};
use zebra_test::{
vectors::{ZIP143_1, ZIP143_2, ZIP243_1, ZIP243_2, ZIP243_3},
zip0143, zip0243, zip0244,
};
use super::super::*;
lazy_static! {
pub static ref EMPTY_V5_TX: Transaction = Transaction::V5 {
network_upgrade: NetworkUpgrade::Nu5,
lock_time: LockTime::min_lock_time_timestamp(),
expiry_height: block::Height(0),
inputs: Vec::new(),
outputs: Vec::new(),
sapling_shielded_data: None,
orchard_shielded_data: None,
};
}
/// Build a mock output list for pre-V5 transactions, with (index+1)
/// copies of `output`, which is used to computed the sighash.
///
/// Pre-V5, the entire output list is not used; only the output for the
/// given index is read. Therefore, we just need a list where `array[index]`
/// is the given `output`.
fn mock_pre_v5_output_list(output: transparent::Output, index: usize) -> Vec<transparent::Output> {
iter::repeat(output).take(index + 1).collect()
}
#[test]
fn transactionhash_struct_from_str_roundtrip() {
let _init_guard = zebra_test::init();
let hash: Hash = "3166411bd5343e0b284a108f39a929fbbb62619784f8c6dafe520703b5b446bf"
.parse()
.unwrap();
assert_eq!(
format!("{hash:?}"),
r#"transaction::Hash("3166411bd5343e0b284a108f39a929fbbb62619784f8c6dafe520703b5b446bf")"#
);
assert_eq!(
hash.to_string(),
"3166411bd5343e0b284a108f39a929fbbb62619784f8c6dafe520703b5b446bf"
);
}
#[test]
fn auth_digest_struct_from_str_roundtrip() {
let _init_guard = zebra_test::init();
let digest: AuthDigest = "3166411bd5343e0b284a108f39a929fbbb62619784f8c6dafe520703b5b446bf"
.parse()
.unwrap();
assert_eq!(
format!("{digest:?}"),
r#"AuthDigest("3166411bd5343e0b284a108f39a929fbbb62619784f8c6dafe520703b5b446bf")"#
);
assert_eq!(
digest.to_string(),
"3166411bd5343e0b284a108f39a929fbbb62619784f8c6dafe520703b5b446bf"
);
}
#[test]
fn wtx_id_struct_from_str_roundtrip() {
let _init_guard = zebra_test::init();
let wtx_id: WtxId = "3166411bd5343e0b284a108f39a929fbbb62619784f8c6dafe520703b5b446bf0000000000000000000000000000000000000000000000000000000000000001"
.parse()
.unwrap();
assert_eq!(
format!("{wtx_id:?}"),
r#"WtxId { id: transaction::Hash("3166411bd5343e0b284a108f39a929fbbb62619784f8c6dafe520703b5b446bf"), auth_digest: AuthDigest("0000000000000000000000000000000000000000000000000000000000000001") }"#
);
assert_eq!(
wtx_id.to_string(),
"3166411bd5343e0b284a108f39a929fbbb62619784f8c6dafe520703b5b446bf0000000000000000000000000000000000000000000000000000000000000001"
);
}
#[test]
fn librustzcash_tx_deserialize_and_round_trip() {
let _init_guard = zebra_test::init();
let tx = Transaction::zcash_deserialize(&zebra_test::vectors::GENERIC_TESTNET_TX[..])
.expect("transaction test vector from librustzcash should deserialize");
let mut data2 = Vec::new();
tx.zcash_serialize(&mut data2).expect("tx should serialize");
assert_eq!(&zebra_test::vectors::GENERIC_TESTNET_TX[..], &data2[..]);
}
#[test]
fn librustzcash_tx_hash() {
let _init_guard = zebra_test::init();
let tx = Transaction::zcash_deserialize(&zebra_test::vectors::GENERIC_TESTNET_TX[..])
.expect("transaction test vector from librustzcash should deserialize");
// TxID taken from comment in zebra_test::vectors
let hash = tx.hash();
let expected = "64f0bd7fe30ce23753358fe3a2dc835b8fba9c0274c4e2c54a6f73114cb55639"
.parse::<Hash>()
.expect("hash should parse correctly");
assert_eq!(hash, expected);
}
#[test]
fn doesnt_deserialize_transaction_with_invalid_value_balance() {
let _init_guard = zebra_test::init();
let dummy_transaction = Transaction::V4 {
inputs: vec![],
outputs: vec![],
lock_time: LockTime::Height(Height(1)),
expiry_height: Height(10),
joinsplit_data: None,
sapling_shielded_data: None,
};
let mut input_bytes = Vec::new();
dummy_transaction
.zcash_serialize(&mut input_bytes)
.expect("dummy transaction should serialize");
// Set value balance to non-zero
// There are 4 * 4 byte fields and 2 * 1 byte compact sizes = 18 bytes before the 8 byte amount
// (Zcash is little-endian unless otherwise specified:
// https://zips.z.cash/protocol/nu5.pdf#endian)
input_bytes[18] = 1;
let result = Transaction::zcash_deserialize(&input_bytes[..]);
assert!(matches!(
result,
Err(SerializationError::BadTransactionBalance)
));
}
#[test]
fn zip143_deserialize_and_round_trip() {
let _init_guard = zebra_test::init();
let tx1 = Transaction::zcash_deserialize(&zebra_test::vectors::ZIP143_1[..])
.expect("transaction test vector from ZIP143 should deserialize");
let mut data1 = Vec::new();
tx1.zcash_serialize(&mut data1)
.expect("tx should serialize");
assert_eq!(&zebra_test::vectors::ZIP143_1[..], &data1[..]);
let tx2 = Transaction::zcash_deserialize(&zebra_test::vectors::ZIP143_2[..])
.expect("transaction test vector from ZIP143 should deserialize");
let mut data2 = Vec::new();
tx2.zcash_serialize(&mut data2)
.expect("tx should serialize");
assert_eq!(&zebra_test::vectors::ZIP143_2[..], &data2[..]);
}
#[test]
fn zip243_deserialize_and_round_trip() {
let _init_guard = zebra_test::init();
let tx1 = Transaction::zcash_deserialize(&zebra_test::vectors::ZIP243_1[..])
.expect("transaction test vector from ZIP243 should deserialize");
let mut data1 = Vec::new();
tx1.zcash_serialize(&mut data1)
.expect("tx should serialize");
assert_eq!(&zebra_test::vectors::ZIP243_1[..], &data1[..]);
let tx2 = Transaction::zcash_deserialize(&zebra_test::vectors::ZIP243_2[..])
.expect("transaction test vector from ZIP243 should deserialize");
let mut data2 = Vec::new();
tx2.zcash_serialize(&mut data2)
.expect("tx should serialize");
assert_eq!(&zebra_test::vectors::ZIP243_2[..], &data2[..]);
let tx3 = Transaction::zcash_deserialize(&zebra_test::vectors::ZIP243_3[..])
.expect("transaction test vector from ZIP243 should deserialize");
let mut data3 = Vec::new();
tx3.zcash_serialize(&mut data3)
.expect("tx should serialize");
assert_eq!(&zebra_test::vectors::ZIP243_3[..], &data3[..]);
}
#[test]
fn deserialize_large_transaction() {
let _init_guard = zebra_test::init();
// Create a dummy input and output.
let input =
transparent::Input::zcash_deserialize(&zebra_test::vectors::DUMMY_INPUT1[..]).unwrap();
let output =
transparent::Output::zcash_deserialize(&zebra_test::vectors::DUMMY_OUTPUT1[..]).unwrap();
// Create a lock time.
let lock_time = LockTime::Time(DateTime::<Utc>::from_naive_utc_and_offset(
NaiveDateTime::from_timestamp_opt(61, 0)
.expect("in-range number of seconds and valid nanosecond"),
Utc,
));
// Serialize the input so that we can determine its serialized size.
let mut input_data = Vec::new();
input
.zcash_serialize(&mut input_data)
.expect("input should serialize");
// Calculate the number of inputs that fit into the transaction size limit.
let tx_inputs_num = MAX_BLOCK_BYTES as usize / input_data.len();
// Set the precalculated amount of inputs and a single output.
let inputs = std::iter::repeat(input)
.take(tx_inputs_num)
.collect::<Vec<_>>();
let outputs = vec![output];
// Create an oversized transaction. Adding the output and lock time causes
// the transaction to overflow the threshold.
let oversized_tx = Transaction::V1 {
inputs,
outputs,
lock_time,
};
// Serialize the transaction.
let mut tx_data = Vec::new();
oversized_tx
.zcash_serialize(&mut tx_data)
.expect("transaction should serialize");
// Check that the transaction is oversized.
assert!(tx_data.len() > MAX_BLOCK_BYTES as usize);
// The deserialization should fail because the transaction is too big.
Transaction::zcash_deserialize(&tx_data[..])
.expect_err("transaction should not deserialize due to its size");
}
// Transaction V5 test vectors
/// An empty transaction v5, with no Orchard, Sapling, or Transparent data
///
/// empty transaction are invalid, but Zebra only checks this rule in
/// zebra_consensus::transaction::Verifier
#[test]
fn empty_v5_round_trip() {
let _init_guard = zebra_test::init();
let tx: &Transaction = &EMPTY_V5_TX;
let data = tx.zcash_serialize_to_vec().expect("tx should serialize");
let tx2: &Transaction = &data
.zcash_deserialize_into()
.expect("tx should deserialize");
assert_eq!(tx, tx2);
let data2 = tx2
.zcash_serialize_to_vec()
.expect("vec serialization is infallible");
assert_eq!(data, data2, "data must be equal if structs are equal");
}
/// An empty transaction v4, with no Sapling, Sprout, or Transparent data
///
/// empty transaction are invalid, but Zebra only checks this rule in
/// zebra_consensus::transaction::Verifier
#[test]
fn empty_v4_round_trip() {
let _init_guard = zebra_test::init();
let tx = Transaction::V4 {
inputs: Vec::new(),
outputs: Vec::new(),
lock_time: LockTime::min_lock_time_timestamp(),
expiry_height: block::Height(0),
joinsplit_data: None,
sapling_shielded_data: None,
};
let data = tx.zcash_serialize_to_vec().expect("tx should serialize");
let tx2 = data
.zcash_deserialize_into()
.expect("tx should deserialize");
assert_eq!(tx, tx2);
let data2 = tx2
.zcash_serialize_to_vec()
.expect("vec serialization is infallible");
assert_eq!(data, data2, "data must be equal if structs are equal");
}
/// Check if an empty V5 transaction can be deserialized by librustzcash too.
#[test]
fn empty_v5_librustzcash_round_trip() {
let _init_guard = zebra_test::init();
let tx: &Transaction = &EMPTY_V5_TX;
let _alt_tx: zcash_primitives::transaction::Transaction = tx.try_into().expect(
"librustzcash deserialization might work for empty zebra serialized transactions. \
Hint: if empty transactions fail, but other transactions work, delete this test",
);
}
/// Do a round-trip test on fake v5 transactions created from v4 transactions
/// in the block test vectors.
///
/// Covers Sapling only, Transparent only, and Sapling/Transparent v5
/// transactions.
#[test]
fn fake_v5_round_trip() {
let _init_guard = zebra_test::init();
fake_v5_round_trip_for_network(Network::Mainnet);
fake_v5_round_trip_for_network(Network::Testnet);
}
fn fake_v5_round_trip_for_network(network: Network) {
let block_iter = network.block_iter();
let overwinter_activation_height = NetworkUpgrade::Overwinter
.activation_height(network)
.expect("a valid height")
.0;
// skip blocks that are before overwinter as they will not have a valid consensus branch id
let blocks_after_overwinter =
block_iter.skip_while(|(height, _)| **height < overwinter_activation_height);
for (height, original_bytes) in blocks_after_overwinter {
let original_block = original_bytes
.zcash_deserialize_into::<Block>()
.expect("block is structurally valid");
// skip this block if it only contains v5 transactions,
// the block round-trip test covers it already
if original_block
.transactions
.iter()
.all(|trans| matches!(trans.as_ref(), &Transaction::V5 { .. }))
{
continue;
}
let mut fake_block = original_block.clone();
fake_block.transactions = fake_block
.transactions
.iter()
.map(AsRef::as_ref)
.map(|t| arbitrary::transaction_to_fake_v5(t, network, Height(*height)))
.map(Into::into)
.collect();
// test each transaction
for (original_tx, fake_tx) in original_block
.transactions
.iter()
.zip(fake_block.transactions.iter())
{
assert_ne!(
&original_tx, &fake_tx,
"v1-v4 transactions must change when converted to fake v5"
);
let fake_bytes = fake_tx
.zcash_serialize_to_vec()
.expect("vec serialization is infallible");
assert_ne!(
&original_bytes[..],
fake_bytes,
"v1-v4 transaction data must change when converted to fake v5"
);
let fake_tx2 = fake_bytes
.zcash_deserialize_into::<Transaction>()
.expect("tx is structurally valid");
assert_eq!(fake_tx.as_ref(), &fake_tx2);
let fake_bytes2 = fake_tx2
.zcash_serialize_to_vec()
.expect("vec serialization is infallible");
assert_eq!(
fake_bytes, fake_bytes2,
"data must be equal if structs are equal"
);
}
// test full blocks
assert_ne!(
&original_block, &fake_block,
"v1-v4 transactions must change when converted to fake v5"
);
let fake_bytes = fake_block
.zcash_serialize_to_vec()
.expect("vec serialization is infallible");
assert_ne!(
&original_bytes[..],
fake_bytes,
"v1-v4 transaction data must change when converted to fake v5"
);
// skip fake blocks which exceed the block size limit
if fake_bytes.len() > MAX_BLOCK_BYTES.try_into().unwrap() {
continue;
}
let fake_block2 = fake_bytes
.zcash_deserialize_into::<Block>()
.expect("block is structurally valid");
assert_eq!(fake_block, fake_block2);
let fake_bytes2 = fake_block2
.zcash_serialize_to_vec()
.expect("vec serialization is infallible");
assert_eq!(
fake_bytes, fake_bytes2,
"data must be equal if structs are equal"
);
}
}
#[test]
fn invalid_orchard_nullifier() {
let _init_guard = zebra_test::init();
use std::convert::TryFrom;
// generated by proptest using something as:
// ```rust
// ...
// array::uniform32(any::<u8>()).prop_map(|x| Self::try_from(x).unwrap()).boxed()
// ...
// ```
let invalid_nullifier_bytes = [
62, 157, 27, 63, 100, 228, 1, 82, 140, 16, 238, 78, 68, 19, 221, 184, 189, 207, 230, 95,
194, 216, 165, 24, 110, 221, 139, 195, 106, 98, 192, 71,
];
assert_eq!(
orchard::Nullifier::try_from(invalid_nullifier_bytes)
.err()
.unwrap()
.to_string(),
SerializationError::Parse("Invalid pallas::Base value for orchard Nullifier").to_string()
);
}
/// Do a round-trip test via librustzcash on fake v5 transactions created from v4 transactions
/// in the block test vectors.
/// Makes sure that zebra-serialized transactions can be deserialized by librustzcash.
#[test]
fn fake_v5_librustzcash_round_trip() {
let _init_guard = zebra_test::init();
fake_v5_librustzcash_round_trip_for_network(Network::Mainnet);
fake_v5_librustzcash_round_trip_for_network(Network::Testnet);
}
fn fake_v5_librustzcash_round_trip_for_network(network: Network) {
let block_iter = network.block_iter();
let overwinter_activation_height = NetworkUpgrade::Overwinter
.activation_height(network)
.expect("a valid height")
.0;
let nu5_activation_height = NetworkUpgrade::Nu5
.activation_height(network)
.unwrap_or(Height::MAX_EXPIRY_HEIGHT)
.0;
// skip blocks that are before overwinter as they will not have a valid consensus branch id
// skip blocks equal or greater Nu5 activation as they are already v5 transactions
let blocks_after_overwinter_and_before_nu5 = block_iter
.skip_while(|(height, _)| **height < overwinter_activation_height)
.take_while(|(height, _)| **height < nu5_activation_height);
for (height, original_bytes) in blocks_after_overwinter_and_before_nu5 {
let original_block = original_bytes
.zcash_deserialize_into::<Block>()
.expect("block is structurally valid");
let mut fake_block = original_block.clone();
fake_block.transactions = fake_block
.transactions
.iter()
.map(AsRef::as_ref)
.map(|t| arbitrary::transaction_to_fake_v5(t, network, Height(*height)))
.map(Into::into)
.collect();
// test each transaction
for (original_tx, fake_tx) in original_block
.transactions
.iter()
.zip(fake_block.transactions.iter())
{
assert_ne!(
&original_tx, &fake_tx,
"v1-v4 transactions must change when converted to fake v5"
);
let fake_bytes = fake_tx
.zcash_serialize_to_vec()
.expect("vec serialization is infallible");
assert_ne!(
&original_bytes[..],
fake_bytes,
"v1-v4 transaction data must change when converted to fake v5"
);
let _alt_tx: zcash_primitives::transaction::Transaction = fake_tx
.as_ref()
.try_into()
.expect("librustzcash deserialization must work for zebra serialized transactions");
}
}
}
#[test]
fn zip244_round_trip() -> Result<()> {
let _init_guard = zebra_test::init();
for test in zip0244::TEST_VECTORS.iter() {
let transaction = test.tx.zcash_deserialize_into::<Transaction>()?;
let reencoded = transaction.zcash_serialize_to_vec()?;
assert_eq!(test.tx, reencoded);
// The borrow is actually needed to call the correct trait impl
#[allow(clippy::needless_borrow)]
let _alt_tx: zcash_primitives::transaction::Transaction = (&transaction)
.try_into()
.expect("librustzcash deserialization must work for zebra serialized transactions");
}
Ok(())
}
#[test]
fn zip244_txid() -> Result<()> {
let _init_guard = zebra_test::init();
for test in zip0244::TEST_VECTORS.iter() {
let transaction = test.tx.zcash_deserialize_into::<Transaction>()?;
let hasher = TxIdBuilder::new(&transaction);
let txid = hasher.txid()?;
assert_eq!(txid.0, test.txid);
}
Ok(())
}
#[test]
fn zip244_auth_digest() -> Result<()> {
let _init_guard = zebra_test::init();
for test in zip0244::TEST_VECTORS.iter() {
let transaction = test.tx.zcash_deserialize_into::<Transaction>()?;
let auth_digest = transaction.auth_digest();
assert_eq!(
auth_digest
.expect("must have auth_digest since it must be a V5 transaction")
.0,
test.auth_digest
);
}
Ok(())
}
#[test]
fn test_vec143_1() -> Result<()> {
let _init_guard = zebra_test::init();
let transaction = ZIP143_1.zcash_deserialize_into::<Transaction>()?;
let hasher = SigHasher::new(
&transaction,
HashType::ALL,
NetworkUpgrade::Overwinter,
&[],
None,
);
let hash = hasher.sighash();
let expected = "a1f1a4e5cd9bd522322d661edd2af1bf2a7019cfab94ece18f4ba935b0a19073";
let result = hex::encode(hash);
let span = tracing::span!(
tracing::Level::ERROR,
"compare_final",
expected.len = expected.len(),
buf.len = result.len()
);
let _guard = span.enter();
assert_eq!(expected, result);
Ok(())
}
#[test]
fn test_vec143_2() -> Result<()> {
let _init_guard = zebra_test::init();
let transaction = ZIP143_2.zcash_deserialize_into::<Transaction>()?;
let value = hex::decode("2f6e04963b4c0100")?.zcash_deserialize_into::<Amount<_>>()?;
let lock_script = Script::new(&hex::decode("53")?);
let input_ind = 1;
let output = transparent::Output { value, lock_script };
let all_previous_outputs = mock_pre_v5_output_list(output, input_ind);
let hasher = SigHasher::new(
&transaction,
HashType::SINGLE,
NetworkUpgrade::Overwinter,
&all_previous_outputs,
Some(input_ind),
);
let hash = hasher.sighash();
let expected = "23652e76cb13b85a0e3363bb5fca061fa791c40c533eccee899364e6e60bb4f7";
let result: &[u8] = hash.as_ref();
let result = hex::encode(result);
let span = tracing::span!(
tracing::Level::ERROR,
"compare_final",
expected.len = expected.len(),
buf.len = result.len()
);
let _guard = span.enter();
assert_eq!(expected, result);
Ok(())
}
#[test]
fn test_vec243_1() -> Result<()> {
let _init_guard = zebra_test::init();
let transaction = ZIP243_1.zcash_deserialize_into::<Transaction>()?;
let hasher = SigHasher::new(
&transaction,
HashType::ALL,
NetworkUpgrade::Sapling,
&[],
None,
);
let hash = hasher.sighash();
let expected = "63d18534de5f2d1c9e169b73f9c783718adbef5c8a7d55b5e7a37affa1dd3ff3";
let result = hex::encode(hash);
let span = tracing::span!(
tracing::Level::ERROR,
"compare_final",
expected.len = expected.len(),
buf.len = result.len()
);
let _guard = span.enter();
assert_eq!(expected, result);
let alt_sighash = crate::primitives::zcash_primitives::sighash(
&transaction,
HashType::ALL,
NetworkUpgrade::Sapling,
&[],
None,
);
let result = hex::encode(alt_sighash);
assert_eq!(expected, result);
Ok(())
}
#[test]
fn test_vec243_2() -> Result<()> {
let _init_guard = zebra_test::init();
let transaction = ZIP243_2.zcash_deserialize_into::<Transaction>()?;
let value = hex::decode("adedf02996510200")?.zcash_deserialize_into::<Amount<_>>()?;
let lock_script = Script::new(&[]);
let input_ind = 1;
let output = transparent::Output { value, lock_script };
let all_previous_outputs = mock_pre_v5_output_list(output, input_ind);
let hasher = SigHasher::new(
&transaction,
HashType::NONE,
NetworkUpgrade::Sapling,
&all_previous_outputs,
Some(input_ind),
);
let hash = hasher.sighash();
let expected = "bbe6d84f57c56b29b914c694baaccb891297e961de3eb46c68e3c89c47b1a1db";
let result = hex::encode(hash);
let span = tracing::span!(
tracing::Level::ERROR,
"compare_final",
expected.len = expected.len(),
buf.len = result.len()
);
let _guard = span.enter();
assert_eq!(expected, result);
let lock_script = Script::new(&[]);
let prevout = transparent::Output { value, lock_script };
let index = input_ind;
let all_previous_outputs = mock_pre_v5_output_list(prevout, input_ind);
let alt_sighash = crate::primitives::zcash_primitives::sighash(
&transaction,
HashType::NONE,
NetworkUpgrade::Sapling,
&all_previous_outputs,
Some(index),
);
let result = hex::encode(alt_sighash);
assert_eq!(expected, result);
Ok(())
}
#[test]
fn test_vec243_3() -> Result<()> {
let _init_guard = zebra_test::init();
let transaction = ZIP243_3.zcash_deserialize_into::<Transaction>()?;
let value = hex::decode("80f0fa0200000000")?.zcash_deserialize_into::<Amount<_>>()?;
let lock_script = Script::new(&hex::decode(
"76a914507173527b4c3318a2aecd793bf1cfed705950cf88ac",
)?);
let input_ind = 0;
let all_previous_outputs = vec![transparent::Output { value, lock_script }];
let hasher = SigHasher::new(
&transaction,
HashType::ALL,
NetworkUpgrade::Sapling,
&all_previous_outputs,
Some(input_ind),
);
let hash = hasher.sighash();
let expected = "f3148f80dfab5e573d5edfe7a850f5fd39234f80b5429d3a57edcc11e34c585b";
let result = hex::encode(hash);
let span = tracing::span!(
tracing::Level::ERROR,
"compare_final",
expected.len = expected.len(),
buf.len = result.len()
);
let _guard = span.enter();
assert_eq!(expected, result);
let lock_script = Script::new(&hex::decode(
"76a914507173527b4c3318a2aecd793bf1cfed705950cf88ac",
)?);
let prevout = transparent::Output { value, lock_script };
let index = input_ind;
let alt_sighash = crate::primitives::zcash_primitives::sighash(
&transaction,
HashType::ALL,
NetworkUpgrade::Sapling,
&[prevout],
Some(index),
);
let result = hex::encode(alt_sighash);
assert_eq!(expected, result);
Ok(())
}
#[test]
fn zip143_sighash() -> Result<()> {
let _init_guard = zebra_test::init();
for (i, test) in zip0143::TEST_VECTORS.iter().enumerate() {
let transaction = test.tx.zcash_deserialize_into::<Transaction>()?;
let (input_index, output) = match test.transparent_input {
Some(transparent_input) => (
Some(transparent_input as usize),
Some(transparent::Output {
value: test.amount.try_into()?,
lock_script: transparent::Script::new(test.script_code.as_ref()),
}),
),
None => (None, None),
};
let all_previous_outputs: Vec<_> = match output {
Some(output) => mock_pre_v5_output_list(output, input_index.unwrap()),
None => vec![],
};
let result = hex::encode(
transaction.sighash(
NetworkUpgrade::from_branch_id(test.consensus_branch_id)
.expect("must be a valid branch ID"),
HashType::from_bits(test.hash_type).expect("must be a valid HashType"),
&all_previous_outputs,
input_index,
),
);
let expected = hex::encode(test.sighash);
assert_eq!(expected, result, "test #{i}: sighash does not match");
}
Ok(())
}
#[test]
fn zip243_sighash() -> Result<()> {
let _init_guard = zebra_test::init();
for (i, test) in zip0243::TEST_VECTORS.iter().enumerate() {
let transaction = test.tx.zcash_deserialize_into::<Transaction>()?;
let (input_index, output) = match test.transparent_input {
Some(transparent_input) => (
Some(transparent_input as usize),
Some(transparent::Output {
value: test.amount.try_into()?,
lock_script: transparent::Script::new(test.script_code.as_ref()),
}),
),
None => (None, None),
};
let all_previous_outputs: Vec<_> = match output {
Some(output) => mock_pre_v5_output_list(output, input_index.unwrap()),
None => vec![],
};
let result = hex::encode(
transaction.sighash(
NetworkUpgrade::from_branch_id(test.consensus_branch_id)
.expect("must be a valid branch ID"),
HashType::from_bits(test.hash_type).expect("must be a valid HashType"),
&all_previous_outputs,
input_index,
),
);
let expected = hex::encode(test.sighash);
assert_eq!(expected, result, "test #{i}: sighash does not match");
}
Ok(())
}
#[test]
fn zip244_sighash() -> Result<()> {
let _init_guard = zebra_test::init();
for (i, test) in zip0244::TEST_VECTORS.iter().enumerate() {
let transaction = test.tx.zcash_deserialize_into::<Transaction>()?;
let all_previous_outputs: Vec<_> = test
.amounts
.iter()
.zip(test.script_pubkeys.iter())
.map(|(amount, script_pubkey)| transparent::Output {
value: (*amount).try_into().unwrap(),
lock_script: transparent::Script::new(script_pubkey.as_ref()),
})
.collect();
let result = hex::encode(transaction.sighash(
NetworkUpgrade::Nu5,
HashType::ALL,
&all_previous_outputs,
None,
));
let expected = hex::encode(test.sighash_shielded);
assert_eq!(expected, result, "test #{i}: sighash does not match");
if let Some(sighash_all) = test.sighash_all {
let result = hex::encode(transaction.sighash(
NetworkUpgrade::Nu5,
HashType::ALL,
&all_previous_outputs,
test.transparent_input.map(|idx| idx as _),
));
let expected = hex::encode(sighash_all);
assert_eq!(expected, result, "test #{i}: sighash does not match");
}
}
Ok(())
}
#[test]
fn binding_signatures() {
let _init_guard = zebra_test::init();
binding_signatures_for_network(Network::Mainnet);
binding_signatures_for_network(Network::Testnet);
}
fn binding_signatures_for_network(network: Network) {
let block_iter = network.block_iter();
for (height, bytes) in block_iter {
let upgrade = NetworkUpgrade::current(network, Height(*height));
let block = bytes
.zcash_deserialize_into::<Block>()
.expect("a valid block");
for tx in block.transactions {
match &*tx {
Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => (),
Transaction::V4 {
sapling_shielded_data,
..
} => {
if let Some(sapling_shielded_data) = sapling_shielded_data {
let shielded_sighash = tx.sighash(upgrade, HashType::ALL, &[], None);
let bvk = redjubjub::VerificationKey::try_from(
sapling_shielded_data.binding_verification_key(),
)
.expect("a valid redjubjub::VerificationKey");
bvk.verify(
shielded_sighash.as_ref(),
&sapling_shielded_data.binding_sig,
)
.expect("must pass verification");
}
}
Transaction::V5 {
sapling_shielded_data,
..
} => {
if let Some(sapling_shielded_data) = sapling_shielded_data {
let shielded_sighash = tx.sighash(upgrade, HashType::ALL, &[], None);
let bvk = redjubjub::VerificationKey::try_from(
sapling_shielded_data.binding_verification_key(),
)
.expect("a valid redjubjub::VerificationKey");
bvk.verify(
shielded_sighash.as_ref(),
&sapling_shielded_data.binding_sig,
)
.expect("must pass verification");
}
}
}
}
}
}