1use crate::Result;
17use ruvix_types::{
18 CapRights, Capability, KernelError, ProofAttestation, ProofPayload, ProofTier, ProofToken,
19};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23#[repr(C)]
24pub struct ProofPolicy {
25 pub required_tier: ProofTier,
27
28 pub max_verification_time_us: u32,
31
32 pub max_validity_window_ns: u64,
35
36 pub require_coherence_cert: bool,
38
39 pub min_coherence_in_proof: u16,
41}
42
43impl Default for ProofPolicy {
44 fn default() -> Self {
45 Self::standard()
46 }
47}
48
49impl ProofPolicy {
50 #[inline]
52 #[must_use]
53 pub const fn reflex() -> Self {
54 Self {
55 required_tier: ProofTier::Reflex,
56 max_verification_time_us: 1,
57 max_validity_window_ns: 1_000_000_000, require_coherence_cert: false,
59 min_coherence_in_proof: 0,
60 }
61 }
62
63 #[inline]
65 #[must_use]
66 pub const fn standard() -> Self {
67 Self {
68 required_tier: ProofTier::Standard,
69 max_verification_time_us: 100,
70 max_validity_window_ns: 1_000_000_000, require_coherence_cert: false,
72 min_coherence_in_proof: 0,
73 }
74 }
75
76 #[inline]
78 #[must_use]
79 pub const fn deep() -> Self {
80 Self {
81 required_tier: ProofTier::Deep,
82 max_verification_time_us: 10_000, max_validity_window_ns: 5_000_000_000, require_coherence_cert: true,
85 min_coherence_in_proof: 5000, }
87 }
88
89 #[inline]
91 #[must_use]
92 pub const fn with_tier(mut self, tier: ProofTier) -> Self {
93 self.required_tier = tier;
94 self
95 }
96
97 #[inline]
99 #[must_use]
100 pub const fn with_max_verification_time_us(mut self, time_us: u32) -> Self {
101 self.max_verification_time_us = time_us;
102 self
103 }
104
105 #[inline]
107 #[must_use]
108 pub const fn with_max_validity_ns(mut self, validity_ns: u64) -> Self {
109 self.max_validity_window_ns = validity_ns;
110 self
111 }
112
113 #[inline]
115 #[must_use]
116 pub const fn tier_satisfies(&self, proof_tier: ProofTier) -> bool {
117 (proof_tier as u8) >= (self.required_tier as u8)
119 }
120}
121
122#[derive(Debug)]
124pub struct NonceTracker {
125 recent_nonces: [u64; 64],
128
129 write_pos: usize,
131
132 count: usize,
134
135 total_tracked: u64,
137}
138
139impl NonceTracker {
140 #[inline]
142 #[must_use]
143 pub const fn new() -> Self {
144 Self {
145 recent_nonces: [0u64; 64],
146 write_pos: 0,
147 count: 0,
148 total_tracked: 0,
149 }
150 }
151
152 pub fn check_and_mark(&mut self, nonce: u64) -> bool {
156 for i in 0..self.count.min(64) {
158 if self.recent_nonces[i] == nonce {
159 return false; }
161 }
162
163 self.recent_nonces[self.write_pos] = nonce;
165 self.write_pos = (self.write_pos + 1) % 64;
166 if self.count < 64 {
167 self.count += 1;
168 }
169 self.total_tracked = self.total_tracked.wrapping_add(1);
170
171 true
172 }
173
174 #[inline]
176 #[must_use]
177 pub const fn count(&self) -> usize {
178 self.count
179 }
180
181 #[inline]
183 #[must_use]
184 pub const fn total_tracked(&self) -> u64 {
185 self.total_tracked
186 }
187
188 pub fn clear_old(&mut self) {
190 self.recent_nonces = [0u64; 64];
191 self.write_pos = 0;
192 self.count = 0;
193 }
194}
195
196impl Default for NonceTracker {
197 fn default() -> Self {
198 Self::new()
199 }
200}
201
202pub struct ProofVerifier {
204 policy: ProofPolicy,
206
207 nonce_tracker: NonceTracker,
209
210 verifier_version: u32,
212
213 #[cfg(feature = "stats")]
215 proofs_verified: u64,
216 #[cfg(feature = "stats")]
217 proofs_rejected: u64,
218}
219
220impl ProofVerifier {
221 #[inline]
223 #[must_use]
224 pub fn new(policy: ProofPolicy) -> Self {
225 Self {
226 policy,
227 nonce_tracker: NonceTracker::new(),
228 verifier_version: 0x00_01_00_00, #[cfg(feature = "stats")]
230 proofs_verified: 0,
231 #[cfg(feature = "stats")]
232 proofs_rejected: 0,
233 }
234 }
235
236 #[inline]
238 #[must_use]
239 pub const fn policy(&self) -> &ProofPolicy {
240 &self.policy
241 }
242
243 pub fn verify(
257 &mut self,
258 proof: &ProofToken,
259 expected_mutation_hash: &[u8; 32],
260 current_time_ns: u64,
261 capability: &Capability,
262 ) -> Result<ProofAttestation> {
263 if !capability.has_rights(CapRights::PROVE) {
265 #[cfg(feature = "stats")]
266 {
267 self.proofs_rejected += 1;
268 }
269 return Err(KernelError::InsufficientRights);
270 }
271
272 if proof.mutation_hash != *expected_mutation_hash {
274 #[cfg(feature = "stats")]
275 {
276 self.proofs_rejected += 1;
277 }
278 return Err(KernelError::ProofRejected);
279 }
280
281 if !self.policy.tier_satisfies(proof.tier) {
283 #[cfg(feature = "stats")]
284 {
285 self.proofs_rejected += 1;
286 }
287 return Err(KernelError::ProofRejected);
288 }
289
290 if proof.is_expired(current_time_ns) {
292 #[cfg(feature = "stats")]
293 {
294 self.proofs_rejected += 1;
295 }
296 return Err(KernelError::ProofRejected);
297 }
298
299 let validity_window = proof.valid_until_ns.saturating_sub(current_time_ns);
301 if validity_window > self.policy.max_validity_window_ns {
302 #[cfg(feature = "stats")]
303 {
304 self.proofs_rejected += 1;
305 }
306 return Err(KernelError::ProofRejected);
307 }
308
309 if !self.nonce_tracker.check_and_mark(proof.nonce) {
311 #[cfg(feature = "stats")]
312 {
313 self.proofs_rejected += 1;
314 }
315 return Err(KernelError::ProofRejected);
316 }
317
318 self.verify_payload(&proof.payload)?;
320
321 #[cfg(feature = "stats")]
323 {
324 self.proofs_verified += 1;
325 }
326
327 Ok(self.create_attestation(proof, current_time_ns))
328 }
329
330 fn verify_payload(&self, payload: &ProofPayload) -> Result<()> {
332 match payload {
333 ProofPayload::Hash { hash: _ } => {
334 Ok(())
337 }
338 ProofPayload::MerkleWitness {
339 root: _,
340 leaf_index: _,
341 path_len,
342 path: _,
343 } => {
344 if *path_len > 32 {
346 return Err(KernelError::ProofRejected);
347 }
348 Ok(())
351 }
352 ProofPayload::CoherenceCert {
353 score_before: _,
354 score_after,
355 partition_id: _,
356 signature: _,
357 } => {
358 if self.policy.require_coherence_cert
360 && *score_after < self.policy.min_coherence_in_proof
361 {
362 return Err(KernelError::CoherenceViolation);
363 }
364 Ok(())
366 }
367 }
368 }
369
370 fn create_attestation(&self, proof: &ProofToken, current_time_ns: u64) -> ProofAttestation {
372 let environment_hash = [0u8; 32];
375
376 ProofAttestation::new(
377 proof.mutation_hash,
378 environment_hash,
379 current_time_ns,
380 self.verifier_version,
381 1, 0, )
384 }
385
386 #[inline]
388 #[must_use]
389 pub const fn verifier_version(&self) -> u32 {
390 self.verifier_version
391 }
392
393 #[inline]
395 #[must_use]
396 pub const fn nonce_tracker(&self) -> &NonceTracker {
397 &self.nonce_tracker
398 }
399}
400
401impl Default for ProofVerifier {
402 fn default() -> Self {
403 Self::new(ProofPolicy::default())
404 }
405}
406
407#[cfg(test)]
408mod tests {
409 use super::*;
410 use ruvix_types::ObjectType;
411
412 fn create_test_capability() -> Capability {
413 Capability::new(
414 1,
415 ObjectType::VectorStore,
416 CapRights::READ | CapRights::WRITE | CapRights::PROVE,
417 0,
418 1,
419 )
420 }
421
422 fn create_test_proof(
423 mutation_hash: [u8; 32],
424 tier: ProofTier,
425 valid_until_ns: u64,
426 nonce: u64,
427 ) -> ProofToken {
428 ProofToken::new(
429 mutation_hash,
430 tier,
431 ProofPayload::Hash { hash: mutation_hash },
432 valid_until_ns,
433 nonce,
434 )
435 }
436
437 #[test]
438 fn test_proof_policy_tiers() {
439 let reflex = ProofPolicy::reflex();
440 assert_eq!(reflex.required_tier, ProofTier::Reflex);
441 assert!(reflex.tier_satisfies(ProofTier::Reflex));
442 assert!(reflex.tier_satisfies(ProofTier::Standard));
443 assert!(reflex.tier_satisfies(ProofTier::Deep));
444
445 let deep = ProofPolicy::deep();
446 assert!(!deep.tier_satisfies(ProofTier::Reflex));
447 assert!(!deep.tier_satisfies(ProofTier::Standard));
448 assert!(deep.tier_satisfies(ProofTier::Deep));
449 }
450
451 #[test]
452 fn test_nonce_tracker_replay() {
453 let mut tracker = NonceTracker::new();
454
455 assert!(tracker.check_and_mark(1));
456 assert!(tracker.check_and_mark(2));
457 assert!(tracker.check_and_mark(3));
458
459 assert!(!tracker.check_and_mark(1));
461 assert!(!tracker.check_and_mark(2));
462
463 assert!(tracker.check_and_mark(4));
465 }
466
467 #[test]
468 fn test_proof_verifier_success() {
469 let mut verifier = ProofVerifier::new(ProofPolicy::reflex());
470 let cap = create_test_capability();
471
472 let mutation_hash = [1u8; 32];
473 let proof = create_test_proof(mutation_hash, ProofTier::Standard, 1_000_000_000, 1);
474
475 let result = verifier.verify(&proof, &mutation_hash, 500_000_000, &cap);
476 assert!(result.is_ok());
477 }
478
479 #[test]
480 fn test_proof_verifier_wrong_hash() {
481 let mut verifier = ProofVerifier::new(ProofPolicy::reflex());
482 let cap = create_test_capability();
483
484 let mutation_hash = [1u8; 32];
485 let wrong_hash = [2u8; 32];
486 let proof = create_test_proof(mutation_hash, ProofTier::Standard, 1_000_000_000, 1);
487
488 let result = verifier.verify(&proof, &wrong_hash, 500_000_000, &cap);
489 assert_eq!(result, Err(KernelError::ProofRejected));
490 }
491
492 #[test]
493 fn test_proof_verifier_expired() {
494 let mut verifier = ProofVerifier::new(ProofPolicy::reflex());
495 let cap = create_test_capability();
496
497 let mutation_hash = [1u8; 32];
498 let proof = create_test_proof(mutation_hash, ProofTier::Standard, 500_000_000, 1);
499
500 let result = verifier.verify(&proof, &mutation_hash, 1_000_000_000, &cap);
502 assert_eq!(result, Err(KernelError::ProofRejected));
503 }
504
505 #[test]
506 fn test_proof_verifier_nonce_reuse() {
507 let mut verifier = ProofVerifier::new(ProofPolicy::reflex());
508 let cap = create_test_capability();
509
510 let mutation_hash = [1u8; 32];
511 let proof1 = create_test_proof(mutation_hash, ProofTier::Standard, 1_000_000_000, 42);
512 let proof2 = create_test_proof(mutation_hash, ProofTier::Standard, 1_000_000_000, 42);
513
514 let result1 = verifier.verify(&proof1, &mutation_hash, 500_000_000, &cap);
516 assert!(result1.is_ok());
517
518 let result2 = verifier.verify(&proof2, &mutation_hash, 500_000_001, &cap);
520 assert_eq!(result2, Err(KernelError::ProofRejected));
521 }
522
523 #[test]
524 fn test_proof_verifier_insufficient_rights() {
525 let mut verifier = ProofVerifier::new(ProofPolicy::reflex());
526
527 let cap = Capability::new(1, ObjectType::VectorStore, CapRights::READ, 0, 1);
529
530 let mutation_hash = [1u8; 32];
531 let proof = create_test_proof(mutation_hash, ProofTier::Standard, 1_000_000_000, 1);
532
533 let result = verifier.verify(&proof, &mutation_hash, 500_000_000, &cap);
534 assert_eq!(result, Err(KernelError::InsufficientRights));
535 }
536
537 #[test]
538 fn test_proof_verifier_tier_mismatch() {
539 let mut verifier = ProofVerifier::new(ProofPolicy::deep());
540 let cap = create_test_capability();
541
542 let mutation_hash = [1u8; 32];
543 let proof = create_test_proof(mutation_hash, ProofTier::Standard, 1_000_000_000, 1);
545
546 let result = verifier.verify(&proof, &mutation_hash, 500_000_000, &cap);
547 assert_eq!(result, Err(KernelError::ProofRejected));
548 }
549}