Add v5 txid & signature hashing.

This commit is contained in:
Kris Nuttycombe 2021-05-12 17:01:17 -06:00
parent 1138343c89
commit 55d1090f70
15 changed files with 1197 additions and 287 deletions

View File

@ -238,8 +238,7 @@ where
},
RecipientAddress::Transparent(addr) => {
let script = addr.script();
tx.transparent_bundle
.as_ref()
tx.transparent_bundle()
.and_then(|b| {
b.vout
.iter()

View File

@ -44,7 +44,7 @@ pub fn decrypt_transaction<P: consensus::Parameters>(
) -> Vec<DecryptedOutput> {
let mut decrypted = vec![];
if let Some(bundle) = tx.sapling_bundle.as_ref() {
if let Some(bundle) = tx.sapling_bundle().as_ref() {
for (account, extfvk) in extfvks.iter() {
let ivk = extfvk.fvk.vk.ivk();
let ovk = extfvk.fvk.ovk;

View File

@ -489,7 +489,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
//
// Assumes that create_spend_to_address() will never be called in parallel, which is a
// reasonable assumption for a light client such as a mobile phone.
if let Some(bundle) = sent_tx.tx.sapling_bundle.as_ref() {
if let Some(bundle) = sent_tx.tx.sapling_bundle().as_ref() {
for spend in &bundle.shielded_spends {
wallet::mark_spent(up, tx_ref, &spend.nullifier)?;
}

View File

@ -650,14 +650,14 @@ pub fn put_tx_data<'a, P>(
if stmts
.stmt_update_tx_data
.execute(params![u32::from(tx.expiry_height), raw_tx, txid,])?
.execute(params![u32::from(tx.expiry_height()), raw_tx, txid,])?
== 0
{
// It isn't there, so insert our transaction into the database.
stmts.stmt_insert_tx_data.execute(params![
txid,
created_at,
u32::from(tx.expiry_height),
u32::from(tx.expiry_height()),
raw_tx
])?;

View File

@ -68,9 +68,9 @@ pub fn get_spendable_notes<P>(
"SELECT diversifier, value, rcm, witness
FROM received_notes
INNER JOIN transactions ON transactions.id_tx = received_notes.tx
INNER JOIN sapling_witnesses ON sapling_witnesses.note = received_notes.id_note
WHERE account = :account
AND spent IS NULL
INNER JOIN sapling_witnesses ON sapling_witnesses.note = received_notes.id_note
WHERE account = :account
AND spent IS NULL
AND transactions.block <= :anchor_height
AND sapling_witnesses.block = :anchor_height",
)?;
@ -153,7 +153,7 @@ mod tests {
use zcash_primitives::{
block::BlockHash,
consensus::BlockHeight,
consensus::{BlockHeight, BranchId},
legacy::TransparentAddress,
sapling::{note_encryption::try_sapling_output_recovery, prover::TxProver},
transaction::{components::Amount, Transaction},
@ -617,7 +617,7 @@ mod tests {
|row| row.get(0),
)
.unwrap();
let tx = Transaction::read(&raw_tx[..]).unwrap();
let tx = Transaction::read(&raw_tx[..], BranchId::Canopy).unwrap();
// Fetch the output index from the database
let output_index: i64 = db_write
@ -632,7 +632,7 @@ mod tests {
.unwrap();
let output =
&tx.sapling_bundle.as_ref().unwrap().shielded_outputs[output_index as usize];
&tx.sapling_bundle().as_ref().unwrap().shielded_outputs[output_index as usize];
try_sapling_output_recovery(
&network,

View File

@ -78,14 +78,14 @@ pub trait Epoch {
/// by the context.
impl<'a> demo::Context for Context<'a> {
fn is_tze_only(&self) -> bool {
self.tx.transparent_bundle.is_none()
&& self.tx.sapling_bundle.is_none()
&& self.tx.sprout_bundle.is_none()
&& self.tx.orchard_bundle.is_none()
self.tx.transparent_bundle().is_none()
&& self.tx.sapling_bundle().is_none()
&& self.tx.sprout_bundle().is_none()
&& self.tx.orchard_bundle().is_none()
}
fn tx_tze_outputs(&self) -> &[TzeOut] {
if let Some(bundle) = &self.tx.tze_bundle {
if let Some(bundle) = &self.tx.tze_bundle() {
&bundle.vout
} else {
&[]

View File

@ -621,14 +621,14 @@ mod tests {
/// by the context.
impl<'a> Context for Ctx<'a> {
fn is_tze_only(&self) -> bool {
self.tx.transparent_bundle.is_none()
&& self.tx.sprout_bundle.is_none()
&& self.tx.sapling_bundle.is_none()
&& self.tx.orchard_bundle.is_none()
self.tx.transparent_bundle().is_none()
&& self.tx.sapling_bundle().is_none()
&& self.tx.sprout_bundle().is_none()
&& self.tx.orchard_bundle().is_none()
}
fn tx_tze_outputs(&self) -> &[TzeOut] {
match &self.tx.tze_bundle {
match self.tx.tze_bundle().as_ref() {
Some(b) => &b.vout,
None => &[],
}
@ -683,20 +683,21 @@ mod tests {
precondition: tze::Precondition::from(0, &Precondition::open(hash_1)),
};
let tx_a = TransactionData {
version: TxVersion::ZFuture,
lock_time: 0,
expiry_height: 0u32.into(),
transparent_bundle: None,
sprout_bundle: None,
sapling_bundle: None,
orchard_bundle: None,
tze_bundle: Some(Bundle {
let tx_a = TransactionData::from_parts(
TxVersion::ZFuture,
BranchId::ZFuture,
0,
0u32.into(),
None,
None,
None,
None,
Some(Bundle {
vin: vec![],
vout: vec![out_a],
}),
}
.freeze(BranchId::ZFuture)
)
.freeze()
.unwrap();
//
@ -712,20 +713,21 @@ mod tests {
precondition: tze::Precondition::from(0, &Precondition::close(hash_2)),
};
let tx_b = TransactionData {
version: TxVersion::ZFuture,
lock_time: 0,
expiry_height: 0u32.into(),
transparent_bundle: None,
sprout_bundle: None,
sapling_bundle: None,
orchard_bundle: None,
tze_bundle: Some(Bundle {
let tx_b = TransactionData::from_parts(
TxVersion::ZFuture,
BranchId::ZFuture,
0,
0u32.into(),
None,
None,
None,
None,
Some(Bundle {
vin: vec![in_b],
vout: vec![out_b],
}),
}
.freeze(BranchId::ZFuture)
)
.freeze()
.unwrap();
//
@ -737,20 +739,21 @@ mod tests {
witness: tze::Witness::from(0, &Witness::close(preimage_2)),
};
let tx_c = TransactionData {
version: TxVersion::ZFuture,
lock_time: 0,
expiry_height: 0u32.into(),
transparent_bundle: None,
sprout_bundle: None,
sapling_bundle: None,
orchard_bundle: None,
tze_bundle: Some(Bundle {
let tx_c = TransactionData::from_parts(
TxVersion::ZFuture,
BranchId::ZFuture,
0,
0u32.into(),
None,
None,
None,
None,
Some(Bundle {
vin: vec![in_c],
vout: vec![],
}),
}
.freeze(BranchId::ZFuture)
)
.freeze()
.unwrap();
// Verify tx_b
@ -758,8 +761,8 @@ mod tests {
let ctx = Ctx { tx: &tx_b };
assert_eq!(
Program.verify(
&tx_a.tze_bundle.as_ref().unwrap().vout[0].precondition,
&tx_b.tze_bundle.as_ref().unwrap().vin[0].witness,
&tx_a.tze_bundle().as_ref().unwrap().vout[0].precondition,
&tx_b.tze_bundle().as_ref().unwrap().vin[0].witness,
&ctx
),
Ok(())
@ -771,8 +774,8 @@ mod tests {
let ctx = Ctx { tx: &tx_c };
assert_eq!(
Program.verify(
&tx_b.tze_bundle.as_ref().unwrap().vout[0].precondition,
&tx_c.tze_bundle.as_ref().unwrap().vin[0].witness,
&tx_b.tze_bundle().as_ref().unwrap().vout[0].precondition,
&tx_c.tze_bundle().as_ref().unwrap().vin[0].witness,
&ctx
),
Ok(())
@ -830,7 +833,7 @@ mod tests {
.build(&prover)
.map_err(|e| format!("build failure: {:?}", e))
.unwrap();
let tze_a = tx_a.tze_bundle.as_ref().unwrap();
let tze_a = tx_a.tze_bundle().unwrap();
//
// Transfer
@ -848,7 +851,7 @@ mod tests {
.build(&prover)
.map_err(|e| format!("build failure: {:?}", e))
.unwrap();
let tze_b = tx_b.tze_bundle.as_ref().unwrap();
let tze_b = tx_b.tze_bundle().unwrap();
//
// Closing transaction
@ -873,7 +876,7 @@ mod tests {
.build(&prover)
.map_err(|e| format!("build failure: {:?}", e))
.unwrap();
let tze_c = tx_c.tze_bundle.as_ref().unwrap();
let tze_c = tx_c.tze_bundle().unwrap();
// Verify tx_b
let ctx0 = Ctx { tx: &tx_b };

View File

@ -27,8 +27,9 @@ use crate::{
},
transparent::{self, builder::TransparentBuilder},
},
signature_hash_data, SignableInput, Transaction, TransactionData, TxVersion, Unauthorized,
SIGHASH_ALL,
sighash::{SignableInput, SIGHASH_ALL},
sighash_v4::v4_signature_hash,
Transaction, TransactionData, TxVersion, Unauthorized,
},
zip32::ExtendedSpendingKey,
};
@ -342,6 +343,7 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
let unauthed_tx = TransactionData {
version,
consensus_branch_id,
lock_time: 0,
expiry_height: self.expiry_height,
transparent_bundle,
@ -357,17 +359,12 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
//
let mut sighash = [0u8; 32];
sighash.copy_from_slice(&signature_hash_data(
&unauthed_tx,
consensus_branch_id,
SIGHASH_ALL,
SignableInput::Shielded,
));
sighash.copy_from_slice(
&v4_signature_hash(&unauthed_tx, SignableInput::Shielded, SIGHASH_ALL).as_ref(),
);
#[cfg(feature = "transparent-inputs")]
let transparent_sigs = self
.transparent_builder
.create_signatures(&unauthed_tx, consensus_branch_id);
let transparent_sigs = self.transparent_builder.create_signatures(&unauthed_tx);
let sapling_sigs = self
.sapling_builder
@ -382,7 +379,6 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
Ok((
Self::apply_signatures(
consensus_branch_id,
unauthed_tx,
#[cfg(feature = "transparent-inputs")]
transparent_sigs,
@ -397,7 +393,6 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
}
fn apply_signatures(
consensus_branch_id: consensus::BranchId,
unauthed_tx: TransactionData<Unauthorized>,
#[cfg(feature = "transparent-inputs")] transparent_sigs: Option<Vec<Script>>,
sapling_sigs: Option<(Vec<redjubjub::Signature>, redjubjub::Signature)>,
@ -448,6 +443,7 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
let authorized_tx = TransactionData {
version: unauthed_tx.version,
consensus_branch_id: unauthed_tx.consensus_branch_id,
lock_time: unauthed_tx.lock_time,
expiry_height: unauthed_tx.expiry_height,
transparent_bundle,
@ -458,7 +454,7 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
tze_bundle: signed_tze_bundle,
};
authorized_tx.freeze(consensus_branch_id)
authorized_tx.freeze()
}
}

View File

@ -12,11 +12,12 @@ use crate::{
#[cfg(feature = "transparent-inputs")]
use crate::{
consensus::{self},
legacy::Script,
transaction::{
components::OutPoint, sighash_v4::signature_hash_data, SignableInput, TransactionData,
Unauthorized, SIGHASH_ALL,
components::OutPoint,
sighash::{SignableInput, SIGHASH_ALL},
sighash_v4::v4_signature_hash,
TransactionData, Unauthorized,
},
};
@ -153,11 +154,7 @@ impl TransparentBuilder {
}
#[cfg(feature = "transparent-inputs")]
pub fn create_signatures(
self,
mtx: &TransactionData<Unauthorized>,
consensus_branch_id: consensus::BranchId,
) -> Option<Vec<Script>> {
pub fn create_signatures(self, mtx: &TransactionData<Unauthorized>) -> Option<Vec<Script>> {
if self.inputs.is_empty() && self.vout.is_empty() {
None
} else {
@ -167,16 +164,18 @@ impl TransparentBuilder {
.enumerate()
.map(|(i, info)| {
let mut sighash = [0u8; 32];
sighash.copy_from_slice(&signature_hash_data(
mtx,
consensus_branch_id,
SIGHASH_ALL,
SignableInput::transparent(
i,
&info.coin.script_pubkey,
info.coin.value,
),
));
sighash.copy_from_slice(
&v4_signature_hash(
mtx,
SignableInput::transparent(
i,
&info.coin.script_pubkey,
info.coin.value,
),
SIGHASH_ALL,
)
.as_ref(),
);
let msg =
secp256k1::Message::from_slice(sighash.as_ref()).expect("32 bytes");

View File

@ -1,4 +1,14 @@
//! Structs and methods for handling Zcash transactions.
pub mod builder;
pub mod components;
pub mod sighash;
pub mod sighash_v4;
pub mod sighash_v5;
pub mod txid;
pub mod util;
#[cfg(test)]
mod tests;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::fmt;
@ -18,21 +28,12 @@ use self::{
sprout::{self, JsDescription},
transparent::{self, TxIn, TxOut},
},
sighash_v4::{signature_hash_data, SignableInput, SIGHASH_ALL},
util::sha256d::{HashReader, HashWriter},
};
#[cfg(feature = "zfuture")]
use self::components::tze;
pub mod builder;
pub mod components;
pub mod sighash_v4;
pub mod util;
#[cfg(test)]
mod tests;
const OVERWINTER_VERSION_GROUP_ID: u32 = 0x03C48270;
const OVERWINTER_TX_VERSION: u32 = 3;
const SAPLING_VERSION_GROUP_ID: u32 = 0x892F2085;
@ -113,6 +114,7 @@ impl TxVersion {
match (version, reader.read_u32::<LittleEndian>()?) {
(OVERWINTER_TX_VERSION, OVERWINTER_VERSION_GROUP_ID) => Ok(TxVersion::Overwinter),
(SAPLING_TX_VERSION, SAPLING_VERSION_GROUP_ID) => Ok(TxVersion::Sapling),
(V5_TX_VERSION, V5_VERSION_GROUP_ID) => Ok(TxVersion::ZcashTxV5),
#[cfg(feature = "zfuture")]
(ZFUTURE_TX_VERSION, ZFUTURE_VERSION_GROUP_ID) => Ok(TxVersion::ZFuture),
_ => Err(io::Error::new(
@ -177,6 +179,10 @@ impl TxVersion {
}
}
pub fn has_overwinter(&self) -> bool {
!matches!(self, TxVersion::Sprout(_))
}
pub fn has_sapling(&self) -> bool {
match self {
TxVersion::Sprout(_) | TxVersion::Overwinter => false,
@ -187,6 +193,20 @@ impl TxVersion {
}
}
pub fn has_orchard(&self) -> bool {
match self {
TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => false,
TxVersion::ZcashTxV5 => true,
#[cfg(feature = "zfuture")]
TxVersion::ZFuture => true,
}
}
#[cfg(feature = "zfuture")]
pub fn has_tze(&self) -> bool {
matches!(self, TxVersion::ZFuture)
}
pub fn suggested_for_branch(consensus_branch_id: BranchId) -> Self {
match consensus_branch_id {
BranchId::Sprout => TxVersion::Sprout(2),
@ -255,15 +275,93 @@ impl PartialEq for Transaction {
}
pub struct TransactionData<A: Authorization> {
pub version: TxVersion,
pub lock_time: u32,
pub expiry_height: BlockHeight,
pub transparent_bundle: Option<transparent::Bundle<A::TransparentAuth>>,
pub sprout_bundle: Option<sprout::Bundle>,
pub sapling_bundle: Option<sapling::Bundle<A::SaplingAuth>>,
pub orchard_bundle: Option<orchard::bundle::Bundle<A::OrchardAuth, Amount>>,
version: TxVersion,
consensus_branch_id: BranchId,
lock_time: u32,
expiry_height: BlockHeight,
transparent_bundle: Option<transparent::Bundle<A::TransparentAuth>>,
sprout_bundle: Option<sprout::Bundle>,
sapling_bundle: Option<sapling::Bundle<A::SaplingAuth>>,
orchard_bundle: Option<orchard::bundle::Bundle<A::OrchardAuth, Amount>>,
#[cfg(feature = "zfuture")]
pub tze_bundle: Option<tze::Bundle<A::TzeAuth>>,
tze_bundle: Option<tze::Bundle<A::TzeAuth>>,
}
impl<A: Authorization> TransactionData<A> {
#[allow(clippy::too_many_arguments)]
pub fn from_parts(
version: TxVersion,
consensus_branch_id: BranchId,
lock_time: u32,
expiry_height: BlockHeight,
transparent_bundle: Option<transparent::Bundle<A::TransparentAuth>>,
sprout_bundle: Option<sprout::Bundle>,
sapling_bundle: Option<sapling::Bundle<A::SaplingAuth>>,
orchard_bundle: Option<orchard::Bundle<A::OrchardAuth, Amount>>,
#[cfg(feature = "zfuture")] tze_bundle: Option<tze::Bundle<A::TzeAuth>>,
) -> Self {
TransactionData {
version,
consensus_branch_id,
lock_time,
expiry_height,
transparent_bundle,
sprout_bundle,
sapling_bundle,
orchard_bundle,
#[cfg(feature = "zfuture")]
tze_bundle,
}
}
pub fn version(&self) -> TxVersion {
self.version
}
pub fn consensus_branch_id(&self) -> BranchId {
self.consensus_branch_id
}
pub fn expiry_height(&self) -> BlockHeight {
self.expiry_height
}
pub fn transparent_bundle(&self) -> Option<&transparent::Bundle<A::TransparentAuth>> {
self.transparent_bundle.as_ref()
}
pub fn sprout_bundle(&self) -> Option<&sprout::Bundle> {
self.sprout_bundle.as_ref()
}
pub fn sapling_bundle(&self) -> Option<&sapling::Bundle<A::SaplingAuth>> {
self.sapling_bundle.as_ref()
}
pub fn orchard_bundle(&self) -> Option<&orchard::Bundle<A::OrchardAuth, Amount>> {
self.orchard_bundle.as_ref()
}
#[cfg(feature = "zfuture")]
pub fn tze_bundle(&self) -> Option<&tze::Bundle<A::TzeAuth>> {
self.tze_bundle.as_ref()
}
pub fn digest<D: TransactionDigest<A>>(&self, digester: D) -> D::Digest {
digester.combine(
digester.digest_header(
self.version,
self.consensus_branch_id,
self.lock_time,
self.expiry_height,
),
digester.digest_transparent(self.transparent_bundle.as_ref()),
digester.digest_sapling(self.sapling_bundle.as_ref()),
digester.digest_orchard(self.orchard_bundle.as_ref()),
#[cfg(feature = "zfuture")]
digester.digest_tze(self.tze_bundle.as_ref()),
)
}
}
impl<A: Authorization> std::fmt::Debug for TransactionData<A> {
@ -272,17 +370,25 @@ impl<A: Authorization> std::fmt::Debug for TransactionData<A> {
f,
"TransactionData(
version = {:?},
consensus_branch_id = {:?},
lock_time = {:?},
expiry_height = {:?},
{}{}{}{}",
transparent_fields = {{{}}}
sprout = {{{}}},
sapling = {{{}}},
orchard = {{{}}},
tze = {{{}}}
)",
self.version,
self.consensus_branch_id,
self.lock_time,
self.expiry_height,
if let Some(b) = &self.transparent_bundle {
format!(
"
vin = {:?},
vout = {:?},",
vin = {:?},
vout = {:?},
",
b.vin, b.vout
)
} else {
@ -291,8 +397,9 @@ impl<A: Authorization> std::fmt::Debug for TransactionData<A> {
if let Some(b) = &self.sprout_bundle {
format!(
"
joinsplits = {:?},
joinsplit_pubkey = {:?},",
joinsplits = {:?},
joinsplit_pubkey = {:?},
",
b.joinsplits, b.joinsplit_pubkey
)
} else {
@ -301,22 +408,36 @@ impl<A: Authorization> std::fmt::Debug for TransactionData<A> {
if let Some(b) = &self.sapling_bundle {
format!(
"
value_balance = {:?},
shielded_spends = {:?},
shielded_outputs = {:?},
binding_sig = {:?},",
value_balance = {:?},
shielded_spends = {:?},
shielded_outputs = {:?},
binding_sig = {:?},
",
b.value_balance, b.shielded_spends, b.shielded_outputs, b.authorization
)
} else {
"".to_string()
},
if let Some(b) = &self.orchard_bundle {
format!(
"
value_balance = {:?},
actions = {:?},
",
b.value_balance(),
b.actions().len()
)
} else {
"".to_string()
},
{
#[cfg(feature = "zfuture")]
if let Some(b) = &self.tze_bundle {
format!(
"
tze_inputs = {:?},
tze_outputs = {:?},",
tze_inputs = {:?},
tze_outputs = {:?},
",
b.vin, b.vout
)
} else {
@ -337,53 +458,14 @@ impl<A: Authorization> TransactionData<A> {
}
}
impl Default for TransactionData<Unauthorized> {
fn default() -> Self {
Self::new()
}
}
impl TransactionData<Unauthorized> {
pub fn new() -> Self {
TransactionData {
version: TxVersion::Sapling,
lock_time: 0,
expiry_height: 0u32.into(),
transparent_bundle: None,
sprout_bundle: None,
sapling_bundle: None,
orchard_bundle: None,
#[cfg(feature = "zfuture")]
tze_bundle: None,
}
}
#[cfg(feature = "zfuture")]
pub fn zfuture() -> Self {
TransactionData {
version: TxVersion::ZFuture,
lock_time: 0,
expiry_height: 0u32.into(),
transparent_bundle: None,
sprout_bundle: None,
sapling_bundle: None,
orchard_bundle: None,
tze_bundle: None,
}
}
}
impl TransactionData<Authorized> {
pub fn freeze(self, consensus_branch_id: BranchId) -> io::Result<Transaction> {
Transaction::from_data(self, consensus_branch_id)
pub fn freeze(self) -> io::Result<Transaction> {
Transaction::from_data(self)
}
}
impl Transaction {
fn from_data(
data: TransactionData<Authorized>,
_consensus_branch_id: BranchId,
) -> io::Result<Self> {
fn from_data(data: TransactionData<Authorized>) -> io::Result<Self> {
let mut tx = Transaction {
txid: TxId([0; 32]),
data,
@ -398,7 +480,8 @@ impl Transaction {
self.txid
}
pub fn read<R: Read>(reader: R) -> io::Result<Self> {
#[allow(clippy::redundant_closure)]
pub fn read<R: Read>(reader: R, consensus_branch_id: BranchId) -> io::Result<Self> {
let mut reader = HashReader::new(reader);
let version = TxVersion::read(&mut reader)?;
@ -462,6 +545,7 @@ impl Transaction {
txid: TxId(txid),
data: TransactionData {
version,
consensus_branch_id,
lock_time,
expiry_height,
transparent_bundle,
@ -615,7 +699,7 @@ pub struct TxDigests<A> {
pub tze_digests: Option<TzeDigests<A>>,
}
pub(crate) trait TransactionDigest<A: Authorization> {
pub trait TransactionDigest<A: Authorization> {
type HeaderDigest;
type TransparentDigest;
type SaplingDigest;
@ -707,14 +791,15 @@ pub mod testing {
#[cfg(not(feature = "zfuture"))]
prop_compose! {
pub fn arb_txdata(branch_id: BranchId)(
version in arb_tx_version(branch_id),
pub fn arb_txdata(consensus_branch_id: BranchId)(
version in arb_tx_version(consensus_branch_id),
lock_time in any::<u32>(),
expiry_height in any::<u32>(),
transparent_bundle in transparent::arb_bundle(),
) -> TransactionData<Authorized> {
TransactionData {
version,
consensus_branch_id,
lock_time,
expiry_height: expiry_height.into(),
transparent_bundle,
@ -727,8 +812,8 @@ pub mod testing {
#[cfg(feature = "zfuture")]
prop_compose! {
pub fn arb_txdata(branch_id: BranchId)(
version in arb_tx_version(branch_id),
pub fn arb_txdata(consensus_branch_id: BranchId)(
version in arb_tx_version(consensus_branch_id),
lock_time in any::<u32>(),
expiry_height in any::<u32>(),
transparent_bundle in transparent::arb_bundle(),
@ -736,6 +821,7 @@ pub mod testing {
) -> TransactionData<Authorized> {
TransactionData {
version,
consensus_branch_id,
lock_time,
expiry_height: expiry_height.into(),
transparent_bundle,
@ -749,7 +835,7 @@ pub mod testing {
prop_compose! {
pub fn arb_tx(branch_id: BranchId)(tx_data in arb_txdata(branch_id)) -> Transaction {
Transaction::from_data(tx_data, branch_id).unwrap()
Transaction::from_data(tx_data).unwrap()
}
}
}

View File

@ -0,0 +1,136 @@
use crate::legacy::Script;
use blake2b_simd::Hash as Blake2bHash;
use std::convert::TryInto;
use super::{
components::{
sapling::{self, GrothProofBytes},
Amount,
},
sighash_v4::v4_signature_hash,
sighash_v5::v5_signature_hash,
Authorization, TransactionData, TxDigests, TxVersion,
};
#[cfg(feature = "zfuture")]
use crate::extensions::transparent::Precondition;
pub const SIGHASH_ALL: u32 = 1;
pub const SIGHASH_NONE: u32 = 2;
pub const SIGHASH_SINGLE: u32 = 3;
pub const SIGHASH_MASK: u32 = 0x1f;
pub const SIGHASH_ANYONECANPAY: u32 = 0x80;
pub struct TransparentInput<'a> {
index: usize,
script_code: &'a Script,
value: Amount,
}
impl<'a> TransparentInput<'a> {
pub fn new(index: usize, script_code: &'a Script, value: Amount) -> Self {
TransparentInput {
index,
script_code,
value,
}
}
pub fn index(&self) -> usize {
self.index
}
pub fn script_code(&self) -> &'a Script {
self.script_code
}
pub fn value(&self) -> Amount {
self.value
}
}
#[cfg(feature = "zfuture")]
pub struct TzeInput<'a> {
index: usize,
precondition: &'a Precondition,
value: Amount,
}
#[cfg(feature = "zfuture")]
impl<'a> TzeInput<'a> {
pub fn new(index: usize, precondition: &'a Precondition, value: Amount) -> Self {
TzeInput {
index,
precondition,
value,
}
}
pub fn index(&self) -> usize {
self.index
}
pub fn precondition(&self) -> &'a Precondition {
self.precondition
}
pub fn value(&self) -> Amount {
self.value
}
}
pub enum SignableInput<'a> {
Shielded,
Transparent(TransparentInput<'a>),
#[cfg(feature = "zfuture")]
Tze(TzeInput<'a>),
}
impl<'a> SignableInput<'a> {
pub fn transparent(index: usize, script_code: &'a Script, value: Amount) -> Self {
SignableInput::Transparent(TransparentInput {
index,
script_code,
value,
})
}
#[cfg(feature = "zfuture")]
pub fn tze(index: usize, precondition: &'a Precondition, value: Amount) -> Self {
SignableInput::Tze(TzeInput {
index,
precondition,
value,
})
}
}
pub struct SignatureHash(Blake2bHash);
impl AsRef<[u8; 32]> for SignatureHash {
fn as_ref(&self) -> &[u8; 32] {
self.0.as_ref().try_into().unwrap()
}
}
pub fn signature_hash<
'a,
SA: sapling::Authorization<Proof = GrothProofBytes>,
A: Authorization<SaplingAuth = SA>,
>(
tx: &TransactionData<A>,
signable_input: SignableInput<'a>,
txid_parts: &TxDigests<Blake2bHash>,
hash_type: u32,
) -> SignatureHash {
SignatureHash(match tx.version {
TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => {
v4_signature_hash(tx, signable_input, hash_type)
}
TxVersion::ZcashTxV5 => v5_signature_hash(tx, txid_parts, signable_input, hash_type),
#[cfg(feature = "zfuture")]
TxVersion::ZFuture => v5_signature_hash(tx, txid_parts, signable_input, hash_type),
})
}

View File

@ -3,22 +3,16 @@ use byteorder::{LittleEndian, WriteBytesExt};
use ff::PrimeField;
use group::GroupEncoding;
use crate::{
consensus::{self, BranchId},
legacy::Script,
};
#[cfg(feature = "zfuture")]
use crate::extensions::transparent::Precondition;
use crate::consensus::BranchId;
use super::{
components::{
amount::Amount,
sapling::{self, GrothProofBytes, OutputDescription, SpendDescription},
sprout::JsDescription,
transparent::{self, TxIn, TxOut},
},
Authorization, Transaction, TransactionData, TxVersion,
sighash::{SignableInput, SIGHASH_ANYONECANPAY, SIGHASH_MASK, SIGHASH_NONE, SIGHASH_SINGLE},
Authorization, TransactionData,
};
const ZCASH_SIGHASH_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZcashSigHash";
@ -29,12 +23,6 @@ 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";
pub const SIGHASH_ALL: u32 = 1;
const SIGHASH_NONE: u32 = 2;
const SIGHASH_SINGLE: u32 = 3;
const SIGHASH_MASK: u32 = 0x1f;
const SIGHASH_ANYONECANPAY: u32 = 0x80;
macro_rules! update_u32 {
($h:expr, $value:expr, $tmp:expr) => {
(&mut $tmp[..4]).write_u32::<LittleEndian>($value).unwrap();
@ -52,10 +40,6 @@ macro_rules! update_hash {
};
}
fn has_overwinter_components(version: &TxVersion) -> bool {
!matches!(version, TxVersion::Sprout(_))
}
fn prevout_hash<TA: transparent::Authorization>(vin: &[TxIn<TA>]) -> Blake2bHash {
let mut data = Vec::with_capacity(vin.len() * 36);
for t_in in vin {
@ -110,7 +94,7 @@ fn joinsplits_hash(
* if consensus_branch_id.sprout_uses_groth_proofs() {
1698 // JSDescription with Groth16 proof
} else {
1802 // JSDescription with PHGR13 proof
1802 // JsDescription with PHGR13 proof
},
);
for js in joinsplits {
@ -130,7 +114,7 @@ fn shielded_spends_hash<A: sapling::Authorization<Proof = GrothProofBytes>>(
for s_spend in shielded_spends {
data.extend_from_slice(&s_spend.cv.to_bytes());
data.extend_from_slice(s_spend.anchor.to_repr().as_ref());
data.extend_from_slice(&s_spend.nullifier.0);
data.extend_from_slice(&s_spend.nullifier.as_ref());
s_spend.rk.write(&mut data).unwrap();
data.extend_from_slice(&s_spend.zkproof);
}
@ -153,55 +137,19 @@ fn shielded_outputs_hash<A: sapling::Authorization<Proof = GrothProofBytes>>(
.hash(&data)
}
pub enum SignableInput<'a> {
Shielded,
Transparent {
index: usize,
script_code: &'a Script,
value: Amount,
},
#[cfg(feature = "zfuture")]
Tze {
index: usize,
precondition: &'a Precondition,
value: Amount,
},
}
impl<'a> SignableInput<'a> {
pub fn transparent(index: usize, script_code: &'a Script, value: Amount) -> Self {
SignableInput::Transparent {
index,
script_code,
value,
}
}
#[cfg(feature = "zfuture")]
pub fn tze(index: usize, precondition: &'a Precondition, value: Amount) -> Self {
SignableInput::Tze {
index,
precondition,
value,
}
}
}
pub fn signature_hash_data<
TA: transparent::Authorization,
pub fn v4_signature_hash<
SA: sapling::Authorization<Proof = GrothProofBytes>,
A: Authorization<SaplingAuth = SA, TransparentAuth = TA>,
A: Authorization<SaplingAuth = SA>,
>(
tx: &TransactionData<A>,
consensus_branch_id: consensus::BranchId,
hash_type: u32,
signable_input: SignableInput<'_>,
) -> Vec<u8> {
if has_overwinter_components(&tx.version) {
hash_type: u32,
) -> Blake2bHash {
if tx.version.has_overwinter() {
let mut personal = [0; 16];
(&mut personal[..12]).copy_from_slice(ZCASH_SIGHASH_PERSONALIZATION_PREFIX);
(&mut personal[12..])
.write_u32::<LittleEndian>(consensus_branch_id.into())
.write_u32::<LittleEndian>(tx.consensus_branch_id.into())
.unwrap();
let mut h = Blake2bParams::new()
@ -223,7 +171,7 @@ pub fn signature_hash_data<
);
update_hash!(
h,
hash_type & SIGHASH_ANYONECANPAY == 0
(hash_type & SIGHASH_ANYONECANPAY) == 0
&& (hash_type & SIGHASH_MASK) != SIGHASH_SINGLE
&& (hash_type & SIGHASH_MASK) != SIGHASH_NONE,
sequence_hash(
@ -246,14 +194,15 @@ pub fn signature_hash_data<
);
} else if (hash_type & SIGHASH_MASK) == SIGHASH_SINGLE {
match (tx.transparent_bundle.as_ref(), &signable_input) {
(Some(b), SignableInput::Transparent { index, .. }) if *index < b.vout.len() => {
h.update(single_output_hash(&b.vout[*index]).as_bytes())
(Some(b), SignableInput::Transparent(input)) if input.index() < b.vout.len() => {
h.update(single_output_hash(&b.vout[input.index()]).as_bytes())
}
_ => h.update(&[0; 32]),
};
} else {
h.update(&[0; 32]);
};
update_hash!(
h,
!tx.sprout_bundle
@ -262,38 +211,27 @@ pub fn signature_hash_data<
{
let bundle = tx.sprout_bundle.as_ref().unwrap();
joinsplits_hash(
consensus_branch_id,
tx.consensus_branch_id,
&bundle.joinsplits,
&bundle.joinsplit_pubkey,
)
}
);
if tx.version.has_sapling() {
update_hash!(
h,
!tx.sapling_bundle
.as_ref()
.map_or(true, |b| b.shielded_spends.is_empty()),
shielded_spends_hash(
tx.sapling_bundle
.as_ref()
.unwrap()
.shielded_spends
.as_slice()
)
shielded_spends_hash(&tx.sapling_bundle.as_ref().unwrap().shielded_spends)
);
update_hash!(
h,
!tx.sapling_bundle
.as_ref()
.map_or(true, |b| b.shielded_outputs.is_empty()),
shielded_outputs_hash(
tx.sapling_bundle
.as_ref()
.unwrap()
.shielded_outputs
.as_slice()
)
shielded_outputs_hash(&tx.sapling_bundle.as_ref().unwrap().shielded_outputs)
);
}
update_u32!(h, tx.lock_time, tmp);
@ -304,19 +242,15 @@ pub fn signature_hash_data<
update_u32!(h, hash_type, tmp);
match signable_input {
SignableInput::Transparent {
index,
script_code,
value,
} => {
SignableInput::Shielded => (),
SignableInput::Transparent(input) => {
if let Some(bundle) = tx.transparent_bundle.as_ref() {
let mut data = vec![];
bundle.vin[index].prevout.write(&mut data).unwrap();
script_code.write(&mut data).unwrap();
data.extend_from_slice(&value.to_i64_le_bytes());
bundle.vin[input.index()].prevout.write(&mut data).unwrap();
input.script_code().write(&mut data).unwrap();
data.extend_from_slice(&input.value().to_i64_le_bytes());
(&mut data)
.write_u32::<LittleEndian>(bundle.vin[index].sequence)
.write_u32::<LittleEndian>(bundle.vin[input.index()].sequence)
.unwrap();
h.update(&data);
} else {
@ -325,25 +259,15 @@ pub fn signature_hash_data<
);
}
}
#[cfg(feature = "zfuture")]
SignableInput::Tze { .. } => {
panic!("A request has been made to sign a TZE input in a V4 transaction.");
}
SignableInput::Shielded => (),
#[cfg(feature = "zfuture")]
SignableInput::Tze(_) => {
panic!("A request has been made to sign a TZE input, but the transaction version is not ZFuture");
}
}
h.finalize().as_ref().to_vec()
h.finalize()
} else {
unimplemented!()
panic!("Signature hashing for pre-overwinter transactions is not supported.")
}
}
pub fn signature_hash(
tx: &Transaction,
consensus_branch_id: consensus::BranchId,
hash_type: u32,
signable_input: SignableInput<'_>,
) -> Vec<u8> {
signature_hash_data(tx, consensus_branch_id, hash_type, signable_input)
}

View File

@ -0,0 +1,182 @@
use std::io::Write;
use blake2b_simd::{Hash as Blake2bHash, Params, State};
use byteorder::{LittleEndian, WriteBytesExt};
use crate::transaction::{
components::transparent::{self, TxOut},
sighash::{
SignableInput, TransparentInput, SIGHASH_ANYONECANPAY, SIGHASH_MASK, SIGHASH_NONE,
SIGHASH_SINGLE,
},
txid::{
to_hash, transparent_outputs_hash, transparent_prevout_hash, transparent_sequence_hash,
},
Authorization, TransactionData, TransparentDigests, TxDigests,
};
#[cfg(feature = "zfuture")]
use std::convert::TryInto;
#[cfg(feature = "zfuture")]
use crate::{
serialize::{CompactSize, Vector},
transaction::{components::tze, sighash::TzeInput, TzeDigests},
};
const ZCASH_TRANSPARENT_INPUT_HASH_PERSONALIZATION: &[u8; 16] = b"Zcash___TxInHash";
#[cfg(feature = "zfuture")]
const ZCASH_TZE_INPUT_HASH_PERSONALIZATION: &[u8; 16] = b"Zcash__TzeInHash";
fn hasher(personal: &[u8; 16]) -> State {
Params::new().hash_length(32).personal(personal).to_state()
}
fn transparent_input_sigdigests<A: transparent::Authorization>(
bundle: &transparent::Bundle<A>,
input: &TransparentInput<'_>,
txid_digests: &TransparentDigests<Blake2bHash>,
hash_type: u32,
) -> TransparentDigests<Blake2bHash> {
let flag_anyonecanpay = hash_type & SIGHASH_ANYONECANPAY != 0;
let flag_single = hash_type & SIGHASH_MASK == SIGHASH_SINGLE;
let flag_none = hash_type & SIGHASH_MASK == SIGHASH_NONE;
let prevout_digest = if flag_anyonecanpay {
transparent_prevout_hash::<A>(&[])
} else {
txid_digests.prevout_digest
};
let sequence_digest = if flag_anyonecanpay || flag_single || flag_none {
transparent_sequence_hash::<A>(&[])
} else {
txid_digests.sequence_digest
};
let outputs_digest = if flag_single {
if input.index() < bundle.vout.len() {
transparent_outputs_hash(&[&bundle.vout[input.index()]])
} else {
transparent_outputs_hash::<TxOut>(&[])
}
} else if flag_none {
transparent_outputs_hash::<TxOut>(&[])
} else {
txid_digests.outputs_digest
};
// If we are serializing an input (i.e. this is not a JoinSplit signature hash):
// a. outpoint (32-byte hash + 4-byte little endian)
// b. scriptCode of the input (serialized as scripts inside CTxOuts)
// c. value of the output spent by this input (8-byte little endian)
// d. nSequence of the input (4-byte little endian)
let mut ch = hasher(ZCASH_TRANSPARENT_INPUT_HASH_PERSONALIZATION);
let txin = &bundle.vin[input.index()];
txin.prevout.write(&mut ch).unwrap();
input.script_code().write(&mut ch).unwrap();
ch.write_all(&input.value().to_i64_le_bytes()).unwrap();
ch.write_u32::<LittleEndian>(txin.sequence).unwrap();
let per_input_digest = ch.finalize();
TransparentDigests {
prevout_digest,
sequence_digest,
outputs_digest,
per_input_digest: Some(per_input_digest),
}
}
#[cfg(feature = "zfuture")]
fn tze_input_sigdigests<A: tze::Authorization>(
bundle: &tze::Bundle<A>,
input: &TzeInput<'_>,
txid_digests: &TzeDigests<Blake2bHash>,
) -> TzeDigests<Blake2bHash> {
let mut ch = hasher(ZCASH_TZE_INPUT_HASH_PERSONALIZATION);
let tzein = &bundle.vin[input.index()];
tzein.prevout.write(&mut ch).unwrap();
CompactSize::write(
&mut ch,
input.precondition().extension_id.try_into().unwrap(),
)
.unwrap();
CompactSize::write(&mut ch, input.precondition().mode.try_into().unwrap()).unwrap();
Vector::write(&mut ch, &input.precondition().payload, |w, e| {
w.write_u8(*e)
})
.unwrap();
ch.write_all(&input.value().to_i64_le_bytes()).unwrap();
let per_input_digest = ch.finalize();
TzeDigests {
inputs_digest: txid_digests.inputs_digest,
outputs_digest: txid_digests.outputs_digest,
per_input_digest: Some(per_input_digest),
}
}
pub fn v5_signature_hash<A: Authorization>(
tx: &TransactionData<A>,
txid_parts: &TxDigests<Blake2bHash>,
signable_input: SignableInput<'_>,
hash_type: u32,
) -> Blake2bHash {
match signable_input {
SignableInput::Shielded => to_hash(
tx.version,
tx.consensus_branch_id,
txid_parts.header_digest,
txid_parts.transparent_digests.as_ref(),
txid_parts.sapling_digest,
txid_parts.orchard_digest,
#[cfg(feature = "zfuture")]
txid_parts.tze_digests.as_ref(),
),
SignableInput::Transparent(input) => {
if let Some((bundle, txid_digests)) = tx
.transparent_bundle
.as_ref()
.zip(txid_parts.transparent_digests.as_ref())
{
to_hash(
tx.version,
tx.consensus_branch_id,
txid_parts.header_digest,
Some(&transparent_input_sigdigests(
bundle,
&input,
txid_digests,
hash_type,
)),
txid_parts.sapling_digest,
txid_parts.orchard_digest,
#[cfg(feature = "zfuture")]
txid_parts.tze_digests.as_ref(),
)
} else {
panic!("It is not possible to sign a transparent input with missing bundle data.")
}
}
#[cfg(feature = "zfuture")]
SignableInput::Tze(input) => {
if let Some((bundle, txid_digests)) =
tx.tze_bundle.as_ref().zip(txid_parts.tze_digests.as_ref())
{
to_hash(
tx.version,
tx.consensus_branch_id,
txid_parts.header_digest,
txid_parts.transparent_digests.as_ref(),
txid_parts.sapling_digest,
txid_parts.orchard_digest,
#[cfg(feature = "zfuture")]
Some(&tze_input_sigdigests(bundle, &input, txid_digests)),
)
} else {
panic!("It is not possible to sign a tze input with missing bundle data.")
}
}
}
}

View File

@ -1,9 +1,9 @@
use proptest::prelude::*;
use crate::consensus::BranchId;
use super::{
components::Amount,
sighash_v4::{signature_hash, SignableInput},
Transaction,
components::Amount, sighash::SignableInput, sighash_v4::v4_signature_hash, Transaction,
};
use super::testing::{arb_branch_id, arb_tx};
@ -11,7 +11,7 @@ use super::testing::{arb_branch_id, arb_tx};
#[test]
fn tx_read_write() {
let data = &self::data::tx_read_write::TX_READ_WRITE;
let tx = Transaction::read(&data[..]).unwrap();
let tx = Transaction::read(&data[..], BranchId::Canopy).unwrap();
assert_eq!(
format!("{}", tx.txid()),
"64f0bd7fe30ce23753358fe3a2dc835b8fba9c0274c4e2c54a6f73114cb55639"
@ -28,7 +28,7 @@ proptest! {
let mut txn_bytes = vec![];
tx.write(&mut txn_bytes).unwrap();
let txo = Transaction::read(&txn_bytes[..]).unwrap();
let txo = Transaction::read(&txn_bytes[..], BranchId::Canopy).unwrap();
prop_assert_eq!(tx.version, txo.version);
prop_assert_eq!(tx.lock_time, txo.lock_time);
@ -43,7 +43,7 @@ mod data;
#[test]
fn zip_0143() {
for tv in self::data::zip_0143::make_test_vectors() {
let tx = Transaction::read(&tv.tx[..]).unwrap();
let tx = Transaction::read(&tv.tx[..], tv.consensus_branch_id).unwrap();
let signable_input = match tv.transparent_input {
Some(n) => SignableInput::transparent(
n as usize,
@ -54,7 +54,7 @@ fn zip_0143() {
};
assert_eq!(
signature_hash(&tx, tv.consensus_branch_id, tv.hash_type, signable_input).as_ref(),
v4_signature_hash(&tx, signable_input, tv.hash_type).as_ref(),
tv.sighash
);
}
@ -63,7 +63,7 @@ fn zip_0143() {
#[test]
fn zip_0243() {
for tv in self::data::zip_0243::make_test_vectors() {
let tx = Transaction::read(&tv.tx[..]).unwrap();
let tx = Transaction::read(&tv.tx[..], tv.consensus_branch_id).unwrap();
let signable_input = match tv.transparent_input {
Some(n) => SignableInput::transparent(
n as usize,
@ -74,7 +74,7 @@ fn zip_0243() {
};
assert_eq!(
signature_hash(&tx, tv.consensus_branch_id, tv.hash_type, signable_input).as_ref(),
v4_signature_hash(&tx, signable_input, tv.hash_type).as_ref(),
tv.sighash
);
}

View File

@ -0,0 +1,585 @@
use std::borrow::Borrow;
use std::convert::TryFrom;
use std::io::Write;
use blake2b_simd::{Hash as Blake2bHash, Params, State};
use byteorder::{LittleEndian, WriteBytesExt};
use ff::PrimeField;
use group::GroupEncoding;
use orchard::bundle::{self as orchard};
use crate::consensus::{BlockHeight, BranchId};
use super::{
components::{
amount::Amount,
orchard as ser_orch,
sapling::{self, OutputDescription, SpendDescription},
transparent::{self, TxIn, TxOut},
},
Authorization, Authorized, TransactionDigest, TransparentDigests, TxDigests, TxId, TxVersion,
};
#[cfg(feature = "zfuture")]
use super::{
components::tze::{self, TzeIn, TzeOut},
TzeDigests,
};
/// TxId tree root personalization
const ZCASH_TX_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZcashTxHash_";
// TxId level 1 node personalization
const ZCASH_HEADERS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdHeadersHash";
const ZCASH_TRANSPARENT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdTranspaHash";
const ZCASH_SAPLING_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSaplingHash";
const ZCASH_ORCHARD_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrchardHash";
#[cfg(feature = "zfuture")]
const ZCASH_TZE_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdTZE____Hash";
// TxId transparent level 2 node personalization
const ZCASH_PREVOUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdPrevoutHash";
const ZCASH_SEQUENCE_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSequencHash";
const ZCASH_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOutputsHash";
// TxId tze level 2 node personalization
#[cfg(feature = "zfuture")]
const ZCASH_TZE_INPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdTZEIns_Hash";
#[cfg(feature = "zfuture")]
const ZCASH_TZE_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdTZEOutsHash";
// TxId sapling level 2 node personalization
const ZCASH_SAPLING_SPENDS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSSpendsHash";
const ZCASH_SAPLING_SPENDS_COMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSSpendCHash";
const ZCASH_SAPLING_SPENDS_NONCOMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSSpendNHash";
const ZCASH_SAPLING_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSOutputHash";
const ZCASH_SAPLING_OUTPUTS_COMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSOutC__Hash";
const ZCASH_SAPLING_OUTPUTS_MEMOS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSOutM__Hash";
const ZCASH_SAPLING_OUTPUTS_NONCOMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSOutN__Hash";
const ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrcActCHash";
const ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrcActMHash";
const ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrcActNHash";
const ZCASH_AUTH_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZTxAuthHash_";
const ZCASH_TRANSPARENT_SCRIPTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxAuthTransHash";
const ZCASH_SAPLING_SIGS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxAuthSapliHash";
const ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxAuthOrchaHash";
#[cfg(feature = "zfuture")]
const ZCASH_TZE_WITNESSES_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxAuthTZE__Hash";
fn hasher(personal: &[u8; 16]) -> State {
Params::new().hash_length(32).personal(personal).to_state()
}
/// Sequentially append the serialized value of each transparent input
/// to a hash personalized by ZCASH_PREVOUTS_HASH_PERSONALIZATION.
/// In the case that no inputs are provided, this produces a default
/// hash from just the personalization string.
pub(crate) fn transparent_prevout_hash<TransparentAuth: transparent::Authorization>(
vin: &[TxIn<TransparentAuth>],
) -> Blake2bHash {
let mut h = hasher(ZCASH_PREVOUTS_HASH_PERSONALIZATION);
for t_in in vin {
t_in.prevout.write(&mut h).unwrap();
}
h.finalize()
}
/// Hash of the little-endian u32 interpretation of the
/// `sequence` values for each TxIn record passed in vin.
pub(crate) fn transparent_sequence_hash<TransparentAuth: transparent::Authorization>(
vin: &[TxIn<TransparentAuth>],
) -> Blake2bHash {
let mut h = hasher(ZCASH_SEQUENCE_HASH_PERSONALIZATION);
for t_in in vin {
(&mut h).write_u32::<LittleEndian>(t_in.sequence).unwrap();
}
h.finalize()
}
/// Sequentially append the full serialized value of each transparent output
/// to a hash personalized by ZCASH_OUTPUTS_HASH_PERSONALIZATION.
/// In the case that no outputs are provided, this produces a default
/// hash from just the personalization string.
pub(crate) fn transparent_outputs_hash<T: Borrow<TxOut>>(vout: &[T]) -> Blake2bHash {
let mut h = hasher(ZCASH_OUTPUTS_HASH_PERSONALIZATION);
for t_out in vout {
t_out.borrow().write(&mut h).unwrap();
}
h.finalize()
}
/// Sequentially append the serialized value of each TZE input, excluding
/// witness data, to a hash personalized by ZCASH_TZE_INPUTS_HASH_PERSONALIZATION.
/// In the case that no inputs are provided, this produces a default
/// hash from just the personalization string.
#[cfg(feature = "zfuture")]
pub(crate) fn hash_tze_inputs<A: tze::Authorization>(tze_inputs: &[TzeIn<A>]) -> Blake2bHash {
let mut h = hasher(ZCASH_TZE_INPUTS_HASH_PERSONALIZATION);
for tzein in tze_inputs {
tzein.write_without_witness(&mut h).unwrap();
}
h.finalize()
}
/// Sequentially append the full serialized value of each TZE output
/// to a hash personalized by ZCASH_TZE_OUTPUTS_HASH_PERSONALIZATION.
/// In the case that no outputs are provided, this produces a default
/// hash from just the personalization string.
#[cfg(feature = "zfuture")]
pub(crate) fn hash_tze_outputs(tze_outputs: &[TzeOut]) -> Blake2bHash {
let mut h = hasher(ZCASH_TZE_OUTPUTS_HASH_PERSONALIZATION);
for tzeout in tze_outputs {
tzeout.write(&mut h).unwrap();
}
h.finalize()
}
/// Write disjoint parts of each Sapling shielded spend to a pair of hashes:
/// * [nullifier*] - personalized with ZCASH_SAPLING_SPENDS_COMPACT_HASH_PERSONALIZATION
/// * [(cv, anchor, rk, zkproof)*] - personalized with ZCASH_SAPLING_SPENDS_NONCOMPACT_HASH_PERSONALIZATION
///
/// Then, hash these together personalized by ZCASH_SAPLING_SPENDS_HASH_PERSONALIZATION
pub(crate) fn hash_sapling_spends<A: sapling::Authorization>(
shielded_spends: &[SpendDescription<A>],
) -> Blake2bHash {
let mut ch = hasher(ZCASH_SAPLING_SPENDS_COMPACT_HASH_PERSONALIZATION);
let mut nh = hasher(ZCASH_SAPLING_SPENDS_NONCOMPACT_HASH_PERSONALIZATION);
for s_spend in shielded_spends {
// we build the hash of nullifiers separately for compact blocks.
ch.write_all(&s_spend.nullifier.as_ref()).unwrap();
nh.write_all(&s_spend.cv.to_bytes()).unwrap();
nh.write_all(&s_spend.anchor.to_repr()).unwrap();
s_spend.rk.write(&mut nh).unwrap();
}
let mut h = hasher(ZCASH_SAPLING_SPENDS_HASH_PERSONALIZATION);
h.write_all(&ch.finalize().as_bytes()).unwrap();
h.write_all(&nh.finalize().as_bytes()).unwrap();
h.finalize()
}
/// Write disjoint parts of each Sapling shielded output as 3 separate hashes:
/// * [(cmu, epk, enc_ciphertext[..52])*] personalized with ZCASH_SAPLING_OUTPUTS_COMPACT_HASH_PERSONALIZATION
/// * [enc_ciphertext[52..564]*] (memo ciphertexts) personalized with ZCASH_SAPLING_OUTPUTS_MEMOS_HASH_PERSONALIZATION
/// * [(cv, enc_ciphertext[564..], out_ciphertext, zkproof)*] personalized with ZCASH_SAPLING_OUTPUTS_NONCOMPACT_HASH_PERSONALIZATION
///
/// Then, hash these together personalized with ZCASH_SAPLING_OUTPUTS_HASH_PERSONALIZATION
pub(crate) fn hash_sapling_outputs<A: sapling::Authorization>(
shielded_outputs: &[OutputDescription<A>],
) -> Blake2bHash {
let mut ch = hasher(ZCASH_SAPLING_OUTPUTS_COMPACT_HASH_PERSONALIZATION);
let mut mh = hasher(ZCASH_SAPLING_OUTPUTS_MEMOS_HASH_PERSONALIZATION);
let mut nh = hasher(ZCASH_SAPLING_OUTPUTS_NONCOMPACT_HASH_PERSONALIZATION);
for s_out in shielded_outputs {
ch.write_all(&s_out.cmu.to_repr().as_ref()).unwrap();
ch.write_all(&s_out.ephemeral_key.to_bytes()).unwrap();
ch.write_all(&s_out.enc_ciphertext[..52]).unwrap();
mh.write_all(&s_out.enc_ciphertext[52..564]).unwrap();
nh.write_all(&s_out.cv.to_bytes()).unwrap();
nh.write_all(&s_out.enc_ciphertext[564..]).unwrap();
nh.write_all(&s_out.out_ciphertext).unwrap();
}
let mut h = hasher(ZCASH_SAPLING_OUTPUTS_HASH_PERSONALIZATION);
h.write_all(&ch.finalize().as_bytes()).unwrap();
h.write_all(&mh.finalize().as_bytes()).unwrap();
h.write_all(&nh.finalize().as_bytes()).unwrap();
h.finalize()
}
/// The txid commits to the hash of all transparent outputs. The
/// prevout and sequence_hash components of txid
fn transparent_digests<A: transparent::Authorization>(
bundle: &transparent::Bundle<A>,
) -> TransparentDigests<Blake2bHash> {
TransparentDigests {
prevout_digest: transparent_prevout_hash(&bundle.vin),
sequence_digest: transparent_sequence_hash(&bundle.vin),
outputs_digest: transparent_outputs_hash(&bundle.vout),
per_input_digest: None,
}
}
#[cfg(feature = "zfuture")]
fn tze_digests<A: tze::Authorization>(bundle: &tze::Bundle<A>) -> TzeDigests<Blake2bHash> {
// The txid commits to the hash for all outputs.
TzeDigests {
inputs_digest: hash_tze_inputs(&bundle.vin),
outputs_digest: hash_tze_outputs(&bundle.vout),
per_input_digest: None,
}
}
fn hash_header_txid_data(
version: TxVersion,
// we commit to the consensus branch ID with the header
consensus_branch_id: BranchId,
lock_time: u32,
expiry_height: BlockHeight,
) -> Blake2bHash {
let mut h = hasher(ZCASH_HEADERS_HASH_PERSONALIZATION);
(&mut h)
.write_u32::<LittleEndian>(version.header())
.unwrap();
(&mut h)
.write_u32::<LittleEndian>(version.version_group_id())
.unwrap();
(&mut h)
.write_u32::<LittleEndian>(consensus_branch_id.into())
.unwrap();
(&mut h).write_u32::<LittleEndian>(lock_time).unwrap();
(&mut h)
.write_u32::<LittleEndian>(expiry_height.into())
.unwrap();
h.finalize()
}
fn hash_transparent_txid_data(t_digests: Option<&TransparentDigests<Blake2bHash>>) -> Blake2bHash {
let mut h = hasher(ZCASH_TRANSPARENT_HASH_PERSONALIZATION);
if let Some(d) = t_digests {
h.write_all(d.prevout_digest.as_bytes()).unwrap();
h.write_all(d.sequence_digest.as_bytes()).unwrap();
h.write_all(d.outputs_digest.as_bytes()).unwrap();
if let Some(s) = d.per_input_digest {
h.write_all(s.as_bytes()).unwrap();
};
}
h.finalize()
}
fn hash_sapling_txid_data<A: sapling::Authorization>(
sapling_bundle: Option<&sapling::Bundle<A>>,
) -> Blake2bHash {
let mut h = hasher(ZCASH_SAPLING_HASH_PERSONALIZATION);
if let Some(bundle) = sapling_bundle {
if !bundle.shielded_spends.is_empty() {
h.write_all(hash_sapling_spends(&bundle.shielded_spends).as_bytes())
.unwrap();
}
if !bundle.shielded_outputs.is_empty() {
h.write_all(hash_sapling_outputs(&bundle.shielded_outputs).as_bytes())
.unwrap();
}
h.write_all(&bundle.value_balance.to_i64_le_bytes())
.unwrap();
}
h.finalize()
}
/// Write disjoint parts of each Orchard shielded action as 3 separate hashes:
/// * [(nullifier, cmx, ephemeral_key, enc_ciphertext[..52])*] personalized
/// with ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION
/// * [enc_ciphertext[52..564]*] (memo ciphertexts) personalized
/// with ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION
/// * [(cv, rk, enc_ciphertext[564..], out_ciphertext)*] personalized
/// with ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION
///
/// Then, hash these together along with (flags, value_balance_orchard, anchor_orchard),
/// personalized with ZCASH_ORCHARD_ACTIONS_HASH_PERSONALIZATION
fn hash_orchard_txid_data<A: orchard::Authorization>(
orchard_bundle: Option<&orchard::Bundle<A, Amount>>,
) -> Blake2bHash {
let mut h = hasher(ZCASH_ORCHARD_HASH_PERSONALIZATION);
if let Some(bundle) = orchard_bundle {
let mut ch = hasher(ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION);
let mut mh = hasher(ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION);
let mut nh = hasher(ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION);
for action in bundle.actions().iter() {
ch.write_all(&action.nullifier().to_bytes()).unwrap();
ch.write_all(&action.cmx().to_bytes()).unwrap();
ch.write_all(&action.encrypted_note().epk_bytes).unwrap();
ch.write_all(&action.encrypted_note().enc_ciphertext[..52])
.unwrap();
mh.write_all(&action.encrypted_note().enc_ciphertext[52..564])
.unwrap();
nh.write_all(&action.cv_net().to_bytes()).unwrap();
nh.write_all(&<[u8; 32]>::from(action.rk())).unwrap();
nh.write_all(&action.encrypted_note().enc_ciphertext[564..])
.unwrap();
nh.write_all(&action.encrypted_note().out_ciphertext)
.unwrap();
}
h.write_all(&ch.finalize().as_bytes()).unwrap();
h.write_all(&mh.finalize().as_bytes()).unwrap();
h.write_all(&nh.finalize().as_bytes()).unwrap();
ser_orch::write_flags(&mut h, bundle.flags()).unwrap();
h.write_all(&bundle.value_balance().to_i64_le_bytes())
.unwrap();
ser_orch::write_anchor(&mut h, bundle.anchor()).unwrap();
}
h.finalize()
}
#[cfg(feature = "zfuture")]
fn hash_tze_txid_data(tze_digests: Option<&TzeDigests<Blake2bHash>>) -> Blake2bHash {
let mut h = hasher(ZCASH_TZE_HASH_PERSONALIZATION);
if let Some(d) = tze_digests {
h.write_all(d.inputs_digest.as_bytes()).unwrap();
h.write_all(d.outputs_digest.as_bytes()).unwrap();
if let Some(s) = d.per_input_digest {
h.write_all(s.as_bytes()).unwrap();
}
}
h.finalize()
}
pub struct TxIdDigester;
// A TransactionDigest implementation that commits to all of the effecting
// data of a transaction to produce a nonmalleable transaction identifier.
//
// This expects and relies upon the existence of canonical encodings for
// each effecting component of a transaction.
impl<A: Authorization> TransactionDigest<A> for TxIdDigester {
type HeaderDigest = Blake2bHash;
type TransparentDigest = Option<TransparentDigests<Blake2bHash>>;
type SaplingDigest = Blake2bHash;
type OrchardDigest = Blake2bHash;
#[cfg(feature = "zfuture")]
type TzeDigest = Option<TzeDigests<Blake2bHash>>;
type Digest = TxDigests<Blake2bHash>;
fn digest_header(
&self,
version: TxVersion,
consensus_branch_id: BranchId,
lock_time: u32,
expiry_height: BlockHeight,
) -> Self::HeaderDigest {
hash_header_txid_data(version, consensus_branch_id, lock_time, expiry_height)
}
fn digest_transparent(
&self,
transparent_bundle: Option<&transparent::Bundle<A::TransparentAuth>>,
) -> Self::TransparentDigest {
transparent_bundle.map(transparent_digests)
}
fn digest_sapling(
&self,
sapling_bundle: Option<&sapling::Bundle<A::SaplingAuth>>,
) -> Self::SaplingDigest {
hash_sapling_txid_data(sapling_bundle)
}
fn digest_orchard(
&self,
orchard_bundle: Option<&orchard::Bundle<A::OrchardAuth, Amount>>,
) -> Self::OrchardDigest {
hash_orchard_txid_data(orchard_bundle)
}
#[cfg(feature = "zfuture")]
fn digest_tze(&self, tze_bundle: Option<&tze::Bundle<A::TzeAuth>>) -> Self::TzeDigest {
tze_bundle.map(tze_digests)
}
fn combine(
&self,
header_digest: Self::HeaderDigest,
transparent_digests: Self::TransparentDigest,
sapling_digest: Self::SaplingDigest,
orchard_digest: Self::OrchardDigest,
#[cfg(feature = "zfuture")] tze_digests: Self::TzeDigest,
) -> Self::Digest {
TxDigests {
header_digest,
transparent_digests,
sapling_digest,
orchard_digest,
#[cfg(feature = "zfuture")]
tze_digests,
}
}
}
pub fn to_hash(
_txversion: TxVersion,
consensus_branch_id: BranchId,
header_digest: Blake2bHash,
transparent_digests: Option<&TransparentDigests<Blake2bHash>>,
sapling_digest: Blake2bHash,
orchard_digest: Blake2bHash,
#[cfg(feature = "zfuture")] tze_digests: Option<&TzeDigests<Blake2bHash>>,
) -> Blake2bHash {
let mut personal = [0; 16];
(&mut personal[..12]).copy_from_slice(ZCASH_TX_PERSONALIZATION_PREFIX);
(&mut personal[12..])
.write_u32::<LittleEndian>(consensus_branch_id.into())
.unwrap();
let mut h = hasher(&personal);
h.write_all(header_digest.as_bytes()).unwrap();
h.write_all(hash_transparent_txid_data(transparent_digests).as_bytes())
.unwrap();
h.write_all(sapling_digest.as_bytes()).unwrap();
h.write_all(orchard_digest.as_bytes()).unwrap();
#[cfg(feature = "zfuture")]
if _txversion.has_tze() {
h.write_all(hash_tze_txid_data(tze_digests).as_bytes())
.unwrap();
}
h.finalize()
}
pub fn to_txid(
txversion: TxVersion,
consensus_branch_id: BranchId,
digests: &TxDigests<Blake2bHash>,
) -> TxId {
let txid_digest = to_hash(
txversion,
consensus_branch_id,
digests.header_digest,
digests.transparent_digests.as_ref(),
digests.sapling_digest,
digests.orchard_digest,
#[cfg(feature = "zfuture")]
digests.tze_digests.as_ref(),
);
TxId(<[u8; 32]>::try_from(txid_digest.as_bytes()).unwrap())
}
/// Digester which constructs a digest of only the witness data.
/// This does not internally commit to the txid, so if that is
/// desired it should be done using the result of this digest
/// function.
pub struct BlockTxCommitmentDigester;
impl TransactionDigest<Authorized> for BlockTxCommitmentDigester {
/// We use the header digest to pass the transaction ID into
/// where it needs to be used for personalization string construction.
type HeaderDigest = BranchId;
type TransparentDigest = Blake2bHash;
type SaplingDigest = Blake2bHash;
type OrchardDigest = Blake2bHash;
#[cfg(feature = "zfuture")]
type TzeDigest = Blake2bHash;
type Digest = Blake2bHash;
fn digest_header(
&self,
_version: TxVersion,
consensus_branch_id: BranchId,
_lock_time: u32,
_expiry_height: BlockHeight,
) -> Self::HeaderDigest {
consensus_branch_id
}
fn digest_transparent(
&self,
transparent_bundle: Option<&transparent::Bundle<transparent::Authorized>>,
) -> Blake2bHash {
let mut h = hasher(ZCASH_TRANSPARENT_SCRIPTS_HASH_PERSONALIZATION);
if let Some(bundle) = transparent_bundle {
for txin in &bundle.vin {
h.write_all(&txin.script_sig.0).unwrap();
}
}
h.finalize()
}
fn digest_sapling(
&self,
sapling_bundle: Option<&sapling::Bundle<sapling::Authorized>>,
) -> Blake2bHash {
let mut h = hasher(ZCASH_SAPLING_SIGS_HASH_PERSONALIZATION);
if let Some(bundle) = sapling_bundle {
for spend in &bundle.shielded_spends {
h.write_all(&spend.zkproof).unwrap();
spend.spend_auth_sig.write(&mut h).unwrap();
}
for output in &bundle.shielded_outputs {
h.write_all(&output.zkproof).unwrap();
}
bundle.authorization.binding_sig.write(&mut h).unwrap();
}
h.finalize()
}
fn digest_orchard(
&self,
orchard_bundle: Option<&orchard::Bundle<orchard::Authorized, Amount>>,
) -> Self::OrchardDigest {
let mut h = hasher(ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION);
if let Some(bundle) = orchard_bundle {
h.write_all(bundle.authorization().proof().as_ref())
.unwrap();
for action in bundle.actions().iter() {
h.write_all(&<[u8; 64]>::from(action.authorization()))
.unwrap();
}
h.write_all(&<[u8; 64]>::from(
bundle.authorization().binding_signature(),
))
.unwrap();
}
h.finalize()
}
#[cfg(feature = "zfuture")]
fn digest_tze(&self, tze_bundle: Option<&tze::Bundle<tze::Authorized>>) -> Blake2bHash {
let mut h = hasher(ZCASH_TZE_WITNESSES_HASH_PERSONALIZATION);
if let Some(bundle) = tze_bundle {
for tzein in &bundle.vin {
h.write_all(&tzein.witness.payload.0).unwrap();
}
}
h.finalize()
}
fn combine(
&self,
consensus_branch_id: Self::HeaderDigest,
transparent_digest: Self::TransparentDigest,
sapling_digest: Self::SaplingDigest,
orchard_digest: Self::OrchardDigest,
#[cfg(feature = "zfuture")] tze_digest: Self::TzeDigest,
) -> Self::Digest {
let digests = [
transparent_digest,
sapling_digest,
orchard_digest,
#[cfg(feature = "zfuture")]
tze_digest,
];
let mut personal = [0; 16];
(&mut personal[..12]).copy_from_slice(ZCASH_AUTH_PERSONALIZATION_PREFIX);
(&mut personal[12..])
.write_u32::<LittleEndian>(consensus_branch_id.into())
.unwrap();
let mut h = hasher(&personal);
for digest in &digests {
h.write_all(digest.as_bytes()).unwrap();
}
h.finalize()
}
}