use crate::Result;
use ruvix_types::{
CapRights, Capability, KernelError, ProofAttestation, ProofPayload, ProofTier, ProofToken,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub struct ProofPolicy {
pub required_tier: ProofTier,
pub max_verification_time_us: u32,
pub max_validity_window_ns: u64,
pub require_coherence_cert: bool,
pub min_coherence_in_proof: u16,
}
impl Default for ProofPolicy {
fn default() -> Self {
Self::standard()
}
}
impl ProofPolicy {
#[inline]
#[must_use]
pub const fn reflex() -> Self {
Self {
required_tier: ProofTier::Reflex,
max_verification_time_us: 1,
max_validity_window_ns: 1_000_000_000, require_coherence_cert: false,
min_coherence_in_proof: 0,
}
}
#[inline]
#[must_use]
pub const fn standard() -> Self {
Self {
required_tier: ProofTier::Standard,
max_verification_time_us: 100,
max_validity_window_ns: 1_000_000_000, require_coherence_cert: false,
min_coherence_in_proof: 0,
}
}
#[inline]
#[must_use]
pub const fn deep() -> Self {
Self {
required_tier: ProofTier::Deep,
max_verification_time_us: 10_000, max_validity_window_ns: 5_000_000_000, require_coherence_cert: true,
min_coherence_in_proof: 5000, }
}
#[inline]
#[must_use]
pub const fn with_tier(mut self, tier: ProofTier) -> Self {
self.required_tier = tier;
self
}
#[inline]
#[must_use]
pub const fn with_max_verification_time_us(mut self, time_us: u32) -> Self {
self.max_verification_time_us = time_us;
self
}
#[inline]
#[must_use]
pub const fn with_max_validity_ns(mut self, validity_ns: u64) -> Self {
self.max_validity_window_ns = validity_ns;
self
}
#[inline]
#[must_use]
pub const fn tier_satisfies(&self, proof_tier: ProofTier) -> bool {
(proof_tier as u8) >= (self.required_tier as u8)
}
}
#[derive(Debug)]
pub struct NonceTracker {
recent_nonces: [u64; 64],
write_pos: usize,
count: usize,
total_tracked: u64,
}
impl NonceTracker {
#[inline]
#[must_use]
pub const fn new() -> Self {
Self {
recent_nonces: [0u64; 64],
write_pos: 0,
count: 0,
total_tracked: 0,
}
}
pub fn check_and_mark(&mut self, nonce: u64) -> bool {
for i in 0..self.count.min(64) {
if self.recent_nonces[i] == nonce {
return false; }
}
self.recent_nonces[self.write_pos] = nonce;
self.write_pos = (self.write_pos + 1) % 64;
if self.count < 64 {
self.count += 1;
}
self.total_tracked = self.total_tracked.wrapping_add(1);
true
}
#[inline]
#[must_use]
pub const fn count(&self) -> usize {
self.count
}
#[inline]
#[must_use]
pub const fn total_tracked(&self) -> u64 {
self.total_tracked
}
pub fn clear_old(&mut self) {
self.recent_nonces = [0u64; 64];
self.write_pos = 0;
self.count = 0;
}
}
impl Default for NonceTracker {
fn default() -> Self {
Self::new()
}
}
pub struct ProofVerifier {
policy: ProofPolicy,
nonce_tracker: NonceTracker,
verifier_version: u32,
#[cfg(feature = "stats")]
proofs_verified: u64,
#[cfg(feature = "stats")]
proofs_rejected: u64,
}
impl ProofVerifier {
#[inline]
#[must_use]
pub fn new(policy: ProofPolicy) -> Self {
Self {
policy,
nonce_tracker: NonceTracker::new(),
verifier_version: 0x00_01_00_00, #[cfg(feature = "stats")]
proofs_verified: 0,
#[cfg(feature = "stats")]
proofs_rejected: 0,
}
}
#[inline]
#[must_use]
pub const fn policy(&self) -> &ProofPolicy {
&self.policy
}
pub fn verify(
&mut self,
proof: &ProofToken,
expected_mutation_hash: &[u8; 32],
current_time_ns: u64,
capability: &Capability,
) -> Result<ProofAttestation> {
if !capability.has_rights(CapRights::PROVE) {
#[cfg(feature = "stats")]
{
self.proofs_rejected += 1;
}
return Err(KernelError::InsufficientRights);
}
if proof.mutation_hash != *expected_mutation_hash {
#[cfg(feature = "stats")]
{
self.proofs_rejected += 1;
}
return Err(KernelError::ProofRejected);
}
if !self.policy.tier_satisfies(proof.tier) {
#[cfg(feature = "stats")]
{
self.proofs_rejected += 1;
}
return Err(KernelError::ProofRejected);
}
if proof.is_expired(current_time_ns) {
#[cfg(feature = "stats")]
{
self.proofs_rejected += 1;
}
return Err(KernelError::ProofRejected);
}
let validity_window = proof.valid_until_ns.saturating_sub(current_time_ns);
if validity_window > self.policy.max_validity_window_ns {
#[cfg(feature = "stats")]
{
self.proofs_rejected += 1;
}
return Err(KernelError::ProofRejected);
}
if !self.nonce_tracker.check_and_mark(proof.nonce) {
#[cfg(feature = "stats")]
{
self.proofs_rejected += 1;
}
return Err(KernelError::ProofRejected);
}
self.verify_payload(&proof.payload)?;
#[cfg(feature = "stats")]
{
self.proofs_verified += 1;
}
Ok(self.create_attestation(proof, current_time_ns))
}
fn verify_payload(&self, payload: &ProofPayload) -> Result<()> {
match payload {
ProofPayload::Hash { hash: _ } => {
Ok(())
}
ProofPayload::MerkleWitness {
root: _,
leaf_index: _,
path_len,
path: _,
} => {
if *path_len > 32 {
return Err(KernelError::ProofRejected);
}
Ok(())
}
ProofPayload::CoherenceCert {
score_before: _,
score_after,
partition_id: _,
signature: _,
} => {
if self.policy.require_coherence_cert
&& *score_after < self.policy.min_coherence_in_proof
{
return Err(KernelError::CoherenceViolation);
}
Ok(())
}
}
}
fn create_attestation(&self, proof: &ProofToken, current_time_ns: u64) -> ProofAttestation {
let environment_hash = [0u8; 32];
ProofAttestation::new(
proof.mutation_hash,
environment_hash,
current_time_ns,
self.verifier_version,
1, 0, )
}
#[inline]
#[must_use]
pub const fn verifier_version(&self) -> u32 {
self.verifier_version
}
#[inline]
#[must_use]
pub const fn nonce_tracker(&self) -> &NonceTracker {
&self.nonce_tracker
}
}
impl Default for ProofVerifier {
fn default() -> Self {
Self::new(ProofPolicy::default())
}
}
#[cfg(test)]
mod tests {
use super::*;
use ruvix_types::ObjectType;
fn create_test_capability() -> Capability {
Capability::new(
1,
ObjectType::VectorStore,
CapRights::READ | CapRights::WRITE | CapRights::PROVE,
0,
1,
)
}
fn create_test_proof(
mutation_hash: [u8; 32],
tier: ProofTier,
valid_until_ns: u64,
nonce: u64,
) -> ProofToken {
ProofToken::new(
mutation_hash,
tier,
ProofPayload::Hash { hash: mutation_hash },
valid_until_ns,
nonce,
)
}
#[test]
fn test_proof_policy_tiers() {
let reflex = ProofPolicy::reflex();
assert_eq!(reflex.required_tier, ProofTier::Reflex);
assert!(reflex.tier_satisfies(ProofTier::Reflex));
assert!(reflex.tier_satisfies(ProofTier::Standard));
assert!(reflex.tier_satisfies(ProofTier::Deep));
let deep = ProofPolicy::deep();
assert!(!deep.tier_satisfies(ProofTier::Reflex));
assert!(!deep.tier_satisfies(ProofTier::Standard));
assert!(deep.tier_satisfies(ProofTier::Deep));
}
#[test]
fn test_nonce_tracker_replay() {
let mut tracker = NonceTracker::new();
assert!(tracker.check_and_mark(1));
assert!(tracker.check_and_mark(2));
assert!(tracker.check_and_mark(3));
assert!(!tracker.check_and_mark(1));
assert!(!tracker.check_and_mark(2));
assert!(tracker.check_and_mark(4));
}
#[test]
fn test_proof_verifier_success() {
let mut verifier = ProofVerifier::new(ProofPolicy::reflex());
let cap = create_test_capability();
let mutation_hash = [1u8; 32];
let proof = create_test_proof(mutation_hash, ProofTier::Standard, 1_000_000_000, 1);
let result = verifier.verify(&proof, &mutation_hash, 500_000_000, &cap);
assert!(result.is_ok());
}
#[test]
fn test_proof_verifier_wrong_hash() {
let mut verifier = ProofVerifier::new(ProofPolicy::reflex());
let cap = create_test_capability();
let mutation_hash = [1u8; 32];
let wrong_hash = [2u8; 32];
let proof = create_test_proof(mutation_hash, ProofTier::Standard, 1_000_000_000, 1);
let result = verifier.verify(&proof, &wrong_hash, 500_000_000, &cap);
assert_eq!(result, Err(KernelError::ProofRejected));
}
#[test]
fn test_proof_verifier_expired() {
let mut verifier = ProofVerifier::new(ProofPolicy::reflex());
let cap = create_test_capability();
let mutation_hash = [1u8; 32];
let proof = create_test_proof(mutation_hash, ProofTier::Standard, 500_000_000, 1);
let result = verifier.verify(&proof, &mutation_hash, 1_000_000_000, &cap);
assert_eq!(result, Err(KernelError::ProofRejected));
}
#[test]
fn test_proof_verifier_nonce_reuse() {
let mut verifier = ProofVerifier::new(ProofPolicy::reflex());
let cap = create_test_capability();
let mutation_hash = [1u8; 32];
let proof1 = create_test_proof(mutation_hash, ProofTier::Standard, 1_000_000_000, 42);
let proof2 = create_test_proof(mutation_hash, ProofTier::Standard, 1_000_000_000, 42);
let result1 = verifier.verify(&proof1, &mutation_hash, 500_000_000, &cap);
assert!(result1.is_ok());
let result2 = verifier.verify(&proof2, &mutation_hash, 500_000_001, &cap);
assert_eq!(result2, Err(KernelError::ProofRejected));
}
#[test]
fn test_proof_verifier_insufficient_rights() {
let mut verifier = ProofVerifier::new(ProofPolicy::reflex());
let cap = Capability::new(1, ObjectType::VectorStore, CapRights::READ, 0, 1);
let mutation_hash = [1u8; 32];
let proof = create_test_proof(mutation_hash, ProofTier::Standard, 1_000_000_000, 1);
let result = verifier.verify(&proof, &mutation_hash, 500_000_000, &cap);
assert_eq!(result, Err(KernelError::InsufficientRights));
}
#[test]
fn test_proof_verifier_tier_mismatch() {
let mut verifier = ProofVerifier::new(ProofPolicy::deep());
let cap = create_test_capability();
let mutation_hash = [1u8; 32];
let proof = create_test_proof(mutation_hash, ProofTier::Standard, 1_000_000_000, 1);
let result = verifier.verify(&proof, &mutation_hash, 500_000_000, &cap);
assert_eq!(result, Err(KernelError::ProofRejected));
}
}