ruvix_types/proof.rs
1//! Proof-gated mutation types (ADR-047 integration).
2//!
3//! In RuVix, proof-gated mutation is a kernel invariant. The kernel physically
4//! prevents state mutation without a valid proof token.
5
6/// Proof tier determining verification complexity.
7///
8/// Higher tiers provide stronger guarantees but take longer to verify.
9/// The scheduler may route based on proof tier.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11#[repr(u8)]
12pub enum ProofTier {
13 /// Sub-microsecond hash check. For high-frequency vector updates.
14 /// Uses precomputed proof cache when available.
15 Reflex = 0,
16
17 /// Merkle witness verification. For graph mutations.
18 /// Verifies against a Merkle root of the current state.
19 Standard = 1,
20
21 /// Full coherence verification with mincut analysis.
22 /// For structural changes that affect graph partitioning.
23 Deep = 2,
24}
25
26impl ProofTier {
27 /// Returns the tier name as a string.
28 #[inline]
29 #[must_use]
30 pub const fn as_str(&self) -> &'static str {
31 match self {
32 Self::Reflex => "Reflex",
33 Self::Standard => "Standard",
34 Self::Deep => "Deep",
35 }
36 }
37
38 /// Returns the maximum allowed verification time in microseconds.
39 #[inline]
40 #[must_use]
41 pub const fn max_verification_time_us(&self) -> u32 {
42 match self {
43 Self::Reflex => 1, // <1us
44 Self::Standard => 100, // <100us
45 Self::Deep => 10_000, // <10ms
46 }
47 }
48
49 /// Converts from a raw u8 value.
50 #[inline]
51 #[must_use]
52 pub const fn from_u8(value: u8) -> Option<Self> {
53 match value {
54 0 => Some(Self::Reflex),
55 1 => Some(Self::Standard),
56 2 => Some(Self::Deep),
57 _ => None,
58 }
59 }
60}
61
62impl Default for ProofTier {
63 fn default() -> Self {
64 Self::Standard
65 }
66}
67
68/// Proof payload containing the actual proof data.
69///
70/// The kernel verifies the proof payload according to the tier.
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72#[repr(C)]
73pub enum ProofPayload {
74 /// Hash check payload for Reflex tier.
75 /// Contains a 32-byte hash of the mutation.
76 Hash {
77 /// SHA-256 hash of the mutation data.
78 hash: [u8; 32],
79 },
80
81 /// Merkle witness for Standard tier.
82 /// Contains the Merkle path from leaf to root.
83 MerkleWitness {
84 /// Root hash of the Merkle tree.
85 root: [u8; 32],
86 /// Leaf index in the Merkle tree.
87 leaf_index: u32,
88 /// Number of path elements (max 32 for depth-32 tree).
89 path_len: u8,
90 /// Path elements (siblings along the path).
91 /// Only first `path_len` elements are valid.
92 path: [[u8; 32]; 32],
93 },
94
95 /// Coherence certificate for Deep tier.
96 /// Contains coherence scores and partition metadata.
97 CoherenceCert {
98 /// Coherence score before mutation (0.0-1.0 as u16, 0-10000).
99 score_before: u16,
100 /// Expected coherence score after mutation.
101 score_after: u16,
102 /// Partition ID affected by the mutation.
103 partition_id: u32,
104 /// Signature over the coherence data.
105 signature: [u8; 64],
106 },
107}
108
109impl Default for ProofPayload {
110 fn default() -> Self {
111 Self::Hash { hash: [0u8; 32] }
112 }
113}
114
115/// A proof token authorizing a specific mutation.
116///
117/// Generated by the Proof Engine and consumed by a mutating syscall.
118/// Proof tokens are single-use (nonce prevents replay).
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120#[repr(C)]
121pub struct ProofToken {
122 /// Hash of the mutation being authorized.
123 pub mutation_hash: [u8; 32],
124
125 /// Proof tier (Reflex, Standard, Deep).
126 pub tier: ProofTier,
127
128 /// The proof payload (varies by tier).
129 pub payload: ProofPayload,
130
131 /// Expiry timestamp (nanoseconds since epoch).
132 /// Proofs are time-bounded to prevent replay.
133 pub valid_until_ns: u64,
134
135 /// Nonce to prevent proof reuse.
136 pub nonce: u64,
137}
138
139impl ProofToken {
140 /// Creates a new proof token.
141 #[inline]
142 #[must_use]
143 pub const fn new(
144 mutation_hash: [u8; 32],
145 tier: ProofTier,
146 payload: ProofPayload,
147 valid_until_ns: u64,
148 nonce: u64,
149 ) -> Self {
150 Self {
151 mutation_hash,
152 tier,
153 payload,
154 valid_until_ns,
155 nonce,
156 }
157 }
158
159 /// Checks if the proof token has expired.
160 #[inline]
161 #[must_use]
162 pub const fn is_expired(&self, current_time_ns: u64) -> bool {
163 current_time_ns > self.valid_until_ns
164 }
165}
166
167impl Default for ProofToken {
168 fn default() -> Self {
169 Self {
170 mutation_hash: [0u8; 32],
171 tier: ProofTier::Standard,
172 payload: ProofPayload::default(),
173 valid_until_ns: 0,
174 nonce: 0,
175 }
176 }
177}
178
179/// A proof attestation recorded in the kernel witness log.
180///
181/// Every successful proof-gated mutation emits an attestation.
182/// Compatible with ADR-047 ProofAttestation (82 bytes).
183#[derive(Debug, Clone, Copy, PartialEq, Eq)]
184#[repr(C)]
185pub struct ProofAttestation {
186 /// Hash of the proof term state (32 bytes).
187 pub proof_term_hash: [u8; 32],
188
189 /// Hash of the environment declarations (32 bytes).
190 pub environment_hash: [u8; 32],
191
192 /// Nanosecond UNIX timestamp of verification.
193 pub verification_timestamp_ns: u64,
194
195 /// Verifier version (e.g., 0x00_01_00_00 = 0.1.0).
196 pub verifier_version: u32,
197
198 /// Number of type-check reduction steps consumed.
199 pub reduction_steps: u32,
200
201 /// Cache hit rate (0-10000 = 0.00%-100.00%).
202 pub cache_hit_rate_bps: u16,
203}
204
205impl ProofAttestation {
206 /// Size of attestation in bytes (82 bytes per ADR-047).
207 pub const SIZE: usize = 82;
208
209 /// Creates a new proof attestation.
210 #[inline]
211 #[must_use]
212 pub const fn new(
213 proof_term_hash: [u8; 32],
214 environment_hash: [u8; 32],
215 verification_timestamp_ns: u64,
216 verifier_version: u32,
217 reduction_steps: u32,
218 cache_hit_rate_bps: u16,
219 ) -> Self {
220 Self {
221 proof_term_hash,
222 environment_hash,
223 verification_timestamp_ns,
224 verifier_version,
225 reduction_steps,
226 cache_hit_rate_bps,
227 }
228 }
229}
230
231impl Default for ProofAttestation {
232 fn default() -> Self {
233 Self {
234 proof_term_hash: [0u8; 32],
235 environment_hash: [0u8; 32],
236 verification_timestamp_ns: 0,
237 verifier_version: 0,
238 reduction_steps: 0,
239 cache_hit_rate_bps: 0,
240 }
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn test_proof_tier_ordering() {
250 assert!((ProofTier::Reflex as u8) < (ProofTier::Standard as u8));
251 assert!((ProofTier::Standard as u8) < (ProofTier::Deep as u8));
252 }
253
254 #[test]
255 fn test_proof_token_expiry() {
256 let token = ProofToken::new(
257 [0u8; 32],
258 ProofTier::Standard,
259 ProofPayload::default(),
260 1000,
261 1,
262 );
263
264 assert!(!token.is_expired(500));
265 assert!(!token.is_expired(1000));
266 assert!(token.is_expired(1001));
267 }
268
269 #[test]
270 fn test_attestation_size() {
271 // Verify the attestation fits in 82 bytes
272 // Note: Due to padding, the actual struct may be larger,
273 // but serialized form should be 82 bytes
274 assert_eq!(ProofAttestation::SIZE, 82);
275 }
276}