diff --git a/zebra-chain/src/primitives.rs b/zebra-chain/src/primitives.rs index fd0a7db0..ac351f22 100644 --- a/zebra-chain/src/primitives.rs +++ b/zebra-chain/src/primitives.rs @@ -15,4 +15,4 @@ pub use x25519_dalek as x25519; pub use proofs::{Bctv14Proof, Groth16Proof, Halo2Proof, ZkSnarkProof}; pub mod zcash_history; -mod zcash_primitives; +pub(crate) mod zcash_primitives; diff --git a/zebra-chain/src/primitives/zcash_primitives.rs b/zebra-chain/src/primitives/zcash_primitives.rs index 95a30697..d1f1a6a8 100644 --- a/zebra-chain/src/primitives/zcash_primitives.rs +++ b/zebra-chain/src/primitives/zcash_primitives.rs @@ -4,9 +4,16 @@ use std::{ convert::{TryFrom, TryInto}, io, + ops::Deref, }; -use crate::{serialization::ZcashSerialize, transaction::Transaction}; +use crate::{ + amount::{Amount, NonNegative}, + parameters::NetworkUpgrade, + serialization::ZcashSerialize, + transaction::{HashType, SigHash, Transaction}, + transparent::{self, Script}, +}; impl TryFrom<&Transaction> for zcash_primitives::transaction::Transaction { type Error = io::Error; @@ -28,19 +35,92 @@ impl TryFrom<&Transaction> for zcash_primitives::transaction::Transaction { | Transaction::V4 { .. } => panic!("Zebra only uses librustzcash for V5 transactions"), }; - let serialized_tx = trans.zcash_serialize_to_vec()?; - // The `read` method currently ignores the BranchId for V5 transactions; - // but we use the correct BranchId anyway. - let branch_id: u32 = network_upgrade - .branch_id() - .expect("Network upgrade must have a Branch ID") - .into(); - // We've already parsed this transaction, so its network upgrade must be valid. - let branch_id: zcash_primitives::consensus::BranchId = branch_id - .try_into() - .expect("zcash_primitives and Zebra have the same branch ids"); - let alt_tx = - zcash_primitives::transaction::Transaction::read(&serialized_tx[..], branch_id)?; - Ok(alt_tx) + convert_tx_to_librustzcash(trans, *network_upgrade) } } + +fn convert_tx_to_librustzcash( + trans: &Transaction, + network_upgrade: NetworkUpgrade, +) -> Result { + let serialized_tx = trans.zcash_serialize_to_vec()?; + let branch_id: u32 = network_upgrade + .branch_id() + .expect("Network upgrade must have a Branch ID") + .into(); + // We've already parsed this transaction, so its network upgrade must be valid. + let branch_id: zcash_primitives::consensus::BranchId = branch_id + .try_into() + .expect("zcash_primitives and Zebra have the same branch ids"); + let alt_tx = zcash_primitives::transaction::Transaction::read(&serialized_tx[..], branch_id)?; + Ok(alt_tx) +} + +/// Convert a Zebra Amount into a librustzcash one. +impl TryFrom> for zcash_primitives::transaction::components::Amount { + type Error = (); + + fn try_from(amount: Amount) -> Result { + zcash_primitives::transaction::components::Amount::from_u64(amount.into()) + } +} + +/// Convert a Zebra Script into a librustzcash one. +impl From<&Script> for zcash_primitives::legacy::Script { + fn from(script: &Script) -> Self { + zcash_primitives::legacy::Script(script.as_raw_bytes().to_vec()) + } +} + +/// Compute a signature hash using librustzcash. +/// +/// # Inputs +/// +/// - `transaction`: the transaction whose signature hash to compute. +/// - `hash_type`: the type of hash (SIGHASH) being used. +/// - `network_upgrade`: the network upgrade of the block containing the transaction. +/// - `input`: information about the transparent input for which this signature +/// hash is being computed, if any. A tuple with the matching output of the +/// previous transaction, the input itself, and the index of the input in +/// the transaction. +pub(crate) fn sighash( + trans: &Transaction, + hash_type: HashType, + network_upgrade: NetworkUpgrade, + input: Option<(&transparent::Output, &transparent::Input, usize)>, +) -> SigHash { + let alt_tx = convert_tx_to_librustzcash(trans, network_upgrade) + .expect("zcash_primitives and Zebra transaction formats must be compatible"); + + let script: zcash_primitives::legacy::Script; + let signable_input = match input { + Some((output, _, idx)) => { + script = (&output.lock_script).into(); + zcash_primitives::transaction::sighash::SignableInput::Transparent( + zcash_primitives::transaction::sighash::TransparentInput::new( + idx, + &script, + output + .value + .try_into() + .expect("amount was previously validated"), + ), + ) + } + None => zcash_primitives::transaction::sighash::SignableInput::Shielded, + }; + + let txid_parts = alt_tx + .deref() + .digest(zcash_primitives::transaction::txid::TxIdDigester); + + SigHash( + *zcash_primitives::transaction::sighash::signature_hash( + alt_tx.deref(), + hash_type.bits(), + &signable_input, + &txid_parts, + ) + .as_ref(), + ) +} diff --git a/zebra-chain/src/transaction.rs b/zebra-chain/src/transaction.rs index 7caecb4e..908e1a74 100644 --- a/zebra-chain/src/transaction.rs +++ b/zebra-chain/src/transaction.rs @@ -21,6 +21,7 @@ pub use lock_time::LockTime; pub use memo::Memo; pub use sapling::FieldNotPresent; pub use sighash::HashType; +pub use sighash::SigHash; use crate::{ amount, block, orchard, @@ -146,7 +147,7 @@ impl Transaction { network_upgrade: NetworkUpgrade, hash_type: sighash::HashType, input: Option<(u32, transparent::Output)>, - ) -> blake2b_simd::Hash { + ) -> SigHash { sighash::SigHasher::new(self, hash_type, network_upgrade, input).sighash() } diff --git a/zebra-chain/src/transaction/sighash.rs b/zebra-chain/src/transaction/sighash.rs index 6d664cf1..baf38f71 100644 --- a/zebra-chain/src/transaction/sighash.rs +++ b/zebra-chain/src/transaction/sighash.rs @@ -12,10 +12,14 @@ use crate::{ transparent, }; -use blake2b_simd::Hash; use byteorder::{LittleEndian, WriteBytesExt}; -use std::io::{self, Write}; +use std::{ + convert::TryInto, + io::{self, Write}, +}; + +use crate::primitives::zcash_primitives::sighash; static ZIP143_EXPLANATION: &str = "Invalid transaction version: after Overwinter activation transaction versions 1 and 2 are rejected"; static ZIP243_EXPLANATION: &str = "Invalid transaction version: after Sapling activation transaction versions 1, 2, and 3 are rejected"; @@ -48,6 +52,23 @@ impl HashType { } } +/// A Signature Hash (or SIGHASH) as specified in +/// https://zips.z.cash/protocol/protocol.pdf#sighash +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct SigHash(pub [u8; 32]); + +impl AsRef<[u8; 32]> for SigHash { + fn as_ref(&self) -> &[u8; 32] { + &self.0 + } +} + +impl AsRef<[u8]> for SigHash { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + pub(super) struct SigHasher<'a> { trans: &'a Transaction, hash_type: HashType, @@ -79,7 +100,7 @@ impl<'a> SigHasher<'a> { } } - pub(super) fn sighash(self) -> Hash { + pub(super) fn sighash(self) -> SigHash { use NetworkUpgrade::*; let mut hash = blake2b_simd::Params::new() .hash_length(32) @@ -94,12 +115,10 @@ impl<'a> SigHasher<'a> { Sapling | Blossom | Heartwood | Canopy => self .hash_sighash_zip243(&mut hash) .expect("serialization into hasher never fails"), - Nu5 => unimplemented!( - "Nu5 upgrade uses a new transaction digest algorithm, as specified in ZIP-244" - ), + Nu5 => return self.hash_sighash_zip244(), } - hash.finalize() + SigHash(hash.finalize().as_ref().try_into().unwrap()) } fn consensus_branch_id(&self) -> ConsensusBranchId { @@ -107,7 +126,7 @@ impl<'a> SigHasher<'a> { } /// Sighash implementation for the overwinter network upgrade - fn hash_sighash_zip143(&self, mut writer: W) -> Result<(), io::Error> { + pub(super) fn hash_sighash_zip143(&self, mut writer: W) -> Result<(), io::Error> { self.hash_header(&mut writer)?; self.hash_groupid(&mut writer)?; self.hash_prevouts(&mut writer)?; @@ -131,7 +150,7 @@ impl<'a> SigHasher<'a> { personal } - fn hash_header(&self, mut writer: W) -> Result<(), io::Error> { + pub(super) fn hash_header(&self, mut writer: W) -> Result<(), io::Error> { let overwintered_flag = 1 << 31; writer.write_u32::(match &self.trans { @@ -142,7 +161,7 @@ impl<'a> SigHasher<'a> { }) } - fn hash_groupid(&self, mut writer: W) -> Result<(), io::Error> { + pub(super) fn hash_groupid(&self, mut writer: W) -> Result<(), io::Error> { writer.write_u32::(match &self.trans { Transaction::V1 { .. } | Transaction::V2 { .. } => unreachable!(ZIP143_EXPLANATION), Transaction::V3 { .. } => OVERWINTER_VERSION_GROUP_ID, @@ -151,7 +170,7 @@ impl<'a> SigHasher<'a> { }) } - fn hash_prevouts(&self, mut writer: W) -> Result<(), io::Error> { + pub(super) fn hash_prevouts(&self, mut writer: W) -> Result<(), io::Error> { if self.hash_type.contains(HashType::ANYONECANPAY) { return writer.write_all(&[0; 32]); } @@ -173,7 +192,7 @@ impl<'a> SigHasher<'a> { writer.write_all(hash.finalize().as_ref()) } - fn hash_sequence(&self, mut writer: W) -> Result<(), io::Error> { + pub(super) fn hash_sequence(&self, mut writer: W) -> Result<(), io::Error> { if self.hash_type.contains(HashType::ANYONECANPAY) || self.hash_type.masked() == HashType::SINGLE || self.hash_type.masked() == HashType::NONE @@ -199,7 +218,7 @@ impl<'a> SigHasher<'a> { } /// Writes the u256 hash of the transactions outputs to the provided Writer - fn hash_outputs(&self, mut writer: W) -> Result<(), io::Error> { + pub(super) fn hash_outputs(&self, mut writer: W) -> Result<(), io::Error> { if self.hash_type.masked() != HashType::SINGLE && self.hash_type.masked() != HashType::NONE { self.outputs_hash(writer) @@ -249,7 +268,7 @@ impl<'a> SigHasher<'a> { writer.write_all(hash.finalize().as_ref()) } - fn hash_joinsplits(&self, mut writer: W) -> Result<(), io::Error> { + pub(super) fn hash_joinsplits(&self, mut writer: W) -> Result<(), io::Error> { let has_joinsplits = match self.trans { Transaction::V1 { .. } | Transaction::V2 { .. } => unreachable!(ZIP143_EXPLANATION), Transaction::V3 { joinsplit_data, .. } => joinsplit_data.is_some(), @@ -318,19 +337,19 @@ impl<'a> SigHasher<'a> { writer.write_all(hash.finalize().as_ref()) } - fn hash_lock_time(&self, mut writer: W) -> Result<(), io::Error> { + pub(super) fn hash_lock_time(&self, mut writer: W) -> Result<(), io::Error> { self.trans.lock_time().zcash_serialize(&mut writer) } - fn hash_expiry_height(&self, mut writer: W) -> Result<(), io::Error> { + pub(super) fn hash_expiry_height(&self, mut writer: W) -> Result<(), io::Error> { writer.write_u32::(self.trans.expiry_height().expect(ZIP143_EXPLANATION).0) } - fn hash_hash_type(&self, mut writer: W) -> Result<(), io::Error> { + pub(super) fn hash_hash_type(&self, mut writer: W) -> Result<(), io::Error> { writer.write_u32::(self.hash_type.bits()) } - fn hash_input(&self, mut writer: W) -> Result<(), io::Error> { + pub(super) fn hash_input(&self, mut writer: W) -> Result<(), io::Error> { if self.input.is_some() { self.hash_input_prevout(&mut writer)?; self.hash_input_script_code(&mut writer)?; @@ -341,7 +360,7 @@ impl<'a> SigHasher<'a> { Ok(()) } - fn hash_input_prevout(&self, mut writer: W) -> Result<(), io::Error> { + pub(super) fn hash_input_prevout(&self, mut writer: W) -> Result<(), io::Error> { let (_, input, _) = self .input .as_ref() @@ -359,7 +378,10 @@ impl<'a> SigHasher<'a> { Ok(()) } - fn hash_input_script_code(&self, mut writer: W) -> Result<(), io::Error> { + pub(super) fn hash_input_script_code( + &self, + mut writer: W, + ) -> Result<(), io::Error> { let (transparent::Output { lock_script, .. }, _, _) = self .input .as_ref() @@ -370,7 +392,7 @@ impl<'a> SigHasher<'a> { Ok(()) } - fn hash_input_amount(&self, mut writer: W) -> Result<(), io::Error> { + pub(super) fn hash_input_amount(&self, mut writer: W) -> Result<(), io::Error> { let (transparent::Output { value, .. }, _, _) = self .input .as_ref() @@ -381,7 +403,7 @@ impl<'a> SigHasher<'a> { Ok(()) } - fn hash_input_sequence(&self, mut writer: W) -> Result<(), io::Error> { + pub(super) fn hash_input_sequence(&self, mut writer: W) -> Result<(), io::Error> { let (_, input, _) = self .input .as_ref() @@ -405,7 +427,7 @@ impl<'a> SigHasher<'a> { /// Sighash implementation for the sapling network upgrade and every /// subsequent network upgrade - fn hash_sighash_zip243(&self, mut writer: W) -> Result<(), io::Error> { + pub(super) fn hash_sighash_zip243(&self, mut writer: W) -> Result<(), io::Error> { self.hash_header(&mut writer)?; self.hash_groupid(&mut writer)?; self.hash_prevouts(&mut writer)?; @@ -423,7 +445,10 @@ impl<'a> SigHasher<'a> { Ok(()) } - fn hash_shielded_spends(&self, mut writer: W) -> Result<(), io::Error> { + pub(super) fn hash_shielded_spends( + &self, + mut writer: W, + ) -> Result<(), io::Error> { use Transaction::*; let sapling_shielded_data = match self.trans { @@ -462,7 +487,10 @@ impl<'a> SigHasher<'a> { writer.write_all(hash.finalize().as_ref()) } - fn hash_shielded_outputs(&self, mut writer: W) -> Result<(), io::Error> { + pub(super) fn hash_shielded_outputs( + &self, + mut writer: W, + ) -> Result<(), io::Error> { use Transaction::*; let sapling_shielded_data = match self.trans { @@ -499,7 +527,7 @@ impl<'a> SigHasher<'a> { writer.write_all(hash.finalize().as_ref()) } - fn hash_value_balance(&self, mut writer: W) -> Result<(), io::Error> { + pub(super) fn hash_value_balance(&self, mut writer: W) -> Result<(), io::Error> { use crate::amount::Amount; use std::convert::TryFrom; use Transaction::*; @@ -520,466 +548,13 @@ impl<'a> SigHasher<'a> { Ok(()) } -} -#[cfg(test)] -mod test { - use super::*; - use crate::{amount::Amount, serialization::ZcashDeserializeInto, transaction::Transaction}; - use color_eyre::eyre; - use eyre::Result; - use transparent::Script; - use zebra_test::vectors::{ZIP143_1, ZIP143_2, ZIP243_1, ZIP243_2, ZIP243_3}; - - macro_rules! assert_hash_eq { - ($expected:literal, $hasher:expr, $f:ident) => { - let mut buf = vec![]; - $hasher - .$f(&mut buf) - .expect("hashing into a vec never fails"); - let expected = $expected; - let result = hex::encode(buf); - let span = tracing::span!( - tracing::Level::ERROR, - "compare_vecs", - expected.len = expected.len(), - result.len = result.len(), - hash_fn = stringify!($f) - ); - let guard = span.enter(); - assert_eq!(expected, result); - drop(guard); - }; - } - - #[test] - fn test_vec143_1() -> Result<()> { - zebra_test::init(); - - let transaction = ZIP143_1.zcash_deserialize_into::()?; - - let hasher = SigHasher { - trans: &transaction, - hash_type: HashType::ALL, - network_upgrade: NetworkUpgrade::Overwinter, - input: None, - }; - - assert_hash_eq!("03000080", hasher, hash_header); - - assert_hash_eq!("7082c403", hasher, hash_groupid); - - assert_hash_eq!( - "d53a633bbecf82fe9e9484d8a0e727c73bb9e68c96e72dec30144f6a84afa136", - hasher, - hash_prevouts - ); - - assert_hash_eq!( - "a5f25f01959361ee6eb56a7401210ee268226f6ce764a4f10b7f29e54db37272", - hasher, - hash_sequence - ); - - assert_hash_eq!( - "ab6f7f6c5ad6b56357b5f37e16981723db6c32411753e28c175e15589172194a", - hasher, - hash_outputs - ); - - assert_hash_eq!( - "0000000000000000000000000000000000000000000000000000000000000000", - hasher, - hash_joinsplits - ); - - assert_hash_eq!("481cdd86", hasher, hash_lock_time); - - assert_hash_eq!("b3cc4318", hasher, hash_expiry_height); - - assert_hash_eq!("01000000", hasher, hash_hash_type); - - assert_hash_eq!( - "030000807082c403d53a633bbecf82fe9e9484d8a0e727c73bb9e68c96e72dec30144f6a84afa136a5f25f01959361ee6eb56a7401210ee268226f6ce764a4f10b7f29e54db37272ab6f7f6c5ad6b56357b5f37e16981723db6c32411753e28c175e15589172194a0000000000000000000000000000000000000000000000000000000000000000481cdd86b3cc431801000000", - hasher, - hash_sighash_zip143 - ); - - let hash = hasher.sighash(); - let expected = "a1f1a4e5cd9bd522322d661edd2af1bf2a7019cfab94ece18f4ba935b0a19073"; - let result = hex::encode(hash.as_bytes()); - 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<()> { - zebra_test::init(); - - let transaction = ZIP143_2.zcash_deserialize_into::()?; - - let value = hex::decode("2f6e04963b4c0100")?.zcash_deserialize_into::>()?; - let lock_script = Script::new(&hex::decode("53")?); - let input_ind = 1; - - let hasher = SigHasher::new( - &transaction, - HashType::SINGLE, - NetworkUpgrade::Overwinter, - Some((input_ind, transparent::Output { value, lock_script })), - ); - - assert_hash_eq!("03000080", hasher, hash_header); - - assert_hash_eq!("7082c403", hasher, hash_groupid); - - assert_hash_eq!( - "92b8af1f7e12cb8de105af154470a2ae0a11e64a24a514a562ff943ca0f35d7f", - hasher, - hash_prevouts - ); - - assert_hash_eq!( - "0000000000000000000000000000000000000000000000000000000000000000", - hasher, - hash_sequence - ); - - assert_hash_eq!( - "edc32cce530f836f7c31c53656f859f514c3ff8dcae642d3e17700fdc6e829a4", - hasher, - hash_outputs - ); - - assert_hash_eq!( - "f59e41b40f3a60be90bee2be11b0956dfff06a6d8e22668c4f215bd87b20d514", - hasher, - hash_joinsplits - ); - - assert_hash_eq!("97b0e4e4", hasher, hash_lock_time); - - assert_hash_eq!("c705fc05", hasher, hash_expiry_height); - - assert_hash_eq!("03000000", hasher, hash_hash_type); - - assert_hash_eq!( - "378af1e40f64e125946f62c2fa7b2fecbcb64b6968912a6381ce3dc166d56a1d62f5a8d7", - hasher, - hash_input_prevout - ); - - assert_hash_eq!("0153", hasher, hash_input_script_code); - - assert_hash_eq!("2f6e04963b4c0100", hasher, hash_input_amount); - - assert_hash_eq!("e8c7203d", hasher, hash_input_sequence); - - let hash = hasher.sighash(); - let expected = "23652e76cb13b85a0e3363bb5fca061fa791c40c533eccee899364e6e60bb4f7"; - let result = hash.as_bytes(); - 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<()> { - zebra_test::init(); - - let transaction = ZIP243_1.zcash_deserialize_into::()?; - - let hasher = SigHasher { - trans: &transaction, - hash_type: HashType::ALL, - network_upgrade: NetworkUpgrade::Sapling, - input: None, - }; - - assert_hash_eq!("04000080", hasher, hash_header); - - assert_hash_eq!("85202f89", hasher, hash_groupid); - - assert_hash_eq!( - "d53a633bbecf82fe9e9484d8a0e727c73bb9e68c96e72dec30144f6a84afa136", - hasher, - hash_prevouts - ); - - assert_hash_eq!( - "a5f25f01959361ee6eb56a7401210ee268226f6ce764a4f10b7f29e54db37272", - hasher, - hash_sequence - ); - - assert_hash_eq!( - "ab6f7f6c5ad6b56357b5f37e16981723db6c32411753e28c175e15589172194a", - hasher, - hash_outputs - ); - - assert_hash_eq!( - "0000000000000000000000000000000000000000000000000000000000000000", - hasher, - hash_joinsplits - ); - - assert_hash_eq!( - "3fd9edb96dccf5b9aeb71e3db3710e74be4f1dfb19234c1217af26181f494a36", - hasher, - hash_shielded_spends - ); - - assert_hash_eq!( - "dafece799f638ba7268bf8fe43f02a5112f0bb32a84c4a8c2f508c41ff1c78b5", - hasher, - hash_shielded_outputs - ); - - assert_hash_eq!("481cdd86", hasher, hash_lock_time); - - assert_hash_eq!("b3cc4318", hasher, hash_expiry_height); - - assert_hash_eq!("442117623ceb0500", hasher, hash_value_balance); - - assert_hash_eq!("01000000", hasher, hash_hash_type); - - assert_hash_eq!( - "0400008085202f89d53a633bbecf82fe9e9484d8a0e727c73bb9e68c96e72dec30144f6a84afa136a5f25f01959361ee6eb56a7401210ee268226f6ce764a4f10b7f29e54db37272ab6f7f6c5ad6b56357b5f37e16981723db6c32411753e28c175e15589172194a00000000000000000000000000000000000000000000000000000000000000003fd9edb96dccf5b9aeb71e3db3710e74be4f1dfb19234c1217af26181f494a36dafece799f638ba7268bf8fe43f02a5112f0bb32a84c4a8c2f508c41ff1c78b5481cdd86b3cc4318442117623ceb050001000000", - hasher, - hash_sighash_zip243 - ); - - let hash = hasher.sighash(); - let expected = "63d18534de5f2d1c9e169b73f9c783718adbef5c8a7d55b5e7a37affa1dd3ff3"; - let result = hex::encode(hash.as_bytes()); - 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_2() -> Result<()> { - zebra_test::init(); - - let transaction = ZIP243_2.zcash_deserialize_into::()?; - - let value = hex::decode("adedf02996510200")?.zcash_deserialize_into::>()?; - let lock_script = Script::new(&[]); - let input_ind = 1; - - let hasher = SigHasher::new( - &transaction, - HashType::NONE, - NetworkUpgrade::Sapling, - Some((input_ind, transparent::Output { value, lock_script })), - ); - - assert_hash_eq!("04000080", hasher, hash_header); - - assert_hash_eq!("85202f89", hasher, hash_groupid); - - assert_hash_eq!( - "cacf0f5210cce5fa65a59f314292b3111d299e7d9d582753cf61e1e408552ae4", - hasher, - hash_prevouts - ); - - assert_hash_eq!( - "0000000000000000000000000000000000000000000000000000000000000000", - hasher, - hash_sequence - ); - - assert_hash_eq!( - "0000000000000000000000000000000000000000000000000000000000000000", - hasher, - hash_outputs - ); - - assert_hash_eq!( - "0000000000000000000000000000000000000000000000000000000000000000", - hasher, - hash_joinsplits - ); - - assert_hash_eq!( - "0000000000000000000000000000000000000000000000000000000000000000", - hasher, - hash_shielded_spends - ); - - assert_hash_eq!( - "b79530fcec83211d21e3c355db538c138d625784c27370e9d1039a8515a23f87", - hasher, - hash_shielded_outputs - ); - - assert_hash_eq!("d7034302", hasher, hash_lock_time); - - assert_hash_eq!("011b9a07", hasher, hash_expiry_height); - - assert_hash_eq!("6620edc067ff0200", hasher, hash_value_balance); - - assert_hash_eq!("02000000", hasher, hash_hash_type); - - assert_hash_eq!( - "090f47a068e227433f9e49d3aa09e356d8d66d0c0121e91a3c4aa3f27fa1b63396e2b41d", - hasher, - hash_input_prevout - ); - - assert_hash_eq!("00", hasher, hash_input_script_code); - - assert_hash_eq!("adedf02996510200", hasher, hash_input_amount); - - assert_hash_eq!("4e970568", hasher, hash_input_sequence); - - assert_hash_eq!( - "0400008085202f89cacf0f5210cce5fa65a59f314292b3111d299e7d9d582753cf61e1e408552ae40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b79530fcec83211d21e3c355db538c138d625784c27370e9d1039a8515a23f87d7034302011b9a076620edc067ff020002000000090f47a068e227433f9e49d3aa09e356d8d66d0c0121e91a3c4aa3f27fa1b63396e2b41d00adedf029965102004e970568", - hasher, - hash_sighash_zip243 - ); - - let hash = hasher.sighash(); - let expected = "bbe6d84f57c56b29b914c694baaccb891297e961de3eb46c68e3c89c47b1a1db"; - let result = hex::encode(hash.as_bytes()); - 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_3() -> Result<()> { - zebra_test::init(); - - let transaction = ZIP243_3.zcash_deserialize_into::()?; - - let value = hex::decode("80f0fa0200000000")?.zcash_deserialize_into::>()?; - let lock_script = Script::new(&hex::decode( - "76a914507173527b4c3318a2aecd793bf1cfed705950cf88ac", - )?); - let input_ind = 0; - - let hasher = SigHasher::new( - &transaction, - HashType::ALL, - NetworkUpgrade::Sapling, - Some((input_ind, transparent::Output { value, lock_script })), - ); - - assert_hash_eq!("04000080", hasher, hash_header); - - assert_hash_eq!("85202f89", hasher, hash_groupid); - - assert_hash_eq!( - "fae31b8dec7b0b77e2c8d6b6eb0e7e4e55abc6574c26dd44464d9408a8e33f11", - hasher, - hash_prevouts - ); - - assert_hash_eq!( - "6c80d37f12d89b6f17ff198723e7db1247c4811d1a695d74d930f99e98418790", - hasher, - hash_sequence - ); - - assert_hash_eq!( - "d2b04118469b7810a0d1cc59568320aad25a84f407ecac40b4f605a4e6868454", - hasher, - hash_outputs - ); - - assert_hash_eq!( - "0000000000000000000000000000000000000000000000000000000000000000", - hasher, - hash_joinsplits - ); - - assert_hash_eq!( - "0000000000000000000000000000000000000000000000000000000000000000", - hasher, - hash_shielded_spends - ); - - assert_hash_eq!( - "0000000000000000000000000000000000000000000000000000000000000000", - hasher, - hash_shielded_outputs - ); - - assert_hash_eq!("29b00400", hasher, hash_lock_time); - - assert_hash_eq!("48b00400", hasher, hash_expiry_height); - - assert_hash_eq!("0000000000000000", hasher, hash_value_balance); - - assert_hash_eq!("01000000", hasher, hash_hash_type); - - assert_hash_eq!( - "a8c685478265f4c14dada651969c45a65e1aeb8cd6791f2f5bb6a1d9952104d901000000", - hasher, - hash_input_prevout - ); - - assert_hash_eq!( - "1976a914507173527b4c3318a2aecd793bf1cfed705950cf88ac", - hasher, - hash_input_script_code - ); - - assert_hash_eq!("80f0fa0200000000", hasher, hash_input_amount); - - assert_hash_eq!("feffffff", hasher, hash_input_sequence); - - assert_hash_eq!( - "0400008085202f89fae31b8dec7b0b77e2c8d6b6eb0e7e4e55abc6574c26dd44464d9408a8e33f116c80d37f12d89b6f17ff198723e7db1247c4811d1a695d74d930f99e98418790d2b04118469b7810a0d1cc59568320aad25a84f407ecac40b4f605a4e686845400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000029b0040048b00400000000000000000001000000a8c685478265f4c14dada651969c45a65e1aeb8cd6791f2f5bb6a1d9952104d9010000001976a914507173527b4c3318a2aecd793bf1cfed705950cf88ac80f0fa0200000000feffffff", - hasher, - hash_sighash_zip243 - ); - - let hash = hasher.sighash(); - let expected = "f3148f80dfab5e573d5edfe7a850f5fd39234f80b5429d3a57edcc11e34c585b"; - let result = hex::encode(hash.as_bytes()); - 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(()) + /// Compute a signature hash for V5 transactions according to ZIP-244. + fn hash_sighash_zip244(&self) -> SigHash { + let input = self + .input + .as_ref() + .map(|(output, input, idx)| (output, *input, *idx)); + sighash(&self.trans, self.hash_type, self.network_upgrade, input) } } diff --git a/zebra-chain/src/transaction/tests/vectors.rs b/zebra-chain/src/transaction/tests/vectors.rs index 5a7f667e..435ea248 100644 --- a/zebra-chain/src/transaction/tests/vectors.rs +++ b/zebra-chain/src/transaction/tests/vectors.rs @@ -10,9 +10,35 @@ use crate::{ block::{Block, Height, MAX_BLOCK_BYTES}, parameters::{Network, NetworkUpgrade}, serialization::{SerializationError, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize}, - transaction::txid::TxIdBuilder, + transaction::{sighash::SigHasher, txid::TxIdBuilder}, }; +use crate::{amount::Amount, transaction::Transaction}; + +use transparent::Script; +use zebra_test::vectors::{ZIP143_1, ZIP143_2, ZIP243_1, ZIP243_2, ZIP243_3}; + +macro_rules! assert_hash_eq { + ($expected:literal, $hasher:expr, $f:ident) => { + let mut buf = vec![]; + $hasher + .$f(&mut buf) + .expect("hashing into a vec never fails"); + let expected = $expected; + let result = hex::encode(buf); + let span = tracing::span!( + tracing::Level::ERROR, + "compare_vecs", + expected.len = expected.len(), + result.len = result.len(), + hash_fn = stringify!($f) + ); + let guard = span.enter(); + assert_eq!(expected, result); + drop(guard); + }; +} + lazy_static! { pub static ref EMPTY_V5_TX: Transaction = Transaction::V5 { network_upgrade: NetworkUpgrade::Nu5, @@ -431,6 +457,23 @@ fn fake_v5_librustzcash_round_trip_for_network(network: Network) { } } +#[test] +fn zip244_round_trip() -> Result<()> { + zebra_test::init(); + + for test in zip0244::TEST_VECTORS.iter() { + let transaction = test.tx.zcash_deserialize_into::()?; + let reencoded = transaction.zcash_serialize_to_vec()?; + assert_eq!(test.tx, reencoded); + + 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<()> { zebra_test::init(); @@ -444,3 +487,547 @@ fn zip244_txid() -> Result<()> { Ok(()) } + +#[test] +fn test_vec143_1() -> Result<()> { + zebra_test::init(); + + let transaction = ZIP143_1.zcash_deserialize_into::()?; + + let hasher = SigHasher::new( + &transaction, + HashType::ALL, + NetworkUpgrade::Overwinter, + None, + ); + + assert_hash_eq!("03000080", hasher, hash_header); + + assert_hash_eq!("7082c403", hasher, hash_groupid); + + assert_hash_eq!( + "d53a633bbecf82fe9e9484d8a0e727c73bb9e68c96e72dec30144f6a84afa136", + hasher, + hash_prevouts + ); + + assert_hash_eq!( + "a5f25f01959361ee6eb56a7401210ee268226f6ce764a4f10b7f29e54db37272", + hasher, + hash_sequence + ); + + assert_hash_eq!( + "ab6f7f6c5ad6b56357b5f37e16981723db6c32411753e28c175e15589172194a", + hasher, + hash_outputs + ); + + assert_hash_eq!( + "0000000000000000000000000000000000000000000000000000000000000000", + hasher, + hash_joinsplits + ); + + assert_hash_eq!("481cdd86", hasher, hash_lock_time); + + assert_hash_eq!("b3cc4318", hasher, hash_expiry_height); + + assert_hash_eq!("01000000", hasher, hash_hash_type); + + assert_hash_eq!( + "030000807082c403d53a633bbecf82fe9e9484d8a0e727c73bb9e68c96e72dec30144f6a84afa136a5f25f01959361ee6eb56a7401210ee268226f6ce764a4f10b7f29e54db37272ab6f7f6c5ad6b56357b5f37e16981723db6c32411753e28c175e15589172194a0000000000000000000000000000000000000000000000000000000000000000481cdd86b3cc431801000000", + hasher, + hash_sighash_zip143 + ); + + 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<()> { + zebra_test::init(); + + let transaction = ZIP143_2.zcash_deserialize_into::()?; + + let value = hex::decode("2f6e04963b4c0100")?.zcash_deserialize_into::>()?; + let lock_script = Script::new(&hex::decode("53")?); + let input_ind = 1; + + let hasher = SigHasher::new( + &transaction, + HashType::SINGLE, + NetworkUpgrade::Overwinter, + Some((input_ind, transparent::Output { value, lock_script })), + ); + + assert_hash_eq!("03000080", hasher, hash_header); + + assert_hash_eq!("7082c403", hasher, hash_groupid); + + assert_hash_eq!( + "92b8af1f7e12cb8de105af154470a2ae0a11e64a24a514a562ff943ca0f35d7f", + hasher, + hash_prevouts + ); + + assert_hash_eq!( + "0000000000000000000000000000000000000000000000000000000000000000", + hasher, + hash_sequence + ); + + assert_hash_eq!( + "edc32cce530f836f7c31c53656f859f514c3ff8dcae642d3e17700fdc6e829a4", + hasher, + hash_outputs + ); + + assert_hash_eq!( + "f59e41b40f3a60be90bee2be11b0956dfff06a6d8e22668c4f215bd87b20d514", + hasher, + hash_joinsplits + ); + + assert_hash_eq!("97b0e4e4", hasher, hash_lock_time); + + assert_hash_eq!("c705fc05", hasher, hash_expiry_height); + + assert_hash_eq!("03000000", hasher, hash_hash_type); + + assert_hash_eq!( + "378af1e40f64e125946f62c2fa7b2fecbcb64b6968912a6381ce3dc166d56a1d62f5a8d7", + hasher, + hash_input_prevout + ); + + assert_hash_eq!("0153", hasher, hash_input_script_code); + + assert_hash_eq!("2f6e04963b4c0100", hasher, hash_input_amount); + + assert_hash_eq!("e8c7203d", hasher, hash_input_sequence); + + 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<()> { + zebra_test::init(); + + let transaction = ZIP243_1.zcash_deserialize_into::()?; + + let hasher = SigHasher::new(&transaction, HashType::ALL, NetworkUpgrade::Sapling, None); + + assert_hash_eq!("04000080", hasher, hash_header); + + assert_hash_eq!("85202f89", hasher, hash_groupid); + + assert_hash_eq!( + "d53a633bbecf82fe9e9484d8a0e727c73bb9e68c96e72dec30144f6a84afa136", + hasher, + hash_prevouts + ); + + assert_hash_eq!( + "a5f25f01959361ee6eb56a7401210ee268226f6ce764a4f10b7f29e54db37272", + hasher, + hash_sequence + ); + + assert_hash_eq!( + "ab6f7f6c5ad6b56357b5f37e16981723db6c32411753e28c175e15589172194a", + hasher, + hash_outputs + ); + + assert_hash_eq!( + "0000000000000000000000000000000000000000000000000000000000000000", + hasher, + hash_joinsplits + ); + + assert_hash_eq!( + "3fd9edb96dccf5b9aeb71e3db3710e74be4f1dfb19234c1217af26181f494a36", + hasher, + hash_shielded_spends + ); + + assert_hash_eq!( + "dafece799f638ba7268bf8fe43f02a5112f0bb32a84c4a8c2f508c41ff1c78b5", + hasher, + hash_shielded_outputs + ); + + assert_hash_eq!("481cdd86", hasher, hash_lock_time); + + assert_hash_eq!("b3cc4318", hasher, hash_expiry_height); + + assert_hash_eq!("442117623ceb0500", hasher, hash_value_balance); + + assert_hash_eq!("01000000", hasher, hash_hash_type); + + assert_hash_eq!( + "0400008085202f89d53a633bbecf82fe9e9484d8a0e727c73bb9e68c96e72dec30144f6a84afa136a5f25f01959361ee6eb56a7401210ee268226f6ce764a4f10b7f29e54db37272ab6f7f6c5ad6b56357b5f37e16981723db6c32411753e28c175e15589172194a00000000000000000000000000000000000000000000000000000000000000003fd9edb96dccf5b9aeb71e3db3710e74be4f1dfb19234c1217af26181f494a36dafece799f638ba7268bf8fe43f02a5112f0bb32a84c4a8c2f508c41ff1c78b5481cdd86b3cc4318442117623ceb050001000000", + hasher, + hash_sighash_zip243 + ); + + 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<()> { + zebra_test::init(); + + let transaction = ZIP243_2.zcash_deserialize_into::()?; + + let value = hex::decode("adedf02996510200")?.zcash_deserialize_into::>()?; + let lock_script = Script::new(&[]); + let input_ind = 1; + + let hasher = SigHasher::new( + &transaction, + HashType::NONE, + NetworkUpgrade::Sapling, + Some((input_ind, transparent::Output { value, lock_script })), + ); + + assert_hash_eq!("04000080", hasher, hash_header); + + assert_hash_eq!("85202f89", hasher, hash_groupid); + + assert_hash_eq!( + "cacf0f5210cce5fa65a59f314292b3111d299e7d9d582753cf61e1e408552ae4", + hasher, + hash_prevouts + ); + + assert_hash_eq!( + "0000000000000000000000000000000000000000000000000000000000000000", + hasher, + hash_sequence + ); + + assert_hash_eq!( + "0000000000000000000000000000000000000000000000000000000000000000", + hasher, + hash_outputs + ); + + assert_hash_eq!( + "0000000000000000000000000000000000000000000000000000000000000000", + hasher, + hash_joinsplits + ); + + assert_hash_eq!( + "0000000000000000000000000000000000000000000000000000000000000000", + hasher, + hash_shielded_spends + ); + + assert_hash_eq!( + "b79530fcec83211d21e3c355db538c138d625784c27370e9d1039a8515a23f87", + hasher, + hash_shielded_outputs + ); + + assert_hash_eq!("d7034302", hasher, hash_lock_time); + + assert_hash_eq!("011b9a07", hasher, hash_expiry_height); + + assert_hash_eq!("6620edc067ff0200", hasher, hash_value_balance); + + assert_hash_eq!("02000000", hasher, hash_hash_type); + + assert_hash_eq!( + "090f47a068e227433f9e49d3aa09e356d8d66d0c0121e91a3c4aa3f27fa1b63396e2b41d", + hasher, + hash_input_prevout + ); + + assert_hash_eq!("00", hasher, hash_input_script_code); + + assert_hash_eq!("adedf02996510200", hasher, hash_input_amount); + + assert_hash_eq!("4e970568", hasher, hash_input_sequence); + + assert_hash_eq!( + "0400008085202f89cacf0f5210cce5fa65a59f314292b3111d299e7d9d582753cf61e1e408552ae40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b79530fcec83211d21e3c355db538c138d625784c27370e9d1039a8515a23f87d7034302011b9a076620edc067ff020002000000090f47a068e227433f9e49d3aa09e356d8d66d0c0121e91a3c4aa3f27fa1b63396e2b41d00adedf029965102004e970568", + hasher, + hash_sighash_zip243 + ); + + 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 as usize; + let inputs = transaction.inputs(); + let input = Some((&prevout, &inputs[index], index)); + + let alt_sighash = crate::primitives::zcash_primitives::sighash( + &transaction, + HashType::NONE, + NetworkUpgrade::Sapling, + input, + ); + let result = hex::encode(alt_sighash); + assert_eq!(expected, result); + + Ok(()) +} + +#[test] +fn test_vec243_3() -> Result<()> { + zebra_test::init(); + + let transaction = ZIP243_3.zcash_deserialize_into::()?; + + let value = hex::decode("80f0fa0200000000")?.zcash_deserialize_into::>()?; + let lock_script = Script::new(&hex::decode( + "76a914507173527b4c3318a2aecd793bf1cfed705950cf88ac", + )?); + let input_ind = 0; + + let hasher = SigHasher::new( + &transaction, + HashType::ALL, + NetworkUpgrade::Sapling, + Some((input_ind, transparent::Output { value, lock_script })), + ); + + assert_hash_eq!("04000080", hasher, hash_header); + + assert_hash_eq!("85202f89", hasher, hash_groupid); + + assert_hash_eq!( + "fae31b8dec7b0b77e2c8d6b6eb0e7e4e55abc6574c26dd44464d9408a8e33f11", + hasher, + hash_prevouts + ); + + assert_hash_eq!( + "6c80d37f12d89b6f17ff198723e7db1247c4811d1a695d74d930f99e98418790", + hasher, + hash_sequence + ); + + assert_hash_eq!( + "d2b04118469b7810a0d1cc59568320aad25a84f407ecac40b4f605a4e6868454", + hasher, + hash_outputs + ); + + assert_hash_eq!( + "0000000000000000000000000000000000000000000000000000000000000000", + hasher, + hash_joinsplits + ); + + assert_hash_eq!( + "0000000000000000000000000000000000000000000000000000000000000000", + hasher, + hash_shielded_spends + ); + + assert_hash_eq!( + "0000000000000000000000000000000000000000000000000000000000000000", + hasher, + hash_shielded_outputs + ); + + assert_hash_eq!("29b00400", hasher, hash_lock_time); + + assert_hash_eq!("48b00400", hasher, hash_expiry_height); + + assert_hash_eq!("0000000000000000", hasher, hash_value_balance); + + assert_hash_eq!("01000000", hasher, hash_hash_type); + + assert_hash_eq!( + "a8c685478265f4c14dada651969c45a65e1aeb8cd6791f2f5bb6a1d9952104d901000000", + hasher, + hash_input_prevout + ); + + assert_hash_eq!( + "1976a914507173527b4c3318a2aecd793bf1cfed705950cf88ac", + hasher, + hash_input_script_code + ); + + assert_hash_eq!("80f0fa0200000000", hasher, hash_input_amount); + + assert_hash_eq!("feffffff", hasher, hash_input_sequence); + + assert_hash_eq!( + "0400008085202f89fae31b8dec7b0b77e2c8d6b6eb0e7e4e55abc6574c26dd44464d9408a8e33f116c80d37f12d89b6f17ff198723e7db1247c4811d1a695d74d930f99e98418790d2b04118469b7810a0d1cc59568320aad25a84f407ecac40b4f605a4e686845400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000029b0040048b00400000000000000000001000000a8c685478265f4c14dada651969c45a65e1aeb8cd6791f2f5bb6a1d9952104d9010000001976a914507173527b4c3318a2aecd793bf1cfed705950cf88ac80f0fa0200000000feffffff", + hasher, + hash_sighash_zip243 + ); + + 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 as usize; + let inputs = transaction.inputs(); + let input = Some((&prevout, &inputs[index], index)); + + let alt_sighash = crate::primitives::zcash_primitives::sighash( + &transaction, + HashType::ALL, + NetworkUpgrade::Sapling, + input, + ); + let result = hex::encode(alt_sighash); + assert_eq!(expected, result); + + Ok(()) +} + +#[test] +fn zip244_sighash() -> Result<()> { + zebra_test::init(); + + for (i, test) in zip0244::TEST_VECTORS.iter().enumerate() { + let transaction = test.tx.zcash_deserialize_into::()?; + let input = match test.amount { + Some(amount) => Some(( + test.transparent_input + .expect("test vector must have transparent_input when it has amount"), + transparent::Output { + value: amount.try_into()?, + lock_script: transparent::Script::new( + test.script_code + .as_ref() + .expect("test vector must have script_code when it has amount"), + ), + }, + )), + None => None, + }; + let result = hex::encode(transaction.sighash(NetworkUpgrade::Nu5, HashType::ALL, input)); + let expected = hex::encode(test.sighash_all); + assert_eq!(expected, result, "test #{}: sighash does not match", i); + } + + Ok(()) +} + +/// Use librustzcash to compute sighashes and compare with zebra sighashes. +#[test] +fn librustzcash_sighash() { + zebra_test::init(); + + librustzcash_sighash_for_network(Network::Mainnet); + librustzcash_sighash_for_network(Network::Testnet); +} + +fn librustzcash_sighash_for_network(network: Network) { + let block_iter = match network { + Network::Mainnet => zebra_test::vectors::MAINNET_BLOCKS.iter(), + Network::Testnet => zebra_test::vectors::TESTNET_BLOCKS.iter(), + }; + + for (height, original_bytes) in block_iter { + let original_block = original_bytes + .zcash_deserialize_into::() + .expect("block is structurally valid"); + + // skip blocks that are before overwinter as they will not have a valid consensus branch id + if *height + < NetworkUpgrade::Overwinter + .activation_height(network) + .expect("a valid height") + .0 + { + continue; + } + + let network_upgrade = NetworkUpgrade::current(network, Height(*height)); + + // Test each transaction. Skip the coinbase transaction + for original_tx in original_block.transactions.iter().skip(1) { + let original_sighash = original_tx.sighash(network_upgrade, HashType::ALL, None); + + let alt_sighash = crate::primitives::zcash_primitives::sighash( + original_tx, + HashType::ALL, + network_upgrade, + None, + ); + + assert_eq!(original_sighash, alt_sighash); + } + } +} diff --git a/zebra-consensus/src/transaction.rs b/zebra-consensus/src/transaction.rs index 5b3f69ed..b8e671d7 100644 --- a/zebra-consensus/src/transaction.rs +++ b/zebra-consensus/src/transaction.rs @@ -19,7 +19,7 @@ use zebra_chain::{ parameters::{Network, NetworkUpgrade}, primitives::Groth16Proof, sapling, - transaction::{self, HashType, Transaction}, + transaction::{self, HashType, SigHash, Transaction}, transparent, }; @@ -398,7 +398,7 @@ where /// Verifies a transaction's Sprout shielded join split data. fn verify_sprout_shielded_data( joinsplit_data: &Option>, - shielded_sighash: &blake2b_simd::Hash, + shielded_sighash: &SigHash, ) -> AsyncChecks { let mut checks = AsyncChecks::new(); @@ -434,7 +434,7 @@ where /// Verifies a transaction's Sapling shielded data. fn verify_sapling_shielded_data( sapling_shielded_data: &Option>, - shielded_sighash: &blake2b_simd::Hash, + shielded_sighash: &SigHash, ) -> Result where A: sapling::AnchorVariant + Clone, diff --git a/zebra-consensus/src/transaction/tests.rs b/zebra-consensus/src/transaction/tests.rs index 9be4ddb6..ddc5808d 100644 --- a/zebra-consensus/src/transaction/tests.rs +++ b/zebra-consensus/src/transaction/tests.rs @@ -620,7 +620,7 @@ fn v4_with_signed_sprout_transfer_is_accepted() { Transaction::V4 { joinsplit_data: Some(joinsplit_data), .. - } => joinsplit_data.sig = signing_key.sign(sighash.as_bytes()), + } => joinsplit_data.sig = signing_key.sign(sighash.as_ref()), _ => unreachable!("Mock transaction was created incorrectly"), } diff --git a/zebra-test/src/zip0244.rs b/zebra-test/src/zip0244.rs index 8c78d468..fce1b5fd 100644 --- a/zebra-test/src/zip0244.rs +++ b/zebra-test/src/zip0244.rs @@ -7,6 +7,8 @@ pub struct TestVector { pub tx: Vec, /// The expected transaction ID. pub txid: [u8; 32], + /// The expected auth digest. + pub auth_digest: [u8; 32], /// Which transparent input the ID refers to, if any. pub transparent_input: Option, /// The script code for the given transparent input, if any. @@ -288,6 +290,11 @@ lazy_static! { 0x8b, 0x3c, 0x87, 0xc7, 0x4a, 0x2a, 0x63, 0xa2, 0x89, 0xd3, 0x00, 0x05, 0xda, 0xd6, 0x98, 0x3d, 0x95, 0x44, ], + auth_digest: [ + 0x1c, 0xbb, 0xe0, 0xd7, 0x25, 0x4c, 0xa6, 0x52, 0xcd, 0xda, 0xa7, 0xa9, 0xd2, 0x91, + 0x5a, 0x60, 0x9e, 0x35, 0x73, 0xc0, 0x3d, 0x27, 0x05, 0xe9, 0xad, 0xd4, 0xe3, 0x2e, + 0xec, 0x0d, 0x76, 0x9e, + ], transparent_input: Some(0), script_code: Some(vec![0x65, 0x00, 0x51]), amount: Some(570688904498311), @@ -459,6 +466,11 @@ lazy_static! { 0xa5, 0xe4, 0xfc, 0xb7, 0xbf, 0xa7, 0xda, 0x79, 0x29, 0xbf, 0xb7, 0x31, 0xac, 0x10, 0xa5, 0x8a, 0xb0, 0x03, ], + auth_digest: [ + 0x64, 0xed, 0x51, 0x50, 0x16, 0x96, 0xf1, 0x14, 0x32, 0xb8, 0xa1, 0xe2, 0xe6, 0x87, + 0xb8, 0x9e, 0x61, 0x25, 0x97, 0xfd, 0x47, 0x49, 0xf9, 0x2c, 0x0f, 0x5f, 0x51, 0x71, + 0xfc, 0xad, 0x78, 0xbe, + ], transparent_input: None, script_code: None, amount: None, @@ -483,6 +495,11 @@ lazy_static! { 0xf9, 0x57, 0x6a, 0xb7, 0x6b, 0xff, 0xc7, 0x1b, 0xcd, 0x98, 0x5b, 0x62, 0xdd, 0xd6, 0x9d, 0x29, 0x97, 0xcd, ], + auth_digest: [ + 0x04, 0xda, 0x78, 0xb6, 0x64, 0x11, 0x1d, 0xe8, 0xe4, 0xfc, 0xfc, 0x14, 0x93, 0x3d, + 0x79, 0xf6, 0xd9, 0x60, 0xda, 0xd8, 0xf1, 0x08, 0xf1, 0xe0, 0xb4, 0x26, 0xcc, 0x20, + 0x36, 0x29, 0x22, 0xed, + ], transparent_input: None, script_code: None, amount: None, @@ -508,6 +525,11 @@ lazy_static! { 0xe3, 0x4f, 0xd5, 0xf9, 0xc7, 0x9c, 0x9f, 0x78, 0xdc, 0x65, 0x05, 0x1a, 0x9a, 0x14, 0xca, 0xc2, 0xb1, 0xbf, ], + auth_digest: [ + 0x04, 0xda, 0x78, 0xb6, 0x64, 0x11, 0x1d, 0xe8, 0xe4, 0xfc, 0xfc, 0x14, 0x93, 0x3d, + 0x79, 0xf6, 0xd9, 0x60, 0xda, 0xd8, 0xf1, 0x08, 0xf1, 0xe0, 0xb4, 0x26, 0xcc, 0x20, + 0x36, 0x29, 0x22, 0xed, + ], transparent_input: None, script_code: None, amount: None, @@ -544,6 +566,11 @@ lazy_static! { 0x90, 0x93, 0x03, 0xd4, 0x58, 0x0e, 0x72, 0x3a, 0xd3, 0x16, 0x2c, 0x06, 0x09, 0x66, 0x48, 0xa2, 0x5b, 0x1e, ], + auth_digest: [ + 0x9e, 0x7c, 0x68, 0x39, 0xb3, 0x1f, 0xb3, 0xe9, 0x12, 0xb6, 0x93, 0xd1, 0x87, 0x9f, + 0xbb, 0xad, 0xdb, 0xe7, 0xa7, 0x4a, 0x50, 0x8e, 0x7b, 0x6d, 0x1f, 0xe1, 0x93, 0x82, + 0x68, 0xc0, 0x4a, 0xb3, + ], transparent_input: Some(1), script_code: Some(vec![0xac, 0x00, 0x00]), amount: Some(693972628630138), @@ -800,6 +827,11 @@ lazy_static! { 0x1f, 0x14, 0x12, 0xe3, 0x8f, 0x89, 0x13, 0x07, 0x52, 0x80, 0xcd, 0x2b, 0x38, 0x02, 0xf5, 0xb2, 0x70, 0x4e, ], + auth_digest: [ + 0xee, 0x3a, 0x4a, 0x6d, 0xd3, 0x89, 0xf2, 0x81, 0xb6, 0xd6, 0xe3, 0xd8, 0xe0, 0xf7, + 0x97, 0xa3, 0xd4, 0xfa, 0x01, 0x4e, 0x3f, 0x41, 0x6a, 0x0e, 0x47, 0x68, 0x4f, 0x76, + 0x02, 0x15, 0x44, 0x58, + ], transparent_input: None, script_code: None, amount: None, @@ -831,6 +863,11 @@ lazy_static! { 0xbc, 0xdf, 0x2d, 0x35, 0xd0, 0x13, 0x55, 0x80, 0x4b, 0xf0, 0xe3, 0xee, 0x1b, 0x17, 0xde, 0x9e, 0x46, 0xcd, ], + auth_digest: [ + 0x2c, 0xbe, 0xf7, 0x5b, 0x3e, 0x2c, 0xff, 0x88, 0xb6, 0xf1, 0x20, 0xbf, 0x70, 0xcb, + 0x3e, 0xc7, 0x57, 0xec, 0x25, 0xd3, 0x11, 0xfa, 0xca, 0xfd, 0xfe, 0x2d, 0xb4, 0x2e, + 0xd3, 0x42, 0x04, 0x0f, + ], transparent_input: Some(0), script_code: Some(vec![0x63, 0x52, 0x51, 0x63, 0x53]), amount: Some(107504874564564), @@ -1092,6 +1129,11 @@ lazy_static! { 0x2a, 0xd9, 0x06, 0x50, 0x34, 0x22, 0x5d, 0xad, 0x9c, 0x89, 0xbf, 0xcb, 0x73, 0x32, 0x8d, 0x3d, 0x4f, 0xe6, ], + auth_digest: [ + 0xbb, 0x48, 0xcf, 0x27, 0x71, 0x54, 0x6c, 0x2c, 0x15, 0x57, 0x7d, 0xb2, 0x0d, 0x04, + 0x86, 0x5a, 0x8c, 0xc0, 0x0c, 0x9c, 0x02, 0xec, 0xb3, 0x10, 0x24, 0x94, 0x31, 0x0f, + 0x18, 0x68, 0x50, 0xcf, + ], transparent_input: None, script_code: None, amount: None, @@ -1200,6 +1242,11 @@ lazy_static! { 0x01, 0x54, 0xeb, 0x2d, 0xeb, 0x76, 0x78, 0x74, 0xa3, 0x1b, 0x5d, 0x10, 0xaa, 0xf7, 0x6b, 0xa8, 0x4f, 0xae, ], + auth_digest: [ + 0xf1, 0x58, 0x29, 0x64, 0xb8, 0xc9, 0xec, 0x20, 0x29, 0xab, 0x97, 0xa2, 0x55, 0x98, + 0xdb, 0xff, 0x28, 0x35, 0x23, 0xe6, 0xf3, 0x7a, 0xdb, 0x19, 0xcc, 0x57, 0x69, 0xb5, + 0x13, 0xc7, 0x33, 0xc0, + ], transparent_input: None, script_code: None, amount: None, @@ -1497,6 +1544,11 @@ lazy_static! { 0xde, 0x07, 0x15, 0x27, 0x5d, 0x15, 0x6c, 0xda, 0xb9, 0x6f, 0x68, 0xdc, 0x70, 0x10, 0x58, 0x3b, 0x02, 0xaa, ], + auth_digest: [ + 0x87, 0x46, 0xc9, 0x9d, 0x98, 0x57, 0x86, 0xb1, 0xb7, 0x7c, 0x40, 0x5f, 0x6e, 0xe8, + 0x31, 0xe2, 0x98, 0x71, 0xca, 0x9d, 0x68, 0xe7, 0x72, 0x4f, 0xb7, 0xf8, 0x78, 0x6f, + 0x28, 0x18, 0x42, 0xa6, + ], transparent_input: Some(0), script_code: Some(vec![]), amount: Some(1405243945822387),