From 89630073976b48bb52ae599041e72e027b503f62 Mon Sep 17 00:00:00 2001 From: Marek Date: Fri, 19 Nov 2021 00:05:52 +0100 Subject: [PATCH] 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 * Refactor rustdoc Co-authored-by: Deirdre Connolly * rustdoc * Rustdoc * rustdoc links * Oops, need the trait in scope to use it * Avoid accessing the wrapped hash directly Co-authored-by: Deirdre Connolly * 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 * Refactor rustdoc Co-authored-by: Deirdre Connolly * rustdoc * Rustdoc * rustdoc links * Oops, need the trait in scope to use it * Avoid accessing the wrapped hash directly Co-authored-by: Deirdre Connolly * 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 Co-authored-by: Deirdre Connolly --- README.md | 8 +- zebra-chain/src/sprout/tests.rs | 4 + zebra-chain/src/sprout/tests/test_vectors.rs | 84 ++++ zebra-chain/src/sprout/tests/tree.rs | 185 +++++++++ zebra-chain/src/sprout/tree.rs | 393 +++++++++--------- zebra-chain/src/transaction.rs | 23 +- zebra-chain/src/transaction/joinsplit.rs | 2 +- zebra-test/src/lib.rs | 2 +- .../src/vectors/block-main-0-000-396.txt | 1 + .../src/vectors/block-test-0-002-259.txt | 1 + zebra-test/src/vectors/block.rs | 98 ++++- 11 files changed, 606 insertions(+), 195 deletions(-) create mode 100644 zebra-chain/src/sprout/tests/test_vectors.rs create mode 100644 zebra-chain/src/sprout/tests/tree.rs create mode 100644 zebra-test/src/vectors/block-main-0-000-396.txt create mode 100644 zebra-test/src/vectors/block-test-0-002-259.txt diff --git a/README.md b/README.md index 3bc39bab..ea70941b 100644 --- a/README.md +++ b/README.md @@ -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: -- 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`. - 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. - Better governance: with a new node deployment, there will be more developers who can implement different features for the Zcash network. - Dev accessibility: supports more developers, which gives new developers 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. - Spec safety: with several node implementations, it is much easier to notice 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`, 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. Currently, Zebra does not validate the following Zcash consensus rules: diff --git a/zebra-chain/src/sprout/tests.rs b/zebra-chain/src/sprout/tests.rs index 67af9b07..1d18eceb 100644 --- a/zebra-chain/src/sprout/tests.rs +++ b/zebra-chain/src/sprout/tests.rs @@ -1 +1,5 @@ +//! Sprout tests. + mod preallocate; +mod test_vectors; +mod tree; diff --git a/zebra-chain/src/sprout/tests/test_vectors.rs b/zebra-chain/src/sprout/tests/test_vectors.rs new file mode 100644 index 00000000..ef1595dd --- /dev/null +++ b/zebra-chain/src/sprout/tests/test_vectors.rs @@ -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). +/// +/// +pub const EMPTY_LEAF: [u8; 32] = [0; 32]; diff --git a/zebra-chain/src/sprout/tests/tree.rs b/zebra-chain/src/sprout/tests/tree.rs new file mode 100644 index 00000000..a40871e8 --- /dev/null +++ b/zebra-chain/src/sprout/tests/tree.rs @@ -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::() + .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::() + .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(()) +} diff --git a/zebra-chain/src/sprout/tree.rs b/zebra-chain/src/sprout/tree.rs index 62dbc274..06fd59de 100644 --- a/zebra-chain/src/sprout/tree.rs +++ b/zebra-chain/src/sprout/tree.rs @@ -9,44 +9,52 @@ //! append-only. //! //! 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 incrementalmerkletree::{bridgetree, Frontier}; use lazy_static::lazy_static; +use thiserror::Error; + +use super::commitment::NoteCommitment; + #[cfg(any(test, feature = "proptest-impl"))] use proptest_derive::Arbitrary; use sha2::digest::generic_array::GenericArray; -use super::commitment::NoteCommitment; +/// Sprout note commitment trees have a max depth of 29. +/// +/// +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] { let mut other_block = [0u8; 64]; other_block[..32].copy_from_slice(&left[..]); 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 let mut state = [ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, ]; + 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 let mut derived_bytes = [0u8; 32]; 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! { - /// 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 - static ref EMPTY_ROOTS: Vec<[u8; 32]> = { - // Uncommitted^Sprout = = [0]^l_MerkleSprout - let mut v = vec![[0u8; 32]]; + /// The list is indexed by the layer number (0: root; `MERKLE_DEPTH`: leaf). + pub(super) static ref EMPTY_ROOTS: Vec<[u8; 32]> = { + // The empty leaf node at layer `MERKLE_DEPTH`. + let mut v = vec![NoteCommitmentTree::uncommitted()]; - for d in 0..MERKLE_DEPTH { - v.push(merkle_crh_sprout(v[d], v[d])); + // Starting with layer `MERKLE_DEPTH` - 1 (the first internal layer, after the leaves), + // 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 @@ -116,186 +128,193 @@ impl From<&Root> for [u8; 32] { } } -/// Sprout Note Commitment Tree -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] -#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] -pub struct NoteCommitmentTree { - /// The root node of the tree (often used as an anchor). - root: Root, - /// The height of the tree (maximum height for Sprout is 29). - height: u8, - /// The number of leaves (note commitments) in this tree. - count: u32, +/// A node of the Sprout note commitment tree. +#[derive(Clone, Debug)] +struct Node([u8; 32]); + +impl incrementalmerkletree::Hashable for Node { + /// Returns an empty leaf. + fn empty_leaf() -> Self { + Self(NoteCommitmentTree::uncommitted()) + } + + /// 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> for NoteCommitmentTree { - fn from(values: Vec) -> Self { - if values.is_empty() { - 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 From for Node { + fn from(cm: NoteCommitment) -> Self { + Node(cm.into()) } } +impl serde::Serialize for Node { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.serialize(serializer) + } +} + +impl<'de> serde::Deserialize<'de> for Node { + fn deserialize(deserializer: D) -> Result + 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, + + /// 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>` (and be careful of deadlocks and + /// async code.) + cached_root: Cell>, +} + impl NoteCommitmentTree { - /// Get the Jubjub-based Pedersen hash of root node of this merkle tree of - /// commitment notes. + /// Appends a note commitment to the leafmost layer of the tree. + /// + /// 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 { - self.root - } - - /// Add a note commitment to the tree. - pub fn append(&mut self, _cm: &NoteCommitment) { - // TODO: https://github.com/ZcashFoundation/zebra/issues/2485 - todo!("implement sprout note commitment trees #2485"); - } -} - -#[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]); + match self.cached_root.get() { + // Return cached root. + Some(root) => root, + None => { + // Compute root and cache it. + let root = Root(self.inner.root().0); + self.cached_root.replace(Some(root)); + root + } } } - #[test] - fn incremental_roots() { - zebra_test::init(); + /// Returns a hash of the Sprout note commitment tree root. + pub fn hash(&self) -> [u8; 32] { + self.root().into() + } - // 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() - let commitments = [ - "62fdad9bfbf17c38ea626a9c9b8af8a748e6b4367c8494caf0ca592999e8b6ba", - "68eb35bc5e1ddb80a761718e63a1ecf4d4977ae22cc19fa732b85515b2a4c943", - "836045484077cf6390184ea7cd48b460e2d0f22b2293b69633bb152314a692fb", - "92498a8295ea36d593eaee7cb8b55be3a3e37b8185d3807693184054cd574ae4", - "ff7c360374a6508ae0904c782127ff5dce90918f3ee81cf92ef1b69afb8bf443", - "68c4d0f69d1f18b756c2ee875c14f1c6cd38682e715ded14bf7e3c1c5610e9fc", - "8b16cd3ec44875e4856e30344c0b4a68a6f929a68be5117b225b80926301e7b1", - "50c0b43061c39191c3ec529734328b7f9cafeb6fd162cc49a4495442d9499a2d", - "70ffdd5fa0f3aea18bd4700f1ac2e2e03cf5d4b7b857e8dd93b862a8319b9653", - "d81ef64a0063573d80cd32222d8d04debbe807345ad7af2e9edf0f44bdfaf817", - "8b92a4ec694271fe1b16cc0ea8a433bf19e78eb5ca733cc137f38e5ecb05789b", - "04e963ab731e4aaaaaf931c3c039ea8c9d7904163936e19a8929434da9adeba3", - "be3f6c181f162824191ecf1f78cae3ffb0ddfda671bb93277ce6ebc9201a0912", - "1880967fc8226380a849c63532bba67990f7d0a10e9c90b848f58d634957c6e9", - "c465bb2893cba233351094f259396301c23d73a6cf6f92bc63428a43f0dd8f8e", - "84c834e7cb38d6f08d82f5cf4839b8920185174b11c7af771fd38dd02b206a20", - ]; + /// Returns an as-yet unused leaf node value of a Sprout note commitment tree. + /// + /// Uncommitted^Sprout = [0]^(l^[Sprout_Merkle]). + /// + /// [Sprout_Merkle]: https://zips.z.cash/protocol/protocol.pdf#constants + pub fn uncommitted() -> [u8; 32] { + [0; 32] + } - // 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. - let roots = [ - "b8e10b6c157be92c43a733e2c9bddb963a2fb9ea80ebcb307acdcc5fc89f1656", - "83a7754b8240699dd1b63bf70cf70db28ffeb74ef87ce2f4dd32c28ae5009f4f", - "c45297124f50dcd3f78eed017afd1e30764cd74cdf0a57751978270fd0721359", - "b61f588fcba9cea79e94376adae1c49583f716d2f20367141f1369a235b95c98", - "a3165c1708f0cc028014b9bf925a81c30091091ca587624de853260cd151b524", - "6bb8c538c550abdd26baa2a7510a4ae50a03dc00e52818b9db3e4ffaa29c1f41", - "e04e4731085ba95e3fa7c8f3d5eb9a56af63363403b783bc68802629c3fe505b", - "c3714ab74d8e3984e8b58a2b4806934d20f6e67d7246cf8f5b2762305294a0ea", - "63657edeead4bc45610b6d5eb80714a0622aad5788119b7d9961453e3aacda21", - "e31b80819221718440c5351525dbb902d60ed16b74865a2528510959a1960077", - "872f13df2e12f5503c39100602930b0f91ea360e5905a9f5ceb45d459efc36b2", - "bdd7105febb3590832e946aa590d07377d1366cf5e7267507efa399dd0febdbc", - "0f45f4adcb846a8bb56833ca0cae96f2fb8747958daa191a46d0f9d93268260a", - "41c6e456e2192ab74f72cb27c444a2734ca8ade5a4788c1bc2546118dda01778", - "8261355fd9bafc52a08d738fed29a859fbe15f2e74a5353954b150be200d0e16", - "90665cb8a43001f0655169952399590cd17f99165587c1dd842eb674fb9f0afe", - ]; + /// Counts the note commitments in the tree. + /// + /// For Sprout, the tree is [capped at 2^29 leaf nodes][spec]. + /// + /// [spec]: https://zips.z.cash/protocol/protocol.pdf#merkletree + pub fn count(&self) -> u64 { + self.inner.position().map_or(0, |pos| u64::from(pos) + 1) + } +} - let mut leaves = vec![]; - - for (i, cm) in commitments.iter().enumerate() { - let mut bytes = [0u8; 32]; - let _ = hex::decode_to_slice(cm, &mut bytes); - - leaves.push(NoteCommitment::from(bytes)); - - let tree = NoteCommitmentTree::from(leaves.clone()); - - assert_eq!(hex::encode(<[u8; 32]>::from(tree.root())), roots[i]); +impl Default for NoteCommitmentTree { + fn default() -> Self { + Self { + inner: bridgetree::Frontier::empty(), + cached_root: Default::default(), } } } + +impl Eq for NoteCommitmentTree {} + +impl PartialEq for NoteCommitmentTree { + fn eq(&self, other: &Self) -> bool { + self.hash() == other.hash() + } +} + +impl From> for NoteCommitmentTree { + /// Builds the tree from a vector of commitments at once. + fn from(values: Vec) -> Self { + let mut tree = Self::default(); + + if values.is_empty() { + return tree; + } + + for cm in values { + let _ = tree.append(cm); + } + + tree + } +} diff --git a/zebra-chain/src/transaction.rs b/zebra-chain/src/transaction.rs index 2f677c47..2a59dbb3 100644 --- a/zebra-chain/src/transaction.rs +++ b/zebra-chain/src/transaction.rs @@ -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 + '_> { + 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 + '_> { // This function returns a boxed iterator because the different // transaction variants end up having different iterator types diff --git a/zebra-chain/src/transaction/joinsplit.rs b/zebra-chain/src/transaction/joinsplit.rs index 523c062a..bb4fe528 100644 --- a/zebra-chain/src/transaction/joinsplit.rs +++ b/zebra-chain/src/transaction/joinsplit.rs @@ -23,7 +23,7 @@ pub struct JoinSplitData { /// /// Storing this separately from `rest` ensures that it is impossible /// to construct an invalid `JoinSplitData` with no `JoinSplit`s. - ///` + /// /// However, it's not necessary to access or process `first` and `rest` /// separately, as the [`JoinSplitData::joinsplits`] method provides an /// iterator over all of the `JoinSplit`s. diff --git a/zebra-test/src/lib.rs b/zebra-test/src/lib.rs index 7516ec81..b9f841bb 100644 --- a/zebra-test/src/lib.rs +++ b/zebra-test/src/lib.rs @@ -8,7 +8,7 @@ #![deny(clippy::await_holding_lock)] #![forbid(unsafe_code)] // Each lazy_static variable uses additional recursion -#![recursion_limit = "256"] +#![recursion_limit = "512"] use color_eyre::section::PanicMessage; use once_cell::sync::Lazy; diff --git a/zebra-test/src/vectors/block-main-0-000-396.txt b/zebra-test/src/vectors/block-main-0-000-396.txt new file mode 100644 index 00000000..1dea9241 --- /dev/null +++ b/zebra-test/src/vectors/block-main-0-000-396.txt @@ -0,0 +1 @@ +0400000072d43a7895d2774d081ccee922a81d77a8879f46a8aa86d65aebd38e9800000012f742113a92b8f55a2474017e30d09ba7f0b3d9e144db761a77e08d0ec476640000000000000000000000000000000000000000000000000000000000000000868c1358a376011e9e02000000000000030000000000000000000000000000000000000000000000fd4005002b983cb99149af0d05b36f45cc2f4d47af59f79d0253cc3ce8395065da1862fc31a5268d5663f0eccd02dee3b544c93fc45900e65d13466f12c039bbff5a076ed18698035914244c108f8355494df71bbb3c7d084293d3838913485b45a1553f42fc7208ae7d7350299c82639bec1a8fd4e8286a73e3750732e0fb05ad08fd80ceca44dd4d53c581356d4e572cfabff96a4c19248c3a87eab33becee256903ded2916cb3accd75042caf1981510fc1c83476b17e66d9be8f04b9dd8d0d75a9d95bcf3367e023b45f8eb06ce9c3f1d7a16013f4b70bfdcaf3530e3177a8c1f5f901fab59a5a771c31e57c8ece507d729a33383358fc7126093bb7d011d7f392235431292fadf2ffd8a929aa95f957503279afa40ef229290996c6ba136a752b5eef96178754461b9d22a51703bf99dbf710f9ec61e1ccb61828cb52ab04564cb434fbd09e79308d4b4ab27bd5b4838b02a0d77023c54e0cf43e44dfadd75392817c75177219c70c7635cbdcf111117780dec878b785a35e1329118ade96dd4dea211784c16d72dbe14f62935fc66c2ebafffc59cf2488e28ada5e586a9f5752d57e5d3e07ab7b332f90a585ffe72164abece07db6cdf1fc961335f3360d1e3c81d490126bcde56a5e16eb56e6f0126bf44358574843b586984a29f6f2163bb7f217be271dbae0142c7f697768f8d2cb54195e5ef675dab203998961afec029dccaa21d16069267630ef7c00881fa7c2bb761b9c2cdfa8a29d40c1db875b8d7d55f008ef8081a8cfbde7632330ebe0281a0092b5507d6c275269400453a46cb2ae27ccf1e18f3622daf9af3f048803cd27cdd72d287af371e921cbf708eab933a0151725a5a3f1e331b378c348594f0d319faf51dd200ed463245f175d5be68aa1a9804812d94a7334f9bc31bb09ce11d1341712e408069dc6c39789c3de18eb00f8af8e010aa8af5407d3a16ca2c59d59abf522391453992e2e18ad57f55f716d80d9c0e9710e4bd79014be01c0792199274b058386a8ebe9067de29cac0221859174079b8b2311c75320a8c96023b9c01e992601b75cfe8090a65356bcf5ec7ac4c5a69f50794f310b2408cdf10f9d9ec85183f4de6c1ba1da0bd99c9a3180eaabb4b50d11b93775d03fce9289818d4d7bf236c25675a49ba3dae9ca9b0561eebd4785cd1c829f01c2a79bdb8a96c0d2a7719dc9a6c8ec9ec6d70cbf56ab1623a5dab59cf74ac9874f7275b705747846881bcf86c8c824ca3944fa31c8e7b88cad2c5309ae0d4b8796a5f8570916fb3c858cdb60f697ae855e532210759d6aa7050fd4df4c56852dccc1fad5d91cd1ce2aa1719319a4951f31280aa33ddfe4fac2fd9b554e125f02ada0cb6906f85666cb07cd0036661859d428166d17c7c7e1c011c5c0366a61ded942898b37583001f0b2cbe66285a730a4d2e4ed7eaa53a85d3e2f4a0493f95927194d1bc80a38710aeb620e7bebfce81b4c14c744d8f7c131e645861c6abe1f72ba50798b655cd94fa7129a9e0fc0cb58f439d0b65ec9eedee0f00366cd8145503c9f1936b084f435f494488a45d19530f574b6de8fe6d32e2b43948e6ae8561a185772aa18d586163554019383abb2ea02482e82ffd59b4ee01aa7c414a09e9071e2a501ad56c9a410f352d1713103fd94762e023aad97a75086a6c49f2d923e8f15640e70117a01969320fb1992d4bbb9c1bef33c7a22d013ee35a896e46d038179a21ad31bc8a5097150d4da2bfc8f1c16e771d1b5bd7419d4c839a76faf5edeba151603821c52284f7722b3c8125e5c3d3901768fb65bf6d51f3fad0277a6eee79b60d8c7f38f995f2b5a1ed3d677fef0e417deacf8d2707f1c265fedf9750938625fc25628cef592aad4dd3ce54ae1f147de71370201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff06028c0102f401ffffffff02c01f2e01000000002321033bf6b16c6987b017991932dc66dc96ccdbde81e4c0b2ea086d246349bedde903acf0874b000000000017a9147d46a730d31f97b1930d3368a967c309bd4d136a870000000002000000010a141a3f21ed57fa8449ceac0b11909f1b5560f06b772753ca008d49675d45310000000048473044022041aaea8391c0182bf71bd974662e99534d99849b167062f7e8372c4f1a16c2d50220291b2ca6ae7616cd1f1bfddcda5ef2f53d78c2e153d3a8db571885f9adb5f05401ffffffff0000000000011070d900000000000000000000000000d7c612c817793191a1e68652121876d6b3bde40f4fa52bc314145ce6e5cdd2597ae7c48e86173b231e84fbdcb4d8f569f28f71ebf0f9b5867f9d4c12e031a2acc0108235936d2fa2d2c968654fbea2a89fde8522ec7c227d2ff3c10bff9c1197d8a290cca91f23792df8e56aed6c142eaa322e66360b5c49132b940689fb2bc5e77f7877bba6d2c4425d9861515cbe8a5c87dfd7cf159e9d4ac9ff63c096fbcd91d2a459877b1ed40748e2f020cdc678cf576a62c63138d820aba3df4074014bb1624b703774e138c706ba394698fd33c58424bb1a8d22be0d7bc8fe58d369e89836fe673c246d8d0cb1d7e1cc94acfa5b8d76010db8d53a36a3f0e33f0ccbc0f861b5e3d0a92e1c05c6bca775ba7389f6444f0e6cbd34141953220718594664022cbbb59465c880f50d42d0d49d6422197b5f823c2b3ffdb341869b98ed2eb2fd031b271702bda61ff885788363a7cf980a134c09a24c9911dc94cbe970bd613b700b0891fe8b8b05d9d2e7e51df9d6959bdf0a3f2310164afb197a229486a0e8e3808d76c75662b568839ebac7fbf740db9d576523282e6cdd1adf8b0f9c183ae95b0301fa1146d35af869cc47c51cfd827b7efceeca3c55884f54a68e38ee7682b5d102131b9b1198ed371e7e3da9f5a8b9ad394ab5a29f67a1d9b6ca1b8449862c69a5022e5d671e6989d33c182e0a6bbbe4a9da491dbd93ca3c01490c8f74a780479c7c031fb473670cacde779713dcd8cbdad802b8d418e007335919837becf46a3b1d0e02120af9d926bed2b28ed8a2b8307b3da2a171b3ee1bc1e6196773b570407df6b43b51b52c43f834ee0854577cd3a57f8fc23b02a3845cc1f0f42410f363d862e436bf06dbc5f94eddd3b83cdf47cf0acbd7750dff5cba86ea6f1f46a5013e0dc76715d7230e44a038a527cb9033f3eeaeac661264dc6a384788a7cd8aed59589bca6205fe1bd683fa392e7a3c6cc364bba36ad75ee9babf90f7b94071953df95effc0b1c3f542913ed1eb68e15534f9ceb7777c946edf55f129df128c3f767d8d60c4aa0c5e61d00f8e495e78334e2a9feddd9302e9880cb6174d201c89a1d6bc6e83a80cbf80ab3959dcc6cdd12e3d2f6f14d226e6948954f05544941d16ed1d498532722fa39bb985c3224915dd42d70be61217fdcb4aa023251af38b5576ff9eb865a471f2cb2dbc674e401d18014e6119464768778ddcd00907f20279bdecda3880fbbb4d00bb6c5aa3e06113a2f12fcc298f34ccb6bc2c2887b0b064f3bc2e2b507d31e022e65800dd7d30f25266914646bfc07c1eafbbf1e1163c439774b47e8e844799bc8fd06db050f97f5c74ca833e81bcdcf9d864be5746f965ef41838a3535666df867ef79e07068dc7ef809fb0e08e1629bab3215fe36d0f0e0f8c6bb319f93a0f408ff4abbd88c21afaec2e7720674eaceb27efb9144f619bad6f033cbefcebfbe66cabe8286f2ff97b91f4aeef5cbd99a9b862cb904dc085d96238caaad259280ff35caa211e00324f51ff03b6a1cd159cd501faef780ef7f25a98cdcd05ef67596d58d4aea1f9f3e95aae44fd4d4ea679c5e393d4670fb35bf12d036ea731bdfad297303239251a91f9a900e06987eb8e9f5bb1fb847f5ae47e6724ddeb5a3ac01b706a02e494c5547ce338302b4906cf2c91d59a87324322763a12e13a512ace3afb897510ad9ec95aa14ca568a9962da64e5bc7fd15b3e103ab461ee7db3fc9da0a523fc403c11254cd567ca48c8dac5e5b54953e5c754e31def90fff6c56d589a5c4b9a710ccb43cd24988b2fb9336b5508aa553cfdbd1f32dfb4ff16eae066b5fb244bc9058a91898c4ae893eaf0006dae1185c7f553e6e09d12a0a2a9c181c5e4d87c8895b74b0e23a8dc87faf5d6acd5e98cb1df5585f026ae94b77db0e95c5fe22692bd2e70e8e87d07d92b98cdfcc5367e52014163a6e4511d482816259215ee7df246e493523ee51617c318e1a9825f82e73e640fbc2d25c12ce5a07875d489db6a111afdc87061047077030d32de45cd4e575c02a60c4048560bd02cf9203426f589f429b413390ace832b3ddd3dd371750d94f9c34f60a0f1b621b445525d2190a185feaab9e56a079c46236161559713d585a07e94f2316a92fffa7838f1aea39d7846638d16f9b4d1a7dc053e0ddc6620f30e3e798eba900fd25c10c5d6672c9ed7d4d2fa80c0f0137ff24933c37fcd91b19bc7cdd828f7f3f1df0e45cafca795d847e83bca8baa321006581b024306e24c4c2294c0f41b932c1e9f7602f377e8484c7eeb184fab1f747b1dff5b6e2e89f1e5c4232b5a0a41ed6a3775f8942217078b7e035747891cabd2099bfcbf6a8d4680f51265d9e7d05794514f02470e0eb003ad1222cd4fe8bcd077310c5aff274b19608c31f77453d01c9aa9c21a8d9b71de44386aee2145648f7ead471cabed297b8610bba370baa42603f21f5f4640e5bc1a0402d40394e176a0db8cedb33a9d84c48b58d3851617046511946a3700aabe8f69cdb0469ee67776480be090cad2c7adc0bf59551ef6f1ac3119e5c29ab3b82dd945dab00dc4a91d3826c4e488047a4f3ab2d57c0abe1ee7aba304784e7ad211c32c4058fca7b1db2e282132e5ccafe79fc51ab37334f03715f4ad8735b6e03f01 diff --git a/zebra-test/src/vectors/block-test-0-002-259.txt b/zebra-test/src/vectors/block-test-0-002-259.txt new file mode 100644 index 00000000..10c4fc8c --- /dev/null +++ b/zebra-test/src/vectors/block-test-0-002-259.txt @@ -0,0 +1 @@ +04000000fe1f034ecc1bc4474a86175de46bd1914cbcf51d7c9ff0aa2e15c45ef61b0100e7882ce51bfd6ef43115108a4306c1211c934ac98b81a8e1abb79491d5a0fcbd000000000000000000000000000000000000000000000000000000000000000010071858eb73011f0500000000000000959486b1c52d3412445c42ed000000000000000000000000fd40050040424dc3949ab5f3a2826131d7a17154a1b492e131521bcb579b14352c3a45917c7d408584015c0da40592d400d899e2e7dde4d0d506399f45704c18c00620e731c69dd13412fd1e42a74f6a28455e9a51a92206cc6e330a9aaa5b93be74ae1ce3e13568bcf9571c08833205c3c3cf4beb71b0d5f3556adc43fce9a09e2e9ff7126f530244eef4763e70773e462cdebabda736dd7302fb507feca14fa7b8c05d2e6642685790f009ace34bd0cf6e227b85211461a5cfd4ac241a46023843a3512d63ed5f62d6d44c9872274791775f8bcf366f76e9916ecf77bd737885124bb4dec6163688553bb096f4b92a00cf5620f4cb61c672a68e189ea8d5120f8647b1d23fcef3edc196817b9c81a7a17719a718bcbedde415e4e169cf65d1f2583e8e62aa9acc4215136530cc6533a9429d81598bde590ab65a79ce933f82dc533e9776fcef9544ffddcb59795794f9b012025ecb8066aa7935e7b9b281d63d947992659308b1229a3ec1101a9f2d0193c6622a6cb849d7b95a1fb20be5c74dda53c789875df3519bfe55aa5656d6b3c827b239b80cd41617469976d0b37b0b474e5edad73b03a4d6cf51b62363e1d7c11e81a523ccd48e0dc892113607961f9314d9cd9615b2017efa59a71a1284fb086a69afa25adadeed8aa32a47fcd3ed50a853ec7e39926b7daf1ba408e5f3a620f6d3917e3b0012d1e6028edef19b4c6607f976b7f564dfd52242c6376c474ae93dfe269b8d1fb70e358227b9f0160185b87af50365aa76e001ae49439177ca8cca69c3c568feb2c9184591a50f90790ce94122ab5364a6911c99bab2790b415b8b6c933b6ad3a4b28997a93ae8d3ceb66ca01ee92bb9e3dffa4d948552e3af2e58ca9329b727c8120d25e1d9d9a1292847d13107c163ea8eda5843dc2093cca91817526ba64db50d09de8f86d99f7828e5015419b40e47a5328828f4710ae9a28a6943bb6e981b1dc723434cdd79d9c9a6b318f6e475d5416fe63e1333315fe99737fdc5adf3d75eab0065265619a8b53f90e5f7ac5cede6e7fdf639e7ff9b8a035a3eaacf019969305e5b2166f2ee301b18c435702d98d26e4b1d3555dfd8552e3193319409b5e64d39245d6beab908583a093ecf061f250a57050fee6f8e0ded725eef0f5221f9534deae27dd00187b33094013c93f8fce10c0aee0fc33328f1d725c3c664ac90e3964d5cea6d17f74c34ea8f980fa3ed142db4b20bca2251568f8b43d5be2e00a4a067688754cfa354ac5db7b2dfbfda5c08ee836ae91f494a6077e2f1ff983e7b73fb9ee0163d6ed9005c9cff7a1d12e3ee6492223eff98f81732cb6bca8558721f3db1b5f880cf50e1e2cab4b902303146370a60bf5dcdbbb32bbacd3eb5708498e504417196bea231f9b7cabf756488e9587afd1e39e720050a6f90ad5a83031f8ae0994ed4cd3934dc8b978f2ad1fdf20fa2a765b533d3e034c6bef51bbd976489100f9569d2a0b2e9211404adaabbf89eda11780b3c15d6cf06f3a8b609fe8ec3a222fb825509efb425da0fefb174a48acb6a68f5b2a9d0335c766577337e703a18dbae435e71b39a8dc4419a51dd69e9f51622f72820f4e05258275180c714965948f3e528271b84ad4b2f8f6ae7b4737fc51a24fd2e71e01582497baa450f9f0a6f8155a8f0d491b2459c3d5a24ddccf237672ba962a0521751bd84d794f43827c09f196019d18c36cad309584e03bff6d5d6f959555926052e3cc6e94b967d4f70162da8b48d769cc5d6f30a2c2dd7ce4a128ddd4b4086fdeef3c154542b7a8cdd7461d4a79b2a57cfe123cebada96a9f5e14473416daf815ac97c1ecced32158fdb72b714542857d6918b2d15ff679b4f9bbecfcf352909e832c69dab69ab3f320fbc5a9d0201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1002d3080c4e060000000000002f4e614effffffff0200a2bb06000000001976a9144c275e3800739237500ed7c5ee428b9f05909a2e88acbcdeae010000000017a914ef775f1f997f122a062fff1a2d7443abd1f9c6428700000000020000000118e2d738377bc9834a1913df08745863554abc25629444b2598b210ac7e22d170000000049483045022100df915d5c9f109fe5363a919dbf80bc9bc3c49bd55869a4715a1fb758dd5a0bbc0220023962c7eb41800e84bcc27ffbe16489d7722e0a56f0de8a0bf356cd3c71f4fe01ffffffff000000000001b09a7805000000000000000000000000d7c612c817793191a1e68652121876d6b3bde40f4fa52bc314145ce6e5cdd259a02f29c66bc482751eeecfe2f675808605729f7592ca669e06d7ee8538ede06241a6f82ca60851a7078589c372b8e2ad81b2c793365bde33d951555589bee5ec452a59f604f4adc8a9d5b5590d84f950d67bc24f61e7c129c627086b7ca2b662f1b36ebda930a97175c75daac4ba53f2e3f262050b94384c5adfd7d2ec9f84f00930920d0075d904fb00fed5299917fd7292d791326dcfdb5def03dc6ae6851dd6b6e70493d52270d9e8daa5e55723e94e6fdd7ab4ca646db24fa7e56c44e21d80ae334215ad4803d1962c69e60943db894cc57900b5d042731cd00b02e067195af115312d6d558fa216fbe4812b9dbee3e4aadde4c1338ca8c9b96adf3227920305885e2c145f3cb89c660c7c110a3e2a8f23bc96a71fb5617346c2339b4448a4031fc52697ea2098014c4d4c8fff79e0208231ff7ba4b2fab51e112fb0677f587c0b015cd691f8bfb4b781573dceef2f923aa2570266ccc06b29c5eb72d81be477ce6dce3015a01a8dfe4db962140e437f8a7ec00c7371a05408ecf9a2a3502fa587021a690246b9707d41dea48904ae054a4abcf5fba709e55c52da9d9f4688ae2999020f5483b48fd88b2c0fcf92205ab484daf9f048e7d10a953ff6ac1f7cfddb4cf4022de5e1bb110ee864320b55762b993eed47b88fa84cb279498ba7107b425e62be022e0a2bd5ceaa6f2e20423751484f0a3935132eedd2b2dceb97cfa57d203e307b031f477cc34457814cab9d7ddf814a8268f7d48573d955679d619b45ca03a1a447bc16cd320d18eaf0145d9d10e307bde83d0d7995ae3d0d81ceef3bbeef43c1489be1a8c78274d9c642ffb325b46f160fd88cce21fd9aac22b797ee37a613e577b232f314cbeacbe9faf80689ea20687adddb49430b616105e4edaaa229f5088d2994fed3e929d77cd4aa5e6cbc0a4290ba63f248399584afaf15df397df6a92f8d77d864a73fcb985c48934369a8d05376931aba7763226cc8be5648004859da1f296d735acbbd2fb1f52a6b03f87403cbe4960591d7dd097c3b456252d62c7b3f99c1cff1a59349a824e5775b0d8fd92a2822be602312895de6ab91561e094d129341535b8979b17e2a7ea258ad0fd890ef70f58baf588c531c93769492628c3cbda12278edd8bc784947ca125e0dfee656a491885be659f8f3c633d7305bfc94897b41bdb7bba8d90e1d153369d40492d74f20caefe7c64453c6d1264c8344c4ab00c10b56565a44eb4b6cd6ec7577962c42b78651024377a0e8bbc22b5aad3ff1319e3761964a7bfc741714afcb24281fc0e9e5c2193d4609d30fe39423b60d5177ac1d04383ccda5d885ba5cc48fc5d86efe3e930c03c48774c49bc94c11853841d54cbc954bdc8f78f8df586d64a7ffd69f2cbb7c6660bb9913dc971049e580cf7c555fc0c40e3e86e91229b3ee446843b3a7457aa8d5fe8c2f83aa99c77f69853b5d29a1c650a032793bb8b2cf9d1516e10878a076a2b81e2012430f677ab4ff25672d939b11285b47d0e36fcd9ae14c331d6e4d0550ff096b0855c6b6bbfd0f0d48cb19602b57652ab48c5ea18f08d7800f3edd37cf2c499443c9533d3e155b332f13e389cfae345e27a4211a951711f4b734490d052c48456a7588cc07f93ca553096b11c621a6f564fa831238474c3a79862508b6272fa2e05ececa68f07bb18dbcfc5ccac5eecfaaf9894a624d3911e4effb2ddf19a562e4d1fe30ea242644d9ffa053d8c9e37bee25be29fa7cf24e0c3424592407cd7579c50564730d6e819fd68cd57e4c682a7bc33c779ea082b457c6ed8aa7857a683957f0559675022f2be9966448949a13e6edf0d218062c38667340bd5693fea45c23dfd4ec0bae597c1b961ca1775c0832c56c22187e63d914e24dc49df12e5260db14f3c462300fbe9bf36ede2e177fcfa889a18874a568dac519fc3521dc727f2bfde7ae973a1408f105056c5e064ab00d27cc478f1717db16c4808d8d5de4e74192147a11c223d76693f633ee1f3b95bc5c395ff4dc91d0e637c8f029b28d1014cf6827fa8a2e5e651a5c0660936f5e79b3269551313b44a4baa1cb6c002d8f9496c8a8321195ef424d6eff17354abcc9596c78fb4b7e8ac7f117fc412faf1713491071be6f6d692fb58e46a77dee09a03aad1cdb7651f5c1aeb19a32a744449e5f376be7e4b02c4ec15574713ac32ca34325f8715c3b9cb542d6af8ea34ac224a3b1105056efb599bd6da0c937f27dc3d03574b2c0854f7fbb10255fe8450703dfd45aecf5e26d40d9bce8f4aeec0494b615f6d08e562203a5c67dda242ab37a2555f0ee7bdb8adb203eb9d0d606501ea9ffe21ba24620da8a446ccd69fa2e561353501defbde067a44bbb92c5a69a6199fd952896bc1fb0f2c86b602c2e0f765ddd8c862d723e12964964d2ff70b2d738881cc358a7b7bceb964fa135391a6551bbf0ac829750030b78efb87e7ce9d5b3320becb7e2d87ebea74312c38edb774d35134f8e21394a212fe410142427c7b473265d8b06b926d619ead780b0769e5997ded93f9851fd0efd4b667afc5bcc2792b26cd4a565b4efa7733535fdc09fa566ca59042785d7fd8043d37fdf9e144465080a diff --git a/zebra-test/src/vectors/block.rs b/zebra-test/src/vectors/block.rs index 170bfafe..beba87af 100644 --- a/zebra-test/src/vectors/block.rs +++ b/zebra-test/src/vectors/block.rs @@ -20,7 +20,6 @@ impl ReverseCollection for [u8; 32] { } lazy_static! { - /// All block test vectors pub static ref BLOCKS: Vec<&'static [u8]> = MAINNET_BLOCKS .iter() @@ -61,6 +60,7 @@ lazy_static! { pub static ref MAINNET_BLOCKS: BTreeMap = [ // Genesis (0, BLOCK_MAINNET_GENESIS_BYTES.as_ref()), + // BeforeOverwinter (1, BLOCK_MAINNET_1_BYTES.as_ref()), (2, BLOCK_MAINNET_2_BYTES.as_ref()), @@ -73,37 +73,62 @@ lazy_static! { (9, BLOCK_MAINNET_9_BYTES.as_ref()), (10, BLOCK_MAINNET_10_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()), + // Overwinter (347_500, BLOCK_MAINNET_347500_BYTES.as_ref()), (347_501, BLOCK_MAINNET_347501_BYTES.as_ref()), (415_000, BLOCK_MAINNET_415000_BYTES.as_ref()), (419_199, BLOCK_MAINNET_419199_BYTES.as_ref()), + // Sapling (419_200, BLOCK_MAINNET_419200_BYTES.as_ref()), (419_201, BLOCK_MAINNET_419201_BYTES.as_ref()), + // A bad version field (434_873, BLOCK_MAINNET_434873_BYTES.as_ref()), (653_599, BLOCK_MAINNET_653599_BYTES.as_ref()), + // Blossom (653_600, BLOCK_MAINNET_653600_BYTES.as_ref()), (653_601, BLOCK_MAINNET_653601_BYTES.as_ref()), (902_999, BLOCK_MAINNET_902999_BYTES.as_ref()), + // Heartwood (903_000, BLOCK_MAINNET_903000_BYTES.as_ref()), (903_001, BLOCK_MAINNET_903001_BYTES.as_ref()), + // Shielded coinbase x3 (949_496, BLOCK_MAINNET_949496_BYTES.as_ref()), (975_066, BLOCK_MAINNET_975066_BYTES.as_ref()), (982_681, BLOCK_MAINNET_982681_BYTES.as_ref()), + // Last Heartwood (1_046_399, BLOCK_MAINNET_1046399_BYTES.as_ref()), + // Canopy and First Coinbase Halving (1_046_400, BLOCK_MAINNET_1046400_BYTES.as_ref()), (1_046_401, BLOCK_MAINNET_1046401_BYTES.as_ref()), (1_180_900, BLOCK_MAINNET_1180900_BYTES.as_ref()), ].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 = [ + // 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 /// /// Pre-sapling roots are all-zeroes. @@ -151,6 +176,8 @@ lazy_static! { (8, BLOCK_TESTNET_8_BYTES.as_ref()), (9, BLOCK_TESTNET_9_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 (141_042, BLOCK_TESTNET_141042_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()), ].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 = [ + // 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 /// /// Pre-sapling roots are all-zeroes. @@ -280,6 +317,10 @@ lazy_static! { pub static ref BLOCK_MAINNET_202_BYTES: Vec = >::from_hex(include_str!("block-main-0-000-202.txt").trim()) .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 = + >::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 /// coinbase height. @@ -287,6 +328,41 @@ lazy_static! { >::from_hex(include_str!("block-main-0-000-202-bad.txt").trim()) .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 // for i in 347499 347500 347501; do // 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 = >::from_hex(include_str!("block-test-0-000-010.txt").trim()) .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 = + >::from_hex(include_str!("block-test-0-002-259.txt").trim()) + .expect("Block bytes are in valid hex representation"); // A large block // i=141042 // 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.) pub static ref BLOCK_TESTNET_141042_BYTES: Vec = 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 // for i in 207499 207500 207501; do // zcash-cli -testnet getblock $i 0 > block-test-$[i/1000000]-$[i/1000%1000]-$[i%1000].txt