Skip to main content

rvm_types/
witness.rs

1//! Witness record types for the audit subsystem.
2//!
3//! Every privileged action in RVM emits a compact, immutable audit record.
4//! This is a core invariant (INV-3): **no witness, no mutation**.
5//!
6//! The witness record is exactly 64 bytes, cache-line aligned, with FNV-1a
7//! hash chaining for tamper evidence. See ADR-134 for the full specification.
8
9/// A single witness record. Exactly 64 bytes, cache-line aligned.
10///
11/// All fields are little-endian. The record is `#[repr(C, align(64))]` to
12/// guarantee layout and alignment on all target architectures (`AArch64`,
13/// RISC-V, x86-64).
14///
15/// # Layout
16///
17/// | Offset | Size | Field                | Description |
18/// |--------|------|----------------------|-------------|
19/// | 0      | 8    | `sequence`           | Monotonic sequence number |
20/// | 8      | 8    | `timestamp_ns`       | Nanosecond timestamp |
21/// | 16     | 1    | `action_kind`        | Privileged action discriminant |
22/// | 17     | 1    | `proof_tier`         | Proof tier (1, 2, or 3) |
23/// | 18     | 1    | `flags`              | Action-specific flags |
24/// | 19     | 1    | `_reserved`          | Reserved (must be zero) |
25/// | 20     | 4    | `actor_partition_id` | Actor partition |
26/// | 24     | 8    | `target_object_id`   | Target object |
27/// | 32     | 4    | `capability_hash`    | Truncated cap hash |
28/// | 36     | 8    | `payload`            | Action-specific data |
29/// | 44     | 4    | `prev_hash`          | FNV-1a chain link |
30/// | 48     | 4    | `record_hash`        | FNV-1a self-integrity |
31/// | 52     | 8    | `aux`                | Secondary payload / TEE sig |
32/// | 60     | 4    | `_pad`               | Padding to 64 bytes |
33#[derive(Debug, Clone, Copy)]
34#[repr(C, align(64))]
35pub struct WitnessRecord {
36    /// Monotonic sequence number. Provides global ordering of all privileged actions.
37    pub sequence: u64,
38    /// Nanosecond timestamp from the system timer (`CNTVCT_EL0` / `rdtsc`).
39    pub timestamp_ns: u64,
40    /// Which privileged action was performed (see [`ActionKind`]).
41    pub action_kind: u8,
42    /// Which proof tier authorized this action (1 = P1, 2 = P2, 3 = P3).
43    pub proof_tier: u8,
44    /// Action-specific flags (interpretation varies by `action_kind`).
45    pub flags: u8,
46    /// Reserved for future use. Must be zero.
47    reserved: u8,
48    /// Partition that performed the action.
49    pub actor_partition_id: u32,
50    /// Object acted upon: partition, region, capability, etc.
51    pub target_object_id: u64,
52    /// Truncated FNV-1a hash of the capability used (not the full token).
53    pub capability_hash: u32,
54    /// Action-specific data, packed by kind.
55    ///
56    /// Examples:
57    /// - `PartitionSplit`: `new_id_a` in bytes \[0..4\], `new_id_b` in bytes \[4..8\].
58    /// - `RegionTransfer`: `from_partition` in bytes \[0..4\], `to_partition` in bytes \[4..8\].
59    pub payload: [u8; 8],
60    /// FNV-1a hash of the previous record (chain link for tamper evidence).
61    pub prev_hash: u32,
62    /// FNV-1a hash of bytes \[0..44\] of this record (self-integrity).
63    pub record_hash: u32,
64    /// Secondary payload or TEE signature fragment.
65    pub aux: [u8; 8],
66    /// Padding to guarantee 64-byte total size.
67    pad: [u8; 4],
68}
69
70// Compile-time size assertion: the record MUST be exactly 64 bytes.
71const _: () = {
72    assert!(core::mem::size_of::<WitnessRecord>() == 64);
73};
74
75impl WitnessRecord {
76    /// Create a zeroed witness record (genesis / placeholder).
77    #[must_use]
78    pub const fn zeroed() -> Self {
79        Self {
80            sequence: 0,
81            timestamp_ns: 0,
82            action_kind: 0,
83            proof_tier: 0,
84            flags: 0,
85            reserved: 0,
86            actor_partition_id: 0,
87            target_object_id: 0,
88            capability_hash: 0,
89            payload: [0; 8],
90            prev_hash: 0,
91            record_hash: 0,
92            aux: [0; 8],
93            pad: [0; 4],
94        }
95    }
96}
97
98/// A 256-bit witness commitment hash.
99///
100/// Used to anchor state transitions in the RVM witness trail. This is
101/// a fixed-size value type suitable for embedding in `no_std` contexts
102/// without heap allocation.
103#[derive(Clone, Copy, PartialEq, Eq, Hash)]
104pub struct WitnessHash {
105    bytes: [u8; 32],
106}
107
108impl WitnessHash {
109    /// The zero hash, used as a sentinel for the genesis state.
110    pub const ZERO: Self = Self { bytes: [0u8; 32] };
111
112    /// Create a witness hash from raw bytes.
113    #[must_use]
114    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
115        Self { bytes }
116    }
117
118    /// Return the raw byte representation.
119    #[must_use]
120    pub const fn as_bytes(&self) -> &[u8; 32] {
121        &self.bytes
122    }
123
124    /// Check whether this is the zero (genesis) hash.
125    #[must_use]
126    pub const fn is_zero(&self) -> bool {
127        let mut i = 0;
128        while i < 32 {
129            if self.bytes[i] != 0 {
130                return false;
131            }
132            i += 1;
133        }
134        true
135    }
136}
137
138impl core::fmt::Debug for WitnessHash {
139    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
140        write!(f, "WitnessHash(")?;
141        for byte in &self.bytes[..4] {
142            write!(f, "{byte:02x}")?;
143        }
144        write!(f, "..)")
145    }
146}
147
148impl core::fmt::Display for WitnessHash {
149    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
150        for byte in &self.bytes {
151            write!(f, "{byte:02x}")?;
152        }
153        Ok(())
154    }
155}
156
157/// Privileged actions that produce witness records (ADR-134, Section 2).
158///
159/// Organized by subsystem. Hex values allow easy filtering by prefix in
160/// audit queries (0x0_ = partition, 0x1_ = capability, 0x2_ = memory, etc.).
161///
162/// If a privileged action exists without a corresponding kind, the system
163/// has an audit gap.
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
165#[repr(u8)]
166pub enum ActionKind {
167    // --- Partition lifecycle (0x01-0x0F) ---
168    /// A new partition was created.
169    PartitionCreate = 0x01,
170    /// A partition was destroyed and its resources freed.
171    PartitionDestroy = 0x02,
172    /// A partition was suspended (tasks paused).
173    PartitionSuspend = 0x03,
174    /// A suspended partition was resumed.
175    PartitionResume = 0x04,
176    /// A partition was split along a mincut boundary.
177    PartitionSplit = 0x05,
178    /// Two partitions were merged into one.
179    PartitionMerge = 0x06,
180    /// A partition was hibernated to dormant/cold storage.
181    PartitionHibernate = 0x07,
182    /// A hibernated partition was reconstructed from its receipt.
183    PartitionReconstruct = 0x08,
184    /// A partition was migrated to another node.
185    PartitionMigrate = 0x09,
186
187    // --- Capability operations (0x10-0x1F) ---
188    /// A capability was granted (copied) to another partition.
189    CapabilityGrant = 0x10,
190    /// A capability was revoked.
191    CapabilityRevoke = 0x11,
192    /// A capability was delegated (with depth decrement).
193    CapabilityDelegate = 0x12,
194    /// Delegation depth was increased (escalation).
195    CapabilityEscalate = 0x13,
196    /// Capability was attenuated during a partition split (DC-8).
197    CapabilityAttenuated = 0x14,
198
199    // --- Memory operations (0x20-0x2F) ---
200    /// A memory region was created.
201    RegionCreate = 0x20,
202    /// A memory region was destroyed.
203    RegionDestroy = 0x21,
204    /// A memory region was transferred to another partition.
205    RegionTransfer = 0x22,
206    /// A memory region was shared (read-only) with another partition.
207    RegionShare = 0x23,
208    /// A shared memory region was unshared.
209    RegionUnshare = 0x24,
210    /// A memory region was promoted to a warmer tier.
211    RegionPromote = 0x25,
212    /// A memory region was demoted to a colder tier.
213    RegionDemote = 0x26,
214    /// A stage-2 mapping was added for a memory region.
215    RegionMap = 0x27,
216    /// A stage-2 mapping was removed for a memory region.
217    RegionUnmap = 0x28,
218
219    // --- Communication (0x30-0x3F) ---
220    /// A communication edge was created between two partitions.
221    CommEdgeCreate = 0x30,
222    /// A communication edge was destroyed.
223    CommEdgeDestroy = 0x31,
224    /// An IPC message was sent.
225    IpcSend = 0x32,
226    /// An IPC message was received.
227    IpcReceive = 0x33,
228    /// A zero-copy memory share was established.
229    ZeroCopyShare = 0x34,
230    /// A notification signal was sent.
231    NotificationSignal = 0x35,
232
233    // --- Device operations (0x40-0x4F) ---
234    /// A device lease was granted.
235    DeviceLeaseGrant = 0x40,
236    /// A device lease was revoked.
237    DeviceLeaseRevoke = 0x41,
238    /// A device lease expired (time-bounded).
239    DeviceLeaseExpire = 0x42,
240    /// A device lease was renewed.
241    DeviceLeaseRenew = 0x43,
242
243    // --- Proof verification (0x50-0x5F) ---
244    /// A P1 capability check passed.
245    ProofVerifiedP1 = 0x50,
246    /// A P2 policy validation passed.
247    ProofVerifiedP2 = 0x51,
248    /// A P3 deep proof passed.
249    ProofVerifiedP3 = 0x52,
250    /// A proof was rejected.
251    ProofRejected = 0x53,
252    /// A proof was escalated to a higher tier.
253    ProofEscalated = 0x54,
254
255    // --- Scheduler decisions (0x60-0x6F) ---
256    /// Scheduler epoch boundary (bulk switch summary per DC-10).
257    SchedulerEpoch = 0x60,
258    /// Scheduler mode switched (Reflex / Flow / Recovery).
259    SchedulerModeSwitch = 0x61,
260    /// A task was spawned within a partition.
261    TaskSpawn = 0x62,
262    /// A task was terminated.
263    TaskTerminate = 0x63,
264    /// Scheduler triggered a structural split.
265    StructuralSplit = 0x64,
266    /// Scheduler triggered a structural merge.
267    StructuralMerge = 0x65,
268
269    // --- Recovery actions (0x70-0x7F) ---
270    /// System entered recovery mode.
271    RecoveryEnter = 0x70,
272    /// System exited recovery mode.
273    RecoveryExit = 0x71,
274    /// A recovery checkpoint was created.
275    CheckpointCreated = 0x72,
276    /// A recovery checkpoint was restored.
277    CheckpointRestored = 0x73,
278    /// Mincut budget was exceeded, stale cut used (DC-2 fallback).
279    MinCutBudgetExceeded = 0x74,
280    /// System entered degraded mode (DC-6).
281    DegradedModeEntered = 0x75,
282    /// System exited degraded mode.
283    DegradedModeExited = 0x76,
284
285    // --- Boot and attestation (0x80-0x8F) ---
286    /// Boot attestation record (genesis witness).
287    BootAttestation = 0x80,
288    /// Boot sequence completed successfully.
289    BootComplete = 0x81,
290    /// TEE-backed attestation record.
291    TeeAttestation = 0x82,
292
293    // --- Vector/Graph mutations (0x90-0x9F) ---
294    /// A vector was inserted into the coherence graph.
295    VectorPut = 0x90,
296    /// A vector was deleted from the coherence graph.
297    VectorDelete = 0x91,
298    /// A graph mutation occurred.
299    GraphMutation = 0x92,
300    /// Coherence scores were recomputed.
301    CoherenceRecomputed = 0x93,
302
303    // --- VMID management (0xA0-0xAF) ---
304    /// A physical VMID was reclaimed from a hibernated partition (DC-12).
305    VmidReclaim = 0xA0,
306    /// Migration timed out and was aborted (DC-7).
307    MigrationTimeout = 0xA1,
308
309    // --- External anchoring (0xB0-0xBF) ---
310    /// A commitment to an external, service-side record (e.g. a ruflo
311    /// ADR-322C evaluation receipt) was anchored into the witness chain
312    /// after independent verification (ADR-156).
313    ///
314    /// Anchoring records provenance only: the anchored record keeps the
315    /// assurance level it was produced under and does not acquire the
316    /// witness chain's guarantees (ADR-285 discipline).
317    AnchorExternalReceipt = 0xB0,
318
319    // --- Governed context namespace (0xC0-0xCF) ---
320    /// A versionless `ruv://` name was resolved to an immutable RVF revision.
321    ContextResolve = 0xC0,
322    /// A progressive context representation was read.
323    ContextRead = 0xC1,
324    /// An authorized context search enumerated candidate results.
325    ContextSearch = 0xC2,
326    /// A new immutable context revision was registered.
327    ContextPut = 0xC3,
328    /// A versionless alias was changed with compare-and-swap.
329    ContextAliasUpdate = 0xC4,
330    /// A context alias was tombstoned and its payload became unreachable.
331    ContextForget = 0xC5,
332    /// Execution of pinned context content was authorized.
333    ContextExecute = 0xC6,
334    /// A cryptographic receipt sealed a context witness epoch.
335    ContextEpochSeal = 0xC7,
336}
337
338impl ActionKind {
339    /// Return the subsystem prefix for this action kind.
340    ///
341    /// Useful for filtering audit queries by subsystem:
342    /// 0 = partition, 1 = capability, 2 = memory, 3 = communication,
343    /// 4 = device, 5 = proof, 6 = scheduler, 7 = recovery,
344    /// 8 = boot, 9 = graph, 0xA = VMID management,
345    /// 0xB = external anchoring, 0xC = governed context.
346    #[must_use]
347    pub const fn subsystem(self) -> u8 {
348        (self as u8) >> 4
349    }
350}
351
352/// FNV-1a hash over a byte slice.
353///
354/// Chosen for speed (< 50 ns for 64 bytes), not cryptographic strength.
355/// For tamper resistance against a capable adversary, use the optional
356/// TEE-backed `WitnessSigner` (ADR-134, Section 9).
357///
358/// Unrolls the per-byte loop by 8 for inputs >= 8 bytes while preserving
359/// standard FNV-1a byte-order sensitivity. The remainder is handled
360/// one byte at a time.
361#[inline]
362#[must_use]
363pub fn fnv1a_64(data: &[u8]) -> u64 {
364    const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
365    const FNV_PRIME: u64 = 0x0000_0100_0000_01B3;
366
367    let mut hash: u64 = FNV_OFFSET;
368    let len = data.len();
369    let mut i = 0;
370
371    // Process 8 bytes at a time (unrolled), preserving standard FNV-1a
372    // per-byte XOR-then-multiply semantics for hash compatibility.
373    while i + 8 <= len {
374        hash ^= u64::from(data[i]);
375        hash = hash.wrapping_mul(FNV_PRIME);
376        hash ^= u64::from(data[i + 1]);
377        hash = hash.wrapping_mul(FNV_PRIME);
378        hash ^= u64::from(data[i + 2]);
379        hash = hash.wrapping_mul(FNV_PRIME);
380        hash ^= u64::from(data[i + 3]);
381        hash = hash.wrapping_mul(FNV_PRIME);
382        hash ^= u64::from(data[i + 4]);
383        hash = hash.wrapping_mul(FNV_PRIME);
384        hash ^= u64::from(data[i + 5]);
385        hash = hash.wrapping_mul(FNV_PRIME);
386        hash ^= u64::from(data[i + 6]);
387        hash = hash.wrapping_mul(FNV_PRIME);
388        hash ^= u64::from(data[i + 7]);
389        hash = hash.wrapping_mul(FNV_PRIME);
390        i += 8;
391    }
392
393    // Handle remaining bytes one at a time.
394    while i < len {
395        hash ^= u64::from(data[i]);
396        hash = hash.wrapping_mul(FNV_PRIME);
397        i += 1;
398    }
399
400    hash
401}
402
403/// FNV-1a hash truncated to 32 bits.
404#[inline]
405#[must_use]
406#[allow(clippy::cast_possible_truncation)]
407pub fn fnv1a_32(data: &[u8]) -> u32 {
408    // Intentional truncation: 64-bit hash folded to 32 bits.
409    fnv1a_64(data) as u32
410}
411
412/// Default witness ring buffer capacity in records.
413///
414/// 16 MiB / 64 bytes = 262,144 records.
415/// At 100,000 privileged actions per second this gives approximately 2.6
416/// seconds of hot storage before overflow drain is needed.
417pub const WITNESS_RING_CAPACITY: usize = 262_144;
418
419/// Witness record size in bytes.
420pub const WITNESS_RECORD_SIZE: usize = 64;