Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ZIP-244 authorizing data commitment (auth_digest) #2547

Merged
merged 6 commits into from
Aug 13, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>()
conradoplg marked this conversation as resolved.
Show resolved Hide resolved
}
}

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].
///
conradoplg marked this conversation as resolved.
Show resolved Hide resolved
/// 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.
dconnolly marked this conversation as resolved.
Show resolved Hide resolved
/// `hashes` must contain the hashes of the tree leaves.
conradoplg marked this conversation as resolved.
Show resolved Hide resolved
/// 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),
dconnolly marked this conversation as resolved.
Show resolved Hide resolved
dconnolly marked this conversation as resolved.
Show resolved Hide resolved
_ => unreachable!("chunks(2)"),
})
.collect();
}
}
teor2345 marked this conversation as resolved.
Show resolved Hide resolved

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])
dconnolly marked this conversation as resolved.
Show resolved Hide resolved
}
}

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].
conradoplg marked this conversation as resolved.
Show resolved Hide resolved
///
/// 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]);
dconnolly marked this conversation as resolved.
Show resolved Hide resolved
conradoplg marked this conversation as resolved.
Show resolved Hide resolved

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.
conradoplg marked this conversation as resolved.
Show resolved Hide resolved
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);
dconnolly marked this conversation as resolved.
Show resolved Hide resolved
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>();
conradoplg marked this conversation as resolved.
Show resolved Hide resolved
// No test vectors for now, so just check it works
conradoplg marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
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.
conradoplg marked this conversation as resolved.
Show resolved Hide resolved
///
/// [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
conradoplg marked this conversation as resolved.
Show resolved Hide resolved
.auth_commitment()
.as_ref()
.try_into()
.expect("digest has the correct size");
AuthDigest(digest_bytes)
conradoplg marked this conversation as resolved.
Show resolved Hide resolved
}
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,
dconnolly marked this conversation as resolved.
Show resolved Hide resolved
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]);
dconnolly marked this conversation as resolved.
Show resolved Hide resolved
conradoplg marked this conversation as resolved.
Show resolved Hide resolved

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(())
}
conradoplg marked this conversation as resolved.
Show resolved Hide resolved

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