zcash_history/
version.rs

1use std::fmt;
2use std::io;
3
4use blake2b_simd::Params as Blake2Params;
5use byteorder::{ByteOrder, LittleEndian};
6
7use crate::{node_data, NodeData, MAX_NODE_DATA_SIZE};
8
9fn blake2b_personal(personalization: &[u8], input: &[u8]) -> [u8; 32] {
10    let hash_result = Blake2Params::new()
11        .hash_length(32)
12        .personal(personalization)
13        .to_state()
14        .update(input)
15        .finalize();
16    let mut result = [0u8; 32];
17    result.copy_from_slice(hash_result.as_bytes());
18    result
19}
20
21fn personalization(branch_id: u32) -> [u8; 16] {
22    let mut result = [0u8; 16];
23    result[..12].copy_from_slice(b"ZcashHistory");
24    LittleEndian::write_u32(&mut result[12..], branch_id);
25    result
26}
27
28/// A version of the chain history tree.
29pub trait Version {
30    /// The node data for this tree version.
31    type NodeData: fmt::Debug;
32
33    /// Returns the consensus branch ID for the given node data.
34    fn consensus_branch_id(data: &Self::NodeData) -> u32;
35
36    /// Returns the start height for the given node data.
37    fn start_height(data: &Self::NodeData) -> u64;
38
39    /// Returns the end height for the given node data.
40    fn end_height(data: &Self::NodeData) -> u64;
41
42    /// Combines two nodes' metadata.
43    fn combine(left: &Self::NodeData, right: &Self::NodeData) -> Self::NodeData {
44        assert_eq!(
45            Self::consensus_branch_id(left),
46            Self::consensus_branch_id(right)
47        );
48
49        let mut hash_buf = [0u8; MAX_NODE_DATA_SIZE * 2];
50        let size = {
51            let mut cursor = ::std::io::Cursor::new(&mut hash_buf[..]);
52            Self::write(left, &mut cursor)
53                .expect("Writing to memory buf with enough length cannot fail; qed");
54            Self::write(right, &mut cursor)
55                .expect("Writing to memory buf with enough length cannot fail; qed");
56            cursor.position() as usize
57        };
58
59        let hash = blake2b_personal(
60            &personalization(Self::consensus_branch_id(left)),
61            &hash_buf[..size],
62        );
63
64        Self::combine_inner(hash, left, right)
65    }
66
67    /// Combines two nodes metadata.
68    ///
69    /// For internal use.
70    fn combine_inner(
71        subtree_commitment: [u8; 32],
72        left: &Self::NodeData,
73        right: &Self::NodeData,
74    ) -> Self::NodeData;
75
76    /// Parses node data from the given reader.
77    fn read<R: io::Read>(consensus_branch_id: u32, r: &mut R) -> io::Result<Self::NodeData>;
78
79    /// Writes the byte representation of the given node data to the given writer.
80    fn write<W: io::Write>(data: &Self::NodeData, w: &mut W) -> io::Result<()>;
81
82    /// Converts to byte representation.
83    #[allow(clippy::wrong_self_convention)]
84    fn to_bytes(data: &Self::NodeData) -> Vec<u8> {
85        let mut buf = [0u8; MAX_NODE_DATA_SIZE];
86        let pos = {
87            let mut cursor = std::io::Cursor::new(&mut buf[..]);
88            Self::write(data, &mut cursor).expect("Cursor cannot fail");
89            cursor.position() as usize
90        };
91
92        buf[0..pos].to_vec()
93    }
94
95    /// Convert from byte representation.
96    fn from_bytes<T: AsRef<[u8]>>(consensus_branch_id: u32, buf: T) -> io::Result<Self::NodeData> {
97        let mut cursor = std::io::Cursor::new(buf);
98        Self::read(consensus_branch_id, &mut cursor)
99    }
100
101    /// Hash node metadata
102    fn hash(data: &Self::NodeData) -> [u8; 32] {
103        let bytes = Self::to_bytes(data);
104
105        blake2b_personal(&personalization(Self::consensus_branch_id(data)), &bytes)
106    }
107}
108
109/// Version 1 of the Zcash chain history tree.
110///
111/// This version was used for the Heartwood and Canopy epochs.
112pub enum V1 {}
113
114impl Version for V1 {
115    type NodeData = NodeData;
116
117    fn consensus_branch_id(data: &Self::NodeData) -> u32 {
118        data.consensus_branch_id
119    }
120
121    fn start_height(data: &Self::NodeData) -> u64 {
122        data.start_height
123    }
124
125    fn end_height(data: &Self::NodeData) -> u64 {
126        data.end_height
127    }
128
129    fn combine_inner(
130        subtree_commitment: [u8; 32],
131        left: &Self::NodeData,
132        right: &Self::NodeData,
133    ) -> Self::NodeData {
134        NodeData::combine_inner(subtree_commitment, left, right)
135    }
136
137    fn read<R: io::Read>(consensus_branch_id: u32, r: &mut R) -> io::Result<Self::NodeData> {
138        NodeData::read(consensus_branch_id, r)
139    }
140
141    fn write<W: io::Write>(data: &Self::NodeData, w: &mut W) -> io::Result<()> {
142        data.write(w)
143    }
144}
145
146/// Version 2 of the Zcash chain history tree.
147///
148/// This version is used from the NU5 epoch.
149pub enum V2 {}
150
151impl Version for V2 {
152    type NodeData = node_data::V2;
153
154    fn consensus_branch_id(data: &Self::NodeData) -> u32 {
155        data.v1.consensus_branch_id
156    }
157
158    fn start_height(data: &Self::NodeData) -> u64 {
159        data.v1.start_height
160    }
161
162    fn end_height(data: &Self::NodeData) -> u64 {
163        data.v1.end_height
164    }
165
166    fn combine_inner(
167        subtree_commitment: [u8; 32],
168        left: &Self::NodeData,
169        right: &Self::NodeData,
170    ) -> Self::NodeData {
171        node_data::V2::combine_inner(subtree_commitment, left, right)
172    }
173
174    fn read<R: io::Read>(consensus_branch_id: u32, r: &mut R) -> io::Result<Self::NodeData> {
175        node_data::V2::read(consensus_branch_id, r)
176    }
177
178    fn write<W: io::Write>(data: &Self::NodeData, w: &mut W) -> io::Result<()> {
179        data.write(w)
180    }
181}