Sprout note commitment trees (#3051)

* Implement incremental note commitment Merkle tree for Sprout

* Add tests for Sprout note commitment tree

* Remove the `Arbitrary` attribute

* Reverse the vector of empty roots

* Add more tests

* Refactor rustdoc

Co-authored-by: Deirdre Connolly <deirdre@zfnd.org>

* Refactor rustdoc

Co-authored-by: Deirdre Connolly <deirdre@zfnd.org>

* rustdoc

* Rustdoc

* rustdoc links

* Oops, need the trait in scope to use it

* Avoid accessing the wrapped hash directly

Co-authored-by: Deirdre Connolly <deirdre@zfnd.org>

* rustfmt

* Add typing

* Avoid accessing the wrapped hash directly

* Implement incremental note commitment Merkle tree for Sprout

* Add tests for Sprout note commitment tree

* Remove the `Arbitrary` attribute

* Reverse the vector of empty roots

* Add more tests

* Refactor rustdoc

Co-authored-by: Deirdre Connolly <deirdre@zfnd.org>

* Refactor rustdoc

Co-authored-by: Deirdre Connolly <deirdre@zfnd.org>

* rustdoc

* Rustdoc

* rustdoc links

* Oops, need the trait in scope to use it

* Avoid accessing the wrapped hash directly

Co-authored-by: Deirdre Connolly <deirdre@zfnd.org>

* rustfmt

* Add typing

* Avoid accessing the wrapped hash directly

* Add Overwinter final roots (test vectors)

* Test sprout note commitments trees on Overwinter blocks

* Add new test vectors

* Finish the tests for the note commitment trees

* Make the wrapped hash in `Root` private

Co-authored-by: Deirdre Connolly <deirdre@zfnd.org>
Co-authored-by: Deirdre Connolly <durumcrustulum@gmail.com>
This commit is contained in:
Marek 2021-11-19 00:05:52 +01:00 committed by GitHub
parent ad81718514
commit 8963007397
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 606 additions and 195 deletions

View File

@ -53,16 +53,16 @@ in terms of speed and resistance to denial of service attacks, for example.
These are some of the advantages or benefits of Zebra: These are some of the advantages or benefits of Zebra:
- Better performance: since it was implemented from scratch in an async, parallelized way, Zebra - Better performance: since it was implemented from scratch in an async, parallelized way, Zebra
is currently faster than `zcashd`. is currently faster than `zcashd`.
- Better security: since it is developed in a memory-safe language (Rust), Zebra - Better security: since it is developed in a memory-safe language (Rust), Zebra
is less likely to be affected by memory-safety and correctness security bugs that is less likely to be affected by memory-safety and correctness security bugs that
could compromise the environment where it is run. could compromise the environment where it is run.
- Better governance: with a new node deployment, there will be more developers - Better governance: with a new node deployment, there will be more developers
who can implement different features for the Zcash network. who can implement different features for the Zcash network.
- Dev accessibility: supports more developers, which gives new developers - Dev accessibility: supports more developers, which gives new developers
options for contributing to Zcash protocol development. options for contributing to Zcash protocol development.
- Runtime safety: with an independeny implementation, the detection of consensus bugs - Runtime safety: with an independent implementation, the detection of consensus bugs
can happen quicker, reducing the risk of consensus splits. can happen quicker, reducing the risk of consensus splits.
- Spec safety: with several node implementations, it is much easier to notice - Spec safety: with several node implementations, it is much easier to notice
bugs and ambiguity in protocol specification. bugs and ambiguity in protocol specification.
@ -78,7 +78,7 @@ Every few weeks, we release a new Zebra beta [release](https://github.com/ZcashF
Zebra's network stack is interoperable with `zcashd`, Zebra's network stack is interoperable with `zcashd`,
and Zebra implements all the features required to reach Zcash network consensus. and Zebra implements all the features required to reach Zcash network consensus.
The goals of the beta release series are for Zebra to act as a fully validating Zcash node for The goals of the beta release series are for Zebra to act as a fully validating Zcash node for
all applicable consensus rules as of NU5 activation. all applicable consensus rules as of NU5 activation.
Currently, Zebra does not validate the following Zcash consensus rules: Currently, Zebra does not validate the following Zcash consensus rules:

View File

@ -1 +1,5 @@
//! Sprout tests.
mod preallocate; mod preallocate;
mod test_vectors;
mod tree;

View File

@ -0,0 +1,84 @@
// From https://github.com/zcash/zcash/blob/master/src/zcash/IncrementalMerkleTree.cpp#L439
pub const HEX_EMPTY_ROOTS: [&str; 30] = [
"0000000000000000000000000000000000000000000000000000000000000000",
"da5698be17b9b46962335799779fbeca8ce5d491c0d26243bafef9ea1837a9d8",
"dc766fab492ccf3d1e49d4f374b5235fa56506aac2224d39f943fcd49202974c",
"3f0a406181105968fdaee30679e3273c66b72bf9a7f5debbf3b5a0a26e359f92",
"26b0052694fc42fdff93e6fb5a71d38c3dd7dc5b6ad710eb048c660233137fab",
"0109ecc0722659ff83450b8f7b8846e67b2859f33c30d9b7acd5bf39cae54e31",
"3f909b8ce3d7ffd8a5b30908f605a03b0db85169558ddc1da7bbbcc9b09fd325",
"40460fa6bc692a06f47521a6725a547c028a6a240d8409f165e63cb54da2d23f",
"8c085674249b43da1b9a31a0e820e81e75f342807b03b6b9e64983217bc2b38e",
"a083450c1ba2a3a7be76fad9d13bc37be4bf83bd3e59fc375a36ba62dc620298",
"1ddddabc2caa2de9eff9e18c8c5a39406d7936e889bc16cfabb144f5c0022682",
"c22d8f0b5e4056e5f318ba22091cc07db5694fbeb5e87ef0d7e2c57ca352359e",
"89a434ae1febd7687eceea21d07f20a2512449d08ce2eee55871cdb9d46c1233",
"7333dbffbd11f09247a2b33a013ec4c4342029d851e22ba485d4461851370c15",
"5dad844ab9466b70f745137195ca221b48f346abd145fb5efc23a8b4ba508022",
"507e0dae81cbfbe457fd370ef1ca4201c2b6401083ddab440e4a038dc1e358c4",
"bdcdb3293188c9807d808267018684cfece07ac35a42c00f2c79b4003825305d",
"bab5800972a16c2c22530c66066d0a5867e987bed21a6d5a450b683cf1cfd709",
"11aa0b4ad29b13b057a31619d6500d636cd735cdd07d811ea265ec4bcbbbd058",
"5145b1b055c2df02b95675e3797b91de1b846d25003c0a803d08900728f2cd6a",
"0323f2850bf3444f4b4c5c09a6057ec7169190f45acb9e46984ab3dfcec4f06a",
"671546e26b1da1af754531e26d8a6a51073a57ddd72dc472efb43fcb257cffff",
"bb23a9bba56de57cb284b0d2b01c642cf79c9a5563f0067a21292412145bd78a",
"f30cc836b9f71b4e7ee3c72b1fd253268af9a27e9d7291a23d02821b21ddfd16",
"58a2753dade103cecbcda50b5ebfce31e12d41d5841dcc95620f7b3d50a1b9a1",
"925e6d474a5d8d3004f29da0dd78d30ae3824ce79dfe4934bb29ec3afaf3d521",
"08f279618616bcdd4eadc9c7a9062691a59b43b07e2c1e237f17bd189cd6a8fe",
"c92b32db42f42e2bf0a59df9055be5c669d3242df45357659b75ae2c27a76f50",
"c0db2a74998c50eb7ba6534f6d410efc27c4bb88acb0222c7906ea28a327b511",
"d7c612c817793191a1e68652121876d6b3bde40f4fa52bc314145ce6e5cdd259",
];
// From https://github.com/zcash/zcash/blob/master/src/test/data/merkle_commitments.json
// Byte-reversed from those ones because the original test vectors are
// loaded using uint256S()
pub const COMMITMENTS: [&str; 16] = [
"62fdad9bfbf17c38ea626a9c9b8af8a748e6b4367c8494caf0ca592999e8b6ba",
"68eb35bc5e1ddb80a761718e63a1ecf4d4977ae22cc19fa732b85515b2a4c943",
"836045484077cf6390184ea7cd48b460e2d0f22b2293b69633bb152314a692fb",
"92498a8295ea36d593eaee7cb8b55be3a3e37b8185d3807693184054cd574ae4",
"ff7c360374a6508ae0904c782127ff5dce90918f3ee81cf92ef1b69afb8bf443",
"68c4d0f69d1f18b756c2ee875c14f1c6cd38682e715ded14bf7e3c1c5610e9fc",
"8b16cd3ec44875e4856e30344c0b4a68a6f929a68be5117b225b80926301e7b1",
"50c0b43061c39191c3ec529734328b7f9cafeb6fd162cc49a4495442d9499a2d",
"70ffdd5fa0f3aea18bd4700f1ac2e2e03cf5d4b7b857e8dd93b862a8319b9653",
"d81ef64a0063573d80cd32222d8d04debbe807345ad7af2e9edf0f44bdfaf817",
"8b92a4ec694271fe1b16cc0ea8a433bf19e78eb5ca733cc137f38e5ecb05789b",
"04e963ab731e4aaaaaf931c3c039ea8c9d7904163936e19a8929434da9adeba3",
"be3f6c181f162824191ecf1f78cae3ffb0ddfda671bb93277ce6ebc9201a0912",
"1880967fc8226380a849c63532bba67990f7d0a10e9c90b848f58d634957c6e9",
"c465bb2893cba233351094f259396301c23d73a6cf6f92bc63428a43f0dd8f8e",
"84c834e7cb38d6f08d82f5cf4839b8920185174b11c7af771fd38dd02b206a20",
];
// Calculated by the above implementation for MERKLE_DEPTH = 29 by the
// same code confirmed to produce the test vectors from
// https://github.com/zcash/zcash/blob/master/src/test/data/merkle_roots.json
// when MERKLE_DEPTH = 4.
pub const ROOTS: [&str; 16] = [
"b8e10b6c157be92c43a733e2c9bddb963a2fb9ea80ebcb307acdcc5fc89f1656",
"83a7754b8240699dd1b63bf70cf70db28ffeb74ef87ce2f4dd32c28ae5009f4f",
"c45297124f50dcd3f78eed017afd1e30764cd74cdf0a57751978270fd0721359",
"b61f588fcba9cea79e94376adae1c49583f716d2f20367141f1369a235b95c98",
"a3165c1708f0cc028014b9bf925a81c30091091ca587624de853260cd151b524",
"6bb8c538c550abdd26baa2a7510a4ae50a03dc00e52818b9db3e4ffaa29c1f41",
"e04e4731085ba95e3fa7c8f3d5eb9a56af63363403b783bc68802629c3fe505b",
"c3714ab74d8e3984e8b58a2b4806934d20f6e67d7246cf8f5b2762305294a0ea",
"63657edeead4bc45610b6d5eb80714a0622aad5788119b7d9961453e3aacda21",
"e31b80819221718440c5351525dbb902d60ed16b74865a2528510959a1960077",
"872f13df2e12f5503c39100602930b0f91ea360e5905a9f5ceb45d459efc36b2",
"bdd7105febb3590832e946aa590d07377d1366cf5e7267507efa399dd0febdbc",
"0f45f4adcb846a8bb56833ca0cae96f2fb8747958daa191a46d0f9d93268260a",
"41c6e456e2192ab74f72cb27c444a2734ca8ade5a4788c1bc2546118dda01778",
"8261355fd9bafc52a08d738fed29a859fbe15f2e74a5353954b150be200d0e16",
"90665cb8a43001f0655169952399590cd17f99165587c1dd842eb674fb9f0afe",
];
/// Empty (unused) Sprout note commitment tree leaf node.
///
/// Uncommitted^Sprout = [0]^(l^Sprout_Merkle).
///
/// <https://zips.z.cash/protocol/protocol.pdf#constants>
pub const EMPTY_LEAF: [u8; 32] = [0; 32];

View File

@ -0,0 +1,185 @@
//! Tests for Sprout Note Commitment Trees.
use std::sync::Arc;
use color_eyre::eyre;
use eyre::Result;
use hex::FromHex;
use zebra_test::vectors;
use crate::block::Block;
use crate::parameters::{Network, NetworkUpgrade};
use crate::serialization::ZcashDeserializeInto;
use crate::sprout::commitment::NoteCommitment;
use crate::sprout::tests::test_vectors;
use crate::sprout::tree;
/// Tests if empty roots are generated correctly.
#[test]
fn empty_roots() {
zebra_test::init();
for i in 0..tree::EMPTY_ROOTS.len() {
assert_eq!(
hex::encode(tree::EMPTY_ROOTS[i]),
// The test vector is in reversed order.
test_vectors::HEX_EMPTY_ROOTS[tree::MERKLE_DEPTH - i]
);
}
}
/// Tests if we have the right unused (empty) leaves.
#[test]
fn empty_leaf() {
assert_eq!(
tree::NoteCommitmentTree::uncommitted(),
test_vectors::EMPTY_LEAF
);
}
/// Tests if we can build the tree correctly.
#[test]
fn incremental_roots() {
zebra_test::init();
let mut leaves = vec![];
let mut incremental_tree = tree::NoteCommitmentTree::default();
for (i, cm) in test_vectors::COMMITMENTS.iter().enumerate() {
let bytes = <[u8; 32]>::from_hex(cm).unwrap();
let cm = NoteCommitment::from(bytes);
// Test if we can append a new note commitment to the tree.
let _ = incremental_tree.append(cm);
let incremental_root_hash = incremental_tree.hash();
assert_eq!(hex::encode(incremental_root_hash), test_vectors::ROOTS[i]);
// The root hashes should match.
let incremental_root = incremental_tree.root();
assert_eq!(<[u8; 32]>::from(incremental_root), incremental_root_hash);
// Test if the note commitments are counted correctly.
assert_eq!(incremental_tree.count(), (i + 1) as u64);
// Test if we can build the tree from a vector of note commitments
// instead of appending only one note commitment to the tree.
leaves.push(cm);
let ad_hoc_tree = tree::NoteCommitmentTree::from(leaves.clone());
let ad_hoc_root_hash = ad_hoc_tree.hash();
assert_eq!(hex::encode(ad_hoc_root_hash), test_vectors::ROOTS[i]);
// The root hashes should match.
let ad_hoc_root = ad_hoc_tree.root();
assert_eq!(<[u8; 32]>::from(ad_hoc_root), ad_hoc_root_hash);
// Test if the note commitments are counted correctly.
assert_eq!(ad_hoc_tree.count(), (i + 1) as u64);
}
}
#[test]
fn incremental_roots_with_blocks() -> Result<()> {
incremental_roots_with_blocks_for_network(Network::Mainnet)?;
incremental_roots_with_blocks_for_network(Network::Testnet)?;
Ok(())
}
fn incremental_roots_with_blocks_for_network(network: Network) -> Result<()> {
// The mainnet block height at which the first JoinSplit occurred.
const MAINNET_FIRST_JOINSPLIT_HEIGHT: u32 = 396;
// The testnet block height at which the first JoinSplit occurred.
const TESTNET_FIRST_JOINSPLIT_HEIGHT: u32 = 2259;
// Load the test data.
let (blocks, sprout_roots, next_height) = match network {
Network::Mainnet => (
&*vectors::MAINNET_BLOCKS,
&*vectors::MAINNET_FINAL_SPROUT_ROOTS,
MAINNET_FIRST_JOINSPLIT_HEIGHT,
),
Network::Testnet => (
&*vectors::TESTNET_BLOCKS,
&*vectors::TESTNET_FINAL_SPROUT_ROOTS,
TESTNET_FIRST_JOINSPLIT_HEIGHT,
),
};
// Load the Genesis height.
let genesis_height = NetworkUpgrade::Genesis
.activation_height(network)
.unwrap()
.0;
// Load the Genesis block.
let genesis_block = Arc::new(
blocks
.get(&genesis_height)
.expect("test vector exists")
.zcash_deserialize_into::<Block>()
.expect("block is structurally valid"),
);
// Build an empty note commitment tree.
let mut note_commitment_tree = tree::NoteCommitmentTree::default();
// Add note commitments from Genesis to the tree.
for transaction in genesis_block.transactions.iter() {
for sprout_note_commitment in transaction.sprout_note_commitments() {
note_commitment_tree
.append(*sprout_note_commitment)
.expect("we should not be able to fill up the tree");
}
}
// Load the Genesis note commitment tree root.
let genesis_anchor = tree::Root::from(
**sprout_roots
.get(&genesis_height)
.expect("test vector exists"),
);
// Check if the root of the note commitment tree of Genesis is correct.
assert_eq!(genesis_anchor, note_commitment_tree.root());
// Load the first block after Genesis that contains a JoinSplit transaction
// so that we can add new note commitments to the tree.
let next_block = Arc::new(
blocks
.get(&(genesis_height + next_height))
.expect("test vector exists")
.zcash_deserialize_into::<Block>()
.expect("block is structurally valid"),
);
// Load the note commitment tree root of `next_block`.
let next_block_anchor = tree::Root::from(
**sprout_roots
.get(&(genesis_height + next_height))
.expect("test vector exists"),
);
// Add the note commitments from `next_block` to the tree.
let mut appended_count = 0;
for transaction in next_block.transactions.iter() {
for sprout_note_commitment in transaction.sprout_note_commitments() {
note_commitment_tree
.append(*sprout_note_commitment)
.expect("test vector is correct");
appended_count += 1;
}
}
// We also want to make sure that sprout_note_commitments() is returning
// the commitments in the right order. But this will only be actually tested
// if there are more than one note commitment in a block.
assert!(appended_count > 1);
// Check if the root of `next_block` is correct.
assert_eq!(next_block_anchor, note_commitment_tree.root());
Ok(())
}

View File

@ -9,44 +9,52 @@
//! append-only. //! append-only.
//! //!
//! A root of a note commitment tree is associated with each treestate. //! A root of a note commitment tree is associated with each treestate.
#![allow(clippy::unit_arg)]
#![allow(dead_code)]
use std::{collections::VecDeque, fmt}; #![allow(clippy::unit_arg)]
use std::{cell::Cell, fmt};
use byteorder::{BigEndian, ByteOrder}; use byteorder::{BigEndian, ByteOrder};
use incrementalmerkletree::{bridgetree, Frontier};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use thiserror::Error;
use super::commitment::NoteCommitment;
#[cfg(any(test, feature = "proptest-impl"))] #[cfg(any(test, feature = "proptest-impl"))]
use proptest_derive::Arbitrary; use proptest_derive::Arbitrary;
use sha2::digest::generic_array::GenericArray; use sha2::digest::generic_array::GenericArray;
use super::commitment::NoteCommitment; /// Sprout note commitment trees have a max depth of 29.
///
/// <https://zips.z.cash/protocol/protocol.pdf#constants>
pub(super) const MERKLE_DEPTH: usize = 29;
const MERKLE_DEPTH: usize = 29; /// [MerkleCRH^Sprout] Hash Function.
/// MerkleCRH^Sprout Hash Function
/// ///
/// Used to hash incremental Merkle tree hash values for Sprout. /// Creates nodes of the note commitment tree.
/// ///
/// MerkleCRH^Sprout(layer, left, right) := SHA256Compress(left || right) /// MerkleCRH^Sprout(layer, left, right) := SHA256Compress(left || right).
/// ///
/// `layer` is unused for Sprout but used for the Sapling equivalent. /// Note: the implementation of MerkleCRH^Sprout does not use the `layer`
/// argument from the definition above since the argument does not affect the output.
/// ///
/// https://zips.z.cash/protocol/protocol.pdf#merklecrh /// [MerkleCRH^Sprout]: https://zips.z.cash/protocol/protocol.pdf#merklecrh.
fn merkle_crh_sprout(left: [u8; 32], right: [u8; 32]) -> [u8; 32] { fn merkle_crh_sprout(left: [u8; 32], right: [u8; 32]) -> [u8; 32] {
let mut other_block = [0u8; 64]; let mut other_block = [0u8; 64];
other_block[..32].copy_from_slice(&left[..]); other_block[..32].copy_from_slice(&left[..]);
other_block[32..].copy_from_slice(&right[..]); other_block[32..].copy_from_slice(&right[..]);
// H256: Sha256 initial state // H256: SHA-256 initial state.
// https://github.com/RustCrypto/hashes/blob/master/sha2/src/consts.rs#L170 // https://github.com/RustCrypto/hashes/blob/master/sha2/src/consts.rs#L170
let mut state = [ let mut state = [
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
0x5be0cd19, 0x5be0cd19,
]; ];
sha2::compress256(&mut state, &[GenericArray::clone_from_slice(&other_block)]); sha2::compress256(&mut state, &[GenericArray::clone_from_slice(&other_block)]);
// Yes, sha256 does big endian here. // Yes, SHA-256 does big endian here.
// https://github.com/RustCrypto/hashes/blob/master/sha2/src/sha256.rs#L40 // https://github.com/RustCrypto/hashes/blob/master/sha2/src/sha256.rs#L40
let mut derived_bytes = [0u8; 32]; let mut derived_bytes = [0u8; 32];
BigEndian::write_u32_into(&state, &mut derived_bytes); BigEndian::write_u32_into(&state, &mut derived_bytes);
@ -55,15 +63,19 @@ fn merkle_crh_sprout(left: [u8; 32], right: [u8; 32]) -> [u8; 32] {
} }
lazy_static! { lazy_static! {
/// Sprout note commitment trees have a max depth of 29. /// List of "empty" Sprout note commitment roots (nodes), one for each layer.
/// ///
/// https://zips.z.cash/protocol/protocol.pdf#constants /// The list is indexed by the layer number (0: root; `MERKLE_DEPTH`: leaf).
static ref EMPTY_ROOTS: Vec<[u8; 32]> = { pub(super) static ref EMPTY_ROOTS: Vec<[u8; 32]> = {
// Uncommitted^Sprout = = [0]^l_MerkleSprout // The empty leaf node at layer `MERKLE_DEPTH`.
let mut v = vec![[0u8; 32]]; let mut v = vec![NoteCommitmentTree::uncommitted()];
for d in 0..MERKLE_DEPTH { // Starting with layer `MERKLE_DEPTH` - 1 (the first internal layer, after the leaves),
v.push(merkle_crh_sprout(v[d], v[d])); // generate the empty roots up to layer 0, the root.
for _ in 0..MERKLE_DEPTH {
// The vector is generated from the end, pushing new nodes to its beginning.
// For this reason, the layer below is v[0].
v.insert(0, merkle_crh_sprout(v[0], v[0]));
} }
v v
@ -116,186 +128,193 @@ impl From<&Root> for [u8; 32] {
} }
} }
/// Sprout Note Commitment Tree /// A node of the Sprout note commitment tree.
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] struct Node([u8; 32]);
pub struct NoteCommitmentTree {
/// The root node of the tree (often used as an anchor). impl incrementalmerkletree::Hashable for Node {
root: Root, /// Returns an empty leaf.
/// The height of the tree (maximum height for Sprout is 29). fn empty_leaf() -> Self {
height: u8, Self(NoteCommitmentTree::uncommitted())
/// The number of leaves (note commitments) in this tree. }
count: u32,
/// Combines two nodes to generate a new node using [MerkleCRH^Sprout].
///
/// Note that Sprout does not use the `level` argument.
///
/// [MerkleCRH^Sprout]: https://zips.z.cash/protocol/protocol.pdf#sproutmerklecrh
fn combine(_level: incrementalmerkletree::Altitude, a: &Self, b: &Self) -> Self {
Self(merkle_crh_sprout(a.0, b.0))
}
/// Returns the node for the level below the given level. (A quirk of the API)
fn empty_root(level: incrementalmerkletree::Altitude) -> Self {
let layer_below: usize = MERKLE_DEPTH - usize::from(level);
Self(EMPTY_ROOTS[layer_below])
}
} }
impl From<Vec<NoteCommitment>> for NoteCommitmentTree { impl From<NoteCommitment> for Node {
fn from(values: Vec<NoteCommitment>) -> Self { fn from(cm: NoteCommitment) -> Self {
if values.is_empty() { Node(cm.into())
return NoteCommitmentTree {
root: Root::default(),
height: 0,
count: 0,
};
}
let count = values.len() as u32;
let mut height = 0u8;
let mut current_layer: VecDeque<[u8; 32]> =
values.into_iter().map(|cm| cm.into()).collect();
while usize::from(height) < MERKLE_DEPTH {
let mut next_layer_up = vec![];
while !current_layer.is_empty() {
let left = current_layer.pop_front().unwrap();
let right;
if current_layer.is_empty() {
right = EMPTY_ROOTS[height as usize];
} else {
right = current_layer.pop_front().unwrap();
}
let node = merkle_crh_sprout(left, right);
next_layer_up.push(node);
}
height += 1;
current_layer = next_layer_up.into();
}
assert!(current_layer.len() == 1);
NoteCommitmentTree {
root: Root(current_layer.pop_front().unwrap()),
height,
count,
}
} }
} }
impl serde::Serialize for Node {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.0.serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for Node {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let bytes = <[u8; 32]>::deserialize(deserializer)?;
let cm = NoteCommitment::from(bytes);
let node = Node::from(cm);
Ok(node)
}
}
#[allow(dead_code, missing_docs)]
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum NoteCommitmentTreeError {
#[error("the note commitment tree is full")]
FullTree,
}
/// [Sprout Note Commitment Tree].
///
/// An incremental Merkle tree of fixed depth used to store Sprout note commitments.
/// It is used to express the existence of value and the capability to spend it. It is _not_ the
/// job of this tree to protect against double-spending, as it is append-only; double-spending
/// is prevented by maintaining the [nullifier set] for each shielded pool.
///
/// Internally this wraps [`incrementalmerkletree::bridgetree::Frontier`], so that we can maintain and increment
/// the full tree with only the minimal amount of non-empty nodes/leaves required.
///
/// [Sprout Note Commitment Tree]: https://zips.z.cash/protocol/protocol.pdf#merkletree
/// [nullifier set]: https://zips.z.cash/protocol/protocol.pdf#nullifierset
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NoteCommitmentTree {
/// The tree represented as a [`incrementalmerkletree::bridgetree::Frontier`].
///
/// A [`incrementalmerkletree::Frontier`] is a subset of the tree that allows to fully specify it. It
/// consists of nodes along the rightmost (newer) branch of the tree that
/// has non-empty nodes. Upper (near root) empty nodes of the branch are not
/// stored.
inner: bridgetree::Frontier<Node, { MERKLE_DEPTH as u8 }>,
/// A cached root of the tree.
///
/// Every time the root is computed by [`Self::root`], it is cached here,
/// and the cached value will be returned by [`Self::root`] until the tree
/// is changed by [`Self::append`]. This greatly increases performance
/// because it avoids recomputing the root when the tree does not change
/// between blocks. In the finalized state, the tree is read from disk for
/// every block processed, which would also require recomputing the root
/// even if it has not changed (note that the cached root is serialized with
/// the tree). This is particularly important since we decided to
/// instantiate the trees from the genesis block, for simplicity.
///
/// [`Cell`] offers interior mutability (it works even with a non-mutable
/// reference to the tree) but it prevents the tree (and anything that uses
/// it) from being shared between threads. If this ever becomes an issue we
/// can leave caching to the callers (which requires much more code), or
/// replace `Cell` with `Arc<Mutex<_>>` (and be careful of deadlocks and
/// async code.)
cached_root: Cell<Option<Root>>,
}
impl NoteCommitmentTree { impl NoteCommitmentTree {
/// Get the Jubjub-based Pedersen hash of root node of this merkle tree of /// Appends a note commitment to the leafmost layer of the tree.
/// commitment notes. ///
/// Returns an error if the tree is full.
pub fn append(&mut self, cm: NoteCommitment) -> Result<(), NoteCommitmentTreeError> {
if self.inner.append(&cm.into()) {
// Invalidate cached root
self.cached_root.replace(None);
Ok(())
} else {
Err(NoteCommitmentTreeError::FullTree)
}
}
/// Returns the current root of the tree; used as an anchor in Sprout
/// shielded transactions.
pub fn root(&self) -> Root { pub fn root(&self) -> Root {
self.root match self.cached_root.get() {
} // Return cached root.
Some(root) => root,
/// Add a note commitment to the tree. None => {
pub fn append(&mut self, _cm: &NoteCommitment) { // Compute root and cache it.
// TODO: https://github.com/ZcashFoundation/zebra/issues/2485 let root = Root(self.inner.root().0);
todo!("implement sprout note commitment trees #2485"); self.cached_root.replace(Some(root));
} root
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_roots() {
zebra_test::init();
// From https://github.com/zcash/zcash/blob/master/src/zcash/IncrementalMerkleTree.cpp#L439
let hex_empty_roots = [
"0000000000000000000000000000000000000000000000000000000000000000",
"da5698be17b9b46962335799779fbeca8ce5d491c0d26243bafef9ea1837a9d8",
"dc766fab492ccf3d1e49d4f374b5235fa56506aac2224d39f943fcd49202974c",
"3f0a406181105968fdaee30679e3273c66b72bf9a7f5debbf3b5a0a26e359f92",
"26b0052694fc42fdff93e6fb5a71d38c3dd7dc5b6ad710eb048c660233137fab",
"0109ecc0722659ff83450b8f7b8846e67b2859f33c30d9b7acd5bf39cae54e31",
"3f909b8ce3d7ffd8a5b30908f605a03b0db85169558ddc1da7bbbcc9b09fd325",
"40460fa6bc692a06f47521a6725a547c028a6a240d8409f165e63cb54da2d23f",
"8c085674249b43da1b9a31a0e820e81e75f342807b03b6b9e64983217bc2b38e",
"a083450c1ba2a3a7be76fad9d13bc37be4bf83bd3e59fc375a36ba62dc620298",
"1ddddabc2caa2de9eff9e18c8c5a39406d7936e889bc16cfabb144f5c0022682",
"c22d8f0b5e4056e5f318ba22091cc07db5694fbeb5e87ef0d7e2c57ca352359e",
"89a434ae1febd7687eceea21d07f20a2512449d08ce2eee55871cdb9d46c1233",
"7333dbffbd11f09247a2b33a013ec4c4342029d851e22ba485d4461851370c15",
"5dad844ab9466b70f745137195ca221b48f346abd145fb5efc23a8b4ba508022",
"507e0dae81cbfbe457fd370ef1ca4201c2b6401083ddab440e4a038dc1e358c4",
"bdcdb3293188c9807d808267018684cfece07ac35a42c00f2c79b4003825305d",
"bab5800972a16c2c22530c66066d0a5867e987bed21a6d5a450b683cf1cfd709",
"11aa0b4ad29b13b057a31619d6500d636cd735cdd07d811ea265ec4bcbbbd058",
"5145b1b055c2df02b95675e3797b91de1b846d25003c0a803d08900728f2cd6a",
"0323f2850bf3444f4b4c5c09a6057ec7169190f45acb9e46984ab3dfcec4f06a",
"671546e26b1da1af754531e26d8a6a51073a57ddd72dc472efb43fcb257cffff",
"bb23a9bba56de57cb284b0d2b01c642cf79c9a5563f0067a21292412145bd78a",
"f30cc836b9f71b4e7ee3c72b1fd253268af9a27e9d7291a23d02821b21ddfd16",
"58a2753dade103cecbcda50b5ebfce31e12d41d5841dcc95620f7b3d50a1b9a1",
"925e6d474a5d8d3004f29da0dd78d30ae3824ce79dfe4934bb29ec3afaf3d521",
"08f279618616bcdd4eadc9c7a9062691a59b43b07e2c1e237f17bd189cd6a8fe",
"c92b32db42f42e2bf0a59df9055be5c669d3242df45357659b75ae2c27a76f50",
"c0db2a74998c50eb7ba6534f6d410efc27c4bb88acb0222c7906ea28a327b511",
"d7c612c817793191a1e68652121876d6b3bde40f4fa52bc314145ce6e5cdd259",
];
for i in 0..EMPTY_ROOTS.len() {
assert_eq!(hex::encode(EMPTY_ROOTS[i]), hex_empty_roots[i]);
} }
} }
#[test] /// Returns a hash of the Sprout note commitment tree root.
fn incremental_roots() { pub fn hash(&self) -> [u8; 32] {
zebra_test::init(); self.root().into()
}
// From https://github.com/zcash/zcash/blob/master/src/test/data/merkle_commitments.json /// Returns an as-yet unused leaf node value of a Sprout note commitment tree.
// ///
// Byte-reversed from those ones because the original test vectors are /// Uncommitted^Sprout = [0]^(l^[Sprout_Merkle]).
// loaded using uint256S() ///
let commitments = [ /// [Sprout_Merkle]: https://zips.z.cash/protocol/protocol.pdf#constants
"62fdad9bfbf17c38ea626a9c9b8af8a748e6b4367c8494caf0ca592999e8b6ba", pub fn uncommitted() -> [u8; 32] {
"68eb35bc5e1ddb80a761718e63a1ecf4d4977ae22cc19fa732b85515b2a4c943", [0; 32]
"836045484077cf6390184ea7cd48b460e2d0f22b2293b69633bb152314a692fb", }
"92498a8295ea36d593eaee7cb8b55be3a3e37b8185d3807693184054cd574ae4",
"ff7c360374a6508ae0904c782127ff5dce90918f3ee81cf92ef1b69afb8bf443",
"68c4d0f69d1f18b756c2ee875c14f1c6cd38682e715ded14bf7e3c1c5610e9fc",
"8b16cd3ec44875e4856e30344c0b4a68a6f929a68be5117b225b80926301e7b1",
"50c0b43061c39191c3ec529734328b7f9cafeb6fd162cc49a4495442d9499a2d",
"70ffdd5fa0f3aea18bd4700f1ac2e2e03cf5d4b7b857e8dd93b862a8319b9653",
"d81ef64a0063573d80cd32222d8d04debbe807345ad7af2e9edf0f44bdfaf817",
"8b92a4ec694271fe1b16cc0ea8a433bf19e78eb5ca733cc137f38e5ecb05789b",
"04e963ab731e4aaaaaf931c3c039ea8c9d7904163936e19a8929434da9adeba3",
"be3f6c181f162824191ecf1f78cae3ffb0ddfda671bb93277ce6ebc9201a0912",
"1880967fc8226380a849c63532bba67990f7d0a10e9c90b848f58d634957c6e9",
"c465bb2893cba233351094f259396301c23d73a6cf6f92bc63428a43f0dd8f8e",
"84c834e7cb38d6f08d82f5cf4839b8920185174b11c7af771fd38dd02b206a20",
];
// Calculated by the above implementation for MERKLE_DEPTH = 29 by the /// Counts the note commitments in the tree.
// same code confirmed to produce the test vectors from ///
// https://github.com/zcash/zcash/blob/master/src/test/data/merkle_roots.json /// For Sprout, the tree is [capped at 2^29 leaf nodes][spec].
// when MERKLE_DEPTH = 4. ///
let roots = [ /// [spec]: https://zips.z.cash/protocol/protocol.pdf#merkletree
"b8e10b6c157be92c43a733e2c9bddb963a2fb9ea80ebcb307acdcc5fc89f1656", pub fn count(&self) -> u64 {
"83a7754b8240699dd1b63bf70cf70db28ffeb74ef87ce2f4dd32c28ae5009f4f", self.inner.position().map_or(0, |pos| u64::from(pos) + 1)
"c45297124f50dcd3f78eed017afd1e30764cd74cdf0a57751978270fd0721359", }
"b61f588fcba9cea79e94376adae1c49583f716d2f20367141f1369a235b95c98", }
"a3165c1708f0cc028014b9bf925a81c30091091ca587624de853260cd151b524",
"6bb8c538c550abdd26baa2a7510a4ae50a03dc00e52818b9db3e4ffaa29c1f41",
"e04e4731085ba95e3fa7c8f3d5eb9a56af63363403b783bc68802629c3fe505b",
"c3714ab74d8e3984e8b58a2b4806934d20f6e67d7246cf8f5b2762305294a0ea",
"63657edeead4bc45610b6d5eb80714a0622aad5788119b7d9961453e3aacda21",
"e31b80819221718440c5351525dbb902d60ed16b74865a2528510959a1960077",
"872f13df2e12f5503c39100602930b0f91ea360e5905a9f5ceb45d459efc36b2",
"bdd7105febb3590832e946aa590d07377d1366cf5e7267507efa399dd0febdbc",
"0f45f4adcb846a8bb56833ca0cae96f2fb8747958daa191a46d0f9d93268260a",
"41c6e456e2192ab74f72cb27c444a2734ca8ade5a4788c1bc2546118dda01778",
"8261355fd9bafc52a08d738fed29a859fbe15f2e74a5353954b150be200d0e16",
"90665cb8a43001f0655169952399590cd17f99165587c1dd842eb674fb9f0afe",
];
let mut leaves = vec![]; impl Default for NoteCommitmentTree {
fn default() -> Self {
for (i, cm) in commitments.iter().enumerate() { Self {
let mut bytes = [0u8; 32]; inner: bridgetree::Frontier::empty(),
let _ = hex::decode_to_slice(cm, &mut bytes); cached_root: Default::default(),
leaves.push(NoteCommitment::from(bytes));
let tree = NoteCommitmentTree::from(leaves.clone());
assert_eq!(hex::encode(<[u8; 32]>::from(tree.root())), roots[i]);
} }
} }
} }
impl Eq for NoteCommitmentTree {}
impl PartialEq for NoteCommitmentTree {
fn eq(&self, other: &Self) -> bool {
self.hash() == other.hash()
}
}
impl From<Vec<NoteCommitment>> for NoteCommitmentTree {
/// Builds the tree from a vector of commitments at once.
fn from(values: Vec<NoteCommitment>) -> Self {
let mut tree = Self::default();
if values.is_empty() {
return tree;
}
for cm in values {
let _ = tree.append(cm);
}
tree
}
}

View File

@ -651,7 +651,28 @@ impl Transaction {
} }
} }
/// Access the note commitments in this transaction, regardless of version. /// Returns the Sprout note commitments in this transaction.
pub fn sprout_note_commitments(
&self,
) -> Box<dyn Iterator<Item = &sprout::commitment::NoteCommitment> + '_> {
match self {
Transaction::V2 {
joinsplit_data: Some(joinsplit_data),
..
} => Box::new(joinsplit_data.note_commitments()),
Transaction::V1 { .. }
| Transaction::V2 {
joinsplit_data: None,
..
}
| Transaction::V3 { .. }
| Transaction::V4 { .. }
| Transaction::V5 { .. } => Box::new(std::iter::empty()),
}
}
/// Returns the Sapling note commitments in this transaction, regardless of version.
pub fn sapling_note_commitments(&self) -> Box<dyn Iterator<Item = &jubjub::Fq> + '_> { pub fn sapling_note_commitments(&self) -> Box<dyn Iterator<Item = &jubjub::Fq> + '_> {
// This function returns a boxed iterator because the different // This function returns a boxed iterator because the different
// transaction variants end up having different iterator types // transaction variants end up having different iterator types

View File

@ -23,7 +23,7 @@ pub struct JoinSplitData<P: ZkSnarkProof> {
/// ///
/// Storing this separately from `rest` ensures that it is impossible /// Storing this separately from `rest` ensures that it is impossible
/// to construct an invalid `JoinSplitData` with no `JoinSplit`s. /// to construct an invalid `JoinSplitData` with no `JoinSplit`s.
///` ///
/// However, it's not necessary to access or process `first` and `rest` /// However, it's not necessary to access or process `first` and `rest`
/// separately, as the [`JoinSplitData::joinsplits`] method provides an /// separately, as the [`JoinSplitData::joinsplits`] method provides an
/// iterator over all of the `JoinSplit`s. /// iterator over all of the `JoinSplit`s.

View File

@ -8,7 +8,7 @@
#![deny(clippy::await_holding_lock)] #![deny(clippy::await_holding_lock)]
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
// Each lazy_static variable uses additional recursion // Each lazy_static variable uses additional recursion
#![recursion_limit = "256"] #![recursion_limit = "512"]
use color_eyre::section::PanicMessage; use color_eyre::section::PanicMessage;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -20,7 +20,6 @@ impl ReverseCollection for [u8; 32] {
} }
lazy_static! { lazy_static! {
/// All block test vectors /// All block test vectors
pub static ref BLOCKS: Vec<&'static [u8]> = MAINNET_BLOCKS pub static ref BLOCKS: Vec<&'static [u8]> = MAINNET_BLOCKS
.iter() .iter()
@ -61,6 +60,7 @@ lazy_static! {
pub static ref MAINNET_BLOCKS: BTreeMap<u32, &'static [u8]> = [ pub static ref MAINNET_BLOCKS: BTreeMap<u32, &'static [u8]> = [
// Genesis // Genesis
(0, BLOCK_MAINNET_GENESIS_BYTES.as_ref()), (0, BLOCK_MAINNET_GENESIS_BYTES.as_ref()),
// BeforeOverwinter // BeforeOverwinter
(1, BLOCK_MAINNET_1_BYTES.as_ref()), (1, BLOCK_MAINNET_1_BYTES.as_ref()),
(2, BLOCK_MAINNET_2_BYTES.as_ref()), (2, BLOCK_MAINNET_2_BYTES.as_ref()),
@ -73,37 +73,62 @@ lazy_static! {
(9, BLOCK_MAINNET_9_BYTES.as_ref()), (9, BLOCK_MAINNET_9_BYTES.as_ref()),
(10, BLOCK_MAINNET_10_BYTES.as_ref()), (10, BLOCK_MAINNET_10_BYTES.as_ref()),
(202, BLOCK_MAINNET_202_BYTES.as_ref()), (202, BLOCK_MAINNET_202_BYTES.as_ref()),
// The first block that contains a tx with a JoinSplit.
(396, BLOCK_MAINNET_396_BYTES.as_ref()),
(347_499, BLOCK_MAINNET_347499_BYTES.as_ref()), (347_499, BLOCK_MAINNET_347499_BYTES.as_ref()),
// Overwinter // Overwinter
(347_500, BLOCK_MAINNET_347500_BYTES.as_ref()), (347_500, BLOCK_MAINNET_347500_BYTES.as_ref()),
(347_501, BLOCK_MAINNET_347501_BYTES.as_ref()), (347_501, BLOCK_MAINNET_347501_BYTES.as_ref()),
(415_000, BLOCK_MAINNET_415000_BYTES.as_ref()), (415_000, BLOCK_MAINNET_415000_BYTES.as_ref()),
(419_199, BLOCK_MAINNET_419199_BYTES.as_ref()), (419_199, BLOCK_MAINNET_419199_BYTES.as_ref()),
// Sapling // Sapling
(419_200, BLOCK_MAINNET_419200_BYTES.as_ref()), (419_200, BLOCK_MAINNET_419200_BYTES.as_ref()),
(419_201, BLOCK_MAINNET_419201_BYTES.as_ref()), (419_201, BLOCK_MAINNET_419201_BYTES.as_ref()),
// A bad version field // A bad version field
(434_873, BLOCK_MAINNET_434873_BYTES.as_ref()), (434_873, BLOCK_MAINNET_434873_BYTES.as_ref()),
(653_599, BLOCK_MAINNET_653599_BYTES.as_ref()), (653_599, BLOCK_MAINNET_653599_BYTES.as_ref()),
// Blossom // Blossom
(653_600, BLOCK_MAINNET_653600_BYTES.as_ref()), (653_600, BLOCK_MAINNET_653600_BYTES.as_ref()),
(653_601, BLOCK_MAINNET_653601_BYTES.as_ref()), (653_601, BLOCK_MAINNET_653601_BYTES.as_ref()),
(902_999, BLOCK_MAINNET_902999_BYTES.as_ref()), (902_999, BLOCK_MAINNET_902999_BYTES.as_ref()),
// Heartwood // Heartwood
(903_000, BLOCK_MAINNET_903000_BYTES.as_ref()), (903_000, BLOCK_MAINNET_903000_BYTES.as_ref()),
(903_001, BLOCK_MAINNET_903001_BYTES.as_ref()), (903_001, BLOCK_MAINNET_903001_BYTES.as_ref()),
// Shielded coinbase x3 // Shielded coinbase x3
(949_496, BLOCK_MAINNET_949496_BYTES.as_ref()), (949_496, BLOCK_MAINNET_949496_BYTES.as_ref()),
(975_066, BLOCK_MAINNET_975066_BYTES.as_ref()), (975_066, BLOCK_MAINNET_975066_BYTES.as_ref()),
(982_681, BLOCK_MAINNET_982681_BYTES.as_ref()), (982_681, BLOCK_MAINNET_982681_BYTES.as_ref()),
// Last Heartwood // Last Heartwood
(1_046_399, BLOCK_MAINNET_1046399_BYTES.as_ref()), (1_046_399, BLOCK_MAINNET_1046399_BYTES.as_ref()),
// Canopy and First Coinbase Halving // Canopy and First Coinbase Halving
(1_046_400, BLOCK_MAINNET_1046400_BYTES.as_ref()), (1_046_400, BLOCK_MAINNET_1046400_BYTES.as_ref()),
(1_046_401, BLOCK_MAINNET_1046401_BYTES.as_ref()), (1_046_401, BLOCK_MAINNET_1046401_BYTES.as_ref()),
(1_180_900, BLOCK_MAINNET_1180900_BYTES.as_ref()), (1_180_900, BLOCK_MAINNET_1180900_BYTES.as_ref()),
].iter().cloned().collect(); ].iter().cloned().collect();
/// Mainnet final Sprout roots, indexed by height.
///
/// If there are no Sprout inputs or outputs in a block, the final Sprout root is the same as the previous block.
pub static ref MAINNET_FINAL_SPROUT_ROOTS: BTreeMap<u32, &'static [u8; 32]> = [
// Genesis
(0, SPROUT_FINAL_ROOT_MAINNET_0_BYTES.as_ref().try_into().unwrap()),
// The first block that contains a tx with a JoinSplit.
(396, SPROUT_FINAL_ROOT_MAINNET_396_BYTES.as_ref().try_into().unwrap()),
// Overwinter
(347_499, SPROUT_FINAL_ROOT_MAINNET_347499_BYTES.as_ref().try_into().unwrap()),
(347_500, SPROUT_FINAL_ROOT_MAINNET_347500_BYTES.as_ref().try_into().unwrap()),
(347_501, SPROUT_FINAL_ROOT_MAINNET_347501_BYTES.as_ref().try_into().unwrap()),
].iter().cloned().collect();
/// Mainnet final sapling roots, indexed by height /// Mainnet final sapling roots, indexed by height
/// ///
/// Pre-sapling roots are all-zeroes. /// Pre-sapling roots are all-zeroes.
@ -151,6 +176,8 @@ lazy_static! {
(8, BLOCK_TESTNET_8_BYTES.as_ref()), (8, BLOCK_TESTNET_8_BYTES.as_ref()),
(9, BLOCK_TESTNET_9_BYTES.as_ref()), (9, BLOCK_TESTNET_9_BYTES.as_ref()),
(10, BLOCK_TESTNET_10_BYTES.as_ref()), (10, BLOCK_TESTNET_10_BYTES.as_ref()),
// The first block that contains a tx with a JoinSplit.
(2_259, BLOCK_TESTNET_2259_BYTES.as_ref()),
// A large block // A large block
(141_042, BLOCK_TESTNET_141042_BYTES.as_ref()), (141_042, BLOCK_TESTNET_141042_BYTES.as_ref()),
(207_499, BLOCK_TESTNET_207499_BYTES.as_ref()), (207_499, BLOCK_TESTNET_207499_BYTES.as_ref()),
@ -195,6 +222,16 @@ lazy_static! {
(1_326_100, BLOCK_TESTNET_1326100_BYTES.as_ref()), (1_326_100, BLOCK_TESTNET_1326100_BYTES.as_ref()),
].iter().cloned().collect(); ].iter().cloned().collect();
/// Testnet final Sprout roots, indexed by height.
///
/// If there are no Sprout inputs or outputs in a block, the final Sprout root is the same as the previous block.
pub static ref TESTNET_FINAL_SPROUT_ROOTS: BTreeMap<u32, &'static [u8; 32]> = [
// Genesis
(0, SPROUT_FINAL_ROOT_TESTNET_0_BYTES.as_ref().try_into().unwrap()),
// The first block that contains a tx with a JoinSplit.
(2259, SPROUT_FINAL_ROOT_TESTNET_2259_BYTES.as_ref().try_into().unwrap()),
].iter().cloned().collect();
/// Testnet final sapling roots, indexed by height /// Testnet final sapling roots, indexed by height
/// ///
/// Pre-sapling roots are all-zeroes. /// Pre-sapling roots are all-zeroes.
@ -280,6 +317,10 @@ lazy_static! {
pub static ref BLOCK_MAINNET_202_BYTES: Vec<u8> = pub static ref BLOCK_MAINNET_202_BYTES: Vec<u8> =
<Vec<u8>>::from_hex(include_str!("block-main-0-000-202.txt").trim()) <Vec<u8>>::from_hex(include_str!("block-main-0-000-202.txt").trim())
.expect("Block bytes are in valid hex representation"); .expect("Block bytes are in valid hex representation");
// zcash-cli getblock 396 0 > block-main-0-000-396.txt
pub static ref BLOCK_MAINNET_396_BYTES: Vec<u8> =
<Vec<u8>>::from_hex(include_str!("block-main-0-000-396.txt").trim())
.expect("Block bytes are in valid hex representation");
/// This contains an encoding of block 202 but with an improperly encoded /// This contains an encoding of block 202 but with an improperly encoded
/// coinbase height. /// coinbase height.
@ -287,6 +328,41 @@ lazy_static! {
<Vec<u8>>::from_hex(include_str!("block-main-0-000-202-bad.txt").trim()) <Vec<u8>>::from_hex(include_str!("block-main-0-000-202-bad.txt").trim())
.expect("Block bytes are in valid hex representation"); .expect("Block bytes are in valid hex representation");
// # Anchors for Sprout, starting at Genesis.
//
// for i in 0 396; do
// zcash-cli z_gettreestate "$i" | \
// jq --arg i "$i" \
// --raw-output \
// '"pub static ref SPROUT_FINAL_ROOT_MAINNET_\($i)_BYTES: [u8; 32] = <[u8; 32]>::from_hex(\"\(.sprout.commitments.finalRoot)\").expect(\"final root bytes are in valid hex representation\").rev();"'
// done
pub static ref SPROUT_FINAL_ROOT_MAINNET_0_BYTES: [u8; 32] =
<[u8; 32]>::from_hex("59d2cde5e65c1414c32ba54f0fe4bdb3d67618125286e6a191317917c812c6d7")
.expect("final root bytes are in valid hex representation").rev();
// The first block that contains a tx with a JoinSplit.
pub static ref SPROUT_FINAL_ROOT_MAINNET_396_BYTES: [u8; 32] =
<[u8; 32]>::from_hex("6a5710d1ca7d079baf1ce6ed1ea1b0756e219e9f3ebb9c0ec5b8ca1ff81c8f06")
.expect("final root bytes are in valid hex representation").rev();
// # Anchors for the Overwinter transition, which is still in the Sprout pool.
//
// for i in 347499 347500 347501; do
// zcash-cli z_gettreestate "$i" | \
// jq --arg i "$i" \
// --raw-output \
// '"pub static ref SPROUT_FINAL_ROOT_MAINNET_\($i)_BYTES: [u8; 32] = <[u8; 32]>::from_hex(\"\(.sprout.commitments.finalRoot)\").expect(\"final root bytes are in valid hex representation\").rev();"'
// done
pub static ref SPROUT_FINAL_ROOT_MAINNET_347499_BYTES: [u8; 32] =
<[u8; 32]>::from_hex("ce01f64025aba7c0e30a29f239f0eecd3cc18e5b1e575ca018c789a99482724f")
.expect("final root bytes are in valid hex representation").rev();
pub static ref SPROUT_FINAL_ROOT_MAINNET_347500_BYTES: [u8; 32] =
<[u8; 32]>::from_hex("ce01f64025aba7c0e30a29f239f0eecd3cc18e5b1e575ca018c789a99482724f")
.expect("final root bytes are in valid hex representation").rev();
pub static ref SPROUT_FINAL_ROOT_MAINNET_347501_BYTES: [u8; 32] =
<[u8; 32]>::from_hex("db036e080299a7401fd816789b5ea1b092ba3dab21e0f1d44161fffa149c65c1")
.expect("final root bytes are in valid hex representation").rev();
// Overwinter transition // Overwinter transition
// for i in 347499 347500 347501; do // for i in 347499 347500 347501; do
// zcash-cli getblock $i 0 > block-main-$[i/1000000]-$[i/1000%1000]-$[i%1000].txt // zcash-cli getblock $i 0 > block-main-$[i/1000000]-$[i/1000%1000]-$[i%1000].txt
@ -498,6 +574,10 @@ lazy_static! {
pub static ref BLOCK_TESTNET_10_BYTES: Vec<u8> = pub static ref BLOCK_TESTNET_10_BYTES: Vec<u8> =
<Vec<u8>>::from_hex(include_str!("block-test-0-000-010.txt").trim()) <Vec<u8>>::from_hex(include_str!("block-test-0-000-010.txt").trim())
.expect("Block bytes are in valid hex representation"); .expect("Block bytes are in valid hex representation");
// zcash-cli -testnet getblock 2259 0 > block-test-0-002-259.txt
pub static ref BLOCK_TESTNET_2259_BYTES: Vec<u8> =
<Vec<u8>>::from_hex(include_str!("block-test-0-002-259.txt").trim())
.expect("Block bytes are in valid hex representation");
// A large block // A large block
// i=141042 // i=141042
// zcash-cli -testnet getblock $i 0 | xxd -revert -plain > block-test-$[i/1000000]-$[i/1000%1000]-0$[i%1000].bin // zcash-cli -testnet getblock $i 0 | xxd -revert -plain > block-test-$[i/1000000]-$[i/1000%1000]-0$[i%1000].bin
@ -511,6 +591,22 @@ lazy_static! {
// (git compresses blocks in transit and in its index, so there is not much need for extra compression.) // (git compresses blocks in transit and in its index, so there is not much need for extra compression.)
pub static ref BLOCK_TESTNET_141042_BYTES: Vec<u8> = include_bytes!("block-test-0-141-042.bin").to_vec(); pub static ref BLOCK_TESTNET_141042_BYTES: Vec<u8> = include_bytes!("block-test-0-141-042.bin").to_vec();
// # Anchors for Sprout, starting at Genesis.
//
// for i in 0 396; do
// zcash-cli z_gettreestate "$i" | \
// jq --arg i "$i" \
// --raw-output \
// '"pub static ref SPROUT_FINAL_ROOT_TESTNET_\($i)_BYTES: [u8; 32] = <[u8; 32]>::from_hex(\"\(.sprout.commitments.finalRoot)\").expect(\"final root bytes are in valid hex representation\").rev();"'
// done
pub static ref SPROUT_FINAL_ROOT_TESTNET_0_BYTES: [u8; 32] =
<[u8; 32]>::from_hex("59d2cde5e65c1414c32ba54f0fe4bdb3d67618125286e6a191317917c812c6d7")
.expect("final root bytes are in valid hex representation").rev();
// The first block that contains a tx with a JoinSplit.
pub static ref SPROUT_FINAL_ROOT_TESTNET_2259_BYTES: [u8; 32] =
<[u8; 32]>::from_hex("2985231c8b3fb5624299fd7289c33667b0270a3fcde420c9047a6bad41f07733")
.expect("final root bytes are in valid hex representation").rev();
// Overwinter transition // Overwinter transition
// for i in 207499 207500 207501; do // for i in 207499 207500 207501; do
// zcash-cli -testnet getblock $i 0 > block-test-$[i/1000000]-$[i/1000%1000]-$[i%1000].txt // zcash-cli -testnet getblock $i 0 > block-test-$[i/1000000]-$[i/1000%1000]-$[i%1000].txt