Skip to main content

dope_runtime/random/
mod.rs

1//! Runtime-owned randomness bound to one exact generative driver domain.
2
3use std::{hash, marker, mem};
4
5use dope_core::{driver, driver::schedule::sequence, random::Seed};
6use o3::base::marker::ThreadBound;
7
8mod sip;
9
10pub(crate) use sip::Sip;
11
12type SipHasher13 = Sip<1, 3>;
13type SipHasher24 = Sip<2, 4>;
14
15/// Owned hashing state that remains confined to its creating thread.
16pub type OwnedHasher = Sip<1, 3>;
17
18pub(crate) fn derive_seed(seed: &Seed, domain: Domain) -> Seed {
19    seed.map(|words| Key::new(words).derive(domain, ()).into_words())
20}
21
22type Invariant<'d> = marker::PhantomData<(fn(&'d ()) -> &'d (), ThreadBound)>;
23
24/// Owned, thread-bound hash key derived from already acquired seed material.
25/// Clones retain the same hash namespace without acquiring OS entropy or a
26/// driver capability.
27#[derive(Clone)]
28pub struct HashSeed {
29    state: (Key, ThreadBound),
30}
31
32impl HashSeed {
33    pub fn from_seed(seed: &Seed, domain: Domain) -> Self {
34        Self {
35            state: (
36                Key::new(*seed.as_ref()).derive(domain, ()),
37                ThreadBound::NEW,
38            ),
39        }
40    }
41
42    pub fn hash<T: hash::Hash + ?Sized>(&self, input: &T) -> u64 {
43        let mut state = self.state.0.hasher();
44        input.hash(&mut state);
45        hash::Hasher::finish(&state)
46    }
47
48    /// Hashes one complete byte string without `Hash` framing.
49    pub fn hash_bytes(&self, input: &[u8]) -> u64 {
50        self.state.0.hasher().hash(input)
51    }
52}
53
54impl hash::BuildHasher for HashSeed {
55    type Hasher = OwnedHasher;
56
57    fn build_hasher(&self) -> Self::Hasher {
58        self.state.0.hasher()
59    }
60}
61
62/// Keyed hash builder issued for one exact generative driver domain.
63///
64/// The driver lifetime is invariant and cannot be widened. Building a hasher
65/// preserves the same invariant driver lifetime.
66pub struct HashState<'d> {
67    state: (Key, Invariant<'d>),
68}
69
70impl<'d> HashState<'d> {
71    pub(crate) fn for_seed(seed: &Seed, domain: Domain) -> Self {
72        Self {
73            state: (
74                Key::new(*seed.as_ref()).derive(domain, ()),
75                marker::PhantomData,
76            ),
77        }
78    }
79}
80
81impl<'d> hash::BuildHasher for HashState<'d> {
82    type Hasher = Hasher<'d>;
83
84    fn build_hasher(&self) -> Self::Hasher {
85        Hasher::new(self.state.0.hasher(), self.state.1)
86    }
87}
88
89/// Hashing state retaining the exact driver brand of its key source.
90pub struct Hasher<'d> {
91    state: (SipHasher13, Invariant<'d>),
92}
93
94impl<'d> Hasher<'d> {
95    const fn new(inner: SipHasher13, proof: Invariant<'d>) -> Self {
96        Self {
97            state: (inner, proof),
98        }
99    }
100}
101
102impl hash::Hasher for Hasher<'_> {
103    fn finish(&self) -> u64 {
104        self.state.0.finish()
105    }
106
107    fn write(&mut self, bytes: &[u8]) {
108        self.state.0.write(bytes);
109    }
110
111    fn write_u8(&mut self, value: u8) {
112        self.state.0.write_u8(value);
113    }
114
115    fn write_u16(&mut self, value: u16) {
116        self.state.0.write_u16(value);
117    }
118
119    fn write_u32(&mut self, value: u32) {
120        self.state.0.write_u32(value);
121    }
122
123    fn write_u64(&mut self, value: u64) {
124        self.state.0.write_u64(value);
125    }
126
127    fn write_usize(&mut self, value: usize) {
128        self.state.0.write_usize(value);
129    }
130}
131
132/// Compile-time purpose separating random roots without runtime state.
133/// Implementations use a stable, globally distinct domain.
134pub trait Purpose {
135    const DOMAIN: Domain;
136}
137
138/// Keyed words and permutations supplied by a purpose-bound random owner.
139/// Word generation and permutation rounds occupy separate domains even when
140/// their compile-time lane is the same.
141pub trait Entropy<P: Purpose> {
142    fn word<const LANE: u64>(&self, input: u64) -> Word<P, LANE>;
143    fn permute<const LANE: u64>(&self, value: u64) -> Word<P, LANE>;
144
145    /// Derives an independent owned hash key, without exporting the root or its
146    /// driver capability. Hash keys occupy a separate domain from public words
147    /// and permutations, including when their compile-time lane is the same.
148    fn hash_seed<const LANE: u64>(&self) -> HashSeed;
149}
150
151type RootState<'d, P> = (Key, Invariant<'d>, marker::PhantomData<fn(P) -> P>);
152type ProcessState<P> = (Key, marker::PhantomData<(fn(P) -> P, ThreadBound)>);
153
154/// Non-cloneable secret root bound invariantly to one driver domain.
155/// Protocol authorities retain it for that lifetime and use compile-time lanes
156/// to separate streams; storage is exactly the two secret key words.
157pub struct Root<'d, P: Purpose> {
158    state: RootState<'d, P>,
159}
160
161impl<'d, P: Purpose> Root<'d, P> {
162    const fn new(seed: Key) -> Self {
163        Self {
164            state: (seed, marker::PhantomData, marker::PhantomData),
165        }
166    }
167
168    /// Hashes a typed identity in a compile-time lane without materializing an
169    /// intermediate byte representation.
170    pub fn hash<const LANE: u64, T: hash::Hash + ?Sized>(&self, input: &T) -> Word<P, LANE> {
171        self.state.0.hash::<P, LANE, T>(input)
172    }
173}
174
175/// Non-cloneable process-owned secret root without a fabricated owner lifetime.
176pub struct Process<P: Purpose> {
177    state: ProcessState<P>,
178}
179
180impl<P: Purpose> Process<P> {
181    pub fn random() -> Result<Self, dope_core::Errno> {
182        let seed = Key::new(*Seed::acquire()?.as_ref()).derive(P::DOMAIN, ());
183        Ok(Self {
184            state: (seed, marker::PhantomData),
185        })
186    }
187}
188
189impl<P: Purpose> Entropy<P> for Root<'_, P> {
190    fn word<const LANE: u64>(&self, input: u64) -> Word<P, LANE> {
191        self.state.0.hash::<P, LANE, _>(&input)
192    }
193
194    fn permute<const LANE: u64>(&self, value: u64) -> Word<P, LANE> {
195        self.state.0.permute::<P, LANE>(value)
196    }
197
198    fn hash_seed<const LANE: u64>(&self) -> HashSeed {
199        self.state.0.hash_seed::<LANE>()
200    }
201}
202
203impl<P: Purpose> Entropy<P> for Process<P> {
204    fn word<const LANE: u64>(&self, input: u64) -> Word<P, LANE> {
205        self.state.0.hash::<P, LANE, _>(&input)
206    }
207
208    fn permute<const LANE: u64>(&self, value: u64) -> Word<P, LANE> {
209        self.state.0.permute::<P, LANE>(value)
210    }
211
212    fn hash_seed<const LANE: u64>(&self) -> HashSeed {
213        self.state.0.hash_seed::<LANE>()
214    }
215}
216
217impl<'d, P: Purpose> Root<'d, P> {
218    /// Consumes a fresh identity from the driver before deriving a purpose root.
219    /// Repeated acquisition never reuses the same derivation input.
220    pub fn try_acquire(driver: &driver::Context<'_, 'd>) -> Result<Self, sequence::Exhausted> {
221        let driver = driver.driver_ref();
222        let instance = driver.try_next_id()?;
223        let seed = Key::new(*driver.random_seed().as_ref());
224        Ok(Self::new(seed.derive(P::DOMAIN, instance)))
225    }
226}
227
228const _: () = assert!(mem::size_of::<HashState<'static>>() == 2 * mem::size_of::<u64>());
229const _: () = assert!(mem::size_of::<HashSeed>() == 2 * mem::size_of::<u64>());
230const _: () = assert!(mem::size_of::<OwnedHasher>() == mem::size_of::<(usize, [u64; 5])>());
231const _: () = assert!(mem::size_of::<Hasher<'static>>() == mem::size_of::<SipHasher13>());
232
233/// Secret key retained by one runtime authority.
234#[repr(transparent)]
235#[derive(Clone)]
236struct Key([u64; 2]);
237
238impl Key {
239    const fn new(words: [u64; 2]) -> Self {
240        Self(words)
241    }
242
243    const fn into_words(self) -> [u64; 2] {
244        self.0
245    }
246
247    fn derive(&self, domain: Domain, instance: impl hash::Hash) -> Self {
248        let mut state = self.prefix(domain);
249        instance.hash(&mut state);
250        Self::from_hasher(state)
251    }
252
253    fn from_hasher(state: SipHasher24) -> Self {
254        Self([0, 1].map(|word| {
255            let mut state = state;
256            hash::Hasher::write_u64(&mut state, word);
257            hash::Hasher::finish(&state)
258        }))
259    }
260
261    fn hasher(&self) -> SipHasher13 {
262        SipHasher13::new(self.0[0], self.0[1])
263    }
264
265    fn hash_seed<const LANE: u64>(&self) -> HashSeed {
266        HashSeed {
267            state: (Self::from_hasher(self.lane::<LANE>(2)), ThreadBound::NEW),
268        }
269    }
270
271    fn hash<P, const LANE: u64, T: hash::Hash + ?Sized>(&self, input: &T) -> Word<P, LANE> {
272        let mut state = self.lane::<LANE>(0);
273        input.hash(&mut state);
274        Word::new(hash::Hasher::finish(&state))
275    }
276
277    fn permute<P, const LANE: u64>(&self, value: u64) -> Word<P, LANE> {
278        let state = self.lane::<LANE>(1);
279        let mut left = (value >> 32) as u32;
280        let mut right = value as u32;
281        let mut round = 0;
282        while round < 4 {
283            let input = ((round as u64) << 32) | u64::from(right);
284            let mut hash = state;
285            hash::Hasher::write_u64(&mut hash, input);
286            let next = left ^ hash::Hasher::finish(&hash) as u32;
287            left = right;
288            right = next;
289            round += 1;
290        }
291        Word::new((u64::from(left) << 32) | u64::from(right))
292    }
293
294    fn lane<const LANE: u64>(&self, kind: u8) -> SipHasher24 {
295        let mut state = SipHasher24::new(self.0[0], self.0[1]);
296        hash::Hasher::write_u64(&mut state, LANE);
297        hash::Hasher::write_u8(&mut state, kind);
298        state
299    }
300
301    fn prefix(&self, domain: Domain) -> SipHasher24 {
302        let mut state = SipHasher24::new(self.0[0], self.0[1]);
303        hash::Hasher::write(&mut state, &domain.namespace);
304        hash::Hasher::write_u64(&mut state, domain.instance);
305        state
306    }
307}
308
309/// Stable, human-readable ownership of one independent random namespace.
310#[derive(Clone, Copy, PartialEq, Eq, Hash)]
311#[repr(transparent)]
312pub struct Namespace([u8; 8]);
313
314impl Namespace {
315    pub const fn new(name: [u8; 8]) -> Self {
316        Self(name)
317    }
318
319    pub const fn fixed(self) -> Domain {
320        self.instance(0)
321    }
322
323    pub const fn instance(self, instance: u64) -> Domain {
324        Domain {
325            namespace: self.0,
326            instance,
327        }
328    }
329}
330
331/// Stable separation between one owned namespace and one exact instance.
332#[derive(Clone, Copy, PartialEq, Eq, Hash)]
333pub struct Domain {
334    namespace: [u8; 8],
335    instance: u64,
336}
337
338/// One purpose- and lane-bound random word.
339#[repr(transparent)]
340pub struct Word<P, const LANE: u64>(u64, marker::PhantomData<fn(P) -> P>);
341
342impl<P, const LANE: u64> Word<P, LANE> {
343    const fn new(value: u64) -> Self {
344        Self(value, marker::PhantomData)
345    }
346
347    /// Consumes this purpose-bound word into its canonical big-endian encoding.
348    pub const fn into_bytes(self) -> [u8; 8] {
349        self.0.to_be_bytes()
350    }
351}
352
353const _: () = {
354    assert!(mem::size_of::<Key>() == 2 * mem::size_of::<u64>());
355    assert!(mem::size_of::<Namespace>() == mem::size_of::<u64>());
356    assert!(mem::size_of::<Domain>() == 2 * mem::size_of::<u64>());
357    assert!(mem::size_of::<Word<(), 0>>() == mem::size_of::<u64>());
358};