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