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 <teor@riseup.net> Co-authored-by: Deirdre Connolly <deirdre@zfnd.org>
This commit is contained in:
parent
454e75e7c0
commit
6744f415d2
|
@ -3094,6 +3094,7 @@ version = "3.0.0-alpha.0"
|
|||
dependencies = [
|
||||
"bech32",
|
||||
"bincode",
|
||||
"bitflags",
|
||||
"bitvec",
|
||||
"blake2b_simd",
|
||||
"blake2s_simd",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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<T, E = Error> = std::result::Result<T, E>;
|
||||
|
||||
|
@ -262,6 +263,39 @@ pub trait Constraint {
|
|||
}
|
||||
}
|
||||
|
||||
impl ZcashSerialize for Amount<NegativeAllowed> {
|
||||
fn zcash_serialize<W: std::io::Write>(&self, mut writer: W) -> Result<(), std::io::Error> {
|
||||
writer.write_i64::<LittleEndian>(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ZcashDeserialize for Amount<NegativeAllowed> {
|
||||
fn zcash_deserialize<R: std::io::Read>(
|
||||
mut reader: R,
|
||||
) -> Result<Self, crate::serialization::SerializationError> {
|
||||
Ok(reader.read_i64::<LittleEndian>()?.try_into()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl ZcashSerialize for Amount<NonNegative> {
|
||||
fn zcash_serialize<W: std::io::Write>(&self, mut writer: W) -> Result<(), std::io::Error> {
|
||||
let amount = self
|
||||
.0
|
||||
.try_into()
|
||||
.expect("constraint guarantees value is positive");
|
||||
|
||||
writer.write_u64::<LittleEndian>(amount)
|
||||
}
|
||||
}
|
||||
|
||||
impl ZcashDeserialize for Amount<NonNegative> {
|
||||
fn zcash_deserialize<R: std::io::Read>(
|
||||
mut reader: R,
|
||||
) -> Result<Self, crate::serialization::SerializationError> {
|
||||
Ok(reader.read_u64::<LittleEndian>()?.try_into()?)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<P: ZkSnarkProof> {
|
|||
|
||||
impl<P: ZkSnarkProof> ZcashSerialize for JoinSplit<P> {
|
||||
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||
writer.write_u64::<LittleEndian>(self.vpub_old.into())?;
|
||||
writer.write_u64::<LittleEndian>(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<P: ZkSnarkProof> ZcashSerialize for JoinSplit<P> {
|
|||
impl<P: ZkSnarkProof> ZcashDeserialize for JoinSplit<P> {
|
||||
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
||||
Ok(JoinSplit::<P> {
|
||||
vpub_old: reader.read_u64::<LittleEndian>()?.try_into()?,
|
||||
vpub_new: reader.read_u64::<LittleEndian>()?.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(),
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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::<LittleEndian>(expiry_height.0)?;
|
||||
writer.write_i64::<LittleEndian>((*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::<LittleEndian>()?);
|
||||
let value_balance = reader.read_i64::<LittleEndian>()?.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)?;
|
||||
|
|
|
@ -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<W: io::Write>(&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::<LittleEndian>(self.consensus_branch_id().into())
|
||||
.unwrap();
|
||||
personal
|
||||
}
|
||||
|
||||
fn hash_header<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||
let overwintered_flag = 1 << 31;
|
||||
|
||||
writer.write_u32::<LittleEndian>(match &self.trans {
|
||||
Transaction::V1 { .. } | Transaction::V2 { .. } => unreachable!(ZIP143_EXPLANATION),
|
||||
Transaction::V3 { .. } => 3 | overwintered_flag,
|
||||
Transaction::V4 { .. } => 4 | overwintered_flag,
|
||||
})
|
||||
}
|
||||
|
||||
fn hash_groupid<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||
writer.write_u32::<LittleEndian>(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<W: io::Write>(&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<W: io::Write>(&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::<LittleEndian>(*sequence))?;
|
||||
|
||||
writer.write_all(hash.finalize().as_ref())
|
||||
}
|
||||
|
||||
/// Writes the u256 hash of the transactions outputs to the provided Writer
|
||||
fn hash_outputs<W: io::Write>(&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<W: io::Write>(&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<W: io::Write>(&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<W: io::Write>(&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<Bctv14Proof>
|
||||
// For v4 joinsplit_data is a JoinSplitData<Groth16Proof>
|
||||
//
|
||||
// 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<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||
self.trans.lock_time().zcash_serialize(&mut writer)
|
||||
}
|
||||
|
||||
fn hash_expiry_height<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||
writer.write_u32::<LittleEndian>(self.trans.expiry_height().expect(ZIP143_EXPLANATION).0)
|
||||
}
|
||||
|
||||
fn hash_hash_type<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||
writer.write_u32::<LittleEndian>(self.hash_type.bits())
|
||||
}
|
||||
|
||||
fn hash_input<W: io::Write>(&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<W: io::Write>(&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<W: io::Write>(&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<W: io::Write>(&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<W: io::Write>(&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::<LittleEndian>(*sequence)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ********************
|
||||
// * ZIP243 ADDITIONS *
|
||||
// ********************
|
||||
|
||||
/// Sighash implementation for the sapling network upgrade and every
|
||||
/// subsequent network upgrade
|
||||
fn hash_sighash_zip243<W: io::Write>(&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<W: io::Write>(&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<W: io::Write>(&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<W: io::Write>(&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::<Transaction>()?;
|
||||
|
||||
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::<Transaction>()?;
|
||||
|
||||
let value = hex::decode("2f6e04963b4c0100")?.zcash_deserialize_into::<Amount<_>>()?;
|
||||
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::<Transaction>()?;
|
||||
|
||||
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::<Transaction>()?;
|
||||
|
||||
let value = hex::decode("adedf02996510200")?.zcash_deserialize_into::<Amount<_>>()?;
|
||||
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::<Transaction>()?;
|
||||
|
||||
let value = hex::decode("80f0fa0200000000")?.zcash_deserialize_into::<Amount<_>>()?;
|
||||
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(())
|
||||
}
|
||||
}
|
|
@ -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<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||
writer.write_u64::<LittleEndian>(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<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
||||
let reader = &mut reader;
|
||||
|
||||
Ok(Output {
|
||||
value: reader.read_u64::<LittleEndian>()?.try_into()?,
|
||||
lock_script: Script::zcash_deserialize(&mut reader)?,
|
||||
value: reader.zcash_deserialize_into()?,
|
||||
lock_script: Script::zcash_deserialize(reader)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue