Skip to main content

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}