Skip to main content

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}