-
Notifications
You must be signed in to change notification settings - Fork 111
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <[email protected]> * Refactor rustdoc Co-authored-by: Deirdre Connolly <[email protected]> * rustdoc * Rustdoc * rustdoc links * Oops, need the trait in scope to use it * Avoid accessing the wrapped hash directly Co-authored-by: Deirdre Connolly <[email protected]> * 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 <[email protected]> * Refactor rustdoc Co-authored-by: Deirdre Connolly <[email protected]> * rustdoc * Rustdoc * rustdoc links * Oops, need the trait in scope to use it * Avoid accessing the wrapped hash directly Co-authored-by: Deirdre Connolly <[email protected]> * 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 <[email protected]> Co-authored-by: Deirdre Connolly <[email protected]>
- Loading branch information
1 parent
ad81718
commit 8963007
Showing
11 changed files
with
600 additions
and
189 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,5 @@ | ||
//! Sprout tests. | ||
mod preallocate; | ||
mod test_vectors; | ||
mod tree; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(()) | ||
} |
Oops, something went wrong.