penumbra_sdk_tct/internal/
hash.rs1use std::{
6 fmt::{self, Debug, Formatter},
7 ops::RangeInclusive,
8};
9
10use ark_ff::{One, Zero};
11use once_cell::sync::Lazy;
12use poseidon377::{hash_1, hash_4, Fq};
13use serde::{Deserialize, Serialize};
14
15use crate::prelude::*;
16
17mod cache;
18mod option;
19pub use {cache::CachedHash, option::OptionHash};
20
21pub trait GetHash {
24 fn hash(&self) -> Hash;
32
33 fn cached_hash(&self) -> Option<Hash>;
42
43 fn clear_cached_hash(&self) {}
47}
48
49impl<T: GetHash> GetHash for &T {
50 #[inline]
51 fn hash(&self) -> Hash {
52 (**self).hash()
53 }
54
55 #[inline]
56 fn cached_hash(&self) -> Option<Hash> {
57 (**self).cached_hash()
58 }
59}
60
61impl<T: GetHash> GetHash for &mut T {
62 #[inline]
63 fn hash(&self) -> Hash {
64 (**self).hash()
65 }
66
67 #[inline]
68 fn cached_hash(&self) -> Option<Hash> {
69 (**self).cached_hash()
70 }
71}
72
73#[derive(Clone, Copy, PartialEq, Eq, std::hash::Hash, Serialize, Deserialize)]
75pub struct Hash(#[serde(with = "crate::storage::serialize::fq")] Fq);
76
77impl From<Hash> for Fq {
78 #[inline]
79 fn from(hash: Hash) -> Self {
80 hash.0
81 }
82}
83
84impl Debug for Hash {
85 fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
86 if *self == Hash::zero() {
87 write!(f, "0")
88 } else if *self == Hash::one() {
89 write!(f, "1")
90 } else if *self == Hash::uninitialized() {
91 write!(f, "!")
92 } else {
93 write!(f, "{}", hex::encode(self.to_bytes()))
94 }
95 }
96}
97
98pub static DOMAIN_SEPARATOR: Lazy<Fq> =
102 Lazy::new(|| Fq::from_le_bytes_mod_order(blake2b_simd::blake2b(b"penumbra.tct").as_bytes()));
103
104#[allow(unused)]
105impl Hash {
106 pub fn new(fq: Fq) -> Self {
108 Self(fq)
109 }
110
111 pub fn to_bytes(self) -> [u8; 32] {
113 self.0.to_bytes()
114 }
115
116 pub fn from_bytes(bytes: [u8; 32]) -> Result<Self, decaf377::EncodingError> {
118 Ok(Self(Fq::from_bytes_checked(&bytes)?))
119 }
120
121 pub fn zero() -> Hash {
123 Self(Fq::zero())
124 }
125
126 pub fn is_zero(&self) -> bool {
128 self.0.is_zero()
129 }
130
131 pub fn one() -> Hash {
133 Self(Fq::one())
134 }
135
136 pub fn is_one(&self) -> bool {
138 self.0.is_one()
139 }
140
141 pub(crate) fn uninitialized() -> Hash {
144 Self(Fq::SENTINEL)
145 }
146
147 pub(crate) fn is_uninitialized(&self) -> bool {
149 *self == Self::uninitialized()
150 }
151
152 #[inline]
154 pub fn of(item: StateCommitment) -> Hash {
155 Self(hash_1(&DOMAIN_SEPARATOR, item.0))
156 }
157
158 #[inline]
161 pub fn node(height: u8, a: Hash, b: Hash, c: Hash, d: Hash) -> Hash {
162 fn hash_node(height: u8, a: Hash, b: Hash, c: Hash, d: Hash) -> Hash {
164 let height = Fq::from_le_bytes_mod_order(&height.to_le_bytes());
165 Hash(hash_4(&(*DOMAIN_SEPARATOR + height), (a.0, b.0, c.0, d.0)))
166 }
167
168 const PRECOMPUTE_HEIGHTS: RangeInclusive<u8> = 9..=16;
175
176 const TOTAL_PRECOMPUTED: usize =
177 *PRECOMPUTE_HEIGHTS.end() as usize - *PRECOMPUTE_HEIGHTS.start() as usize + 1;
178
179 static PRECOMPUTED_HASH_PAIRS: Lazy<[(Hash, Hash); TOTAL_PRECOMPUTED]> = Lazy::new(|| {
181 let mut hashes: Vec<(Hash, Hash)> = Vec::with_capacity(PRECOMPUTE_HEIGHTS.len());
182
183 for height in PRECOMPUTE_HEIGHTS {
184 let below = hashes.last().map(|below| below.1).unwrap_or_else(Hash::one);
185 hashes.push((below, hash_node(height, below, below, below, below)));
186 }
187
188 hashes
189 .try_into()
190 .expect("precomputed hashes should be the right length")
191 });
192
193 if PRECOMPUTE_HEIGHTS.contains(&height) {
196 let index = usize::from(height - PRECOMPUTE_HEIGHTS.start());
197 let (input, output) = PRECOMPUTED_HASH_PAIRS[index];
198 if [a, b, c, d] == [input, input, input, input] {
199 debug_assert_eq!(
200 output,
201 hash_node(height, a, b, c, d),
202 "precomputed hash mismatched calculated hash"
203 );
204 return output;
205 }
206 }
207
208 hash_node(height, a, b, c, d)
210 }
211}
212
213#[derive(
215 Derivative,
216 Clone,
217 Copy,
218 PartialEq,
219 Eq,
220 PartialOrd,
221 Ord,
222 std::hash::Hash,
223 Serialize,
224 Deserialize,
225 Default,
226)]
227#[cfg_attr(any(test, feature = "arbitrary"), derive(proptest_derive::Arbitrary))]
228#[serde(from = "u64", into = "u64")]
229pub struct Forgotten([u8; 6]);
230
231impl Debug for Forgotten {
232 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
233 write!(f, "{}", u64::from(*self))
234 }
235}
236
237impl Forgotten {
238 pub fn next(&self) -> Self {
240 Self::from(
241 u64::from(*self)
242 .checked_add(1)
243 .expect("forgotten should never overflow"),
244 )
245 }
246}
247
248impl From<Forgotten> for u64 {
249 fn from(forgotten: Forgotten) -> Self {
250 let mut eight_bytes = <[u8; 8]>::default();
251 for (in_byte, out_byte) in eight_bytes.iter_mut().zip(forgotten.0) {
252 *in_byte = out_byte;
253 }
254
255 u64::from_le_bytes(eight_bytes)
256 }
257}
258
259impl From<u64> for Forgotten {
260 fn from(u: u64) -> Self {
261 let bytes = u.to_le_bytes();
262
263 let mut six_bytes = [0; 6];
264 for (in_byte, out_byte) in six_bytes.iter_mut().zip(&bytes[..6]) {
265 *in_byte = *out_byte;
266 }
267
268 Self(six_bytes)
269 }
270}
271
272#[cfg(any(test, feature = "arbitrary"))]
273mod arbitrary {
274 use poseidon377::Fq;
275
276 use super::Hash;
277
278 impl proptest::arbitrary::Arbitrary for Hash {
279 type Parameters = ();
280
281 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
282 HashStrategy
283 }
284
285 type Strategy = HashStrategy;
286 }
287
288 #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
289 pub struct HashStrategy;
290
291 impl proptest::strategy::Strategy for HashStrategy {
292 type Tree = proptest::strategy::Just<Hash>;
293
294 type Value = Hash;
295
296 fn new_tree(
297 &self,
298 runner: &mut proptest::test_runner::TestRunner,
299 ) -> proptest::strategy::NewTree<Self> {
300 use proptest::prelude::RngCore;
301 let rng = runner.rng();
302 let mut bytes = [0u8; 32];
303 rng.fill_bytes(&mut bytes);
304 Ok(proptest::strategy::Just(Hash(Fq::from_le_bytes_mod_order(
305 &bytes,
306 ))))
307 }
308 }
309}
310
311#[cfg(test)]
312mod test {
313 #[test]
314 fn forgotten_increments() {
315 use super::Forgotten;
316
317 let mut last = Forgotten::default();
318 for _ in 0..10 {
319 let next = last.next();
320 assert_eq!(u64::from(next), u64::from(last) + 1);
321 last = next;
322 }
323 }
324}