Skip to content

Commit

Permalink
Implement ZIP-244 authorizing data commitment (auth_digest)
Browse files Browse the repository at this point in the history
  • Loading branch information
conradoplg committed Aug 9, 2021
1 parent f09f2a9 commit 3f61277
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 11 deletions.
9 changes: 9 additions & 0 deletions zebra-chain/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use serde::{Deserialize, Serialize};

use crate::{
amount::NegativeAllowed,
block::merkle::AuthDataRoot,
fmt::DisplayToDebug,
orchard,
parameters::{Network, NetworkUpgrade},
Expand Down Expand Up @@ -198,6 +199,14 @@ impl Block {

Ok(transaction_value_balance_total.neg())
}

/// Compute the root of the authorizing data Merkle tree,
/// as defined in [ZIP-244].
///
/// [ZIP-244]: https://zips.z.cash/zip-0244
pub fn auth_data_root(&self) -> AuthDataRoot {
self.transactions.iter().collect::<AuthDataRoot>()
}
}

impl<'a> From<&'a Block> for Hash {
Expand Down
92 changes: 82 additions & 10 deletions zebra-chain/src/block/merkle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ use crate::transaction::{self, Transaction};
/// The root of the Bitcoin-inherited transaction Merkle tree, binding the
/// block header to the transactions in the block.
///
/// Note: for V5-onward transactions it does not bind to authorizing data
/// (signature and proofs) which makes it non-malleable [ZIP-244].
///
/// Note that because of a flaw in Bitcoin's design, the `merkle_root` does
/// not always precisely bind the contents of the block (CVE-2012-2459). It
/// is sometimes possible for an attacker to create multiple distinct sets of
Expand Down Expand Up @@ -61,6 +64,8 @@ use crate::transaction::{self, Transaction};
/// This vulnerability does not apply to Zebra, because it does not store invalid
/// data on disk, and because it does not permanently fail blocks or use an
/// aggressive anti-DoS mechanism.
///
/// [ZIP-244]: https://zips.z.cash/zip-0244
#[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
pub struct Root(pub [u8; 32]);
Expand All @@ -78,6 +83,23 @@ fn hash(h1: &[u8; 32], h2: &[u8; 32]) -> [u8; 32] {
w.finish()
}

/// Compute the root of a Merke tree as used in Bitcoin.
/// `hashes` must contain the hashes of the tree leaves.
/// The root is written to the the first element of the input vector.
/// See [`Root`] for an important disclaimer.
fn root(hashes: &mut Vec<[u8; 32]>) {
while hashes.len() > 1 {
*hashes = hashes
.chunks(2)
.map(|chunk| match chunk {
[h1, h2] => hash(h1, h2),
[h1] => hash(h1, h1),
_ => unreachable!("chunks(2)"),
})
.collect();
}
}

impl<T> std::iter::FromIterator<T> for Root
where
T: std::convert::AsRef<Transaction>,
Expand All @@ -99,18 +121,59 @@ impl std::iter::FromIterator<transaction::Hash> for Root {
I: IntoIterator<Item = transaction::Hash>,
{
let mut hashes = hashes.into_iter().map(|hash| hash.0).collect::<Vec<_>>();
root(&mut hashes);
Self(hashes[0])
}
}

while hashes.len() > 1 {
hashes = hashes
.chunks(2)
.map(|chunk| match chunk {
[h1, h2] => hash(h1, h2),
[h1] => hash(h1, h1),
_ => unreachable!("chunks(2)"),
})
.collect();
}
/// The root of the authorizing data Merkle tree, binding the
/// block header to the authorizing data of the block (signatures, proofs)
/// as defined in [ZIP-244].
///
/// See [`Root`] for an important disclaimer.
///
/// [ZIP-244]: https://zips.z.cash/zip-0244
#[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
pub struct AuthDataRoot(pub [u8; 32]);

impl fmt::Debug for AuthDataRoot {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("AuthRoot")
.field(&hex::encode(&self.0))
.finish()
}
}

impl<T> std::iter::FromIterator<T> for AuthDataRoot
where
T: std::convert::AsRef<Transaction>,
{
fn from_iter<I>(transactions: I) -> Self
where
I: IntoIterator<Item = T>,
{
// > For transaction versions before v5, a placeholder value consisting
// > of 32 bytes of 0xFF is used in place of the authorizing data commitment.
// > This is only used in the tree committed to by hashAuthDataRoot.
transactions
.into_iter()
.map(|tx| {
tx.as_ref()
.auth_digest()
.unwrap_or_else(|| transaction::AuthDigest([0xFF; 32]))
})
.collect()
}
}

impl std::iter::FromIterator<transaction::AuthDigest> for AuthDataRoot {
fn from_iter<I>(hashes: I) -> Self
where
I: IntoIterator<Item = transaction::AuthDigest>,
{
let mut hashes = hashes.into_iter().map(|hash| hash.0).collect::<Vec<_>>();
root(&mut hashes);
Self(hashes[0])
}
}
Expand Down Expand Up @@ -140,4 +203,13 @@ mod tests {
);
}
}

#[test]
fn auth_digest() {
for block_bytes in zebra_test::vectors::BLOCKS.iter() {
let block = Block::zcash_deserialize(&**block_bytes).unwrap();
let _merkle_root = block.transactions.iter().collect::<AuthDataRoot>();
// No test vectors for now, so just check it works
}
}
}
22 changes: 21 additions & 1 deletion zebra-chain/src/primitives/zcash_primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
amount::{Amount, NonNegative},
parameters::NetworkUpgrade,
serialization::ZcashSerialize,
transaction::{HashType, SigHash, Transaction},
transaction::{AuthDigest, HashType, SigHash, Transaction},
transparent::{self, Script},
};

Expand Down Expand Up @@ -124,3 +124,23 @@ pub(crate) fn sighash(
.as_ref(),
)
}

/// Compute the authorizing data commitment of this transaction as specified
/// in [ZIP-244].
///
/// # Panics
///
/// If passed a pre-v5 transaction.
///
/// [ZIP-244]: https://zips.z.cash/zip-0244.
pub(crate) fn auth_digest(trans: &Transaction) -> AuthDigest {
let alt_tx: zcash_primitives::transaction::Transaction = trans
.try_into()
.expect("zcash_primitives and Zebra transaction formats must be compatible");
let digest_bytes: [u8; 32] = alt_tx
.auth_commitment()
.as_ref()
.try_into()
.expect("digest has the correct size");
AuthDigest(digest_bytes)
}
18 changes: 18 additions & 0 deletions zebra-chain/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use halo2::pasta::pallas;
use serde::{Deserialize, Serialize};

mod auth_digest;
mod hash;
mod joinsplit;
mod lock_time;
Expand All @@ -16,6 +17,7 @@ pub mod arbitrary;
#[cfg(test)]
mod tests;

pub use auth_digest::AuthDigest;
pub use hash::Hash;
pub use joinsplit::JoinSplitData;
pub use lock_time::LockTime;
Expand Down Expand Up @@ -160,6 +162,22 @@ impl Transaction {
sighash::SigHasher::new(self, hash_type, network_upgrade, input).sighash()
}

/// Compute the authorizing data commitment of this transaction as specified
/// in [ZIP-244].
///
/// Returns None for pre-v5 transactions.
///
/// [ZIP-244]: https://zips.z.cash/zip-0244.
pub fn auth_digest(&self) -> Option<AuthDigest> {
match self {
Transaction::V1 { .. }
| Transaction::V2 { .. }
| Transaction::V3 { .. }
| Transaction::V4 { .. } => None,
Transaction::V5 { .. } => Some(AuthDigest::from(self)),
}
}

// other properties

/// Does this transaction have transparent or shielded inputs?
Expand Down
20 changes: 20 additions & 0 deletions zebra-chain/src/transaction/auth_digest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use crate::primitives::zcash_primitives::auth_digest;

use super::Transaction;

/// An authorizing data commitment hash as specified in [ZIP-244].
///
/// [ZIP-244]: https://zips.z.cash/zip-0244..
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct AuthDigest(pub [u8; 32]);

impl<'a> From<&'a Transaction> for AuthDigest {
/// Computes the authorizing data commitment for a transaction.
///
/// # Panics
///
/// If passed a pre-v5 transaction.
fn from(transaction: &'a Transaction) -> Self {
auth_digest(transaction)
}
}
18 changes: 18 additions & 0 deletions zebra-chain/src/transaction/tests/vectors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,24 @@ fn zip244_txid() -> Result<()> {
Ok(())
}

#[test]
fn zip244_auth_digest() -> Result<()> {
zebra_test::init();

for test in zip0244::TEST_VECTORS.iter() {
let transaction = test.tx.zcash_deserialize_into::<Transaction>()?;
let auth_digest = transaction.auth_digest();
assert_eq!(
auth_digest
.expect("must have auth_digest since it must be a V5 transaction")
.0,
test.auth_digest
);
}

Ok(())
}

#[test]
fn test_vec143_1() -> Result<()> {
zebra_test::init();
Expand Down

0 comments on commit 3f61277

Please sign in to comment.