Merge pull request #472 from nuttycom/zip_244/hw_wallet_commitments
Make transparent signatures commit to all input amounts & scripts.
This commit is contained in:
commit
81c69dd0a9
|
@ -94,8 +94,8 @@ impl Shl<&[u8]> for Script {
|
|||
/// A transparent address corresponding to either a public key or a `Script`.
|
||||
#[derive(Debug, PartialEq, PartialOrd, Hash, Clone)]
|
||||
pub enum TransparentAddress {
|
||||
PublicKey([u8; 20]),
|
||||
Script([u8; 20]),
|
||||
PublicKey([u8; 20]), // TODO: Rename to PublicKeyHash
|
||||
Script([u8; 20]), // TODO: Rename to ScriptHash
|
||||
}
|
||||
|
||||
impl TransparentAddress {
|
||||
|
|
|
@ -27,7 +27,7 @@ use crate::{
|
|||
},
|
||||
transparent::{self, builder::TransparentBuilder},
|
||||
},
|
||||
sighash::{signature_hash, SignableInput, SIGHASH_ALL},
|
||||
sighash::{signature_hash, SignableInput},
|
||||
txid::TxIdDigester,
|
||||
Transaction, TransactionData, TxVersion, Unauthorized,
|
||||
},
|
||||
|
@ -372,12 +372,8 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
|||
// the commitment being signed is shared across all Sapling inputs; once
|
||||
// V4 transactions are deprecated this should just be the txid, but
|
||||
// for now we need to continue to compute it here.
|
||||
let shielded_sig_commitment = signature_hash(
|
||||
&unauthed_tx,
|
||||
SIGHASH_ALL,
|
||||
&SignableInput::Shielded,
|
||||
&txid_parts,
|
||||
);
|
||||
let shielded_sig_commitment =
|
||||
signature_hash(&unauthed_tx, &SignableInput::Shielded, &txid_parts);
|
||||
|
||||
let (sapling_bundle, tx_metadata) = match unauthed_tx
|
||||
.sapling_bundle
|
||||
|
|
|
@ -10,7 +10,7 @@ const PHGR_PROOF_SIZE: usize = 33 + 33 + 65 + 33 + 33 + 33 + 33 + 33;
|
|||
const ZC_NUM_JS_INPUTS: usize = 2;
|
||||
const ZC_NUM_JS_OUTPUTS: usize = 2;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Bundle {
|
||||
pub joinsplits: Vec<JsDescription>,
|
||||
pub joinsplit_pubkey: [u8; 32],
|
||||
|
|
|
@ -6,22 +6,22 @@ use std::fmt;
|
|||
use blake2b_simd::Hash as Blake2bHash;
|
||||
|
||||
use crate::{
|
||||
legacy::TransparentAddress,
|
||||
transaction::components::{
|
||||
amount::Amount,
|
||||
transparent::{self, Authorization, Authorized, Bundle, TxIn, TxOut},
|
||||
legacy::{Script, TransparentAddress},
|
||||
transaction::{
|
||||
components::{
|
||||
amount::Amount,
|
||||
transparent::{self, Authorization, Authorized, Bundle, TxIn, TxOut},
|
||||
},
|
||||
sighash::TransparentAuthorizingContext,
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
use crate::{
|
||||
legacy::Script,
|
||||
transaction::{
|
||||
self as tx,
|
||||
components::OutPoint,
|
||||
sighash::{signature_hash, SignableInput, SIGHASH_ALL},
|
||||
TransactionData, TxDigests,
|
||||
},
|
||||
use crate::transaction::{
|
||||
self as tx,
|
||||
components::OutPoint,
|
||||
sighash::{signature_hash, SignableInput, SIGHASH_ALL},
|
||||
TransactionData, TxDigests,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
@ -188,6 +188,32 @@ impl TxIn<Unauthorized> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "transparent-inputs"))]
|
||||
impl TransparentAuthorizingContext for Unauthorized {
|
||||
fn input_amounts(&self) -> Vec<Amount> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn input_scriptpubkeys(&self) -> Vec<Script> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
impl TransparentAuthorizingContext for Unauthorized {
|
||||
fn input_amounts(&self) -> Vec<Amount> {
|
||||
return self.inputs.iter().map(|txin| txin.coin.value).collect();
|
||||
}
|
||||
|
||||
fn input_scriptpubkeys(&self) -> Vec<Script> {
|
||||
return self
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|txin| txin.coin.script_pubkey.clone())
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
|
||||
impl Bundle<Unauthorized> {
|
||||
pub fn apply_signatures(
|
||||
self,
|
||||
|
@ -200,11 +226,16 @@ impl Bundle<Unauthorized> {
|
|||
.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, info)| {
|
||||
.map(|(index, info)| {
|
||||
let sighash = signature_hash(
|
||||
mtx,
|
||||
SIGHASH_ALL,
|
||||
&SignableInput::transparent(i, &info.coin.script_pubkey, info.coin.value),
|
||||
&SignableInput::Transparent {
|
||||
hash_type: SIGHASH_ALL,
|
||||
index,
|
||||
script_code: &info.coin.script_pubkey, // for p2pkh, always the same as script_pubkey
|
||||
script_pubkey: &info.coin.script_pubkey,
|
||||
value: info.coin.value,
|
||||
},
|
||||
txid_parts_cache,
|
||||
);
|
||||
|
||||
|
|
|
@ -330,6 +330,10 @@ impl<A: Authorization> TransactionData<A> {
|
|||
self.consensus_branch_id
|
||||
}
|
||||
|
||||
pub fn lock_time(&self) -> u32 {
|
||||
self.lock_time
|
||||
}
|
||||
|
||||
pub fn expiry_height(&self) -> BlockHeight {
|
||||
self.expiry_height
|
||||
}
|
||||
|
@ -924,10 +928,9 @@ impl Transaction {
|
|||
|
||||
#[derive(Clone)]
|
||||
pub struct TransparentDigests<A> {
|
||||
pub prevout_digest: A,
|
||||
pub prevouts_digest: A,
|
||||
pub sequence_digest: A,
|
||||
pub outputs_digest: A,
|
||||
pub per_input_digest: Option<A>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::convert::TryInto;
|
|||
use super::{
|
||||
components::{
|
||||
sapling::{self, GrothProofBytes},
|
||||
Amount,
|
||||
transparent, Amount,
|
||||
},
|
||||
sighash_v4::v4_signature_hash,
|
||||
sighash_v5::v5_signature_hash,
|
||||
|
@ -15,93 +15,37 @@ use super::{
|
|||
#[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 const SIGHASH_ALL: u8 = 0x01;
|
||||
pub const SIGHASH_NONE: u8 = 0x02;
|
||||
pub const SIGHASH_SINGLE: u8 = 0x03;
|
||||
pub const SIGHASH_MASK: u8 = 0x1f;
|
||||
pub const SIGHASH_ANYONECANPAY: u8 = 0x80;
|
||||
|
||||
pub enum SignableInput<'a> {
|
||||
Shielded,
|
||||
Transparent(TransparentInput<'a>),
|
||||
Transparent {
|
||||
hash_type: u8,
|
||||
index: usize,
|
||||
script_code: &'a Script,
|
||||
script_pubkey: &'a Script,
|
||||
value: Amount,
|
||||
},
|
||||
#[cfg(feature = "zfuture")]
|
||||
Tze(TzeInput<'a>),
|
||||
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(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 fn hash_type(&self) -> u8 {
|
||||
match self {
|
||||
SignableInput::Shielded => SIGHASH_ALL,
|
||||
SignableInput::Transparent { hash_type, .. } => *hash_type,
|
||||
#[cfg(feature = "zfuture")]
|
||||
SignableInput::Tze { .. } => SIGHASH_ALL,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,24 +57,43 @@ impl AsRef<[u8; 32]> for SignatureHash {
|
|||
}
|
||||
}
|
||||
|
||||
/// Addtional context that is needed to compute signature hashes
|
||||
/// for transactions that include transparent inputs or outputs.
|
||||
pub trait TransparentAuthorizingContext: transparent::Authorization {
|
||||
/// Returns the list of all transparent input amounts, provided
|
||||
/// so that wallets can commit to the transparent input breakdown
|
||||
/// without requiring the full data of the previous transactions
|
||||
/// providing these inputs.
|
||||
fn input_amounts(&self) -> Vec<Amount>;
|
||||
/// Returns the list of all transparent input scriptPubKeys, provided
|
||||
/// so that wallets can commit to the transparent input breakdown
|
||||
/// without requiring the full data of the previous transactions
|
||||
/// providing these inputs.
|
||||
fn input_scriptpubkeys(&self) -> Vec<Script>;
|
||||
}
|
||||
|
||||
/// Computes the signature hash for an input to a transaction, given
|
||||
/// the full data of the transaction, the input being signed, and the
|
||||
/// set of precomputed hashes produced in the construction of the
|
||||
/// transaction ID.
|
||||
pub fn signature_hash<
|
||||
'a,
|
||||
TA: TransparentAuthorizingContext,
|
||||
SA: sapling::Authorization<Proof = GrothProofBytes>,
|
||||
A: Authorization<SaplingAuth = SA>,
|
||||
A: Authorization<SaplingAuth = SA, TransparentAuth = TA>,
|
||||
>(
|
||||
tx: &TransactionData<A>,
|
||||
hash_type: u32,
|
||||
signable_input: &SignableInput<'a>,
|
||||
txid_parts: &TxDigests<Blake2bHash>,
|
||||
) -> SignatureHash {
|
||||
SignatureHash(match tx.version {
|
||||
TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => {
|
||||
v4_signature_hash(tx, hash_type, signable_input)
|
||||
v4_signature_hash(tx, signable_input)
|
||||
}
|
||||
|
||||
TxVersion::Zip225 => v5_signature_hash(tx, hash_type, signable_input, txid_parts),
|
||||
TxVersion::Zip225 => v5_signature_hash(tx, signable_input, txid_parts),
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
TxVersion::ZFuture => v5_signature_hash(tx, hash_type, signable_input, txid_parts),
|
||||
TxVersion::ZFuture => v5_signature_hash(tx, signable_input, txid_parts),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -140,9 +140,9 @@ pub fn v4_signature_hash<
|
|||
A: Authorization<SaplingAuth = SA>,
|
||||
>(
|
||||
tx: &TransactionData<A>,
|
||||
hash_type: u32,
|
||||
signable_input: &SignableInput<'_>,
|
||||
) -> Blake2bHash {
|
||||
let hash_type = signable_input.hash_type();
|
||||
if tx.version.has_overwinter() {
|
||||
let mut personal = [0; 16];
|
||||
(&mut personal[..12]).copy_from_slice(ZCASH_SIGHASH_PERSONALIZATION_PREFIX);
|
||||
|
@ -192,8 +192,8 @@ pub fn v4_signature_hash<
|
|||
);
|
||||
} else if (hash_type & SIGHASH_MASK) == SIGHASH_SINGLE {
|
||||
match (tx.transparent_bundle.as_ref(), signable_input) {
|
||||
(Some(b), SignableInput::Transparent(input)) if input.index() < b.vout.len() => {
|
||||
h.update(single_output_hash(&b.vout[input.index()]).as_bytes())
|
||||
(Some(b), SignableInput::Transparent { index, .. }) if index < &b.vout.len() => {
|
||||
h.update(single_output_hash(&b.vout[*index]).as_bytes())
|
||||
}
|
||||
_ => h.update(&[0; 32]),
|
||||
};
|
||||
|
@ -237,18 +237,23 @@ pub fn v4_signature_hash<
|
|||
if tx.version.has_sapling() {
|
||||
h.update(&tx.sapling_value_balance().to_i64_le_bytes());
|
||||
}
|
||||
update_u32!(h, hash_type, tmp);
|
||||
update_u32!(h, hash_type.into(), tmp);
|
||||
|
||||
match signable_input {
|
||||
SignableInput::Shielded => (),
|
||||
SignableInput::Transparent(input) => {
|
||||
SignableInput::Transparent {
|
||||
index,
|
||||
script_code,
|
||||
value,
|
||||
..
|
||||
} => {
|
||||
if let Some(bundle) = tx.transparent_bundle.as_ref() {
|
||||
let mut data = vec![];
|
||||
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());
|
||||
bundle.vin[*index].prevout.write(&mut data).unwrap();
|
||||
script_code.write(&mut data).unwrap();
|
||||
data.extend_from_slice(&value.to_i64_le_bytes());
|
||||
(&mut data)
|
||||
.write_u32::<LittleEndian>(bundle.vin[input.index()].sequence)
|
||||
.write_u32::<LittleEndian>(bundle.vin[*index].sequence)
|
||||
.unwrap();
|
||||
h.update(&data);
|
||||
} else {
|
||||
|
@ -259,7 +264,7 @@ pub fn v4_signature_hash<
|
|||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
SignableInput::Tze(_) => {
|
||||
SignableInput::Tze { .. } => {
|
||||
panic!("A request has been made to sign a TZE input, but the transaction version is not ZFuture");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,15 +2,17 @@ use std::io::Write;
|
|||
|
||||
use blake2b_simd::{Hash as Blake2bHash, Params, State};
|
||||
use byteorder::{LittleEndian, WriteBytesExt};
|
||||
use zcash_encoding::Array;
|
||||
|
||||
use crate::transaction::{
|
||||
components::transparent::{self, TxOut},
|
||||
sighash::{
|
||||
SignableInput, TransparentInput, SIGHASH_ANYONECANPAY, SIGHASH_MASK, SIGHASH_NONE,
|
||||
SIGHASH_SINGLE,
|
||||
SignableInput, TransparentAuthorizingContext, SIGHASH_ANYONECANPAY, SIGHASH_MASK,
|
||||
SIGHASH_NONE, SIGHASH_SINGLE,
|
||||
},
|
||||
txid::{
|
||||
to_hash, transparent_outputs_hash, transparent_prevout_hash, transparent_sequence_hash,
|
||||
hash_transparent_txid_data, to_hash, transparent_outputs_hash, transparent_prevout_hash,
|
||||
transparent_sequence_hash, ZCASH_TRANSPARENT_HASH_PERSONALIZATION,
|
||||
},
|
||||
Authorization, TransactionData, TransparentDigests, TxDigests,
|
||||
};
|
||||
|
@ -22,9 +24,11 @@ use std::convert::TryInto;
|
|||
use zcash_encoding::{CompactSize, Vector};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use crate::transaction::{components::tze, sighash::TzeInput, TzeDigests};
|
||||
use crate::transaction::{components::tze, TzeDigests};
|
||||
|
||||
const ZCASH_TRANSPARENT_INPUT_HASH_PERSONALIZATION: &[u8; 16] = b"Zcash___TxInHash";
|
||||
const ZCASH_TRANSPARENT_AMOUNTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxTrAmountsHash";
|
||||
const ZCASH_TRANSPARENT_SCRIPTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxTrScriptsHash";
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
const ZCASH_TZE_INPUT_HASH_PERSONALIZATION: &[u8; 16] = b"Zcash__TzeInHash";
|
||||
|
@ -33,81 +37,121 @@ 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<'_>,
|
||||
/// Implements [ZIP 244 section S.2](https://zips.z.cash/zip-0244#s-2-transparent-sig-digest)
|
||||
/// but only when used to produce the hash for a signature over a transparent input.
|
||||
fn transparent_sig_digest<A: TransparentAuthorizingContext>(
|
||||
txid_digests: &TransparentDigests<Blake2bHash>,
|
||||
hash_type: u32,
|
||||
) -> TransparentDigests<Blake2bHash> {
|
||||
bundle: &transparent::Bundle<A>,
|
||||
input: &SignableInput<'_>,
|
||||
) -> Blake2bHash {
|
||||
let hash_type = input.hash_type();
|
||||
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 {
|
||||
let prevouts_digest = if flag_anyonecanpay {
|
||||
transparent_prevout_hash::<A>(&[])
|
||||
} else {
|
||||
txid_digests.prevout_digest
|
||||
txid_digests.prevouts_digest
|
||||
};
|
||||
|
||||
let sequence_digest = if flag_anyonecanpay || flag_single || flag_none {
|
||||
let amounts_digest = {
|
||||
let mut h = hasher(ZCASH_TRANSPARENT_AMOUNTS_HASH_PERSONALIZATION);
|
||||
if !flag_anyonecanpay {
|
||||
Array::write(&mut h, bundle.authorization.input_amounts(), |w, amount| {
|
||||
w.write_all(&amount.to_i64_le_bytes())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
h.finalize()
|
||||
};
|
||||
|
||||
let scripts_digest = {
|
||||
let mut h = hasher(ZCASH_TRANSPARENT_SCRIPTS_HASH_PERSONALIZATION);
|
||||
if !flag_anyonecanpay {
|
||||
Array::write(
|
||||
&mut h,
|
||||
bundle.authorization.input_scriptpubkeys(),
|
||||
|w, script| script.write(w),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
h.finalize()
|
||||
};
|
||||
|
||||
let sequence_digest = if flag_anyonecanpay {
|
||||
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 {
|
||||
let outputs_digest = if let SignableInput::Transparent { index, .. } = input {
|
||||
if flag_single {
|
||||
if *index < bundle.vout.len() {
|
||||
transparent_outputs_hash(&[&bundle.vout[*index]])
|
||||
} else {
|
||||
transparent_outputs_hash::<TxOut>(&[])
|
||||
}
|
||||
} else if flag_none {
|
||||
transparent_outputs_hash::<TxOut>(&[])
|
||||
} else {
|
||||
txid_digests.outputs_digest
|
||||
}
|
||||
} 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)
|
||||
//S.2g.i: prevout (field encoding)
|
||||
//S.2g.ii: value (8-byte signed little-endian)
|
||||
//S.2g.iii: scriptPubKey (field encoding)
|
||||
//S.2g.iv: nSequence (4-byte unsigned 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),
|
||||
if let SignableInput::Transparent {
|
||||
index,
|
||||
script_pubkey,
|
||||
value,
|
||||
..
|
||||
} = input
|
||||
{
|
||||
let txin = &bundle.vin[*index];
|
||||
txin.prevout.write(&mut ch).unwrap();
|
||||
ch.write_all(&value.to_i64_le_bytes()).unwrap();
|
||||
script_pubkey.write(&mut ch).unwrap();
|
||||
ch.write_u32::<LittleEndian>(txin.sequence).unwrap();
|
||||
}
|
||||
let txin_sig_digest = ch.finalize();
|
||||
|
||||
let mut h = hasher(ZCASH_TRANSPARENT_HASH_PERSONALIZATION);
|
||||
h.write_all(&[hash_type]).unwrap();
|
||||
h.write_all(prevouts_digest.as_bytes()).unwrap();
|
||||
h.write_all(amounts_digest.as_bytes()).unwrap();
|
||||
h.write_all(scripts_digest.as_bytes()).unwrap();
|
||||
h.write_all(sequence_digest.as_bytes()).unwrap();
|
||||
h.write_all(outputs_digest.as_bytes()).unwrap();
|
||||
h.write_all(txin_sig_digest.as_bytes()).unwrap();
|
||||
h.finalize()
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
fn tze_input_sigdigests<A: tze::Authorization>(
|
||||
bundle: &tze::Bundle<A>,
|
||||
input: &TzeInput<'_>,
|
||||
input: &SignableInput<'_>,
|
||||
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();
|
||||
if let SignableInput::Tze {
|
||||
index,
|
||||
precondition,
|
||||
value,
|
||||
} = input
|
||||
{
|
||||
let tzein = &bundle.vin[*index];
|
||||
tzein.prevout.write(&mut ch).unwrap();
|
||||
CompactSize::write(&mut ch, precondition.extension_id.try_into().unwrap()).unwrap();
|
||||
CompactSize::write(&mut ch, precondition.mode.try_into().unwrap()).unwrap();
|
||||
Vector::write(&mut ch, &precondition.payload, |w, e| w.write_u8(*e)).unwrap();
|
||||
ch.write_all(&value.to_i64_le_bytes()).unwrap();
|
||||
}
|
||||
let per_input_digest = ch.finalize();
|
||||
|
||||
TzeDigests {
|
||||
|
@ -117,66 +161,38 @@ fn tze_input_sigdigests<A: tze::Authorization>(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn v5_signature_hash<A: Authorization>(
|
||||
/// Implements the [Signature Digest section of ZIP 244](https://zips.z.cash/zip-0244#signature-digest)
|
||||
pub fn v5_signature_hash<
|
||||
TA: TransparentAuthorizingContext,
|
||||
A: Authorization<TransparentAuth = TA>,
|
||||
>(
|
||||
tx: &TransactionData<A>,
|
||||
hash_type: u32,
|
||||
signable_input: &SignableInput<'_>,
|
||||
txid_parts: &TxDigests<Blake2bHash>,
|
||||
) -> 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!("Transaction has no transparent inputs to sign.")
|
||||
}
|
||||
}
|
||||
to_hash(
|
||||
tx.version,
|
||||
tx.consensus_branch_id,
|
||||
txid_parts.header_digest,
|
||||
if let Some(bundle) = &tx.transparent_bundle {
|
||||
transparent_sig_digest(
|
||||
txid_parts
|
||||
.transparent_digests
|
||||
.as_ref()
|
||||
.expect("Transparent txid digests are missing."),
|
||||
&bundle,
|
||||
signable_input,
|
||||
)
|
||||
} else {
|
||||
hash_transparent_txid_data(None)
|
||||
},
|
||||
txid_parts.sapling_digest,
|
||||
txid_parts.orchard_digest,
|
||||
#[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!("Transaction has no TZE inputs to sign.")
|
||||
}
|
||||
}
|
||||
}
|
||||
tx.tze_bundle
|
||||
.as_ref()
|
||||
.zip(txid_parts.tze_digests.as_ref())
|
||||
.map(|(bundle, tze_digests)| tze_input_sigdigests(bundle, signable_input, tze_digests))
|
||||
.as_ref(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use blake2b_simd::Hash as Blake2bHash;
|
||||
use std::ops::Deref;
|
||||
|
||||
use proptest::prelude::*;
|
||||
|
@ -6,14 +7,22 @@ use crate::{consensus::BranchId, legacy::Script};
|
|||
|
||||
use super::{
|
||||
components::Amount,
|
||||
sighash::{SignableInput, SIGHASH_ALL, SIGHASH_ANYONECANPAY, SIGHASH_NONE, SIGHASH_SINGLE},
|
||||
sapling,
|
||||
sighash::{
|
||||
SignableInput, TransparentAuthorizingContext, SIGHASH_ALL, SIGHASH_ANYONECANPAY,
|
||||
SIGHASH_NONE, SIGHASH_SINGLE,
|
||||
},
|
||||
sighash_v4::v4_signature_hash,
|
||||
sighash_v5::v5_signature_hash,
|
||||
testing::arb_tx,
|
||||
transparent::{self},
|
||||
txid::TxIdDigester,
|
||||
Transaction,
|
||||
Authorization, Transaction, TransactionData, TxDigests, TxIn,
|
||||
};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use super::components::tze;
|
||||
|
||||
#[test]
|
||||
fn tx_read_write() {
|
||||
let data = &self::data::tx_read_write::TX_READ_WRITE;
|
||||
|
@ -120,16 +129,18 @@ fn zip_0143() {
|
|||
for tv in self::data::zip_0143::make_test_vectors() {
|
||||
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,
|
||||
&tv.script_code,
|
||||
Amount::from_nonnegative_i64(tv.amount).unwrap(),
|
||||
),
|
||||
Some(n) => SignableInput::Transparent {
|
||||
hash_type: tv.hash_type as u8,
|
||||
index: n as usize,
|
||||
script_code: &tv.script_code,
|
||||
script_pubkey: &tv.script_code,
|
||||
value: Amount::from_nonnegative_i64(tv.amount).unwrap(),
|
||||
},
|
||||
_ => SignableInput::Shielded,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
v4_signature_hash(tx.deref(), tv.hash_type, &signable_input).as_ref(),
|
||||
v4_signature_hash(tx.deref(), &signable_input).as_ref(),
|
||||
tv.sighash
|
||||
);
|
||||
}
|
||||
|
@ -140,98 +151,190 @@ fn zip_0243() {
|
|||
for tv in self::data::zip_0243::make_test_vectors() {
|
||||
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,
|
||||
&tv.script_code,
|
||||
Amount::from_nonnegative_i64(tv.amount).unwrap(),
|
||||
),
|
||||
Some(n) => SignableInput::Transparent {
|
||||
hash_type: tv.hash_type as u8,
|
||||
index: n as usize,
|
||||
script_code: &tv.script_code,
|
||||
script_pubkey: &tv.script_code,
|
||||
value: Amount::from_nonnegative_i64(tv.amount).unwrap(),
|
||||
},
|
||||
_ => SignableInput::Shielded,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
v4_signature_hash(tx.deref(), tv.hash_type, &signable_input).as_ref(),
|
||||
v4_signature_hash(tx.deref(), &signable_input).as_ref(),
|
||||
tv.sighash
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestTransparentAuth {
|
||||
input_amounts: Vec<Amount>,
|
||||
input_scriptpubkeys: Vec<Script>,
|
||||
}
|
||||
|
||||
impl transparent::Authorization for TestTransparentAuth {
|
||||
type ScriptSig = Script;
|
||||
}
|
||||
|
||||
impl TransparentAuthorizingContext for TestTransparentAuth {
|
||||
fn input_amounts(&self) -> Vec<Amount> {
|
||||
self.input_amounts.clone()
|
||||
}
|
||||
|
||||
fn input_scriptpubkeys(&self) -> Vec<Script> {
|
||||
self.input_scriptpubkeys.clone()
|
||||
}
|
||||
}
|
||||
|
||||
struct TestUnauthorized;
|
||||
|
||||
impl Authorization for TestUnauthorized {
|
||||
type TransparentAuth = TestTransparentAuth;
|
||||
type SaplingAuth = sapling::Authorized;
|
||||
type OrchardAuth = orchard::bundle::Authorized;
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
type TzeAuth = tze::Authorized;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zip_0244() {
|
||||
for tv in self::data::zip_0244::make_test_vectors() {
|
||||
fn to_test_txdata(
|
||||
tv: &self::data::zip_0244::TestVector,
|
||||
) -> (TransactionData<TestUnauthorized>, TxDigests<Blake2bHash>) {
|
||||
let tx = Transaction::read(&tv.tx[..], BranchId::Nu5).unwrap();
|
||||
|
||||
assert_eq!(tx.txid.as_ref(), &tv.txid);
|
||||
assert_eq!(tx.auth_commitment().as_ref(), &tv.auth_digest);
|
||||
|
||||
let txid_parts = tx.deref().digest(TxIdDigester);
|
||||
match tv.transparent_input {
|
||||
Some(n) => {
|
||||
let script = Script(tv.script_code.unwrap());
|
||||
let signable_input = SignableInput::transparent(
|
||||
n as usize,
|
||||
&script,
|
||||
Amount::from_nonnegative_i64(tv.amount.unwrap()).unwrap(),
|
||||
);
|
||||
let txdata = tx.deref();
|
||||
|
||||
assert_eq!(
|
||||
v5_signature_hash(tx.deref(), SIGHASH_ALL, &signable_input, &txid_parts)
|
||||
.as_ref(),
|
||||
&tv.sighash_all
|
||||
);
|
||||
let input_amounts = tv
|
||||
.amounts
|
||||
.iter()
|
||||
.map(|amount| Amount::from_nonnegative_i64(*amount).unwrap())
|
||||
.collect();
|
||||
let input_scriptpubkeys = tv
|
||||
.script_pubkeys
|
||||
.iter()
|
||||
.map(|s| Script(s.clone()))
|
||||
.collect();
|
||||
|
||||
assert_eq!(
|
||||
v5_signature_hash(tx.deref(), SIGHASH_NONE, &signable_input, &txid_parts)
|
||||
.as_ref(),
|
||||
&tv.sighash_none.unwrap()
|
||||
);
|
||||
let test_bundle = txdata
|
||||
.transparent_bundle
|
||||
.as_ref()
|
||||
.map(|b| transparent::Bundle {
|
||||
// we have to do this map/clone to make the types line up, since the
|
||||
// Authorization::ScriptSig type is bound to transparent::Authorized, and we need
|
||||
// it to be bound to TestTransparentAuth.
|
||||
vin: b
|
||||
.vin
|
||||
.iter()
|
||||
.map(|vin| TxIn {
|
||||
prevout: vin.prevout.clone(),
|
||||
script_sig: vin.script_sig.clone(),
|
||||
sequence: vin.sequence,
|
||||
})
|
||||
.collect(),
|
||||
vout: b.vout.clone(),
|
||||
authorization: TestTransparentAuth {
|
||||
input_amounts,
|
||||
input_scriptpubkeys,
|
||||
},
|
||||
});
|
||||
|
||||
(
|
||||
TransactionData::from_parts(
|
||||
txdata.version(),
|
||||
txdata.consensus_branch_id(),
|
||||
txdata.lock_time(),
|
||||
txdata.expiry_height(),
|
||||
test_bundle,
|
||||
txdata.sprout_bundle().cloned(),
|
||||
txdata.sapling_bundle().cloned(),
|
||||
txdata.orchard_bundle().cloned(),
|
||||
#[cfg(feature = "zfuture")]
|
||||
txdata.tze_bundle().cloned(),
|
||||
),
|
||||
txdata.digest(TxIdDigester),
|
||||
)
|
||||
}
|
||||
|
||||
for tv in self::data::zip_0244::make_test_vectors() {
|
||||
let (txdata, txid_parts) = to_test_txdata(&tv);
|
||||
|
||||
if let Some(index) = tv.transparent_input {
|
||||
let bundle = txdata.transparent_bundle().unwrap();
|
||||
let value = bundle.authorization.input_amounts[index];
|
||||
let script_pubkey = &bundle.authorization.input_scriptpubkeys[index];
|
||||
let signable_input = |hash_type| SignableInput::Transparent {
|
||||
hash_type,
|
||||
index,
|
||||
script_code: script_pubkey,
|
||||
script_pubkey,
|
||||
value,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
v5_signature_hash(&txdata, &signable_input(SIGHASH_ALL), &txid_parts).as_ref(),
|
||||
&tv.sighash_all.unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
v5_signature_hash(&txdata, &signable_input(SIGHASH_NONE), &txid_parts).as_ref(),
|
||||
&tv.sighash_none.unwrap()
|
||||
);
|
||||
|
||||
if index < bundle.vout.len() {
|
||||
assert_eq!(
|
||||
v5_signature_hash(tx.deref(), SIGHASH_SINGLE, &signable_input, &txid_parts)
|
||||
v5_signature_hash(&txdata, &signable_input(SIGHASH_SINGLE), &txid_parts)
|
||||
.as_ref(),
|
||||
&tv.sighash_single.unwrap()
|
||||
);
|
||||
} else {
|
||||
assert_eq!(tv.sighash_single, None);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
v5_signature_hash(
|
||||
&txdata,
|
||||
&signable_input(SIGHASH_ALL | SIGHASH_ANYONECANPAY),
|
||||
&txid_parts,
|
||||
)
|
||||
.as_ref(),
|
||||
&tv.sighash_all_anyone.unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
v5_signature_hash(
|
||||
&txdata,
|
||||
&signable_input(SIGHASH_NONE | SIGHASH_ANYONECANPAY),
|
||||
&txid_parts,
|
||||
)
|
||||
.as_ref(),
|
||||
&tv.sighash_none_anyone.unwrap()
|
||||
);
|
||||
|
||||
if index < bundle.vout.len() {
|
||||
assert_eq!(
|
||||
v5_signature_hash(
|
||||
tx.deref(),
|
||||
SIGHASH_ALL | SIGHASH_ANYONECANPAY,
|
||||
&signable_input,
|
||||
&txid_parts,
|
||||
)
|
||||
.as_ref(),
|
||||
&tv.sighash_all_anyone.unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
v5_signature_hash(
|
||||
tx.deref(),
|
||||
SIGHASH_NONE | SIGHASH_ANYONECANPAY,
|
||||
&signable_input,
|
||||
&txid_parts,
|
||||
)
|
||||
.as_ref(),
|
||||
&tv.sighash_none_anyone.unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
v5_signature_hash(
|
||||
tx.deref(),
|
||||
SIGHASH_SINGLE | SIGHASH_ANYONECANPAY,
|
||||
&signable_input,
|
||||
&txdata,
|
||||
&signable_input(SIGHASH_SINGLE | SIGHASH_ANYONECANPAY),
|
||||
&txid_parts,
|
||||
)
|
||||
.as_ref(),
|
||||
&tv.sighash_single_anyone.unwrap()
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
let signable_input = SignableInput::Shielded;
|
||||
|
||||
assert_eq!(
|
||||
v5_signature_hash(tx.deref(), SIGHASH_ALL, &signable_input, &txid_parts)
|
||||
.as_ref(),
|
||||
tv.sighash_all
|
||||
);
|
||||
} else {
|
||||
assert_eq!(tv.sighash_single_anyone, None);
|
||||
}
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
v5_signature_hash(&txdata, &SignableInput::Shielded, &txid_parts).as_ref(),
|
||||
tv.sighash_shielded
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -30,7 +30,7 @@ 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";
|
||||
pub(crate) const ZCASH_TRANSPARENT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdTranspaHash";
|
||||
const ZCASH_SAPLING_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSaplingHash";
|
||||
#[cfg(feature = "zfuture")]
|
||||
const ZCASH_TZE_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdTZE____Hash";
|
||||
|
@ -130,6 +130,8 @@ pub(crate) fn hash_tze_outputs(tze_outputs: &[TzeOut]) -> Blake2bHash {
|
|||
h.finalize()
|
||||
}
|
||||
|
||||
/// Implements [ZIP 244 section T.3a](https://zips.z.cash/zip-0244#t-3a-sapling-spends-digest)
|
||||
///
|
||||
/// 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
|
||||
|
@ -159,6 +161,8 @@ pub(crate) fn hash_sapling_spends<A: sapling::Authorization>(
|
|||
h.finalize()
|
||||
}
|
||||
|
||||
/// Implements [ZIP 244 section T.3b](https://zips.z.cash/zip-0244#t-3b-sapling-outputs-digest)
|
||||
///
|
||||
/// 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
|
||||
|
@ -196,10 +200,9 @@ fn transparent_digests<A: transparent::Authorization>(
|
|||
bundle: &transparent::Bundle<A>,
|
||||
) -> TransparentDigests<Blake2bHash> {
|
||||
TransparentDigests {
|
||||
prevout_digest: transparent_prevout_hash(&bundle.vin),
|
||||
prevouts_digest: transparent_prevout_hash(&bundle.vin),
|
||||
sequence_digest: transparent_sequence_hash(&bundle.vin),
|
||||
outputs_digest: transparent_outputs_hash(&bundle.vout),
|
||||
per_input_digest: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -213,6 +216,7 @@ fn tze_digests<A: tze::Authorization>(bundle: &tze::Bundle<A>) -> TzeDigests<Bla
|
|||
}
|
||||
}
|
||||
|
||||
/// Implements [ZIP 244 section T.1](https://zips.z.cash/zip-0244#t-1-header-digest)
|
||||
fn hash_header_txid_data(
|
||||
version: TxVersion,
|
||||
// we commit to the consensus branch ID with the header
|
||||
|
@ -233,19 +237,20 @@ fn hash_header_txid_data(
|
|||
h.finalize()
|
||||
}
|
||||
|
||||
fn hash_transparent_txid_data(t_digests: Option<&TransparentDigests<Blake2bHash>>) -> Blake2bHash {
|
||||
/// Implements [ZIP 244 section T.2](https://zips.z.cash/zip-0244#t-2-transparent-digest)
|
||||
pub(crate) 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.prevouts_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()
|
||||
}
|
||||
|
||||
/// Implements [ZIP 244 section T.3](https://zips.z.cash/zip-0244#t-3-sapling-digest)
|
||||
fn hash_sapling_txid_data<A: sapling::Authorization>(bundle: &sapling::Bundle<A>) -> Blake2bHash {
|
||||
let mut h = hasher(ZCASH_SAPLING_HASH_PERSONALIZATION);
|
||||
if !(bundle.shielded_spends.is_empty() && bundle.shielded_outputs.is_empty()) {
|
||||
|
@ -278,13 +283,15 @@ fn hash_tze_txid_data(tze_digests: Option<&TzeDigests<Blake2bHash>>) -> Blake2bH
|
|||
h.finalize()
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// This implements the [TxId Digest section of ZIP 244](https://zips.z.cash/zip-0244#txid-digest)
|
||||
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>>;
|
||||
|
@ -355,7 +362,7 @@ pub(crate) fn to_hash(
|
|||
_txversion: TxVersion,
|
||||
consensus_branch_id: BranchId,
|
||||
header_digest: Blake2bHash,
|
||||
transparent_digests: Option<&TransparentDigests<Blake2bHash>>,
|
||||
transparent_digest: Blake2bHash,
|
||||
sapling_digest: Option<Blake2bHash>,
|
||||
orchard_digest: Option<Blake2bHash>,
|
||||
#[cfg(feature = "zfuture")] tze_digests: Option<&TzeDigests<Blake2bHash>>,
|
||||
|
@ -368,8 +375,7 @@ pub(crate) fn to_hash(
|
|||
|
||||
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(transparent_digest.as_bytes()).unwrap();
|
||||
h.write_all(
|
||||
sapling_digest
|
||||
.unwrap_or_else(hash_sapling_txid_empty)
|
||||
|
@ -401,7 +407,7 @@ pub fn to_txid(
|
|||
txversion,
|
||||
consensus_branch_id,
|
||||
digests.header_digest,
|
||||
digests.transparent_digests.as_ref(),
|
||||
hash_transparent_txid_data(digests.transparent_digests.as_ref()),
|
||||
digests.sapling_digest,
|
||||
digests.orchard_digest,
|
||||
#[cfg(feature = "zfuture")]
|
||||
|
|
Loading…
Reference in New Issue