thruput_sdk/hasher.rs
1use crate::SHA256Hash;
2use crate::re_throw::OrSyscallThrow;
3use crate::sys::{SysResult, SyscallNo};
4use core::fmt::*;
5
6type HasherIndex = u32;
7
8/// An incremental hasher, implemented by the VM.
9///
10/// A value of this type is meaningful only in the current contract invocation,
11/// so it can't be stored.
12pub struct Hasher {
13 index: HasherIndex,
14}
15
16impl Hasher {
17 /// Get a hasher.
18 ///
19 /// Throws a syscall exception if the VM refuses the stream — its
20 /// live-stream cap (the chain's `max_sha256_buffers` network config
21 /// parameter) is reached.
22 pub fn new() -> Hasher {
23 Hasher::try_new().or_syscall_throw(SyscallNo::kSHA256Update)
24 }
25
26 /// Get a hasher if the VM has a free stream, or the refusing syscall's
27 /// error.
28 pub fn try_new() -> SysResult<Hasher> {
29 let index = next_index();
30 // Create the VM-side stream now (an empty reset update): every live
31 // hasher owns a stream, so cap exhaustion surfaces here as the real
32 // syscall error rather than on some later use.
33 crate::sys::raw_SHA256Update(index, &[], true)?;
34 Ok(Hasher { index })
35 }
36
37 /// Append data to the hasher input.
38 ///
39 /// Throws a syscall exception (aborting the frame) if the host call
40 /// fails.
41 pub fn update<I: AsRef<[u8]>>(&mut self, input: &I) {
42 crate::sys::raw_SHA256Update(self.index, input.as_ref(), false)
43 .or_syscall_throw(SyscallNo::kSHA256Update);
44 }
45
46 /// Fork the hasher: a new hasher with a copy of the absorbed state,
47 /// after which the two diverge independently. The copy is
48 /// constant-time in the VM, so absorb a shared prefix once and fork
49 /// per suffix.
50 ///
51 /// Throws a syscall exception if the VM refuses the stream.
52 pub fn fork(&self) -> Hasher {
53 self.try_fork().or_syscall_throw(SyscallNo::kSHA256Copy)
54 }
55
56 /// Fork the hasher if the VM has a free stream (see
57 /// [`fork`](Self::fork)), or the refusing syscall's error.
58 pub fn try_fork(&self) -> SysResult<Hasher> {
59 let index = next_index();
60 crate::sys::raw_SHA256Copy(self.index, index)?;
61 Ok(Hasher { index })
62 }
63
64 /// Consume the hasher and return the SHA256 hash of the prior input,
65 /// freeing its VM-side stream.
66 ///
67 /// Throws a syscall exception (aborting the frame) if the host call
68 /// fails.
69 pub fn finish(self) -> SHA256Hash {
70 let index = self.index;
71 // The finalize below frees the stream itself; skip Drop's discard.
72 core::mem::forget(self);
73
74 let mut output = core::mem::MaybeUninit::uninit();
75
76 crate::sys::uninit_SHA256Finalize(index, &mut output)
77 .or_syscall_throw(SyscallNo::kSHA256Finalize);
78
79 // SAFETY: The syscall succeeded, so the output has been initialized
80 unsafe { output.assume_init() }
81 }
82}
83
84impl Drop for Hasher {
85 fn drop(&mut self) {
86 // Indices are never reused, so free the VM-side stream here
87 // (finalize, hash and errors ignored). Skipped while unwinding:
88 // the frame's streams die with it anyway, and a throwing syscall
89 // would double-panic. rv32 guests never unwind.
90 if !unwinding() {
91 let mut discard = core::mem::MaybeUninit::uninit();
92 let _ =
93 crate::sys::uninit_SHA256Finalize(self.index, &mut discard);
94 }
95 }
96}
97
98#[cfg(all(not(any(doc, target_arch = "riscv32")), feature = "std"))]
99fn unwinding() -> bool {
100 std::thread::panicking()
101}
102
103#[cfg(any(doc, target_arch = "riscv32", not(feature = "std")))]
104fn unwinding() -> bool {
105 false
106}
107
108impl Default for Hasher {
109 fn default() -> Self {
110 Hasher::new()
111 }
112}
113
114impl Debug for Hasher {
115 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
116 write!(f, "Hasher_{}", self.index)
117 }
118}
119
120////////////////////////////////////////////////////////////
121#[allow(unused)]
122use crate::cell::ContractCell;
123use crate::contract_local;
124
125// Stream indices are just names: the VM keys streams by arbitrary u32 and
126// caps only how many are live at once, so allocation is a bare counter and
127// indices are never reused or reclaimed (u32 does not run out within an
128// invocation). Capacity is the VM's to enforce — creation syscalls fail at
129// the cap.
130contract_local! {
131 static NEXT_INDEX: ContractCell<u32> = const { ContractCell::new(0) };
132}
133
134#[cfg(any(doc, feature = "std", target_arch = "riscv32"))]
135fn next_index() -> HasherIndex {
136 let index = NEXT_INDEX.get();
137 NEXT_INDEX.set(index + 1);
138 index
139}
140
141// Suppress compile errors when building for native without std
142#[cfg(not(any(doc, feature = "std", target_arch = "riscv32")))]
143fn next_index() -> HasherIndex {
144 0
145}
146
147#[test]
148fn test_indices_are_never_reused() {
149 let mut last = next_index();
150 for _ in 0..16 {
151 let index = next_index();
152 assert!(index > last, "indices must increase monotonically");
153 last = index;
154 }
155}