Backport V5 txid generation for transactions that don't involve Orchard.

This commit is contained in:
Kris Nuttycombe 2022-05-19 17:09:57 -06:00
parent 5f229e7121
commit 1d9dd128b9
3 changed files with 308 additions and 8 deletions

View File

@ -406,6 +406,10 @@ pub mod testing {
Ok(Vec::new())
}
fn get_all_nullifiers(&self) -> Result<Vec<(AccountId, Nullifier)>, Self::Error> {
Ok(Vec::new())
}
fn get_unspent_sapling_notes(
&self,
_account: AccountId,

View File

@ -12,11 +12,15 @@ use crate::{
serialize::{Array, CompactSize, Vector},
};
use self::util::sha256d::{HashReader, HashWriter};
use self::{
txid::to_txid,
util::sha256d::{HashReader, HashWriter},
};
pub mod builder;
pub mod components;
mod sighash;
mod txid;
pub mod util;
#[cfg(test)]
@ -486,7 +490,8 @@ impl Transaction {
}
fn read_v5<R: Read>(mut reader: R, version: TxVersion) -> io::Result<Self> {
let (_, lock_time, expiry_height) = Self::read_v5_header_fragment(&mut reader)?;
let (consensus_branch_id, lock_time, expiry_height) =
Self::read_v5_header_fragment(&mut reader)?;
let vin = Vector::read(&mut reader, TxIn::read)?;
let vout = Vector::read(&mut reader, TxOut::read)?;
let (value_balance, shielded_spends, shielded_outputs, binding_sig) =
@ -524,12 +529,7 @@ impl Transaction {
binding_sig,
};
let txid = TxId([0u8; 32]);
//let txid = to_txid(
// data.version,
// data.consensus_branch_id,
// &data.digest(TxIdDigester),
//);
let txid = to_txid(&data, consensus_branch_id);
Ok(Transaction { txid, data })
}

View File

@ -0,0 +1,296 @@
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 crate::consensus::{BlockHeight, BranchId};
use super::{
components::{
sapling::{OutputDescription, SpendDescription},
transparent::{TxIn, TxOut},
},
TransactionData, TxId, TxVersion,
};
#[cfg(feature = "zfuture")]
use super::components::tze::{TzeIn, TzeOut};
/// 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";
pub(crate) 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";
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(vin: &[TxIn]) -> 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(vin: &[TxIn]) -> 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(tze_inputs: &[TzeIn]) -> 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()
}
/// 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
///
/// Then, hash these together personalized by ZCASH_SAPLING_SPENDS_HASH_PERSONALIZATION
pub(crate) fn hash_sapling_spends(shielded_spends: &[SpendDescription]) -> Blake2bHash {
let mut h = hasher(ZCASH_SAPLING_SPENDS_HASH_PERSONALIZATION);
if !shielded_spends.is_empty() {
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.0).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 compact_digest = ch.finalize();
h.write_all(compact_digest.as_bytes()).unwrap();
let noncompact_digest = nh.finalize();
h.write_all(noncompact_digest.as_bytes()).unwrap();
}
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
/// * \[(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(shielded_outputs: &[OutputDescription]) -> Blake2bHash {
let mut h = hasher(ZCASH_SAPLING_OUTPUTS_HASH_PERSONALIZATION);
if !shielded_outputs.is_empty() {
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();
}
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()
}
/// 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
consensus_branch_id: BranchId,
lock_time: u32,
expiry_height: BlockHeight,
) -> Blake2bHash {
let mut h = hasher(ZCASH_HEADERS_HASH_PERSONALIZATION);
h.write_u32::<LittleEndian>(version.header()).unwrap();
h.write_u32::<LittleEndian>(version.version_group_id())
.unwrap();
h.write_u32::<LittleEndian>(consensus_branch_id.into())
.unwrap();
h.write_u32::<LittleEndian>(lock_time).unwrap();
h.write_u32::<LittleEndian>(expiry_height.into()).unwrap();
h.finalize()
}
/// Implements [ZIP 244 section T.2](https://zips.z.cash/zip-0244#t-2-transparent-digest)
pub(crate) fn hash_transparent_txid_data(txdata: &TransactionData) -> Blake2bHash {
let mut h = hasher(ZCASH_TRANSPARENT_HASH_PERSONALIZATION);
if !txdata.vin.is_empty() || !txdata.vout.is_empty() {
h.write_all(transparent_prevout_hash(&txdata.vin).as_bytes())
.unwrap();
h.write_all(transparent_sequence_hash(&txdata.vin).as_bytes())
.unwrap();
h.write_all(transparent_outputs_hash(&txdata.vout).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(txdata: &TransactionData) -> Blake2bHash {
let mut h = hasher(ZCASH_SAPLING_HASH_PERSONALIZATION);
if !(txdata.shielded_spends.is_empty() && txdata.shielded_outputs.is_empty()) {
h.write_all(hash_sapling_spends(&txdata.shielded_spends).as_bytes())
.unwrap();
h.write_all(hash_sapling_outputs(&txdata.shielded_outputs).as_bytes())
.unwrap();
h.write_all(&txdata.value_balance.to_i64_le_bytes())
.unwrap();
}
h.finalize()
}
#[cfg(feature = "zfuture")]
fn hash_tze_txid_data(txdata: &TransactionData) -> Blake2bHash {
let mut h = hasher(ZCASH_TZE_HASH_PERSONALIZATION);
if !(txdata.tze_inputs.is_empty() && txdata.tze_outputs.is_empty()) {
h.write_all(hash_tze_inputs(&txdata.tze_inputs).as_bytes())
.unwrap();
h.write_all(hash_tze_outputs(&txdata.tze_outputs).as_bytes())
.unwrap();
}
h.finalize()
}
pub(crate) fn to_hash(
consensus_branch_id: BranchId,
header_digest: Blake2bHash,
transparent_digest: Blake2bHash,
sapling_digest: Blake2bHash,
#[cfg(feature = "zfuture")] tze_digest: Option<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(transparent_digest.as_bytes()).unwrap();
h.write_all(sapling_digest.as_bytes()).unwrap();
h.write_all(
hasher(ZCASH_ORCHARD_HASH_PERSONALIZATION)
.finalize()
.as_bytes(),
)
.unwrap();
#[cfg(feature = "zfuture")]
if let Some(digest) = tze_digest {
h.write_all(digest.as_bytes()).unwrap();
}
h.finalize()
}
pub fn to_txid(txdata: &TransactionData, consensus_branch_id: BranchId) -> TxId {
let txid_digest = to_hash(
consensus_branch_id,
hash_header_txid_data(
txdata.version,
consensus_branch_id,
txdata.lock_time,
txdata.expiry_height,
),
hash_transparent_txid_data(&txdata),
hash_sapling_txid_data(&txdata),
#[cfg(feature = "zfuture")]
if txdata.version.has_tze() {
Some(hash_tze_txid_data(&txdata))
} else {
None
},
);
TxId(<[u8; 32]>::try_from(txid_digest.as_bytes()).unwrap())
}