From 6744f415d27ec5fb1ebe47f825af00a74e789377 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Sat, 5 Sep 2020 16:31:11 -0700 Subject: [PATCH] Implement sighash (#870) * Implement sighash * move sighash logic to a separate module * start filling in more of the alg * start setting up a test case * make the test useful * Iter transaction inputs * better error message for expect * add support for zip243 sighash * ohey first testvector is passing, yayyy * pass the second testvector * add last testvector * move a use statement * use common deserialization code for amount everywhere * cleanup attributes * bring in fixed preimage * fix discrepancy with spec * always deserialize as a signed value * Update zebra-chain/src/transaction/sighash.rs * update unreachable statements * add serialization impls for nonnegative amounts * Apply suggestions from code review * document sighash fn * tweek docs * fix mistake in translation for zip243 * consistent error messages * reorder because i like it more that way * document more panics * Update zebra-chain/src/amount.rs * Add comment regarding the serialization of spend descriptions in sighash Co-authored-by: teor Co-authored-by: Deirdre Connolly --- Cargo.lock | 1 + zebra-chain/Cargo.toml | 1 + zebra-chain/src/amount.rs | 36 +- zebra-chain/src/lib.rs | 1 + zebra-chain/src/sprout/joinsplit.rs | 14 +- zebra-chain/src/transaction.rs | 26 + zebra-chain/src/transaction/serialize.rs | 9 +- zebra-chain/src/transaction/sighash.rs | 934 +++++++++++++++++++++++ zebra-chain/src/transparent/serialize.rs | 16 +- 9 files changed, 1018 insertions(+), 20 deletions(-) create mode 100644 zebra-chain/src/transaction/sighash.rs diff --git a/Cargo.lock b/Cargo.lock index 61afb972..796b2429 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3094,6 +3094,7 @@ version = "3.0.0-alpha.0" dependencies = [ "bech32", "bincode", + "bitflags", "bitvec", "blake2b_simd", "blake2s_simd", diff --git a/zebra-chain/Cargo.toml b/zebra-chain/Cargo.toml index 9347b34b..037f709a 100644 --- a/zebra-chain/Cargo.toml +++ b/zebra-chain/Cargo.toml @@ -34,6 +34,7 @@ displaydoc = "0.1.7" ed25519-zebra = "1" equihash = "0.1" redjubjub = "0.2" +bitflags = "1.2.1" [dev-dependencies] bincode = "1" diff --git a/zebra-chain/src/amount.rs b/zebra-chain/src/amount.rs index 23b6e9c6..9f4d74ac 100644 --- a/zebra-chain/src/amount.rs +++ b/zebra-chain/src/amount.rs @@ -11,7 +11,8 @@ use std::{ ops::RangeInclusive, }; -use byteorder::{ByteOrder, LittleEndian}; +use crate::serialization::{ZcashDeserialize, ZcashSerialize}; +use byteorder::{ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt}; type Result = std::result::Result; @@ -262,6 +263,39 @@ pub trait Constraint { } } +impl ZcashSerialize for Amount { + fn zcash_serialize(&self, mut writer: W) -> Result<(), std::io::Error> { + writer.write_i64::(self.0) + } +} + +impl ZcashDeserialize for Amount { + fn zcash_deserialize( + mut reader: R, + ) -> Result { + Ok(reader.read_i64::()?.try_into()?) + } +} + +impl ZcashSerialize for Amount { + fn zcash_serialize(&self, mut writer: W) -> Result<(), std::io::Error> { + let amount = self + .0 + .try_into() + .expect("constraint guarantees value is positive"); + + writer.write_u64::(amount) + } +} + +impl ZcashDeserialize for Amount { + fn zcash_deserialize( + mut reader: R, + ) -> Result { + Ok(reader.read_u64::()?.try_into()?) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/zebra-chain/src/lib.rs b/zebra-chain/src/lib.rs index 86bb0b15..59a6bbe3 100644 --- a/zebra-chain/src/lib.rs +++ b/zebra-chain/src/lib.rs @@ -7,6 +7,7 @@ #![doc(html_logo_url = "https://www.zfnd.org/images/zebra-icon.png")] #![doc(html_root_url = "https://doc.zebra.zfnd.org/zebra_chain")] #![deny(missing_docs)] +#![allow(clippy::try_err)] #[macro_use] extern crate serde; diff --git a/zebra-chain/src/sprout/joinsplit.rs b/zebra-chain/src/sprout/joinsplit.rs index de64e760..3eeb9201 100644 --- a/zebra-chain/src/sprout/joinsplit.rs +++ b/zebra-chain/src/sprout/joinsplit.rs @@ -1,13 +1,13 @@ -use std::{convert::TryInto, io}; +use std::io; -use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use serde::{Deserialize, Serialize}; use crate::{ amount::{Amount, NonNegative}, primitives::{x25519, ZkSnarkProof}, serialization::{ - ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashSerialize, + ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashDeserializeInto, + ZcashSerialize, }, }; @@ -51,8 +51,8 @@ pub struct JoinSplit { impl ZcashSerialize for JoinSplit

{ fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { - writer.write_u64::(self.vpub_old.into())?; - writer.write_u64::(self.vpub_new.into())?; + self.vpub_old.zcash_serialize(&mut writer)?; + self.vpub_new.zcash_serialize(&mut writer)?; writer.write_32_bytes(&self.anchor.into())?; writer.write_32_bytes(&self.nullifiers[0].into())?; writer.write_32_bytes(&self.nullifiers[1].into())?; @@ -72,8 +72,8 @@ impl ZcashSerialize for JoinSplit

{ impl ZcashDeserialize for JoinSplit

{ fn zcash_deserialize(mut reader: R) -> Result { Ok(JoinSplit::

{ - vpub_old: reader.read_u64::()?.try_into()?, - vpub_new: reader.read_u64::()?.try_into()?, + vpub_old: (&mut reader).zcash_deserialize_into()?, + vpub_new: (&mut reader).zcash_deserialize_into()?, anchor: tree::Root::from(reader.read_32_bytes()?), nullifiers: [ reader.read_32_bytes()?.into(), diff --git a/zebra-chain/src/transaction.rs b/zebra-chain/src/transaction.rs index 28d03616..f814893d 100644 --- a/zebra-chain/src/transaction.rs +++ b/zebra-chain/src/transaction.rs @@ -8,6 +8,7 @@ mod lock_time; mod memo; mod serialize; mod shielded_data; +mod sighash; #[cfg(test)] mod tests; @@ -21,6 +22,7 @@ pub use shielded_data::ShieldedData; use crate::{ amount::Amount, block, + parameters::NetworkUpgrade, primitives::{Bctv14Proof, Groth16Proof}, transparent, }; @@ -157,4 +159,28 @@ impl Transaction { Some(transparent::Input::Coinbase { .. }) ) } + + /// Calculate the sighash for the current transaction + /// + /// # Details + /// + /// The `input` argument indicates the transparent Input for which we are + /// producing a sighash. It is comprised of the index identifying the + /// transparent::Input within the transaction and the transparent::Output + /// representing the UTXO being spent by that input. + /// + /// # Panics + /// + /// - if passed in any NetworkUpgrade from before NetworkUpgrade::Overwinter + /// - if called on a v1 or v2 transaction + /// - if the input index points to a transparent::Input::CoinBase + /// - if the input index is out of bounds for self.inputs() + pub fn sighash( + &self, + network_upgrade: NetworkUpgrade, + hash_type: sighash::HashType, + input: Option<(u32, transparent::Output)>, + ) -> blake2b_simd::Hash { + sighash::SigHasher::new(self, hash_type, network_upgrade, input).sighash() + } } diff --git a/zebra-chain/src/transaction/serialize.rs b/zebra-chain/src/transaction/serialize.rs index acba1274..8def6a05 100644 --- a/zebra-chain/src/transaction/serialize.rs +++ b/zebra-chain/src/transaction/serialize.rs @@ -1,14 +1,15 @@ //! Contains impls of `ZcashSerialize`, `ZcashDeserialize` for all of the //! transaction types, so that all of the serialization logic is in one place. -use std::{convert::TryInto, io, sync::Arc}; +use std::{io, sync::Arc}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use crate::{ primitives::ZkSnarkProof, serialization::{ - ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashSerialize, + ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashDeserializeInto, + ZcashSerialize, }, sprout, }; @@ -143,7 +144,7 @@ impl ZcashSerialize for Transaction { outputs.zcash_serialize(&mut writer)?; lock_time.zcash_serialize(&mut writer)?; writer.write_u32::(expiry_height.0)?; - writer.write_i64::((*value_balance).into())?; + value_balance.zcash_serialize(&mut writer)?; // The previous match arms serialize in one go, because the // internal structure happens to nicely line up with the @@ -250,7 +251,7 @@ impl ZcashDeserialize for Transaction { let outputs = Vec::zcash_deserialize(&mut reader)?; let lock_time = LockTime::zcash_deserialize(&mut reader)?; let expiry_height = block::Height(reader.read_u32::()?); - let value_balance = reader.read_i64::()?.try_into()?; + let value_balance = (&mut reader).zcash_deserialize_into()?; let mut shielded_spends = Vec::zcash_deserialize(&mut reader)?; let mut shielded_outputs = Vec::zcash_deserialize(&mut reader)?; let joinsplit_data = OptV4JSD::zcash_deserialize(&mut reader)?; diff --git a/zebra-chain/src/transaction/sighash.rs b/zebra-chain/src/transaction/sighash.rs new file mode 100644 index 00000000..916b5d34 --- /dev/null +++ b/zebra-chain/src/transaction/sighash.rs @@ -0,0 +1,934 @@ +use super::Transaction; +use crate::{ + parameters::{ConsensusBranchId, NetworkUpgrade}, + serialization::{WriteZcashExt, ZcashSerialize}, + transparent, +}; +use blake2b_simd::Hash; +use byteorder::{LittleEndian, WriteBytesExt}; +use io::Write; +use std::io; + +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"; + +const OVERWINTER_VERSION_GROUP_ID: u32 = 0x03C4_8270; +const SAPLING_VERSION_GROUP_ID: u32 = 0x892F_2085; + +const ZCASH_SIGHASH_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZcashSigHash"; +const ZCASH_PREVOUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashPrevoutHash"; +const ZCASH_SEQUENCE_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSequencHash"; +const ZCASH_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashOutputsHash"; +const ZCASH_JOINSPLITS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashJSplitsHash"; +const ZCASH_SHIELDED_SPENDS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSSpendsHash"; +const ZCASH_SHIELDED_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSOutputHash"; + +bitflags::bitflags! { + pub struct HashType: u32 { + const ALL = 0b0000_0001; + const NONE = 0b0000_0010; + const SINGLE = Self::ALL.bits | Self::NONE.bits; + const ANYONECANPAY = 0b1000_0000; + } +} + +impl HashType { + fn masked(self) -> Self { + Self::from_bits_truncate(self.bits & 0b0001_1111) + } +} + +pub(super) struct SigHasher<'a> { + trans: &'a Transaction, + hash_type: HashType, + network_upgrade: NetworkUpgrade, + input: Option<(transparent::Output, &'a transparent::Input, usize)>, +} + +impl<'a> SigHasher<'a> { + pub fn new( + trans: &'a Transaction, + hash_type: HashType, + network_upgrade: NetworkUpgrade, + input: Option<(u32, transparent::Output)>, + ) -> Self { + let input = if let Some((index, prevout)) = input { + let index = index as usize; + let inputs = trans.inputs(); + + Some((prevout, &inputs[index], index)) + } else { + None + }; + + SigHasher { + trans, + hash_type, + network_upgrade, + input, + } + } + + pub(super) fn sighash(self) -> Hash { + use NetworkUpgrade::*; + let mut hash = blake2b_simd::Params::new() + .hash_length(32) + .personal(&self.personal()) + .to_state(); + + match self.network_upgrade { + Genesis | BeforeOverwinter => unreachable!(ZIP143_EXPLANATION), + Overwinter => self + .hash_sighash_zip143(&mut hash) + .expect("serialization into hasher never fails"), + Sapling | Blossom | Heartwood | Canopy => self + .hash_sighash_zip243(&mut hash) + .expect("serialization into hasher never fails"), + } + + hash.finalize() + } + + fn consensus_branch_id(&self) -> ConsensusBranchId { + self.network_upgrade.branch_id().expect(ZIP143_EXPLANATION) + } + + /// Sighash implementation for the overwinter network upgrade + 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)?; + self.hash_sequence(&mut writer)?; + self.hash_outputs(&mut writer)?; + self.hash_joinsplits(&mut writer)?; + self.hash_lock_time(&mut writer)?; + self.hash_expiry_height(&mut writer)?; + self.hash_hash_type(&mut writer)?; + self.hash_input(&mut writer)?; + + Ok(()) + } + + pub(super) fn personal(&self) -> [u8; 16] { + let mut personal = [0; 16]; + (&mut personal[..12]).copy_from_slice(ZCASH_SIGHASH_PERSONALIZATION_PREFIX); + (&mut personal[12..]) + .write_u32::(self.consensus_branch_id().into()) + .unwrap(); + personal + } + + fn hash_header(&self, mut writer: W) -> Result<(), io::Error> { + let overwintered_flag = 1 << 31; + + writer.write_u32::(match &self.trans { + Transaction::V1 { .. } | Transaction::V2 { .. } => unreachable!(ZIP143_EXPLANATION), + Transaction::V3 { .. } => 3 | overwintered_flag, + Transaction::V4 { .. } => 4 | overwintered_flag, + }) + } + + 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, + Transaction::V4 { .. } => SAPLING_VERSION_GROUP_ID, + }) + } + + fn hash_prevouts(&self, mut writer: W) -> Result<(), io::Error> { + if self.hash_type.contains(HashType::ANYONECANPAY) { + return writer.write_all(&[0; 32]); + } + + let mut hash = blake2b_simd::Params::new() + .hash_length(32) + .personal(ZCASH_PREVOUTS_HASH_PERSONALIZATION) + .to_state(); + + self.trans + .inputs() + .iter() + .filter_map(|input| match input { + transparent::Input::PrevOut { outpoint, .. } => Some(outpoint), + transparent::Input::Coinbase { .. } => None, + }) + .try_for_each(|outpoint| outpoint.zcash_serialize(&mut hash))?; + + writer.write_all(hash.finalize().as_ref()) + } + + 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 + { + return writer.write_all(&[0; 32]); + } + + let mut hash = blake2b_simd::Params::new() + .hash_length(32) + .personal(ZCASH_SEQUENCE_HASH_PERSONALIZATION) + .to_state(); + + self.trans + .inputs() + .iter() + .map(|input| match input { + transparent::Input::PrevOut { sequence, .. } => sequence, + transparent::Input::Coinbase { sequence, .. } => sequence, + }) + .try_for_each(|sequence| (&mut hash).write_u32::(*sequence))?; + + writer.write_all(hash.finalize().as_ref()) + } + + /// Writes the u256 hash of the transactions outputs to the provided Writer + 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) + } else if self.hash_type.masked() == HashType::SINGLE + && self + .input + .as_ref() + .map(|&(_, _, index)| index) + .map(|index| index < self.trans.outputs().len()) + .unwrap_or(false) + { + self.single_output_hash(writer) + } else { + writer.write_all(&[0; 32]) + } + } + + fn outputs_hash(&self, mut writer: W) -> Result<(), io::Error> { + let mut hash = blake2b_simd::Params::new() + .hash_length(32) + .personal(ZCASH_OUTPUTS_HASH_PERSONALIZATION) + .to_state(); + + self.trans + .outputs() + .iter() + .try_for_each(|output| output.zcash_serialize(&mut hash))?; + + writer.write_all(hash.finalize().as_ref()) + } + + fn single_output_hash(&self, mut writer: W) -> Result<(), io::Error> { + let &(_, _, output) = self + .input + .as_ref() + .expect("already checked index is some in `hash_outputs`"); + + let output = &self.trans.outputs()[output]; + + let mut hash = blake2b_simd::Params::new() + .hash_length(32) + .personal(ZCASH_OUTPUTS_HASH_PERSONALIZATION) + .to_state(); + + output.zcash_serialize(&mut hash)?; + + writer.write_all(hash.finalize().as_ref()) + } + + 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(), + Transaction::V4 { joinsplit_data, .. } => joinsplit_data.is_some(), + }; + + if !has_joinsplits { + return writer.write_all(&[0; 32]); + } + + let mut hash = blake2b_simd::Params::new() + .hash_length(32) + .personal(ZCASH_JOINSPLITS_HASH_PERSONALIZATION) + .to_state(); + + // This code, and the check above for has_joinsplits cannot be combined + // into a single branch because the `joinsplit_data` type of each + // tranaction kind has a different type. + // + // For v3 joinsplit_data is a JoinSplitData + // For v4 joinsplit_data is a JoinSplitData + // + // The type parameter on these types prevents them from being unified, + // which forces us to duplicate the logic in each branch even though the + // code within each branch is identical. + match self.trans { + Transaction::V3 { + joinsplit_data: Some(jsd), + .. + } => { + for joinsplit in jsd.joinsplits() { + joinsplit.zcash_serialize(&mut hash)?; + } + (&mut hash).write_all(&<[u8; 32]>::from(jsd.pub_key)[..])?; + } + Transaction::V4 { + joinsplit_data: Some(jsd), + .. + } => { + for joinsplit in jsd.joinsplits() { + joinsplit.zcash_serialize(&mut hash)?; + } + (&mut hash).write_all(&<[u8; 32]>::from(jsd.pub_key)[..])?; + } + _ => unreachable!("already checked transaction kind above"), + }; + + writer.write_all(hash.finalize().as_ref()) + } + + 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> { + writer.write_u32::(self.trans.expiry_height().expect(ZIP143_EXPLANATION).0) + } + + 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> { + if self.input.is_some() { + self.hash_input_prevout(&mut writer)?; + self.hash_input_script_code(&mut writer)?; + self.hash_input_amount(&mut writer)?; + self.hash_input_sequence(&mut writer)?; + } + + Ok(()) + } + + fn hash_input_prevout(&self, mut writer: W) -> Result<(), io::Error> { + let (_, input, _) = self + .input + .as_ref() + .expect("caller verifies input is not none"); + + let outpoint = match input { + transparent::Input::PrevOut { outpoint, .. } => outpoint, + transparent::Input::Coinbase { .. } => { + unreachable!("sighash should only ever be called for valid Input types") + } + }; + + outpoint.zcash_serialize(&mut writer)?; + + Ok(()) + } + + fn hash_input_script_code(&self, mut writer: W) -> Result<(), io::Error> { + let (transparent::Output { lock_script, .. }, _, _) = self + .input + .as_ref() + .expect("caller verifies input is not none"); + + writer.write_all(&lock_script.0)?; + + Ok(()) + } + + fn hash_input_amount(&self, mut writer: W) -> Result<(), io::Error> { + let (transparent::Output { value, .. }, _, _) = self + .input + .as_ref() + .expect("caller verifies input is not none"); + + writer.write_all(&value.to_bytes())?; + + Ok(()) + } + + fn hash_input_sequence(&self, mut writer: W) -> Result<(), io::Error> { + let (_, input, _) = self + .input + .as_ref() + .expect("caller verifies input is not none"); + + let sequence = match input { + transparent::Input::PrevOut { sequence, .. } => sequence, + transparent::Input::Coinbase { .. } => { + unreachable!("sighash should only ever be called for valid Input types") + } + }; + + writer.write_u32::(*sequence)?; + + Ok(()) + } + + // ******************** + // * ZIP243 ADDITIONS * + // ******************** + + /// Sighash implementation for the sapling network upgrade and every + /// subsequent network upgrade + 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)?; + self.hash_sequence(&mut writer)?; + self.hash_outputs(&mut writer)?; + self.hash_joinsplits(&mut writer)?; + self.hash_shielded_spends(&mut writer)?; + self.hash_shielded_outputs(&mut writer)?; + self.hash_lock_time(&mut writer)?; + self.hash_expiry_height(&mut writer)?; + self.hash_value_balance(&mut writer)?; + self.hash_hash_type(&mut writer)?; + self.hash_input(&mut writer)?; + + Ok(()) + } + + fn hash_shielded_spends(&self, mut writer: W) -> Result<(), io::Error> { + use Transaction::*; + + let shielded_data = match self.trans { + Transaction::V4 { + shielded_data: Some(shielded_data), + .. + } => shielded_data, + Transaction::V4 { + shielded_data: None, + .. + } => return writer.write_all(&[0; 32]), + V1 { .. } | V2 { .. } | V3 { .. } => unreachable!(ZIP243_EXPLANATION), + }; + + if shielded_data.spends().next().is_none() { + return writer.write_all(&[0; 32]); + } + + let mut hash = blake2b_simd::Params::new() + .hash_length(32) + .personal(ZCASH_SHIELDED_SPENDS_HASH_PERSONALIZATION) + .to_state(); + + for spend in shielded_data.spends() { + // This is the canonical transaction serialization, minus the `spendAuthSig`. + spend.cv.zcash_serialize(&mut hash)?; + hash.write_all(&spend.anchor.0[..])?; + hash.write_32_bytes(&spend.nullifier.into())?; + hash.write_all(&<[u8; 32]>::from(spend.rk)[..])?; + spend.zkproof.zcash_serialize(&mut hash)?; + } + + writer.write_all(hash.finalize().as_ref()) + } + + fn hash_shielded_outputs(&self, mut writer: W) -> Result<(), io::Error> { + use Transaction::*; + + let shielded_data = match self.trans { + Transaction::V4 { + shielded_data: Some(shielded_data), + .. + } => shielded_data, + Transaction::V4 { + shielded_data: None, + .. + } => return writer.write_all(&[0; 32]), + V1 { .. } | V2 { .. } | V3 { .. } => unreachable!(ZIP243_EXPLANATION), + }; + + if shielded_data.outputs().next().is_none() { + return writer.write_all(&[0; 32]); + } + + let mut hash = blake2b_simd::Params::new() + .hash_length(32) + .personal(ZCASH_SHIELDED_OUTPUTS_HASH_PERSONALIZATION) + .to_state(); + + for output in shielded_data.outputs() { + output.zcash_serialize(&mut hash)?; + } + + writer.write_all(hash.finalize().as_ref()) + } + + fn hash_value_balance(&self, mut writer: W) -> Result<(), io::Error> { + use Transaction::*; + + let value_balance = match self.trans { + Transaction::V4 { value_balance, .. } => value_balance, + V1 { .. } | V2 { .. } | V3 { .. } => unreachable!(ZIP243_EXPLANATION), + }; + + writer.write_all(&value_balance.to_bytes())?; + + 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(hex::decode("0153")?); + 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(hex::decode("00")?); + 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(hex::decode( + "1976a914507173527b4c3318a2aecd793bf1cfed705950cf88ac", + )?); + 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(()) + } +} diff --git a/zebra-chain/src/transparent/serialize.rs b/zebra-chain/src/transparent/serialize.rs index 64595fee..68937ba6 100644 --- a/zebra-chain/src/transparent/serialize.rs +++ b/zebra-chain/src/transparent/serialize.rs @@ -1,14 +1,12 @@ -use std::{ - convert::TryInto, - io::{self, Read}, -}; +use std::io::{self, Read}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use crate::{ block, serialization::{ - ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashSerialize, + ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashDeserializeInto, + ZcashSerialize, }, transaction, }; @@ -216,7 +214,7 @@ impl ZcashDeserialize for Input { impl ZcashSerialize for Output { fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { - writer.write_u64::(self.value.into())?; + self.value.zcash_serialize(&mut writer)?; self.lock_script.zcash_serialize(&mut writer)?; Ok(()) } @@ -224,9 +222,11 @@ impl ZcashSerialize for Output { impl ZcashDeserialize for Output { fn zcash_deserialize(mut reader: R) -> Result { + let reader = &mut reader; + Ok(Output { - value: reader.read_u64::()?.try_into()?, - lock_script: Script::zcash_deserialize(&mut reader)?, + value: reader.zcash_deserialize_into()?, + lock_script: Script::zcash_deserialize(reader)?, }) } }