diff --git a/Cargo.lock b/Cargo.lock index 8f52415c..463aaf2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,17 +77,6 @@ dependencies = [ "cpufeatures", ] -[[package]] -name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom 0.2.10", - "once_cell", - "version_check", -] - [[package]] name = "ahash" version = "0.8.3" @@ -550,12 +539,12 @@ dependencies = [ ] [[package]] -name = "bs58" -version = "0.4.0" +name = "bridgetree" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" +checksum = "3a813dadc684e4c78a4547757debd99666282545d90e4ccc3210913ed4337ad2" dependencies = [ - "sha2 0.9.9", + "incrementalmerkletree", ] [[package]] @@ -1072,9 +1061,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" +checksum = "109308c20e8445959c2792e81871054c6a17e6976489a93d2769641a2ba5839c" dependencies = [ "cc", "cxxbridge-flags", @@ -1096,15 +1085,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" +checksum = "882074421238e84fe3b4c65d0081de34e5b323bf64555d3e61991f76eb64a7bb" [[package]] name = "cxxbridge-macro" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" +checksum = "4a076022ece33e7686fb76513518e219cca4fce5750a8ae6d1ce6c0f48fd1af9" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.29", @@ -1211,15 +1200,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "directories" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" -dependencies = [ - "dirs-sys", -] - [[package]] name = "dirs" version = "5.0.1" @@ -1764,7 +1744,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.3", + "ahash", ] [[package]] @@ -1773,7 +1753,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" dependencies = [ - "ahash 0.8.3", + "ahash", "allocator-api2", ] @@ -1792,14 +1772,15 @@ dependencies = [ [[package]] name = "hdwallet" -version = "0.3.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cd89bf343be18dbe1e505100e48168bbd084760e842a8fed0317d2361470193" +checksum = "5a03ba7d4c9ea41552cd4351965ff96883e629693ae85005c501bb4b9e1c48a7" dependencies = [ "lazy_static", "rand_core 0.6.4", "ring", - "secp256k1", + "secp256k1 0.26.0", + "thiserror", ] [[package]] @@ -1850,6 +1831,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "hostname" version = "0.3.1" @@ -2045,11 +2035,11 @@ dependencies = [ [[package]] name = "incrementalmerkletree" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5ad43a3f5795945459d577f6589cf62a476e92c79b75e70cd954364e14ce17b" +checksum = "2eb91780c91bfc79769006a55c49127b83e1c1a6cf2b3b149ce3f247cbe342f0" dependencies = [ - "serde", + "either", ] [[package]] @@ -2089,7 +2079,7 @@ dependencies = [ "console", "instant", "number_prefix", - "portable-atomic 1.3.3", + "portable-atomic", "unicode-width", ] @@ -2099,7 +2089,7 @@ version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fb7c1b80a1dfa604bb4a649a5c5aeef3d913f7c520cb42b40e534e8a61bcdfc" dependencies = [ - "ahash 0.8.3", + "ahash", "is-terminal", "itoa", "log", @@ -2291,6 +2281,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "known-folders" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b6f1427d9c43b1cce87434c4d9eca33f43bdbb6246a762aa823a582f74c1684" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -2478,26 +2477,15 @@ dependencies = [ "nonempty", ] -[[package]] -name = "metrics" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b9b8653cec6897f73b519a43fba5ee3d50f62fe9af80b428accdcc093b4a849" -dependencies = [ - "ahash 0.7.6", - "metrics-macros 0.6.0", - "portable-atomic 0.3.20", -] - [[package]] name = "metrics" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fde3af1a009ed76a778cb84fdef9e7dbbdf5775ae3e4cc1f434a6a307f6f76c5" dependencies = [ - "ahash 0.8.3", - "metrics-macros 0.7.0", - "portable-atomic 1.3.3", + "ahash", + "metrics-macros", + "portable-atomic", ] [[package]] @@ -2510,24 +2498,13 @@ dependencies = [ "hyper", "indexmap 1.9.3", "ipnet", - "metrics 0.21.1", + "metrics", "metrics-util", "quanta", "thiserror", "tokio", ] -[[package]] -name = "metrics-macros" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "731f8ecebd9f3a4aa847dfe75455e4757a45da40a7793d2f0b1f9b6ed18b23f3" -dependencies = [ - "proc-macro2 1.0.63", - "quote 1.0.29", - "syn 1.0.109", -] - [[package]] name = "metrics-macros" version = "0.7.0" @@ -2548,7 +2525,7 @@ dependencies = [ "crossbeam-epoch", "crossbeam-utils", "hashbrown 0.13.2", - "metrics 0.21.1", + "metrics", "num_cpus", "quanta", "sketches-ddsketch", @@ -2829,9 +2806,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "orchard" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6f418f2c25573923f81a091f38b4b19bc20f6c92b5070fb8f0711e64a2b998" +checksum = "5f4e7a52f510cb8c39e639e662a353adbaf86025478af89ae54a0551f8ca35e2" dependencies = [ "aes", "bitvec", @@ -3156,15 +3133,6 @@ dependencies = [ "universal-hash", ] -[[package]] -name = "portable-atomic" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e30165d31df606f5726b090ec7592c308a0eaf61721ff64c9a3018e344a8753e" -dependencies = [ - "portable-atomic 1.3.3", -] - [[package]] name = "portable-atomic" version = "1.3.3" @@ -3911,10 +3879,19 @@ version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c42e6f1735c5f00f51e43e28d6634141f2bcad10931b2609ddd74a86d751260" dependencies = [ - "secp256k1-sys", + "secp256k1-sys 0.4.2", "serde", ] +[[package]] +name = "secp256k1" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4124a35fe33ae14259c490fd70fa199a32b9ce9502f2ee6bc4f81ec06fa65894" +dependencies = [ + "secp256k1-sys 0.8.1", +] + [[package]] name = "secp256k1-sys" version = "0.4.2" @@ -3924,6 +3901,15 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +dependencies = [ + "cc", +] + [[package]] name = "secrecy" version = "0.8.0" @@ -5538,6 +5524,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "xdg" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688597db5a750e9cad4511cb94729a078e274308099a0382b5b8203bbc767fee" +dependencies = [ + "home", +] + [[package]] name = "yaml-rust" version = "0.4.5" @@ -5549,12 +5544,12 @@ dependencies = [ [[package]] name = "zcash_address" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52be35a205369d480378646bff9c9fedafd8efe8af1e0e54bb858f405883f2b2" +checksum = "8944af5c206cf2e37020ad54618e1825501b98548d35a638b73e0ec5762df8d5" dependencies = [ "bech32", - "bs58 0.4.0", + "bs58", "f4jumble", "zcash_encoding", ] @@ -5582,9 +5577,9 @@ dependencies = [ [[package]] name = "zcash_note_encryption" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb2149e6cd5fbee36c5b87c601715a8c35554602f7fe84af38b636afa2db318" +checksum = "5b4580cd6cee12e44421dac43169be8d23791650816bdb34e6ddfa70ac89c1c5" dependencies = [ "chacha20", "chacha20poly1305", @@ -5595,9 +5590,9 @@ dependencies = [ [[package]] name = "zcash_primitives" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914d2195a478d5b63191584dff126f552751115181857b290211ec88e68acc3e" +checksum = "de1a231e6a58d3dcdd6e21d229db33d7c10f9b54d8c170e122b267f6826bb48f" dependencies = [ "aes", "bip0039", @@ -5621,7 +5616,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "ripemd", - "secp256k1", + "secp256k1 0.26.0", "sha2 0.10.6", "subtle", "zcash_address", @@ -5631,34 +5626,38 @@ dependencies = [ [[package]] name = "zcash_proofs" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5c8147884952748b00aa443d36511ae2d7b49acfec74cfd39c0959fbb61ef14" +checksum = "59d2e066a717f28451a081f2ebd483ddda896cf00d572972c10979d645ffa6c4" dependencies = [ "bellman", "blake2b_simd", "bls12_381", - "directories", "group", + "home", + "incrementalmerkletree", "jubjub", + "known-folders", "lazy_static", "minreq", "rand_core 0.6.4", "redjubjub", "tracing", + "xdg", "zcash_primitives", ] [[package]] name = "zcash_script" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f5d794b254efc2759d249b477f53faa751f67543a4b4d1c7a5ff7df212d4ba5" +checksum = "8c4f95043fd34d402b8d5debb0e54a28c2b84fc99591f5973b4999e9c5b01bfd" dependencies = [ "bellman", "bindgen", "blake2b_simd", "bls12_381", + "bridgetree", "byteorder", "cc", "crossbeam-channel", @@ -5669,8 +5668,9 @@ dependencies = [ "jubjub", "libc", "memuse", - "metrics 0.20.1", + "metrics", "orchard", + "rand 0.8.5", "rand_core 0.6.4", "rayon", "subtle", @@ -5692,7 +5692,8 @@ dependencies = [ "bitvec", "blake2b_simd", "blake2s_simd", - "bs58 0.5.0", + "bridgetree", + "bs58", "byteorder", "chrono", "color-eyre", @@ -5721,7 +5722,7 @@ dependencies = [ "reddsa", "redjubjub", "ripemd", - "secp256k1", + "secp256k1 0.21.3", "serde", "serde-big-array", "serde_json", @@ -5760,7 +5761,7 @@ dependencies = [ "howudoin", "jubjub", "lazy_static", - "metrics 0.21.1", + "metrics", "num-integer", "once_cell", "orchard", @@ -5804,7 +5805,7 @@ dependencies = [ "indexmap 2.0.0", "itertools 0.11.0", "lazy_static", - "metrics 0.21.1", + "metrics", "num-integer", "ordered-map", "pin-project", @@ -5904,7 +5905,7 @@ dependencies = [ "itertools 0.11.0", "jubjub", "lazy_static", - "metrics 0.21.1", + "metrics", "mset", "once_cell", "proptest", @@ -5998,7 +5999,7 @@ dependencies = [ "jsonrpc-core", "lazy_static", "log", - "metrics 0.21.1", + "metrics", "metrics-exporter-prometheus", "num-integer", "once_cell", diff --git a/deny.toml b/deny.toml index 2e8e9738..7e932dbf 100644 --- a/deny.toml +++ b/deny.toml @@ -70,26 +70,24 @@ skip-tree = [ # wait for zcashd and zcash_script to upgrade # https://github.com/ZcashFoundation/zcash_script/pulls - { name = "metrics", version = "=0.20.1" }, { name = "sha2", version = "=0.9.9" }, - # wait for ed25519-zebra, indexmap, metrics-util, and metrics to upgrade - # ed25519-zebra/hashbrown: https://github.com/ZcashFoundation/ed25519-zebra/pull/65 - { name = "ahash", version = "=0.7.6" }, - # wait for indexmap, toml_edit, serde_json, tower to upgrade { name = "hashbrown", version = "=0.12.3" }, # wait for metrics-exporter-prometheus to upgrade { name = "hashbrown", version = "=0.13.2" }, + # wait for zebra-chain to upgrade + { name = "secp256k1", version = "=0.21.3" }, + + # wait for zebra-chain to upgrade `secp256k1` + { name = "secp256k1-sys", version = "=0.4.2" }, + # ECC crates # wait for zcash_primitives to remove duplicated dependencies { name = "block-buffer", version = "=0.9.0" }, - # wait for zcash_address to upgrade - { name = "bs58", version = "=0.4.0" }, - # wait for minreq and zcash_proofs to upgrade { name = "rustls", version = "=0.20.8" }, diff --git a/zebra-chain/Cargo.toml b/zebra-chain/Cargo.toml index 6c200b32..d003b92b 100644 --- a/zebra-chain/Cargo.toml +++ b/zebra-chain/Cargo.toml @@ -53,11 +53,12 @@ bitflags = "2.3.3" bitflags-serde-legacy = "0.1.1" blake2b_simd = "1.0.1" blake2s_simd = "1.0.1" +bridgetree = "0.3.0" bs58 = { version = "0.5.0", features = ["check"] } byteorder = "1.4.3" equihash = "0.2.0" group = "0.13.0" -incrementalmerkletree = "0.3.1" +incrementalmerkletree = "0.4.0" jubjub = "0.10.0" lazy_static = "1.4.0" num-integer = "0.1.45" @@ -72,11 +73,11 @@ x25519-dalek = { version = "2.0.0-rc.3", features = ["serde"] } # ECC deps halo2 = { package = "halo2_proofs", version = "0.3.0" } -orchard = "0.4.0" +orchard = "0.5.0" zcash_encoding = "0.2.0" zcash_history = "0.3.0" -zcash_note_encryption = "0.3.0" -zcash_primitives = { version = "0.11.0", features = ["transparent-inputs"] } +zcash_note_encryption = "0.4.0" +zcash_primitives = { version = "0.12.0", features = ["transparent-inputs"] } # Time chrono = { version = "0.4.26", default-features = false, features = ["clock", "std", "serde"] } @@ -108,7 +109,7 @@ reddsa = "0.5.0" serde_json = { version = "1.0.100", optional = true } # Experimental feature getblocktemplate-rpcs -zcash_address = { version = "0.2.1", optional = true } +zcash_address = { version = "0.3.0", optional = true } # Optional testing dependencies proptest = { version = "1.2.0", optional = true } diff --git a/zebra-chain/src/orchard/tree.rs b/zebra-chain/src/orchard/tree.rs index c212033a..9862bd8f 100644 --- a/zebra-chain/src/orchard/tree.rs +++ b/zebra-chain/src/orchard/tree.rs @@ -18,11 +18,12 @@ use std::{ }; use bitvec::prelude::*; +use bridgetree; use halo2::pasta::{group::ff::PrimeField, pallas}; -use incrementalmerkletree::{bridgetree, Frontier}; +use incrementalmerkletree::Hashable; use lazy_static::lazy_static; use thiserror::Error; -use zcash_primitives::merkle_tree::{self, CommitmentTree}; +use zcash_primitives::merkle_tree::{write_commitment_tree, HashSer}; use super::sinsemilla::*; @@ -30,6 +31,9 @@ use crate::serialization::{ serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize, }; +pub mod legacy; +use legacy::LegacyNoteCommitmentTree; + /// The type that is used to update the note commitment tree. /// /// Unfortunately, this is not the same as `orchard::NoteCommitment`. @@ -164,18 +168,18 @@ impl ZcashDeserialize for Root { /// A node of the Orchard Incremental Note Commitment Tree. #[derive(Copy, Clone, Debug, Eq, PartialEq)] -struct Node(pallas::Base); +pub struct Node(pallas::Base); /// Required to convert [`NoteCommitmentTree`] into [`SerializedTree`]. /// /// Zebra stores Orchard note commitment trees as [`Frontier`][1]s while the /// [`z_gettreestate`][2] RPC requires [`CommitmentTree`][3]s. Implementing -/// [`merkle_tree::Hashable`] for [`Node`]s allows the conversion. +/// [`HashSer`] for [`Node`]s allows the conversion. /// /// [1]: bridgetree::Frontier /// [2]: https://zcash.github.io/rpc/z_gettreestate.html -/// [3]: merkle_tree::CommitmentTree -impl merkle_tree::Hashable for Node { +/// [3]: incrementalmerkletree::frontier::CommitmentTree +impl HashSer for Node { fn read(mut reader: R) -> io::Result { let mut repr = [0u8; 32]; reader.read_exact(&mut repr)?; @@ -192,24 +196,9 @@ impl merkle_tree::Hashable for Node { fn write(&self, mut writer: W) -> io::Result<()> { writer.write_all(&self.0.to_repr()) } - - fn combine(level: usize, a: &Self, b: &Self) -> Self { - let level = u8::try_from(level).expect("level must fit into u8"); - let layer = MERKLE_DEPTH - 1 - level; - Self(merkle_crh_orchard(layer, a.0, b.0)) - } - - fn blank() -> Self { - Self(NoteCommitmentTree::uncommitted()) - } - - fn empty_root(level: usize) -> Self { - let layer_below = usize::from(MERKLE_DEPTH) - level; - Self(EMPTY_ROOTS[layer_below]) - } } -impl incrementalmerkletree::Hashable for Node { +impl Hashable for Node { fn empty_leaf() -> Self { Self(NoteCommitmentTree::uncommitted()) } @@ -217,13 +206,13 @@ impl incrementalmerkletree::Hashable for Node { /// Combine two nodes to generate a new node in the given level. /// Level 0 is the layer above the leaves (layer 31). /// Level 31 is the root (layer 0). - fn combine(level: incrementalmerkletree::Altitude, a: &Self, b: &Self) -> Self { + fn combine(level: incrementalmerkletree::Level, a: &Self, b: &Self) -> Self { let layer = MERKLE_DEPTH - 1 - u8::from(level); Self(merkle_crh_orchard(layer, a.0, b.0)) } /// Return the node for the level below the given level. (A quirk of the API) - fn empty_root(level: incrementalmerkletree::Altitude) -> Self { + fn empty_root(level: incrementalmerkletree::Level) -> Self { let layer_below = usize::from(MERKLE_DEPTH) - usize::from(level); Self(EMPTY_ROOTS[layer_below]) } @@ -265,6 +254,8 @@ pub enum NoteCommitmentTreeError { /// Orchard Incremental Note Commitment Tree #[derive(Debug, Serialize, Deserialize)] +#[serde(into = "LegacyNoteCommitmentTree")] +#[serde(from = "LegacyNoteCommitmentTree")] pub struct NoteCommitmentTree { /// The tree represented as a Frontier. /// @@ -311,7 +302,7 @@ impl NoteCommitmentTree { /// Returns an error if the tree is full. #[allow(clippy::unwrap_in_result)] pub fn append(&mut self, cm_x: NoteCommitmentUpdate) -> Result<(), NoteCommitmentTreeError> { - if self.inner.append(&cm_x.into()) { + if self.inner.append(cm_x.into()) { // Invalidate cached root let cached_root = self .cached_root @@ -385,7 +376,9 @@ impl NoteCommitmentTree { /// /// For Orchard, the tree is capped at 2^32. pub fn count(&self) -> u64 { - self.inner.position().map_or(0, |pos| u64::from(pos) + 1) + self.inner + .value() + .map_or(0, |x| u64::from(x.position()) + 1) } /// Checks if the tree roots and inner data structures of `self` and `other` are equal. @@ -459,7 +452,7 @@ impl From> for NoteCommitmentTree { /// A serialized Orchard note commitment tree. /// /// The format of the serialized data is compatible with -/// [`CommitmentTree`](merkle_tree::CommitmentTree) from `librustzcash` and not +/// [`CommitmentTree`](incrementalmerkletree::frontier::CommitmentTree) from `librustzcash` and not /// with [`Frontier`](bridgetree::Frontier) from the crate /// [`incrementalmerkletree`]. Zebra follows the former format in order to stay /// consistent with `zcashd` in RPCs. Note that [`NoteCommitmentTree`] itself is @@ -468,7 +461,7 @@ impl From> for NoteCommitmentTree { /// The formats are semantically equivalent. The primary difference between them /// is that in [`Frontier`](bridgetree::Frontier), the vector of parents is /// dense (we know where the gaps are from the position of the leaf in the -/// overall tree); whereas in [`CommitmentTree`](merkle_tree::CommitmentTree), +/// overall tree); whereas in [`CommitmentTree`](incrementalmerkletree::frontier::CommitmentTree), /// the vector of parent hashes is sparse with [`None`] values in the gaps. /// /// The sparse format, used in this implementation, allows representing invalid @@ -498,8 +491,9 @@ impl From<&NoteCommitmentTree> for SerializedTree { // Convert the note commitment tree from // [`Frontier`](bridgetree::Frontier) to // [`CommitmentTree`](merkle_tree::CommitmentTree). - let tree = CommitmentTree::from_frontier(&tree.inner); - tree.write(&mut serialized_tree) + let tree = incrementalmerkletree::frontier::CommitmentTree::from_frontier(&tree.inner); + + write_commitment_tree(&tree, &mut serialized_tree) .expect("note commitment tree should be serializable"); Self(serialized_tree) } diff --git a/zebra-chain/src/orchard/tree/legacy.rs b/zebra-chain/src/orchard/tree/legacy.rs new file mode 100644 index 00000000..b4d97cf4 --- /dev/null +++ b/zebra-chain/src/orchard/tree/legacy.rs @@ -0,0 +1,122 @@ +//! Orchard serialization legacy code. +//! +//! We create a [`LegacyNoteCommitmentTree`] which is a copy of [`NoteCommitmentTree`] but where serialization and +//! deserialization can be derived. +//! To do this we create a [`LegacyFrontier`] which is a legacy `Frontier` structure that can be found in [1], +//! In order to make [`LegacyFrontier`] serializable we also have our own versions of `NonEmptyFrontier` ([`LegacyNonEmptyFrontier`]), +//! `Leaf`([`LegacyLeaf`]) and `Position`([`LegacyPosition`]) that can be found in [1] or [2]. +//! +//! Conversions methods to/from [`LegacyNoteCommitmentTree`] to/from [`NoteCommitmentTree`] are defined also in this file. +//! +//! [1]: https://github.com/zcash/incrementalmerkletree/blob/incrementalmerkletree-v0.3.1/src/bridgetree.rs +//! [2]: https://github.com/zcash/incrementalmerkletree/blob/incrementalmerkletree-v0.3.1/src/lib.rs + +use incrementalmerkletree::{frontier::Frontier, Position}; + +use super::{Node, NoteCommitmentTree, Root, MERKLE_DEPTH}; + +/// A legacy version of [`NoteCommitmentTree`]. +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename = "NoteCommitmentTree")] +#[allow(missing_docs)] +pub struct LegacyNoteCommitmentTree { + pub inner: LegacyFrontier, + cached_root: std::sync::RwLock>, +} + +impl From for LegacyNoteCommitmentTree { + fn from(nct: NoteCommitmentTree) -> Self { + LegacyNoteCommitmentTree { + inner: nct.inner.into(), + cached_root: nct.cached_root, + } + } +} + +impl From for NoteCommitmentTree { + fn from(legacy_nct: LegacyNoteCommitmentTree) -> Self { + NoteCommitmentTree { + inner: legacy_nct.inner.into(), + cached_root: legacy_nct.cached_root, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename = "Frontier")] +#[allow(missing_docs)] +pub struct LegacyFrontier { + frontier: Option>, +} + +impl From> for Frontier { + fn from(legacy_frontier: LegacyFrontier) -> Self { + if let Some(legacy_frontier_data) = legacy_frontier.frontier { + let mut ommers = legacy_frontier_data.ommers; + let position = Position::from( + u64::try_from(legacy_frontier_data.position.0) + .expect("old `usize` always fits in `u64`"), + ); + let leaf = match legacy_frontier_data.leaf { + LegacyLeaf::Left(a) => a, + LegacyLeaf::Right(a, b) => { + ommers.insert(0, a); + b + } + }; + Frontier::from_parts( + position, + leaf, + ommers, + ) + .expect("We should be able to construct a frontier from parts given legacy frontier is not empty") + } else { + Frontier::empty() + } + } +} + +impl From> for LegacyFrontier { + fn from(frontier: Frontier) -> Self { + if let Some(frontier_data) = frontier.value() { + let leaf_from_frontier = *frontier_data.leaf(); + let mut leaf = LegacyLeaf::Left(leaf_from_frontier); + let mut ommers = frontier_data.ommers().to_vec(); + let position = usize::try_from(u64::from(frontier_data.position())) + .expect("new position should fit in a `usize`"); + if frontier_data.position().is_odd() { + let left = ommers.remove(0); + leaf = LegacyLeaf::Right(left, leaf_from_frontier); + } + LegacyFrontier { + frontier: Some(LegacyNonEmptyFrontier { + position: LegacyPosition(position), + leaf, + ommers: ommers.to_vec(), + }), + } + } else { + LegacyFrontier { frontier: None } + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename = "NonEmptyFrontier")] +struct LegacyNonEmptyFrontier { + position: LegacyPosition, + leaf: LegacyLeaf, + ommers: Vec, +} + +/// A set of leaves of a Merkle tree. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename = "Leaf")] +enum LegacyLeaf { + Left(A), + Right(A, A), +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[repr(transparent)] +struct LegacyPosition(usize); diff --git a/zebra-chain/src/sapling/tree.rs b/zebra-chain/src/sapling/tree.rs index 06029731..ea045e32 100644 --- a/zebra-chain/src/sapling/tree.rs +++ b/zebra-chain/src/sapling/tree.rs @@ -18,14 +18,13 @@ use std::{ }; use bitvec::prelude::*; -use incrementalmerkletree::{ - bridgetree::{self, Leaf}, - Frontier, -}; +use bridgetree::{self}; +use incrementalmerkletree::{frontier::Frontier, Hashable}; + use lazy_static::lazy_static; use thiserror::Error; use zcash_encoding::{Optional, Vector}; -use zcash_primitives::merkle_tree::{self, Hashable}; +use zcash_primitives::merkle_tree::HashSer; use super::commitment::pedersen_hashes::pedersen_hash; @@ -33,6 +32,9 @@ use crate::serialization::{ serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize, }; +pub mod legacy; +use legacy::{LegacyLeaf, LegacyNoteCommitmentTree}; + /// The type that is used to update the note commitment tree. /// /// Unfortunately, this is not the same as `sapling::NoteCommitment`. @@ -85,12 +87,6 @@ lazy_static! { }; } -/// The index of a note's commitment at the leafmost layer of its Note -/// Commitment Tree. -/// -/// -pub struct Position(pub(crate) u64); - /// Sapling note commitment tree root node hash. /// /// The root hash in LEBS2OSP256(rt) encoding of the Sapling note @@ -167,7 +163,7 @@ impl ZcashDeserialize for Root { /// Note that it's handled as a byte buffer and not a point coordinate (jubjub::Fq) /// because that's how the spec handles the MerkleCRH^Sapling function inputs and outputs. #[derive(Copy, Clone, Eq, PartialEq)] -struct Node([u8; 32]); +pub struct Node([u8; 32]); impl fmt::Debug for Node { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -179,12 +175,12 @@ impl fmt::Debug for Node { /// /// Zebra stores Sapling note commitment trees as [`Frontier`][1]s while the /// [`z_gettreestate`][2] RPC requires [`CommitmentTree`][3]s. Implementing -/// [`merkle_tree::Hashable`] for [`Node`]s allows the conversion. +/// [`incrementalmerkletree::Hashable`] for [`Node`]s allows the conversion. /// /// [1]: bridgetree::Frontier /// [2]: https://zcash.github.io/rpc/z_gettreestate.html -/// [3]: merkle_tree::CommitmentTree -impl merkle_tree::Hashable for Node { +/// [3]: incrementalmerkletree::frontier::CommitmentTree +impl HashSer for Node { fn read(mut reader: R) -> io::Result { let mut node = [0u8; 32]; reader.read_exact(&mut node)?; @@ -194,24 +190,9 @@ impl merkle_tree::Hashable for Node { fn write(&self, mut writer: W) -> io::Result<()> { writer.write_all(self.0.as_ref()) } - - fn combine(level: usize, a: &Self, b: &Self) -> Self { - let level = u8::try_from(level).expect("level must fit into u8"); - let layer = MERKLE_DEPTH - 1 - level; - Self(merkle_crh_sapling(layer, a.0, b.0)) - } - - fn blank() -> Self { - Self(NoteCommitmentTree::uncommitted()) - } - - fn empty_root(level: usize) -> Self { - let layer_below = usize::from(MERKLE_DEPTH) - level; - Self(EMPTY_ROOTS[layer_below]) - } } -impl incrementalmerkletree::Hashable for Node { +impl Hashable for Node { fn empty_leaf() -> Self { Self(NoteCommitmentTree::uncommitted()) } @@ -219,13 +200,13 @@ impl incrementalmerkletree::Hashable for Node { /// Combine two nodes to generate a new node in the given level. /// Level 0 is the layer above the leaves (layer 31). /// Level 31 is the root (layer 0). - fn combine(level: incrementalmerkletree::Altitude, a: &Self, b: &Self) -> Self { + fn combine(level: incrementalmerkletree::Level, a: &Self, b: &Self) -> Self { let layer = MERKLE_DEPTH - 1 - u8::from(level); Self(merkle_crh_sapling(layer, a.0, b.0)) } /// Return the node for the level below the given level. (A quirk of the API) - fn empty_root(level: incrementalmerkletree::Altitude) -> Self { + fn empty_root(level: incrementalmerkletree::Level) -> Self { let layer_below = usize::from(MERKLE_DEPTH) - usize::from(level); Self(EMPTY_ROOTS[layer_below]) } @@ -267,6 +248,8 @@ pub enum NoteCommitmentTreeError { /// Sapling Incremental Note Commitment Tree. #[derive(Debug, Serialize, Deserialize)] +#[serde(into = "LegacyNoteCommitmentTree")] +#[serde(from = "LegacyNoteCommitmentTree")] pub struct NoteCommitmentTree { /// The tree represented as a [`Frontier`](bridgetree::Frontier). /// @@ -284,7 +267,7 @@ pub struct NoteCommitmentTree { /// /// /// Note: MerkleDepth^Sapling = MERKLE_DEPTH = 32. - inner: bridgetree::Frontier, + inner: Frontier, /// A cached root of the tree. /// @@ -314,7 +297,7 @@ impl NoteCommitmentTree { /// Returns an error if the tree is full. #[allow(clippy::unwrap_in_result)] pub fn append(&mut self, cm_u: NoteCommitmentUpdate) -> Result<(), NoteCommitmentTreeError> { - if self.inner.append(&cm_u.into()) { + if self.inner.append(cm_u.into()) { // Invalidate cached root let cached_root = self .cached_root @@ -388,7 +371,9 @@ impl NoteCommitmentTree { /// /// For Sapling, the tree is capped at 2^32. pub fn count(&self) -> u64 { - self.inner.position().map_or(0, |pos| u64::from(pos) + 1) + self.inner + .value() + .map_or(0, |x| u64::from(x.position()) + 1) } /// Checks if the tree roots and inner data structures of `self` and `other` are equal. @@ -463,7 +448,7 @@ impl From> for NoteCommitmentTree { /// A serialized Sapling note commitment tree. /// /// The format of the serialized data is compatible with -/// [`CommitmentTree`](merkle_tree::CommitmentTree) from `librustzcash` and not +/// [`CommitmentTree`](incrementalmerkletree::frontier::CommitmentTree) from `librustzcash` and not /// with [`Frontier`](bridgetree::Frontier) from the crate /// [`incrementalmerkletree`]. Zebra follows the former format in order to stay /// consistent with `zcashd` in RPCs. Note that [`NoteCommitmentTree`] itself is @@ -472,7 +457,7 @@ impl From> for NoteCommitmentTree { /// The formats are semantically equivalent. The primary difference between them /// is that in [`Frontier`](bridgetree::Frontier), the vector of parents is /// dense (we know where the gaps are from the position of the leaf in the -/// overall tree); whereas in [`CommitmentTree`](merkle_tree::CommitmentTree), +/// overall tree); whereas in [`CommitmentTree`](incrementalmerkletree::frontier::CommitmentTree), /// the vector of parent hashes is sparse with [`None`] values in the gaps. /// /// The sparse format, used in this implementation, allows representing invalid @@ -489,6 +474,9 @@ impl From<&NoteCommitmentTree> for SerializedTree { fn from(tree: &NoteCommitmentTree) -> Self { let mut serialized_tree = vec![]; + // + let legacy_tree = LegacyNoteCommitmentTree::from(tree.clone()); + // Convert the note commitment tree represented as a frontier into the // format compatible with `zcashd`. // @@ -502,20 +490,22 @@ impl From<&NoteCommitmentTree> for SerializedTree { // sparse formats for Sapling. // // [1]: - if let Some(frontier) = tree.inner.value() { - let (left_leaf, right_leaf) = match frontier.leaf() { - Leaf::Left(left_value) => (Some(left_value), None), - Leaf::Right(left_value, right_value) => (Some(left_value), Some(right_value)), + if let Some(frontier) = legacy_tree.inner.frontier { + let (left_leaf, right_leaf) = match frontier.leaf { + LegacyLeaf::Left(left_value) => (Some(left_value), None), + LegacyLeaf::Right(left_value, right_value) => (Some(left_value), Some(right_value)), }; // Ommers are siblings of parent nodes along the branch from the // most recent leaf to the root of the tree. - let mut ommers_iter = frontier.ommers().iter(); + let mut ommers_iter = frontier.ommers.iter(); // Set bits in the binary representation of the position indicate // the presence of ommers along the branch from the most recent leaf // node to the root of the tree, except for the lowest bit. - let mut position: usize = frontier.position().into(); + let mut position: u64 = (frontier.position.0) + .try_into() + .expect("old usize position always fit in u64"); // The lowest bit does not indicate the presence of any ommers. We // clear it so that we can test if there are no set bits left in @@ -552,7 +542,6 @@ impl From<&NoteCommitmentTree> for SerializedTree { } // Serialize the converted note commitment tree. - Optional::write(&mut serialized_tree, left_leaf, |tree, leaf| { leaf.write(tree) }) diff --git a/zebra-chain/src/sapling/tree/legacy.rs b/zebra-chain/src/sapling/tree/legacy.rs new file mode 100644 index 00000000..0e66e8ae --- /dev/null +++ b/zebra-chain/src/sapling/tree/legacy.rs @@ -0,0 +1,125 @@ +//! Sapling serialization legacy code. +//! +//! We create a [`LegacyNoteCommitmentTree`] which is a copy of [`NoteCommitmentTree`] but where serialization and +//! deserialization can be derived. +//! To do this we create a [`LegacyFrontier`] which is a legacy `Frontier` structure that can be found in [1], +//! In order to make [`LegacyFrontier`] serializable we also have our own versions of `NonEmptyFrontier` ([`LegacyNonEmptyFrontier`]), +//! `Leaf`([`LegacyLeaf`]) and `Position`([`LegacyPosition`]) that can be found in [1] or [2]. +//! +//! Conversions methods to/from [`LegacyNoteCommitmentTree`] to/from [`NoteCommitmentTree`] are defined also in this file. +//! +//! [1]: https://github.com/zcash/incrementalmerkletree/blob/incrementalmerkletree-v0.3.1/src/bridgetree.rs +//! [2]: https://github.com/zcash/incrementalmerkletree/blob/incrementalmerkletree-v0.3.1/src/lib.rs + +use incrementalmerkletree::{frontier::Frontier, Position}; + +use super::{Node, NoteCommitmentTree, Root, MERKLE_DEPTH}; + +/// A legacy version of [`NoteCommitmentTree`]. +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename = "NoteCommitmentTree")] +#[allow(missing_docs)] +pub struct LegacyNoteCommitmentTree { + pub inner: LegacyFrontier, + cached_root: std::sync::RwLock>, +} + +impl From for LegacyNoteCommitmentTree { + fn from(nct: NoteCommitmentTree) -> Self { + LegacyNoteCommitmentTree { + inner: nct.inner.into(), + cached_root: nct.cached_root, + } + } +} + +impl From for NoteCommitmentTree { + fn from(nct: LegacyNoteCommitmentTree) -> Self { + NoteCommitmentTree { + inner: nct.inner.into(), + cached_root: nct.cached_root, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename = "Frontier")] +#[allow(missing_docs)] +pub struct LegacyFrontier { + pub frontier: Option>, +} + +impl From> for Frontier { + fn from(legacy_frontier: LegacyFrontier) -> Self { + if let Some(legacy_frontier_data) = legacy_frontier.frontier { + let mut ommers = legacy_frontier_data.ommers; + let position = Position::from( + u64::try_from(legacy_frontier_data.position.0) + .expect("old `usize` always fits in `u64`"), + ); + let leaf = match legacy_frontier_data.leaf { + LegacyLeaf::Left(a) => a, + LegacyLeaf::Right(a, b) => { + ommers.insert(0, a); + b + } + }; + Frontier::from_parts( + position, + leaf, + ommers, + ) + .expect("We should be able to construct a frontier from parts given legacy frontier is not empty") + } else { + Frontier::empty() + } + } +} + +impl From> for LegacyFrontier { + fn from(frontier: Frontier) -> Self { + if let Some(frontier_data) = frontier.value() { + let leaf_from_frontier = *frontier_data.leaf(); + let mut leaf = LegacyLeaf::Left(leaf_from_frontier); + let mut ommers = frontier_data.ommers().to_vec(); + let position = usize::try_from(u64::from(frontier_data.position())) + .expect("new position should fit in a `usize`"); + if frontier_data.position().is_odd() { + let left = ommers.remove(0); + leaf = LegacyLeaf::Right(left, leaf_from_frontier); + } + LegacyFrontier { + frontier: Some(LegacyNonEmptyFrontier { + position: LegacyPosition(position), + leaf, + ommers: ommers.to_vec(), + }), + } + } else { + LegacyFrontier { frontier: None } + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename = "NonEmptyFrontier")] +#[allow(missing_docs)] +pub struct LegacyNonEmptyFrontier { + pub position: LegacyPosition, + pub leaf: LegacyLeaf, + pub ommers: Vec, +} + +/// A set of leaves of a Merkle tree. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename = "Leaf")] +#[allow(missing_docs)] +pub enum LegacyLeaf { + Left(A), + Right(A, A), +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[repr(transparent)] +#[allow(missing_docs)] +pub struct LegacyPosition(pub usize); diff --git a/zebra-chain/src/sprout/tree.rs b/zebra-chain/src/sprout/tree.rs index af1d964d..2b70b0a3 100644 --- a/zebra-chain/src/sprout/tree.rs +++ b/zebra-chain/src/sprout/tree.rs @@ -13,13 +13,16 @@ use std::fmt; use byteorder::{BigEndian, ByteOrder}; -use incrementalmerkletree::{bridgetree, Frontier}; +use incrementalmerkletree::frontier::Frontier; use lazy_static::lazy_static; use sha2::digest::generic_array::GenericArray; use thiserror::Error; use super::commitment::NoteCommitment; +pub mod legacy; +use legacy::LegacyNoteCommitmentTree; + #[cfg(any(test, feature = "proptest-impl"))] use proptest_derive::Arbitrary; @@ -128,7 +131,7 @@ impl From<&Root> for [u8; 32] { /// A node of the Sprout note commitment tree. #[derive(Clone, Copy, Eq, PartialEq)] -struct Node([u8; 32]); +pub struct Node([u8; 32]); impl fmt::Debug for Node { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -147,12 +150,12 @@ impl incrementalmerkletree::Hashable for Node { /// 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 { + fn combine(_level: incrementalmerkletree::Level, 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 { + fn empty_root(level: incrementalmerkletree::Level) -> Self { let layer_below = usize::from(MERKLE_DEPTH) - usize::from(level); Self(EMPTY_ROOTS[layer_below]) } @@ -200,16 +203,18 @@ pub enum NoteCommitmentTreeError { /// 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 +/// Internally this wraps [`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(Debug, Serialize, Deserialize)] +#[serde(into = "LegacyNoteCommitmentTree")] +#[serde(from = "LegacyNoteCommitmentTree")] pub struct NoteCommitmentTree { - /// The tree represented as a [`incrementalmerkletree::bridgetree::Frontier`]. + /// The tree represented as a [`bridgetree::Frontier`]. /// - /// A [`incrementalmerkletree::Frontier`] is a subset of the tree that allows to fully specify it. It + /// A [`bridgetree::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. @@ -222,7 +227,7 @@ pub struct NoteCommitmentTree { /// /// /// Note: MerkleDepth^Sprout = MERKLE_DEPTH = 29. - inner: bridgetree::Frontier, + inner: Frontier, /// A cached root of the tree. /// @@ -248,7 +253,7 @@ impl NoteCommitmentTree { /// Returns an error if the tree is full. #[allow(clippy::unwrap_in_result)] pub fn append(&mut self, cm: NoteCommitment) -> Result<(), NoteCommitmentTreeError> { - if self.inner.append(&cm.into()) { + if self.inner.append(cm.into()) { // Invalidate cached root let cached_root = self .cached_root @@ -323,7 +328,9 @@ impl NoteCommitmentTree { /// /// [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) + self.inner + .value() + .map_or(0, |x| u64::from(x.position()) + 1) } /// Checks if the tree roots and inner data structures of `self` and `other` are equal. @@ -360,7 +367,7 @@ impl Clone for NoteCommitmentTree { impl Default for NoteCommitmentTree { fn default() -> Self { Self { - inner: bridgetree::Frontier::empty(), + inner: Frontier::empty(), cached_root: Default::default(), } } diff --git a/zebra-chain/src/sprout/tree/legacy.rs b/zebra-chain/src/sprout/tree/legacy.rs new file mode 100644 index 00000000..b11e674b --- /dev/null +++ b/zebra-chain/src/sprout/tree/legacy.rs @@ -0,0 +1,121 @@ +//! Sprout serialization legacy code. +//! +//! We create a [`LegacyNoteCommitmentTree`] which is a copy of [`NoteCommitmentTree`] but where serialization and +//! deserialization can be derived. +//! To do this we create a [`LegacyFrontier`] which is a legacy `Frontier` structure that can be found in [1], +//! In order to make [`LegacyFrontier`] serializable we also have our own versions of `NonEmptyFrontier` ([`LegacyNonEmptyFrontier`]), +//! `Leaf`([`LegacyLeaf`]) and `Position`([`LegacyPosition`]) that can be found in [1] or [2]. +//! +//! Conversions methods to/from [`LegacyNoteCommitmentTree`] to/from [`NoteCommitmentTree`] are defined also in this file. +//! +//! [1]: https://github.com/zcash/incrementalmerkletree/blob/incrementalmerkletree-v0.3.1/src/bridgetree.rs +//! [2]: https://github.com/zcash/incrementalmerkletree/blob/incrementalmerkletree-v0.3.1/src/lib.rs + +use incrementalmerkletree::{frontier::Frontier, Position}; + +use super::{Node, NoteCommitmentTree, Root, MERKLE_DEPTH}; + +/// A legacy version of [`NoteCommitmentTree`]. +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename = "NoteCommitmentTree")] +pub struct LegacyNoteCommitmentTree { + inner: LegacyFrontier, + cached_root: std::sync::RwLock>, +} + +impl From for LegacyNoteCommitmentTree { + fn from(nct: NoteCommitmentTree) -> Self { + LegacyNoteCommitmentTree { + inner: nct.inner.into(), + cached_root: nct.cached_root, + } + } +} + +impl From for NoteCommitmentTree { + fn from(nct: LegacyNoteCommitmentTree) -> Self { + NoteCommitmentTree { + inner: nct.inner.into(), + cached_root: nct.cached_root, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename = "Frontier")] +#[allow(missing_docs)] +pub struct LegacyFrontier { + frontier: Option>, +} + +impl From> for Frontier { + fn from(legacy_frontier: LegacyFrontier) -> Self { + if let Some(legacy_frontier_data) = legacy_frontier.frontier { + let mut ommers = legacy_frontier_data.ommers; + let position = Position::from( + u64::try_from(legacy_frontier_data.position.0) + .expect("old `usize` always fits in `u64`"), + ); + let leaf = match legacy_frontier_data.leaf { + LegacyLeaf::Left(a) => a, + LegacyLeaf::Right(a, b) => { + ommers.insert(0, a); + b + } + }; + Frontier::from_parts( + position, + leaf, + ommers, + ) + .expect("We should be able to construct a frontier from parts given legacy frontier is not empty") + } else { + Frontier::empty() + } + } +} + +impl From> for LegacyFrontier { + fn from(frontier: Frontier) -> Self { + if let Some(frontier_data) = frontier.value() { + let leaf_from_frontier = *frontier_data.leaf(); + let mut leaf = LegacyLeaf::Left(leaf_from_frontier); + let mut ommers = frontier_data.ommers().to_vec(); + let position = usize::try_from(u64::from(frontier_data.position())) + .expect("new position should fit in a `usize`"); + if frontier_data.position().is_odd() { + let left = ommers.remove(0); + leaf = LegacyLeaf::Right(left, leaf_from_frontier); + } + LegacyFrontier { + frontier: Some(LegacyNonEmptyFrontier { + position: LegacyPosition(position), + leaf, + ommers: ommers.to_vec(), + }), + } + } else { + LegacyFrontier { frontier: None } + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename = "NonEmptyFrontier")] +struct LegacyNonEmptyFrontier { + position: LegacyPosition, + leaf: LegacyLeaf, + ommers: Vec, +} + +/// A set of leaves of a Merkle tree. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename = "Leaf")] +enum LegacyLeaf { + Left(A), + Right(A, A), +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[repr(transparent)] +struct LegacyPosition(usize); diff --git a/zebra-consensus/Cargo.toml b/zebra-consensus/Cargo.toml index 639a27de..35fabab2 100644 --- a/zebra-consensus/Cargo.toml +++ b/zebra-consensus/Cargo.toml @@ -58,9 +58,9 @@ tower = { version = "0.4.13", features = ["timeout", "util", "buffer"] } tracing = "0.1.37" tracing-futures = "0.2.5" -orchard = "0.4.0" +orchard = "0.5.0" -zcash_proofs = { version = "0.11.0", features = ["local-prover", "multicore", "download-params"] } +zcash_proofs = { version = "0.12.1", features = ["local-prover", "multicore", "download-params"] } tower-fallback = { path = "../tower-fallback/", version = "0.2.41-beta.3" } tower-batch-control = { path = "../tower-batch-control/", version = "0.2.41-beta.3" } diff --git a/zebra-rpc/Cargo.toml b/zebra-rpc/Cargo.toml index c387f31c..d0f6adec 100644 --- a/zebra-rpc/Cargo.toml +++ b/zebra-rpc/Cargo.toml @@ -65,7 +65,7 @@ serde = { version = "1.0.168", features = ["serde_derive"] } # Experimental feature getblocktemplate-rpcs rand = { version = "0.8.5", optional = true } # ECC deps used by getblocktemplate-rpcs feature -zcash_address = { version = "0.2.1", optional = true } +zcash_address = { version = "0.3.0", optional = true } # Test-only feature proptest-impl proptest = { version = "1.2.0", optional = true } diff --git a/zebra-script/Cargo.toml b/zebra-script/Cargo.toml index eb284f9f..747c8c0d 100644 --- a/zebra-script/Cargo.toml +++ b/zebra-script/Cargo.toml @@ -15,7 +15,7 @@ keywords = ["zebra", "zcash"] categories = ["api-bindings", "cryptography::cryptocurrencies"] [dependencies] -zcash_script = "0.1.12" +zcash_script = "0.1.13" zebra-chain = { path = "../zebra-chain", version = "1.0.0-beta.27" } diff --git a/zebra-state/src/service/finalized_state/tests/vectors.rs b/zebra-state/src/service/finalized_state/tests/vectors.rs index 8df81b66..2390ed72 100644 --- a/zebra-state/src/service/finalized_state/tests/vectors.rs +++ b/zebra-state/src/service/finalized_state/tests/vectors.rs @@ -10,9 +10,16 @@ use rand::random; use halo2::pasta::{group::ff::PrimeField, pallas}; use zebra_chain::{ - orchard::tree::NoteCommitmentTree as OrchardNoteCommitmentTree, - sapling::tree::NoteCommitmentTree as SaplingNoteCommitmentTree, + orchard::{ + tree::legacy::LegacyNoteCommitmentTree as LegacyOrchardNoteCommitmentTree, + tree::NoteCommitmentTree as OrchardNoteCommitmentTree, + }, + sapling::{ + tree::legacy::LegacyNoteCommitmentTree as LegacySaplingNoteCommitmentTree, + tree::NoteCommitmentTree as SaplingNoteCommitmentTree, + }, sprout::{ + tree::legacy::LegacyNoteCommitmentTree as LegacySproutNoteCommitmentTree, tree::NoteCommitmentTree as SproutNoteCommitmentTree, NoteCommitment as SproutNoteCommitment, }, @@ -20,26 +27,6 @@ use zebra_chain::{ use crate::service::finalized_state::disk_format::{FromDisk, IntoDisk}; -// Currently, these tests check these structs are equal: -// * commitments -> tree struct -// * commitments -> tree struct -> serialize -> deserialize -> tree struct -// And these serialized formats are equal: -// * fixed serialized test vector -// * commitments -> tree struct -> serialize -// * commitments -> tree struct -> serialize -> deserialize -> tree struct -> serialize -// -// TODO: apply these tests to the new tree structs, and update the serialization format -// (keeping the tests for the old format is optional, because the tests below cover it) -// -// TODO: test that old and new serializations produce the same format: -// Tree roots built from the same commitments should match: -// * commitments -> old tree struct -> new tree struct -> un-cached root -// * commitments -> new tree struct -> un-cached root -// Even when serialized and deserialized: -// * commitments -> old tree struct -> old serialize -> old deserialize -> old tree struct -> new tree struct -> un-cached root -// * commitments -> new tree struct -> new serialize -> new deserialize -> new tree struct -> un-cached root -// * commitments -> new tree struct -> un-cached root - /// Check that the sprout tree database serialization format has not changed. #[test] fn sprout_note_commitment_tree_serialization() { @@ -73,21 +60,8 @@ fn sprout_note_commitment_tree_serialization() { // The purpose of this test is to make sure the serialization format does // not change by accident. let expected_serialized_tree_hex = "010200836045484077cf6390184ea7cd48b460e2d0f22b2293b69633bb152314a692fb019f5b2b1e4bf7e7318d0a1f417ca6bca36077025b3d11e074b94cd55ce9f3861801c45297124f50dcd3f78eed017afd1e30764cd74cdf0a57751978270fd0721359"; - let serialized_tree = incremental_tree.as_bytes(); - assert_eq!(hex::encode(&serialized_tree), expected_serialized_tree_hex); - let deserialized_tree = SproutNoteCommitmentTree::from_bytes(&serialized_tree); - - // This check isn't enough to show that the entire struct is the same, because it just compares - // the cached serialized/deserialized roots. (NoteCommitmentTree::eq() also just compares - // roots.) - assert_eq!(incremental_tree.root(), deserialized_tree.root()); - - incremental_tree.assert_frontier_eq(&deserialized_tree); - - // Double-check that the internal format is the same by re-serializing the tree. - let re_serialized_tree = deserialized_tree.as_bytes(); - assert_eq!(serialized_tree, re_serialized_tree); + sprout_checks(incremental_tree, expected_serialized_tree_hex); } /// Check that the sprout tree database serialization format has not changed for one commitment. @@ -119,21 +93,8 @@ fn sprout_note_commitment_tree_serialization_one() { // The purpose of this test is to make sure the serialization format does // not change by accident. let expected_serialized_tree_hex = "010000836045484077cf6390184ea7cd48b460e2d0f22b2293b69633bb152314a692fb000193e5f97ce1d5d94d0c6e1b66a4a262c9ae89e56e28f3f6e4a557b6fb70e173a8"; - let serialized_tree = incremental_tree.as_bytes(); - assert_eq!(hex::encode(&serialized_tree), expected_serialized_tree_hex); - let deserialized_tree = SproutNoteCommitmentTree::from_bytes(&serialized_tree); - - // This check isn't enough to show that the entire struct is the same, because it just compares - // the cached serialized/deserialized roots. (NoteCommitmentTree::eq() also just compares - // roots.) - assert_eq!(incremental_tree.root(), deserialized_tree.root()); - - incremental_tree.assert_frontier_eq(&deserialized_tree); - - // Double-check that the internal format is the same by re-serializing the tree. - let re_serialized_tree = deserialized_tree.as_bytes(); - assert_eq!(serialized_tree, re_serialized_tree); + sprout_checks(incremental_tree, expected_serialized_tree_hex); } /// Check that the sprout tree database serialization format has not changed when the number of @@ -174,21 +135,8 @@ fn sprout_note_commitment_tree_serialization_pow2() { // The purpose of this test is to make sure the serialization format does // not change by accident. let expected_serialized_tree_hex = "010301836045484077cf6390184ea7cd48b460e2d0f22b2293b69633bb152314a692fb92498a8295ea36d593eaee7cb8b55be3a3e37b8185d3807693184054cd574ae4019f5b2b1e4bf7e7318d0a1f417ca6bca36077025b3d11e074b94cd55ce9f3861801b61f588fcba9cea79e94376adae1c49583f716d2f20367141f1369a235b95c98"; - let serialized_tree = incremental_tree.as_bytes(); - assert_eq!(hex::encode(&serialized_tree), expected_serialized_tree_hex); - let deserialized_tree = SproutNoteCommitmentTree::from_bytes(&serialized_tree); - - // This check isn't enough to show that the entire struct is the same, because it just compares - // the cached serialized/deserialized roots. (NoteCommitmentTree::eq() also just compares - // roots.) - assert_eq!(incremental_tree.root(), deserialized_tree.root()); - - incremental_tree.assert_frontier_eq(&deserialized_tree); - - // Double-check that the internal format is the same by re-serializing the tree. - let re_serialized_tree = deserialized_tree.as_bytes(); - assert_eq!(serialized_tree, re_serialized_tree); + sprout_checks(incremental_tree, expected_serialized_tree_hex); } /// Check that the sapling tree database serialization format has not changed. @@ -224,21 +172,8 @@ fn sapling_note_commitment_tree_serialization() { // The purpose of this test is to make sure the serialization format does // not change by accident. let expected_serialized_tree_hex = "0102007c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e036c0162324ff2c329e99193a74d28a585a3c167a93bf41a255135529c913bd9b1e66601ddaa1ab86de5c153993414f34ba97e9674c459dfadde112b89eeeafa0e5a204c"; - let serialized_tree = incremental_tree.as_bytes(); - assert_eq!(hex::encode(&serialized_tree), expected_serialized_tree_hex); - let deserialized_tree = SaplingNoteCommitmentTree::from_bytes(&serialized_tree); - - // This check isn't enough to show that the entire struct is the same, because it just compares - // the cached serialized/deserialized roots. (NoteCommitmentTree::eq() also just compares - // roots.) - assert_eq!(incremental_tree.root(), deserialized_tree.root()); - - incremental_tree.assert_frontier_eq(&deserialized_tree); - - // Double-check that the internal format is the same by re-serializing the tree. - let re_serialized_tree = deserialized_tree.as_bytes(); - assert_eq!(serialized_tree, re_serialized_tree); + sapling_checks(incremental_tree, expected_serialized_tree_hex); } /// Check that the sapling tree database serialization format has not changed for one commitment. @@ -270,21 +205,8 @@ fn sapling_note_commitment_tree_serialization_one() { // The purpose of this test is to make sure the serialization format does // not change by accident. let expected_serialized_tree_hex = "010000225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b1145800012c60c7de033d7539d123fb275011edfe08d57431676981d162c816372063bc71"; - let serialized_tree = incremental_tree.as_bytes(); - assert_eq!(hex::encode(&serialized_tree), expected_serialized_tree_hex); - let deserialized_tree = SaplingNoteCommitmentTree::from_bytes(&serialized_tree); - - // This check isn't enough to show that the entire struct is the same, because it just compares - // the cached serialized/deserialized roots. (NoteCommitmentTree::eq() also just compares - // roots.) - assert_eq!(incremental_tree.root(), deserialized_tree.root()); - - incremental_tree.assert_frontier_eq(&deserialized_tree); - - // Double-check that the internal format is the same by re-serializing the tree. - let re_serialized_tree = deserialized_tree.as_bytes(); - assert_eq!(serialized_tree, re_serialized_tree); + sapling_checks(incremental_tree, expected_serialized_tree_hex); } /// Check that the sapling tree database serialization format has not changed when the number of @@ -329,21 +251,8 @@ fn sapling_note_commitment_tree_serialization_pow2() { // The purpose of this test is to make sure the serialization format does // not change by accident. let expected_serialized_tree_hex = "010701f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c3a3661bc12b72646c94bc6c92796e81953985ee62d80a9ec3645a9a95740ac15025991131c5c25911b35fcea2a8343e2dfd7a4d5b45493390e0cb184394d91c349002df68503da9247dfde6585cb8c9fa94897cf21735f8fc1b32116ef474de05c01d23765f3d90dfd97817ed6d995bd253d85967f77b9f1eaef6ecbcb0ef6796812"; - let serialized_tree = incremental_tree.as_bytes(); - assert_eq!(hex::encode(&serialized_tree), expected_serialized_tree_hex); - let deserialized_tree = SaplingNoteCommitmentTree::from_bytes(&serialized_tree); - - // This check isn't enough to show that the entire struct is the same, because it just compares - // the cached serialized/deserialized roots. (NoteCommitmentTree::eq() also just compares - // roots.) - assert_eq!(incremental_tree.root(), deserialized_tree.root()); - - incremental_tree.assert_frontier_eq(&deserialized_tree); - - // Double-check that the internal format is the same by re-serializing the tree. - let re_serialized_tree = deserialized_tree.as_bytes(); - assert_eq!(serialized_tree, re_serialized_tree); + sapling_checks(incremental_tree, expected_serialized_tree_hex); } /// Check that the orchard tree database serialization format has not changed. @@ -389,21 +298,8 @@ fn orchard_note_commitment_tree_serialization() { // The purpose of this test is to make sure the serialization format does // not change by accident. let expected_serialized_tree_hex = "010200ee9488053a30c596b43014105d3477e6f578c89240d1d1ee1743b77bb6adc40a01a34b69a4e4d9ccf954d46e5da1004d361a5497f511aeb4d481d23c0be177813301a0be6dab19bc2c65d8299258c16e14d48ec4d4959568c6412aa85763c222a702"; - let serialized_tree = incremental_tree.as_bytes(); - assert_eq!(hex::encode(&serialized_tree), expected_serialized_tree_hex); - let deserialized_tree = OrchardNoteCommitmentTree::from_bytes(&serialized_tree); - - // This check isn't enough to show that the entire struct is the same, because it just compares - // the cached serialized/deserialized roots. (NoteCommitmentTree::eq() also just compares - // roots.) - assert_eq!(incremental_tree.root(), deserialized_tree.root()); - - incremental_tree.assert_frontier_eq(&deserialized_tree); - - // Double-check that the internal format is the same by re-serializing the tree. - let re_serialized_tree = deserialized_tree.as_bytes(); - assert_eq!(serialized_tree, re_serialized_tree); + orchard_checks(incremental_tree, expected_serialized_tree_hex); } /// Check that the orchard tree database serialization format has not changed for one commitment. @@ -437,21 +333,8 @@ fn orchard_note_commitment_tree_serialization_one() { // The purpose of this test is to make sure the serialization format does // not change by accident. let expected_serialized_tree_hex = "01000068135cf49933229099a44ec99a75e1e1cb4640f9b5bdec6b3223856fea16390a000178afd4da59c541e9c2f317f9aff654f1fb38d14dc99431cbbfa93601c7068117"; - let serialized_tree = incremental_tree.as_bytes(); - assert_eq!(hex::encode(&serialized_tree), expected_serialized_tree_hex); - let deserialized_tree = OrchardNoteCommitmentTree::from_bytes(&serialized_tree); - - // This check isn't enough to show that the entire struct is the same, because it just compares - // the cached serialized/deserialized roots. (NoteCommitmentTree::eq() also just compares - // roots.) - assert_eq!(incremental_tree.root(), deserialized_tree.root()); - - incremental_tree.assert_frontier_eq(&deserialized_tree); - - // Double-check that the internal format is the same by re-serializing the tree. - let re_serialized_tree = deserialized_tree.as_bytes(); - assert_eq!(serialized_tree, re_serialized_tree); + orchard_checks(incremental_tree, expected_serialized_tree_hex); } /// Check that the orchard tree database serialization format has not changed when the number of @@ -496,19 +379,156 @@ fn orchard_note_commitment_tree_serialization_pow2() { // The purpose of this test is to make sure the serialization format does // not change by accident. let expected_serialized_tree_hex = "01010178315008fb2998b430a5731d6726207dc0f0ec81ea64af5cf612956901e72f0eee9488053a30c596b43014105d3477e6f578c89240d1d1ee1743b77bb6adc40a0001d3d525931005e45f5a29bc82524e871e5ee1b6d77839deb741a6e50cd99fdf1a"; + + orchard_checks(incremental_tree, expected_serialized_tree_hex); +} + +fn sprout_checks(incremental_tree: SproutNoteCommitmentTree, expected_serialized_tree_hex: &str) { let serialized_tree = incremental_tree.as_bytes(); + + assert_eq!(hex::encode(&serialized_tree), expected_serialized_tree_hex); + + let deserialized_tree = SproutNoteCommitmentTree::from_bytes(&serialized_tree); + + // Get a legacy deserialized tree from the deserialized tree. + let deserialized_legacy_tree = LegacySproutNoteCommitmentTree::from(deserialized_tree.clone()); + + // Get a deserialized tree from a legacy deserialized tree. + let deserialized_legacy_tree_as_new = deserialized_legacy_tree.into(); + + // Check frontiers are the same. + incremental_tree.assert_frontier_eq(&deserialized_tree); + incremental_tree.assert_frontier_eq(&deserialized_legacy_tree_as_new); + + // Check cached roots are the same. + assert_eq!(incremental_tree.root(), deserialized_tree.root()); + assert_eq!( + incremental_tree.root(), + deserialized_legacy_tree_as_new.root() + ); + + // Check recalculated roots are the same + assert_eq!( + incremental_tree.recalculate_root(), + deserialized_tree.recalculate_root() + ); + assert_eq!( + incremental_tree.recalculate_root(), + deserialized_legacy_tree_as_new.recalculate_root() + ); + + // Check reclaculated and cached roots are the same + assert_eq!( + incremental_tree.recalculate_root(), + deserialized_tree + .cached_root() + .expect("cached root was serialized") + ); + + // Double-check that the internal format is the same by re-serializing the tree. + let re_serialized_tree = deserialized_tree.as_bytes(); + let re_serialized_legacy_tree = deserialized_legacy_tree_as_new.as_bytes(); + + assert_eq!(serialized_tree, re_serialized_tree); + assert_eq!(re_serialized_legacy_tree, re_serialized_tree); +} + +fn sapling_checks(incremental_tree: SaplingNoteCommitmentTree, expected_serialized_tree_hex: &str) { + let serialized_tree = incremental_tree.as_bytes(); + + assert_eq!(hex::encode(&serialized_tree), expected_serialized_tree_hex); + + let deserialized_tree = SaplingNoteCommitmentTree::from_bytes(&serialized_tree); + + // Get a legacy deserialized tree from the deserialized tree. + let deserialized_legacy_tree = LegacySaplingNoteCommitmentTree::from(deserialized_tree.clone()); + + // Get a deserialized tree from a legacy deserialized tree. + let deserialized_legacy_tree_as_new = deserialized_legacy_tree.into(); + + // Check frontiers are the same. + incremental_tree.assert_frontier_eq(&deserialized_tree); + incremental_tree.assert_frontier_eq(&deserialized_legacy_tree_as_new); + + // Check cached roots are the same. + assert_eq!(incremental_tree.root(), deserialized_tree.root()); + assert_eq!( + incremental_tree.root(), + deserialized_legacy_tree_as_new.root() + ); + + // Check recalculated roots are the same + assert_eq!( + incremental_tree.recalculate_root(), + deserialized_tree.recalculate_root() + ); + assert_eq!( + incremental_tree.recalculate_root(), + deserialized_legacy_tree_as_new.recalculate_root() + ); + + // Check reclaculated and cached roots are the same + assert_eq!( + incremental_tree.recalculate_root(), + deserialized_tree + .cached_root() + .expect("cached root was serialized") + ); + + // Double-check that the internal format is the same by re-serializing the tree. + let re_serialized_tree = deserialized_tree.as_bytes(); + let re_serialized_legacy_tree = deserialized_legacy_tree_as_new.as_bytes(); + + assert_eq!(serialized_tree, re_serialized_tree); + assert_eq!(re_serialized_legacy_tree, re_serialized_tree); +} + +fn orchard_checks(incremental_tree: OrchardNoteCommitmentTree, expected_serialized_tree_hex: &str) { + let serialized_tree = incremental_tree.as_bytes(); + assert_eq!(hex::encode(&serialized_tree), expected_serialized_tree_hex); let deserialized_tree = OrchardNoteCommitmentTree::from_bytes(&serialized_tree); - // This check isn't enough to show that the entire struct is the same, because it just compares - // the cached serialized/deserialized roots. (NoteCommitmentTree::eq() also just compares - // roots.) - assert_eq!(incremental_tree.root(), deserialized_tree.root()); + // Get a legacy deserialized tree from the deserialized tree. + let deserialized_legacy_tree = LegacyOrchardNoteCommitmentTree::from(deserialized_tree.clone()); + // Get a deserialized tree from a legacy deserialized tree. + let deserialized_legacy_tree_as_new = deserialized_legacy_tree.into(); + + // Check frontiers are the same. incremental_tree.assert_frontier_eq(&deserialized_tree); + incremental_tree.assert_frontier_eq(&deserialized_legacy_tree_as_new); + + // Check cached roots are the same. + assert_eq!(incremental_tree.root(), deserialized_tree.root()); + assert_eq!( + incremental_tree.root(), + deserialized_legacy_tree_as_new.root() + ); + + // Check recalculated roots are the same + assert_eq!( + incremental_tree.recalculate_root(), + deserialized_tree.recalculate_root() + ); + assert_eq!( + incremental_tree.recalculate_root(), + deserialized_legacy_tree_as_new.recalculate_root() + ); + + // Check reclaculated and cached roots are the same + assert_eq!( + incremental_tree.recalculate_root(), + deserialized_tree + .cached_root() + .expect("cached root was serialized") + ); // Double-check that the internal format is the same by re-serializing the tree. let re_serialized_tree = deserialized_tree.as_bytes(); + let re_serialized_legacy_tree = deserialized_legacy_tree_as_new.as_bytes(); + assert_eq!(serialized_tree, re_serialized_tree); + assert_eq!(re_serialized_legacy_tree, re_serialized_tree); }