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:
parent
ad81718514
commit
8963007397
|
@ -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:
|
||||
|
|
|
@ -1 +1,5 @@
|
|||
//! Sprout tests.
|
||||
|
||||
mod preallocate;
|
||||
mod test_vectors;
|
||||
mod tree;
|
||||
|
|
|
@ -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];
|
|
@ -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(())
|
||||
}
|
|
@ -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.
|
||||
///
|
||||
/// <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] {
|
||||
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<Vec<NoteCommitment>> for NoteCommitmentTree {
|
||||
fn from(values: Vec<NoteCommitment>) -> 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<NoteCommitment> for Node {
|
||||
fn from(cm: NoteCommitment) -> Self {
|
||||
Node(cm.into())
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
/// 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<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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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> + '_> {
|
||||
// This function returns a boxed iterator because the different
|
||||
// transaction variants end up having different iterator types
|
||||
|
|
|
@ -23,7 +23,7 @@ pub struct JoinSplitData<P: ZkSnarkProof> {
|
|||
///
|
||||
/// 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.
|
||||
|
|
|
@ -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;
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -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<u32, &'static [u8]> = [
|
||||
// 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<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
|
||||
///
|
||||
/// 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<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
|
||||
///
|
||||
/// Pre-sapling roots are all-zeroes.
|
||||
|
@ -280,6 +317,10 @@ lazy_static! {
|
|||
pub static ref BLOCK_MAINNET_202_BYTES: Vec<u8> =
|
||||
<Vec<u8>>::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<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
|
||||
/// coinbase height.
|
||||
|
@ -287,6 +328,41 @@ lazy_static! {
|
|||
<Vec<u8>>::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<u8> =
|
||||
<Vec<u8>>::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<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
|
||||
// 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<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
|
||||
// for i in 207499 207500 207501; do
|
||||
// zcash-cli -testnet getblock $i 0 > block-test-$[i/1000000]-$[i/1000%1000]-$[i%1000].txt
|
||||
|
|
Loading…
Reference in New Issue