Skip to main content

qv_core/
verify.rs

1use crate::claims::Claims;
2use crate::crypto::{QVVerifyingKey, verify};
3use crate::error::{QVError, QVResult};
4use crate::issuance::decrypt_payload;
5use crate::mutation::{MutationChain, certify_entropy};
6use crate::token::QVRawToken;
7
8/// Output of a successful 7-layer verification.
9pub struct VerifyOutput {
10    pub claims:      Claims,
11    pub issued_at:   u64,
12    pub ttl:         u32,
13    pub mutation_ctr: u64,
14}
15
16/// 7-layer Sigvault verification pipeline.
17///
18/// Layer 1 — MAGIC / VERSION / SUITE structural check (done in QVRawToken::from_bytes)
19/// Layer 2 — KOLMOGOROV entropy certification on nonce
20/// Layer 3 — Temporal validity (not expired, not future-dated)
21/// Layer 4 — ML-DSA-87 signature verification
22/// Layer 5 — Payload decryption (XChaCha20-Poly1305)
23/// Layer 6 — Mutation counter anti-replay
24/// Layer 7 — Claims well-formedness
25pub fn verify_token(
26    raw:          &QVRawToken,
27    vk:           &QVVerifyingKey,
28    encrypt_key:  &[u8; 32],
29    chain:        &MutationChain,
30) -> QVResult<VerifyOutput> {
31
32    // Layer 2 — entropy.
33    certify_entropy(&raw.header.nonce)?;
34
35    // Layer 3 — temporal check.
36    let now_us = std::time::SystemTime::now()
37        .duration_since(std::time::UNIX_EPOCH)
38        .map_err(|e| QVError::SerializationError(e.to_string()))?
39        .as_micros() as u64;
40
41    if raw.header.issued_at > now_us + 5_000_000 {
42        return Err(QVError::NotYetValid);
43    }
44    let expiry_us = raw.header.issued_at + (raw.header.ttl as u64) * 1_000_000;
45    if now_us > expiry_us {
46        return Err(QVError::Expired { issued_at: raw.header.issued_at, ttl: raw.header.ttl });
47    }
48
49    // Layer 4 — signature.
50    let msg = raw.signed_bytes();
51    verify(vk, &msg, &raw.signature)?;
52
53    // Layer 5 — decrypt payload.
54    let plaintext = decrypt_payload(&raw.encrypted_payload, encrypt_key, &raw.header.nonce)?;
55
56    // Layer 6 — mutation counter.
57    chain.check_token_counter(raw.header.mutation_ctr)?;
58
59    // Layer 7 — decode claims.
60    let claims = Claims::decode(&plaintext)?;
61
62    Ok(VerifyOutput {
63        claims,
64        issued_at: raw.header.issued_at,
65        ttl: raw.header.ttl,
66        mutation_ctr: raw.header.mutation_ctr,
67    })
68}
69
70// ─── Falcon verify paths (suite-dispatched in v4.2) ─────────────────────────
71
72/// Run the shared non-signature layers (entropy / temporal / decrypt / chain / claims).
73/// Used by the Falcon verify helpers below so layers 2/3/5/6/7 are not copy-pasted.
74#[cfg_attr(not(feature = "falcon"), allow(dead_code))]
75fn verify_non_sig_layers(
76    raw: &QVRawToken,
77    encrypt_key: &[u8; 32],
78    chain: &MutationChain,
79) -> QVResult<VerifyOutput> {
80    certify_entropy(&raw.header.nonce)?;
81
82    let now_us = std::time::SystemTime::now()
83        .duration_since(std::time::UNIX_EPOCH)
84        .map_err(|e| QVError::SerializationError(e.to_string()))?
85        .as_micros() as u64;
86    if raw.header.issued_at > now_us + 5_000_000 {
87        return Err(QVError::NotYetValid);
88    }
89    let expiry_us = raw.header.issued_at + (raw.header.ttl as u64) * 1_000_000;
90    if now_us > expiry_us {
91        return Err(QVError::Expired { issued_at: raw.header.issued_at, ttl: raw.header.ttl });
92    }
93
94    let plaintext = decrypt_payload(&raw.encrypted_payload, encrypt_key, &raw.header.nonce)?;
95    chain.check_token_counter(raw.header.mutation_ctr)?;
96    let claims = Claims::decode(&plaintext)?;
97
98    Ok(VerifyOutput {
99        claims,
100        issued_at: raw.header.issued_at,
101        ttl: raw.header.ttl,
102        mutation_ctr: raw.header.mutation_ctr,
103    })
104}
105
106#[cfg(feature = "falcon")]
107pub fn verify_token_falcon512(
108    raw: &QVRawToken,
109    vk: &crate::falcon::falcon512::QVFalcon512VerifyingKey,
110    encrypt_key: &[u8; 32],
111    chain: &MutationChain,
112) -> QVResult<VerifyOutput> {
113    if raw.header.suite != crate::crypto::SuiteId::Falcon512 {
114        return Err(QVError::UnknownSuite(raw.header.suite.as_byte()));
115    }
116    let msg = raw.signed_bytes();
117    crate::falcon::falcon512::verify(vk, &msg, &raw.signature)?;
118    verify_non_sig_layers(raw, encrypt_key, chain)
119}
120
121#[cfg(feature = "falcon")]
122pub fn verify_token_falcon1024(
123    raw: &QVRawToken,
124    vk: &crate::falcon::falcon1024::QVFalcon1024VerifyingKey,
125    encrypt_key: &[u8; 32],
126    chain: &MutationChain,
127) -> QVResult<VerifyOutput> {
128    if raw.header.suite != crate::crypto::SuiteId::Falcon1024 {
129        return Err(QVError::UnknownSuite(raw.header.suite.as_byte()));
130    }
131    let msg = raw.signed_bytes();
132    crate::falcon::falcon1024::verify(vk, &msg, &raw.signature)?;
133    verify_non_sig_layers(raw, encrypt_key, chain)
134}