Skip to main content

yule_attest/
lib.rs

1pub mod log;
2pub mod session;
3
4use serde::{Deserialize, Serialize};
5
6/// A signed record of one inference session.
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct AttestationRecord {
9    pub session_id: String,
10    pub timestamp: u64,
11    pub model: ModelAttestation,
12    pub sandbox: SandboxAttestation,
13    pub inference: InferenceAttestation,
14    /// Ed25519 signature over the canonical JSON of all fields above.
15    pub signature: Vec<u8>,
16    /// blake3 hash of the previous record (chain integrity).
17    pub prev_hash: [u8; 32],
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct ModelAttestation {
22    pub name: String,
23    pub merkle_root: [u8; 32],
24    pub publisher: Option<String>,
25    pub signature_verified: bool,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct SandboxAttestation {
30    pub platform: String,
31    pub active: bool,
32    pub memory_limit_bytes: u64,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct InferenceAttestation {
37    pub tokens_generated: u64,
38    pub prompt_hash: [u8; 32],
39    pub output_hash: [u8; 32],
40    pub temperature: f32,
41    pub top_p: f32,
42}
43
44impl AttestationRecord {
45    /// Compute blake3 hash of this record (for chain linking).
46    pub fn hash(&self) -> [u8; 32] {
47        let bytes = serde_json::to_vec(self).unwrap_or_default();
48        blake3::hash(&bytes).into()
49    }
50
51    /// The content that gets signed = all fields except signature and prev_hash.
52    pub fn signable_bytes(&self) -> Vec<u8> {
53        // sign over the core attestation data, not the chain/signature metadata
54        let content = serde_json::json!({
55            "session_id": self.session_id,
56            "timestamp": self.timestamp,
57            "model": self.model,
58            "sandbox": self.sandbox,
59            "inference": self.inference,
60        });
61        serde_json::to_vec(&content).unwrap_or_default()
62    }
63
64    /// Verify the Ed25519 signature on this record.
65    pub fn verify_signature(&self, public_key: &[u8; 32]) -> bool {
66        if self.signature.len() != 64 {
67            return false;
68        }
69        let verifier = yule_verify::signature::SignatureVerifier::new();
70        let sig: [u8; 64] = self.signature[..64].try_into().unwrap();
71        verifier.verify_ed25519(public_key, &self.signable_bytes(), &sig)
72            .unwrap_or(false)
73    }
74}