Add v5 txid & signature hashing.
This commit is contained in:
parent
1138343c89
commit
55d1090f70
|
@ -238,8 +238,7 @@ where
|
||||||
},
|
},
|
||||||
RecipientAddress::Transparent(addr) => {
|
RecipientAddress::Transparent(addr) => {
|
||||||
let script = addr.script();
|
let script = addr.script();
|
||||||
tx.transparent_bundle
|
tx.transparent_bundle()
|
||||||
.as_ref()
|
|
||||||
.and_then(|b| {
|
.and_then(|b| {
|
||||||
b.vout
|
b.vout
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -44,7 +44,7 @@ pub fn decrypt_transaction<P: consensus::Parameters>(
|
||||||
) -> Vec<DecryptedOutput> {
|
) -> Vec<DecryptedOutput> {
|
||||||
let mut decrypted = vec![];
|
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() {
|
for (account, extfvk) in extfvks.iter() {
|
||||||
let ivk = extfvk.fvk.vk.ivk();
|
let ivk = extfvk.fvk.vk.ivk();
|
||||||
let ovk = extfvk.fvk.ovk;
|
let ovk = extfvk.fvk.ovk;
|
||||||
|
|
|
@ -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
|
// 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.
|
// 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 {
|
for spend in &bundle.shielded_spends {
|
||||||
wallet::mark_spent(up, tx_ref, &spend.nullifier)?;
|
wallet::mark_spent(up, tx_ref, &spend.nullifier)?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -650,14 +650,14 @@ pub fn put_tx_data<'a, P>(
|
||||||
|
|
||||||
if stmts
|
if stmts
|
||||||
.stmt_update_tx_data
|
.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
|
== 0
|
||||||
{
|
{
|
||||||
// It isn't there, so insert our transaction into the database.
|
// It isn't there, so insert our transaction into the database.
|
||||||
stmts.stmt_insert_tx_data.execute(params![
|
stmts.stmt_insert_tx_data.execute(params![
|
||||||
txid,
|
txid,
|
||||||
created_at,
|
created_at,
|
||||||
u32::from(tx.expiry_height),
|
u32::from(tx.expiry_height()),
|
||||||
raw_tx
|
raw_tx
|
||||||
])?;
|
])?;
|
||||||
|
|
||||||
|
|
|
@ -153,7 +153,7 @@ mod tests {
|
||||||
|
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
block::BlockHash,
|
block::BlockHash,
|
||||||
consensus::BlockHeight,
|
consensus::{BlockHeight, BranchId},
|
||||||
legacy::TransparentAddress,
|
legacy::TransparentAddress,
|
||||||
sapling::{note_encryption::try_sapling_output_recovery, prover::TxProver},
|
sapling::{note_encryption::try_sapling_output_recovery, prover::TxProver},
|
||||||
transaction::{components::Amount, Transaction},
|
transaction::{components::Amount, Transaction},
|
||||||
|
@ -617,7 +617,7 @@ mod tests {
|
||||||
|row| row.get(0),
|
|row| row.get(0),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let tx = Transaction::read(&raw_tx[..]).unwrap();
|
let tx = Transaction::read(&raw_tx[..], BranchId::Canopy).unwrap();
|
||||||
|
|
||||||
// Fetch the output index from the database
|
// Fetch the output index from the database
|
||||||
let output_index: i64 = db_write
|
let output_index: i64 = db_write
|
||||||
|
@ -632,7 +632,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let output =
|
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(
|
try_sapling_output_recovery(
|
||||||
&network,
|
&network,
|
||||||
|
|
|
@ -78,14 +78,14 @@ pub trait Epoch {
|
||||||
/// by the context.
|
/// by the context.
|
||||||
impl<'a> demo::Context for Context<'a> {
|
impl<'a> demo::Context for Context<'a> {
|
||||||
fn is_tze_only(&self) -> bool {
|
fn is_tze_only(&self) -> bool {
|
||||||
self.tx.transparent_bundle.is_none()
|
self.tx.transparent_bundle().is_none()
|
||||||
&& self.tx.sapling_bundle.is_none()
|
&& self.tx.sapling_bundle().is_none()
|
||||||
&& self.tx.sprout_bundle.is_none()
|
&& self.tx.sprout_bundle().is_none()
|
||||||
&& self.tx.orchard_bundle.is_none()
|
&& self.tx.orchard_bundle().is_none()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tx_tze_outputs(&self) -> &[TzeOut] {
|
fn tx_tze_outputs(&self) -> &[TzeOut] {
|
||||||
if let Some(bundle) = &self.tx.tze_bundle {
|
if let Some(bundle) = &self.tx.tze_bundle() {
|
||||||
&bundle.vout
|
&bundle.vout
|
||||||
} else {
|
} else {
|
||||||
&[]
|
&[]
|
||||||
|
|
|
@ -621,14 +621,14 @@ mod tests {
|
||||||
/// by the context.
|
/// by the context.
|
||||||
impl<'a> Context for Ctx<'a> {
|
impl<'a> Context for Ctx<'a> {
|
||||||
fn is_tze_only(&self) -> bool {
|
fn is_tze_only(&self) -> bool {
|
||||||
self.tx.transparent_bundle.is_none()
|
self.tx.transparent_bundle().is_none()
|
||||||
&& self.tx.sprout_bundle.is_none()
|
&& self.tx.sapling_bundle().is_none()
|
||||||
&& self.tx.sapling_bundle.is_none()
|
&& self.tx.sprout_bundle().is_none()
|
||||||
&& self.tx.orchard_bundle.is_none()
|
&& self.tx.orchard_bundle().is_none()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tx_tze_outputs(&self) -> &[TzeOut] {
|
fn tx_tze_outputs(&self) -> &[TzeOut] {
|
||||||
match &self.tx.tze_bundle {
|
match self.tx.tze_bundle().as_ref() {
|
||||||
Some(b) => &b.vout,
|
Some(b) => &b.vout,
|
||||||
None => &[],
|
None => &[],
|
||||||
}
|
}
|
||||||
|
@ -683,20 +683,21 @@ mod tests {
|
||||||
precondition: tze::Precondition::from(0, &Precondition::open(hash_1)),
|
precondition: tze::Precondition::from(0, &Precondition::open(hash_1)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let tx_a = TransactionData {
|
let tx_a = TransactionData::from_parts(
|
||||||
version: TxVersion::ZFuture,
|
TxVersion::ZFuture,
|
||||||
lock_time: 0,
|
BranchId::ZFuture,
|
||||||
expiry_height: 0u32.into(),
|
0,
|
||||||
transparent_bundle: None,
|
0u32.into(),
|
||||||
sprout_bundle: None,
|
None,
|
||||||
sapling_bundle: None,
|
None,
|
||||||
orchard_bundle: None,
|
None,
|
||||||
tze_bundle: Some(Bundle {
|
None,
|
||||||
|
Some(Bundle {
|
||||||
vin: vec![],
|
vin: vec![],
|
||||||
vout: vec![out_a],
|
vout: vec![out_a],
|
||||||
}),
|
}),
|
||||||
}
|
)
|
||||||
.freeze(BranchId::ZFuture)
|
.freeze()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -712,20 +713,21 @@ mod tests {
|
||||||
precondition: tze::Precondition::from(0, &Precondition::close(hash_2)),
|
precondition: tze::Precondition::from(0, &Precondition::close(hash_2)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let tx_b = TransactionData {
|
let tx_b = TransactionData::from_parts(
|
||||||
version: TxVersion::ZFuture,
|
TxVersion::ZFuture,
|
||||||
lock_time: 0,
|
BranchId::ZFuture,
|
||||||
expiry_height: 0u32.into(),
|
0,
|
||||||
transparent_bundle: None,
|
0u32.into(),
|
||||||
sprout_bundle: None,
|
None,
|
||||||
sapling_bundle: None,
|
None,
|
||||||
orchard_bundle: None,
|
None,
|
||||||
tze_bundle: Some(Bundle {
|
None,
|
||||||
|
Some(Bundle {
|
||||||
vin: vec![in_b],
|
vin: vec![in_b],
|
||||||
vout: vec![out_b],
|
vout: vec![out_b],
|
||||||
}),
|
}),
|
||||||
}
|
)
|
||||||
.freeze(BranchId::ZFuture)
|
.freeze()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -737,20 +739,21 @@ mod tests {
|
||||||
witness: tze::Witness::from(0, &Witness::close(preimage_2)),
|
witness: tze::Witness::from(0, &Witness::close(preimage_2)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let tx_c = TransactionData {
|
let tx_c = TransactionData::from_parts(
|
||||||
version: TxVersion::ZFuture,
|
TxVersion::ZFuture,
|
||||||
lock_time: 0,
|
BranchId::ZFuture,
|
||||||
expiry_height: 0u32.into(),
|
0,
|
||||||
transparent_bundle: None,
|
0u32.into(),
|
||||||
sprout_bundle: None,
|
None,
|
||||||
sapling_bundle: None,
|
None,
|
||||||
orchard_bundle: None,
|
None,
|
||||||
tze_bundle: Some(Bundle {
|
None,
|
||||||
|
Some(Bundle {
|
||||||
vin: vec![in_c],
|
vin: vec![in_c],
|
||||||
vout: vec![],
|
vout: vec![],
|
||||||
}),
|
}),
|
||||||
}
|
)
|
||||||
.freeze(BranchId::ZFuture)
|
.freeze()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Verify tx_b
|
// Verify tx_b
|
||||||
|
@ -758,8 +761,8 @@ mod tests {
|
||||||
let ctx = Ctx { tx: &tx_b };
|
let ctx = Ctx { tx: &tx_b };
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Program.verify(
|
Program.verify(
|
||||||
&tx_a.tze_bundle.as_ref().unwrap().vout[0].precondition,
|
&tx_a.tze_bundle().as_ref().unwrap().vout[0].precondition,
|
||||||
&tx_b.tze_bundle.as_ref().unwrap().vin[0].witness,
|
&tx_b.tze_bundle().as_ref().unwrap().vin[0].witness,
|
||||||
&ctx
|
&ctx
|
||||||
),
|
),
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -771,8 +774,8 @@ mod tests {
|
||||||
let ctx = Ctx { tx: &tx_c };
|
let ctx = Ctx { tx: &tx_c };
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Program.verify(
|
Program.verify(
|
||||||
&tx_b.tze_bundle.as_ref().unwrap().vout[0].precondition,
|
&tx_b.tze_bundle().as_ref().unwrap().vout[0].precondition,
|
||||||
&tx_c.tze_bundle.as_ref().unwrap().vin[0].witness,
|
&tx_c.tze_bundle().as_ref().unwrap().vin[0].witness,
|
||||||
&ctx
|
&ctx
|
||||||
),
|
),
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -830,7 +833,7 @@ mod tests {
|
||||||
.build(&prover)
|
.build(&prover)
|
||||||
.map_err(|e| format!("build failure: {:?}", e))
|
.map_err(|e| format!("build failure: {:?}", e))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let tze_a = tx_a.tze_bundle.as_ref().unwrap();
|
let tze_a = tx_a.tze_bundle().unwrap();
|
||||||
|
|
||||||
//
|
//
|
||||||
// Transfer
|
// Transfer
|
||||||
|
@ -848,7 +851,7 @@ mod tests {
|
||||||
.build(&prover)
|
.build(&prover)
|
||||||
.map_err(|e| format!("build failure: {:?}", e))
|
.map_err(|e| format!("build failure: {:?}", e))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let tze_b = tx_b.tze_bundle.as_ref().unwrap();
|
let tze_b = tx_b.tze_bundle().unwrap();
|
||||||
|
|
||||||
//
|
//
|
||||||
// Closing transaction
|
// Closing transaction
|
||||||
|
@ -873,7 +876,7 @@ mod tests {
|
||||||
.build(&prover)
|
.build(&prover)
|
||||||
.map_err(|e| format!("build failure: {:?}", e))
|
.map_err(|e| format!("build failure: {:?}", e))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let tze_c = tx_c.tze_bundle.as_ref().unwrap();
|
let tze_c = tx_c.tze_bundle().unwrap();
|
||||||
|
|
||||||
// Verify tx_b
|
// Verify tx_b
|
||||||
let ctx0 = Ctx { tx: &tx_b };
|
let ctx0 = Ctx { tx: &tx_b };
|
||||||
|
|
|
@ -27,8 +27,9 @@ use crate::{
|
||||||
},
|
},
|
||||||
transparent::{self, builder::TransparentBuilder},
|
transparent::{self, builder::TransparentBuilder},
|
||||||
},
|
},
|
||||||
signature_hash_data, SignableInput, Transaction, TransactionData, TxVersion, Unauthorized,
|
sighash::{SignableInput, SIGHASH_ALL},
|
||||||
SIGHASH_ALL,
|
sighash_v4::v4_signature_hash,
|
||||||
|
Transaction, TransactionData, TxVersion, Unauthorized,
|
||||||
},
|
},
|
||||||
zip32::ExtendedSpendingKey,
|
zip32::ExtendedSpendingKey,
|
||||||
};
|
};
|
||||||
|
@ -342,6 +343,7 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
||||||
|
|
||||||
let unauthed_tx = TransactionData {
|
let unauthed_tx = TransactionData {
|
||||||
version,
|
version,
|
||||||
|
consensus_branch_id,
|
||||||
lock_time: 0,
|
lock_time: 0,
|
||||||
expiry_height: self.expiry_height,
|
expiry_height: self.expiry_height,
|
||||||
transparent_bundle,
|
transparent_bundle,
|
||||||
|
@ -357,17 +359,12 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
||||||
//
|
//
|
||||||
|
|
||||||
let mut sighash = [0u8; 32];
|
let mut sighash = [0u8; 32];
|
||||||
sighash.copy_from_slice(&signature_hash_data(
|
sighash.copy_from_slice(
|
||||||
&unauthed_tx,
|
&v4_signature_hash(&unauthed_tx, SignableInput::Shielded, SIGHASH_ALL).as_ref(),
|
||||||
consensus_branch_id,
|
);
|
||||||
SIGHASH_ALL,
|
|
||||||
SignableInput::Shielded,
|
|
||||||
));
|
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
let transparent_sigs = self
|
let transparent_sigs = self.transparent_builder.create_signatures(&unauthed_tx);
|
||||||
.transparent_builder
|
|
||||||
.create_signatures(&unauthed_tx, consensus_branch_id);
|
|
||||||
|
|
||||||
let sapling_sigs = self
|
let sapling_sigs = self
|
||||||
.sapling_builder
|
.sapling_builder
|
||||||
|
@ -382,7 +379,6 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
Self::apply_signatures(
|
Self::apply_signatures(
|
||||||
consensus_branch_id,
|
|
||||||
unauthed_tx,
|
unauthed_tx,
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
transparent_sigs,
|
transparent_sigs,
|
||||||
|
@ -397,7 +393,6 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_signatures(
|
fn apply_signatures(
|
||||||
consensus_branch_id: consensus::BranchId,
|
|
||||||
unauthed_tx: TransactionData<Unauthorized>,
|
unauthed_tx: TransactionData<Unauthorized>,
|
||||||
#[cfg(feature = "transparent-inputs")] transparent_sigs: Option<Vec<Script>>,
|
#[cfg(feature = "transparent-inputs")] transparent_sigs: Option<Vec<Script>>,
|
||||||
sapling_sigs: Option<(Vec<redjubjub::Signature>, redjubjub::Signature)>,
|
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 {
|
let authorized_tx = TransactionData {
|
||||||
version: unauthed_tx.version,
|
version: unauthed_tx.version,
|
||||||
|
consensus_branch_id: unauthed_tx.consensus_branch_id,
|
||||||
lock_time: unauthed_tx.lock_time,
|
lock_time: unauthed_tx.lock_time,
|
||||||
expiry_height: unauthed_tx.expiry_height,
|
expiry_height: unauthed_tx.expiry_height,
|
||||||
transparent_bundle,
|
transparent_bundle,
|
||||||
|
@ -458,7 +454,7 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
||||||
tze_bundle: signed_tze_bundle,
|
tze_bundle: signed_tze_bundle,
|
||||||
};
|
};
|
||||||
|
|
||||||
authorized_tx.freeze(consensus_branch_id)
|
authorized_tx.freeze()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,11 +12,12 @@ use crate::{
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
use crate::{
|
use crate::{
|
||||||
consensus::{self},
|
|
||||||
legacy::Script,
|
legacy::Script,
|
||||||
transaction::{
|
transaction::{
|
||||||
components::OutPoint, sighash_v4::signature_hash_data, SignableInput, TransactionData,
|
components::OutPoint,
|
||||||
Unauthorized, SIGHASH_ALL,
|
sighash::{SignableInput, SIGHASH_ALL},
|
||||||
|
sighash_v4::v4_signature_hash,
|
||||||
|
TransactionData, Unauthorized,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -153,11 +154,7 @@ impl TransparentBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
pub fn create_signatures(
|
pub fn create_signatures(self, mtx: &TransactionData<Unauthorized>) -> Option<Vec<Script>> {
|
||||||
self,
|
|
||||||
mtx: &TransactionData<Unauthorized>,
|
|
||||||
consensus_branch_id: consensus::BranchId,
|
|
||||||
) -> Option<Vec<Script>> {
|
|
||||||
if self.inputs.is_empty() && self.vout.is_empty() {
|
if self.inputs.is_empty() && self.vout.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
@ -167,16 +164,18 @@ impl TransparentBuilder {
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, info)| {
|
.map(|(i, info)| {
|
||||||
let mut sighash = [0u8; 32];
|
let mut sighash = [0u8; 32];
|
||||||
sighash.copy_from_slice(&signature_hash_data(
|
sighash.copy_from_slice(
|
||||||
|
&v4_signature_hash(
|
||||||
mtx,
|
mtx,
|
||||||
consensus_branch_id,
|
|
||||||
SIGHASH_ALL,
|
|
||||||
SignableInput::transparent(
|
SignableInput::transparent(
|
||||||
i,
|
i,
|
||||||
&info.coin.script_pubkey,
|
&info.coin.script_pubkey,
|
||||||
info.coin.value,
|
info.coin.value,
|
||||||
),
|
),
|
||||||
));
|
SIGHASH_ALL,
|
||||||
|
)
|
||||||
|
.as_ref(),
|
||||||
|
);
|
||||||
|
|
||||||
let msg =
|
let msg =
|
||||||
secp256k1::Message::from_slice(sighash.as_ref()).expect("32 bytes");
|
secp256k1::Message::from_slice(sighash.as_ref()).expect("32 bytes");
|
||||||
|
|
|
@ -1,4 +1,14 @@
|
||||||
//! Structs and methods for handling Zcash transactions.
|
//! 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 byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -18,21 +28,12 @@ use self::{
|
||||||
sprout::{self, JsDescription},
|
sprout::{self, JsDescription},
|
||||||
transparent::{self, TxIn, TxOut},
|
transparent::{self, TxIn, TxOut},
|
||||||
},
|
},
|
||||||
sighash_v4::{signature_hash_data, SignableInput, SIGHASH_ALL},
|
|
||||||
util::sha256d::{HashReader, HashWriter},
|
util::sha256d::{HashReader, HashWriter},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "zfuture")]
|
#[cfg(feature = "zfuture")]
|
||||||
use self::components::tze;
|
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_VERSION_GROUP_ID: u32 = 0x03C48270;
|
||||||
const OVERWINTER_TX_VERSION: u32 = 3;
|
const OVERWINTER_TX_VERSION: u32 = 3;
|
||||||
const SAPLING_VERSION_GROUP_ID: u32 = 0x892F2085;
|
const SAPLING_VERSION_GROUP_ID: u32 = 0x892F2085;
|
||||||
|
@ -113,6 +114,7 @@ impl TxVersion {
|
||||||
match (version, reader.read_u32::<LittleEndian>()?) {
|
match (version, reader.read_u32::<LittleEndian>()?) {
|
||||||
(OVERWINTER_TX_VERSION, OVERWINTER_VERSION_GROUP_ID) => Ok(TxVersion::Overwinter),
|
(OVERWINTER_TX_VERSION, OVERWINTER_VERSION_GROUP_ID) => Ok(TxVersion::Overwinter),
|
||||||
(SAPLING_TX_VERSION, SAPLING_VERSION_GROUP_ID) => Ok(TxVersion::Sapling),
|
(SAPLING_TX_VERSION, SAPLING_VERSION_GROUP_ID) => Ok(TxVersion::Sapling),
|
||||||
|
(V5_TX_VERSION, V5_VERSION_GROUP_ID) => Ok(TxVersion::ZcashTxV5),
|
||||||
#[cfg(feature = "zfuture")]
|
#[cfg(feature = "zfuture")]
|
||||||
(ZFUTURE_TX_VERSION, ZFUTURE_VERSION_GROUP_ID) => Ok(TxVersion::ZFuture),
|
(ZFUTURE_TX_VERSION, ZFUTURE_VERSION_GROUP_ID) => Ok(TxVersion::ZFuture),
|
||||||
_ => Err(io::Error::new(
|
_ => 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 {
|
pub fn has_sapling(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
TxVersion::Sprout(_) | TxVersion::Overwinter => false,
|
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 {
|
pub fn suggested_for_branch(consensus_branch_id: BranchId) -> Self {
|
||||||
match consensus_branch_id {
|
match consensus_branch_id {
|
||||||
BranchId::Sprout => TxVersion::Sprout(2),
|
BranchId::Sprout => TxVersion::Sprout(2),
|
||||||
|
@ -255,15 +275,93 @@ impl PartialEq for Transaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TransactionData<A: Authorization> {
|
pub struct TransactionData<A: Authorization> {
|
||||||
pub version: TxVersion,
|
version: TxVersion,
|
||||||
pub lock_time: u32,
|
consensus_branch_id: BranchId,
|
||||||
pub expiry_height: BlockHeight,
|
lock_time: u32,
|
||||||
pub transparent_bundle: Option<transparent::Bundle<A::TransparentAuth>>,
|
expiry_height: BlockHeight,
|
||||||
pub sprout_bundle: Option<sprout::Bundle>,
|
transparent_bundle: Option<transparent::Bundle<A::TransparentAuth>>,
|
||||||
pub sapling_bundle: Option<sapling::Bundle<A::SaplingAuth>>,
|
sprout_bundle: Option<sprout::Bundle>,
|
||||||
pub orchard_bundle: Option<orchard::bundle::Bundle<A::OrchardAuth, Amount>>,
|
sapling_bundle: Option<sapling::Bundle<A::SaplingAuth>>,
|
||||||
|
orchard_bundle: Option<orchard::bundle::Bundle<A::OrchardAuth, Amount>>,
|
||||||
#[cfg(feature = "zfuture")]
|
#[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> {
|
impl<A: Authorization> std::fmt::Debug for TransactionData<A> {
|
||||||
|
@ -272,17 +370,25 @@ impl<A: Authorization> std::fmt::Debug for TransactionData<A> {
|
||||||
f,
|
f,
|
||||||
"TransactionData(
|
"TransactionData(
|
||||||
version = {:?},
|
version = {:?},
|
||||||
|
consensus_branch_id = {:?},
|
||||||
lock_time = {:?},
|
lock_time = {:?},
|
||||||
expiry_height = {:?},
|
expiry_height = {:?},
|
||||||
{}{}{}{}",
|
transparent_fields = {{{}}}
|
||||||
|
sprout = {{{}}},
|
||||||
|
sapling = {{{}}},
|
||||||
|
orchard = {{{}}},
|
||||||
|
tze = {{{}}}
|
||||||
|
)",
|
||||||
self.version,
|
self.version,
|
||||||
|
self.consensus_branch_id,
|
||||||
self.lock_time,
|
self.lock_time,
|
||||||
self.expiry_height,
|
self.expiry_height,
|
||||||
if let Some(b) = &self.transparent_bundle {
|
if let Some(b) = &self.transparent_bundle {
|
||||||
format!(
|
format!(
|
||||||
"
|
"
|
||||||
vin = {:?},
|
vin = {:?},
|
||||||
vout = {:?},",
|
vout = {:?},
|
||||||
|
",
|
||||||
b.vin, b.vout
|
b.vin, b.vout
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -292,7 +398,8 @@ impl<A: Authorization> std::fmt::Debug for TransactionData<A> {
|
||||||
format!(
|
format!(
|
||||||
"
|
"
|
||||||
joinsplits = {:?},
|
joinsplits = {:?},
|
||||||
joinsplit_pubkey = {:?},",
|
joinsplit_pubkey = {:?},
|
||||||
|
",
|
||||||
b.joinsplits, b.joinsplit_pubkey
|
b.joinsplits, b.joinsplit_pubkey
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -304,19 +411,33 @@ impl<A: Authorization> std::fmt::Debug for TransactionData<A> {
|
||||||
value_balance = {:?},
|
value_balance = {:?},
|
||||||
shielded_spends = {:?},
|
shielded_spends = {:?},
|
||||||
shielded_outputs = {:?},
|
shielded_outputs = {:?},
|
||||||
binding_sig = {:?},",
|
binding_sig = {:?},
|
||||||
|
",
|
||||||
b.value_balance, b.shielded_spends, b.shielded_outputs, b.authorization
|
b.value_balance, b.shielded_spends, b.shielded_outputs, b.authorization
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
"".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")]
|
#[cfg(feature = "zfuture")]
|
||||||
if let Some(b) = &self.tze_bundle {
|
if let Some(b) = &self.tze_bundle {
|
||||||
format!(
|
format!(
|
||||||
"
|
"
|
||||||
tze_inputs = {:?},
|
tze_inputs = {:?},
|
||||||
tze_outputs = {:?},",
|
tze_outputs = {:?},
|
||||||
|
",
|
||||||
b.vin, b.vout
|
b.vin, b.vout
|
||||||
)
|
)
|
||||||
} else {
|
} 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> {
|
impl TransactionData<Authorized> {
|
||||||
pub fn freeze(self, consensus_branch_id: BranchId) -> io::Result<Transaction> {
|
pub fn freeze(self) -> io::Result<Transaction> {
|
||||||
Transaction::from_data(self, consensus_branch_id)
|
Transaction::from_data(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Transaction {
|
impl Transaction {
|
||||||
fn from_data(
|
fn from_data(data: TransactionData<Authorized>) -> io::Result<Self> {
|
||||||
data: TransactionData<Authorized>,
|
|
||||||
_consensus_branch_id: BranchId,
|
|
||||||
) -> io::Result<Self> {
|
|
||||||
let mut tx = Transaction {
|
let mut tx = Transaction {
|
||||||
txid: TxId([0; 32]),
|
txid: TxId([0; 32]),
|
||||||
data,
|
data,
|
||||||
|
@ -398,7 +480,8 @@ impl Transaction {
|
||||||
self.txid
|
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 mut reader = HashReader::new(reader);
|
||||||
|
|
||||||
let version = TxVersion::read(&mut reader)?;
|
let version = TxVersion::read(&mut reader)?;
|
||||||
|
@ -462,6 +545,7 @@ impl Transaction {
|
||||||
txid: TxId(txid),
|
txid: TxId(txid),
|
||||||
data: TransactionData {
|
data: TransactionData {
|
||||||
version,
|
version,
|
||||||
|
consensus_branch_id,
|
||||||
lock_time,
|
lock_time,
|
||||||
expiry_height,
|
expiry_height,
|
||||||
transparent_bundle,
|
transparent_bundle,
|
||||||
|
@ -615,7 +699,7 @@ pub struct TxDigests<A> {
|
||||||
pub tze_digests: Option<TzeDigests<A>>,
|
pub tze_digests: Option<TzeDigests<A>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait TransactionDigest<A: Authorization> {
|
pub trait TransactionDigest<A: Authorization> {
|
||||||
type HeaderDigest;
|
type HeaderDigest;
|
||||||
type TransparentDigest;
|
type TransparentDigest;
|
||||||
type SaplingDigest;
|
type SaplingDigest;
|
||||||
|
@ -707,14 +791,15 @@ pub mod testing {
|
||||||
|
|
||||||
#[cfg(not(feature = "zfuture"))]
|
#[cfg(not(feature = "zfuture"))]
|
||||||
prop_compose! {
|
prop_compose! {
|
||||||
pub fn arb_txdata(branch_id: BranchId)(
|
pub fn arb_txdata(consensus_branch_id: BranchId)(
|
||||||
version in arb_tx_version(branch_id),
|
version in arb_tx_version(consensus_branch_id),
|
||||||
lock_time in any::<u32>(),
|
lock_time in any::<u32>(),
|
||||||
expiry_height in any::<u32>(),
|
expiry_height in any::<u32>(),
|
||||||
transparent_bundle in transparent::arb_bundle(),
|
transparent_bundle in transparent::arb_bundle(),
|
||||||
) -> TransactionData<Authorized> {
|
) -> TransactionData<Authorized> {
|
||||||
TransactionData {
|
TransactionData {
|
||||||
version,
|
version,
|
||||||
|
consensus_branch_id,
|
||||||
lock_time,
|
lock_time,
|
||||||
expiry_height: expiry_height.into(),
|
expiry_height: expiry_height.into(),
|
||||||
transparent_bundle,
|
transparent_bundle,
|
||||||
|
@ -727,8 +812,8 @@ pub mod testing {
|
||||||
|
|
||||||
#[cfg(feature = "zfuture")]
|
#[cfg(feature = "zfuture")]
|
||||||
prop_compose! {
|
prop_compose! {
|
||||||
pub fn arb_txdata(branch_id: BranchId)(
|
pub fn arb_txdata(consensus_branch_id: BranchId)(
|
||||||
version in arb_tx_version(branch_id),
|
version in arb_tx_version(consensus_branch_id),
|
||||||
lock_time in any::<u32>(),
|
lock_time in any::<u32>(),
|
||||||
expiry_height in any::<u32>(),
|
expiry_height in any::<u32>(),
|
||||||
transparent_bundle in transparent::arb_bundle(),
|
transparent_bundle in transparent::arb_bundle(),
|
||||||
|
@ -736,6 +821,7 @@ pub mod testing {
|
||||||
) -> TransactionData<Authorized> {
|
) -> TransactionData<Authorized> {
|
||||||
TransactionData {
|
TransactionData {
|
||||||
version,
|
version,
|
||||||
|
consensus_branch_id,
|
||||||
lock_time,
|
lock_time,
|
||||||
expiry_height: expiry_height.into(),
|
expiry_height: expiry_height.into(),
|
||||||
transparent_bundle,
|
transparent_bundle,
|
||||||
|
@ -749,7 +835,7 @@ pub mod testing {
|
||||||
|
|
||||||
prop_compose! {
|
prop_compose! {
|
||||||
pub fn arb_tx(branch_id: BranchId)(tx_data in arb_txdata(branch_id)) -> Transaction {
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
})
|
||||||
|
}
|
|
@ -3,22 +3,16 @@ use byteorder::{LittleEndian, WriteBytesExt};
|
||||||
use ff::PrimeField;
|
use ff::PrimeField;
|
||||||
use group::GroupEncoding;
|
use group::GroupEncoding;
|
||||||
|
|
||||||
use crate::{
|
use crate::consensus::BranchId;
|
||||||
consensus::{self, BranchId},
|
|
||||||
legacy::Script,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "zfuture")]
|
|
||||||
use crate::extensions::transparent::Precondition;
|
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
components::{
|
components::{
|
||||||
amount::Amount,
|
|
||||||
sapling::{self, GrothProofBytes, OutputDescription, SpendDescription},
|
sapling::{self, GrothProofBytes, OutputDescription, SpendDescription},
|
||||||
sprout::JsDescription,
|
sprout::JsDescription,
|
||||||
transparent::{self, TxIn, TxOut},
|
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";
|
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_SPENDS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSSpendsHash";
|
||||||
const ZCASH_SHIELDED_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSOutputHash";
|
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 {
|
macro_rules! update_u32 {
|
||||||
($h:expr, $value:expr, $tmp:expr) => {
|
($h:expr, $value:expr, $tmp:expr) => {
|
||||||
(&mut $tmp[..4]).write_u32::<LittleEndian>($value).unwrap();
|
(&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 {
|
fn prevout_hash<TA: transparent::Authorization>(vin: &[TxIn<TA>]) -> Blake2bHash {
|
||||||
let mut data = Vec::with_capacity(vin.len() * 36);
|
let mut data = Vec::with_capacity(vin.len() * 36);
|
||||||
for t_in in vin {
|
for t_in in vin {
|
||||||
|
@ -110,7 +94,7 @@ fn joinsplits_hash(
|
||||||
* if consensus_branch_id.sprout_uses_groth_proofs() {
|
* if consensus_branch_id.sprout_uses_groth_proofs() {
|
||||||
1698 // JSDescription with Groth16 proof
|
1698 // JSDescription with Groth16 proof
|
||||||
} else {
|
} else {
|
||||||
1802 // JSDescription with PHGR13 proof
|
1802 // JsDescription with PHGR13 proof
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
for js in joinsplits {
|
for js in joinsplits {
|
||||||
|
@ -130,7 +114,7 @@ fn shielded_spends_hash<A: sapling::Authorization<Proof = GrothProofBytes>>(
|
||||||
for s_spend in shielded_spends {
|
for s_spend in shielded_spends {
|
||||||
data.extend_from_slice(&s_spend.cv.to_bytes());
|
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.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();
|
s_spend.rk.write(&mut data).unwrap();
|
||||||
data.extend_from_slice(&s_spend.zkproof);
|
data.extend_from_slice(&s_spend.zkproof);
|
||||||
}
|
}
|
||||||
|
@ -153,55 +137,19 @@ fn shielded_outputs_hash<A: sapling::Authorization<Proof = GrothProofBytes>>(
|
||||||
.hash(&data)
|
.hash(&data)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum SignableInput<'a> {
|
pub fn v4_signature_hash<
|
||||||
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,
|
|
||||||
SA: sapling::Authorization<Proof = GrothProofBytes>,
|
SA: sapling::Authorization<Proof = GrothProofBytes>,
|
||||||
A: Authorization<SaplingAuth = SA, TransparentAuth = TA>,
|
A: Authorization<SaplingAuth = SA>,
|
||||||
>(
|
>(
|
||||||
tx: &TransactionData<A>,
|
tx: &TransactionData<A>,
|
||||||
consensus_branch_id: consensus::BranchId,
|
|
||||||
hash_type: u32,
|
|
||||||
signable_input: SignableInput<'_>,
|
signable_input: SignableInput<'_>,
|
||||||
) -> Vec<u8> {
|
hash_type: u32,
|
||||||
if has_overwinter_components(&tx.version) {
|
) -> Blake2bHash {
|
||||||
|
if tx.version.has_overwinter() {
|
||||||
let mut personal = [0; 16];
|
let mut personal = [0; 16];
|
||||||
(&mut personal[..12]).copy_from_slice(ZCASH_SIGHASH_PERSONALIZATION_PREFIX);
|
(&mut personal[..12]).copy_from_slice(ZCASH_SIGHASH_PERSONALIZATION_PREFIX);
|
||||||
(&mut personal[12..])
|
(&mut personal[12..])
|
||||||
.write_u32::<LittleEndian>(consensus_branch_id.into())
|
.write_u32::<LittleEndian>(tx.consensus_branch_id.into())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut h = Blake2bParams::new()
|
let mut h = Blake2bParams::new()
|
||||||
|
@ -223,7 +171,7 @@ pub fn signature_hash_data<
|
||||||
);
|
);
|
||||||
update_hash!(
|
update_hash!(
|
||||||
h,
|
h,
|
||||||
hash_type & SIGHASH_ANYONECANPAY == 0
|
(hash_type & SIGHASH_ANYONECANPAY) == 0
|
||||||
&& (hash_type & SIGHASH_MASK) != SIGHASH_SINGLE
|
&& (hash_type & SIGHASH_MASK) != SIGHASH_SINGLE
|
||||||
&& (hash_type & SIGHASH_MASK) != SIGHASH_NONE,
|
&& (hash_type & SIGHASH_MASK) != SIGHASH_NONE,
|
||||||
sequence_hash(
|
sequence_hash(
|
||||||
|
@ -246,14 +194,15 @@ pub fn signature_hash_data<
|
||||||
);
|
);
|
||||||
} else if (hash_type & SIGHASH_MASK) == SIGHASH_SINGLE {
|
} else if (hash_type & SIGHASH_MASK) == SIGHASH_SINGLE {
|
||||||
match (tx.transparent_bundle.as_ref(), &signable_input) {
|
match (tx.transparent_bundle.as_ref(), &signable_input) {
|
||||||
(Some(b), SignableInput::Transparent { index, .. }) if *index < b.vout.len() => {
|
(Some(b), SignableInput::Transparent(input)) if input.index() < b.vout.len() => {
|
||||||
h.update(single_output_hash(&b.vout[*index]).as_bytes())
|
h.update(single_output_hash(&b.vout[input.index()]).as_bytes())
|
||||||
}
|
}
|
||||||
_ => h.update(&[0; 32]),
|
_ => h.update(&[0; 32]),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
h.update(&[0; 32]);
|
h.update(&[0; 32]);
|
||||||
};
|
};
|
||||||
|
|
||||||
update_hash!(
|
update_hash!(
|
||||||
h,
|
h,
|
||||||
!tx.sprout_bundle
|
!tx.sprout_bundle
|
||||||
|
@ -262,38 +211,27 @@ pub fn signature_hash_data<
|
||||||
{
|
{
|
||||||
let bundle = tx.sprout_bundle.as_ref().unwrap();
|
let bundle = tx.sprout_bundle.as_ref().unwrap();
|
||||||
joinsplits_hash(
|
joinsplits_hash(
|
||||||
consensus_branch_id,
|
tx.consensus_branch_id,
|
||||||
&bundle.joinsplits,
|
&bundle.joinsplits,
|
||||||
&bundle.joinsplit_pubkey,
|
&bundle.joinsplit_pubkey,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if tx.version.has_sapling() {
|
if tx.version.has_sapling() {
|
||||||
update_hash!(
|
update_hash!(
|
||||||
h,
|
h,
|
||||||
!tx.sapling_bundle
|
!tx.sapling_bundle
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(true, |b| b.shielded_spends.is_empty()),
|
.map_or(true, |b| b.shielded_spends.is_empty()),
|
||||||
shielded_spends_hash(
|
shielded_spends_hash(&tx.sapling_bundle.as_ref().unwrap().shielded_spends)
|
||||||
tx.sapling_bundle
|
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.shielded_spends
|
|
||||||
.as_slice()
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
update_hash!(
|
update_hash!(
|
||||||
h,
|
h,
|
||||||
!tx.sapling_bundle
|
!tx.sapling_bundle
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(true, |b| b.shielded_outputs.is_empty()),
|
.map_or(true, |b| b.shielded_outputs.is_empty()),
|
||||||
shielded_outputs_hash(
|
shielded_outputs_hash(&tx.sapling_bundle.as_ref().unwrap().shielded_outputs)
|
||||||
tx.sapling_bundle
|
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.shielded_outputs
|
|
||||||
.as_slice()
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
update_u32!(h, tx.lock_time, tmp);
|
update_u32!(h, tx.lock_time, tmp);
|
||||||
|
@ -304,19 +242,15 @@ pub fn signature_hash_data<
|
||||||
update_u32!(h, hash_type, tmp);
|
update_u32!(h, hash_type, tmp);
|
||||||
|
|
||||||
match signable_input {
|
match signable_input {
|
||||||
SignableInput::Transparent {
|
SignableInput::Shielded => (),
|
||||||
index,
|
SignableInput::Transparent(input) => {
|
||||||
script_code,
|
|
||||||
value,
|
|
||||||
} => {
|
|
||||||
if let Some(bundle) = tx.transparent_bundle.as_ref() {
|
if let Some(bundle) = tx.transparent_bundle.as_ref() {
|
||||||
let mut data = vec![];
|
let mut data = vec![];
|
||||||
|
bundle.vin[input.index()].prevout.write(&mut data).unwrap();
|
||||||
bundle.vin[index].prevout.write(&mut data).unwrap();
|
input.script_code().write(&mut data).unwrap();
|
||||||
script_code.write(&mut data).unwrap();
|
data.extend_from_slice(&input.value().to_i64_le_bytes());
|
||||||
data.extend_from_slice(&value.to_i64_le_bytes());
|
|
||||||
(&mut data)
|
(&mut data)
|
||||||
.write_u32::<LittleEndian>(bundle.vin[index].sequence)
|
.write_u32::<LittleEndian>(bundle.vin[input.index()].sequence)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
h.update(&data);
|
h.update(&data);
|
||||||
} else {
|
} else {
|
||||||
|
@ -325,25 +259,15 @@ pub fn signature_hash_data<
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "zfuture")]
|
#[cfg(feature = "zfuture")]
|
||||||
SignableInput::Tze { .. } => {
|
SignableInput::Tze(_) => {
|
||||||
panic!("A request has been made to sign a TZE input in a V4 transaction.");
|
panic!("A request has been made to sign a TZE input, but the transaction version is not ZFuture");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SignableInput::Shielded => (),
|
h.finalize()
|
||||||
}
|
|
||||||
|
|
||||||
h.finalize().as_ref().to_vec()
|
|
||||||
} else {
|
} 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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
use proptest::prelude::*;
|
use proptest::prelude::*;
|
||||||
|
|
||||||
|
use crate::consensus::BranchId;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
components::Amount,
|
components::Amount, sighash::SignableInput, sighash_v4::v4_signature_hash, Transaction,
|
||||||
sighash_v4::{signature_hash, SignableInput},
|
|
||||||
Transaction,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::testing::{arb_branch_id, arb_tx};
|
use super::testing::{arb_branch_id, arb_tx};
|
||||||
|
@ -11,7 +11,7 @@ use super::testing::{arb_branch_id, arb_tx};
|
||||||
#[test]
|
#[test]
|
||||||
fn tx_read_write() {
|
fn tx_read_write() {
|
||||||
let data = &self::data::tx_read_write::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!(
|
assert_eq!(
|
||||||
format!("{}", tx.txid()),
|
format!("{}", tx.txid()),
|
||||||
"64f0bd7fe30ce23753358fe3a2dc835b8fba9c0274c4e2c54a6f73114cb55639"
|
"64f0bd7fe30ce23753358fe3a2dc835b8fba9c0274c4e2c54a6f73114cb55639"
|
||||||
|
@ -28,7 +28,7 @@ proptest! {
|
||||||
let mut txn_bytes = vec![];
|
let mut txn_bytes = vec![];
|
||||||
tx.write(&mut txn_bytes).unwrap();
|
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.version, txo.version);
|
||||||
prop_assert_eq!(tx.lock_time, txo.lock_time);
|
prop_assert_eq!(tx.lock_time, txo.lock_time);
|
||||||
|
@ -43,7 +43,7 @@ mod data;
|
||||||
#[test]
|
#[test]
|
||||||
fn zip_0143() {
|
fn zip_0143() {
|
||||||
for tv in self::data::zip_0143::make_test_vectors() {
|
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 {
|
let signable_input = match tv.transparent_input {
|
||||||
Some(n) => SignableInput::transparent(
|
Some(n) => SignableInput::transparent(
|
||||||
n as usize,
|
n as usize,
|
||||||
|
@ -54,7 +54,7 @@ fn zip_0143() {
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(
|
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
|
tv.sighash
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ fn zip_0143() {
|
||||||
#[test]
|
#[test]
|
||||||
fn zip_0243() {
|
fn zip_0243() {
|
||||||
for tv in self::data::zip_0243::make_test_vectors() {
|
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 {
|
let signable_input = match tv.transparent_input {
|
||||||
Some(n) => SignableInput::transparent(
|
Some(n) => SignableInput::transparent(
|
||||||
n as usize,
|
n as usize,
|
||||||
|
@ -74,7 +74,7 @@ fn zip_0243() {
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(
|
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
|
tv.sighash
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue