Skip to main content

bitrouter_attestation/near/
report.rs

1//! Wire types for NEAR AI Cloud's attestation report
2//! (`GET {base}/v1/attestation/report`).
3//!
4//! NEAR's **model** report is a full dstack attestation: each entry in
5//! `model_attestations[]` carries an Intel TDX quote, an NVIDIA GPU payload, a
6//! dstack event log, and an `info` block exposing the KMS root, app/workload
7//! id, and image digests (spec §1.5, Decision 8). We model only the fields the
8//! verifier consumes; serde ignores the rest.
9
10/// Top-level report body: `{ gateway_attestation, model_attestations }`.
11#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
12pub struct AttestationReport {
13    /// A list — NEAR serves multi-node and caches the signature per node, so a
14    /// model can present more than one attested signing identity (spec §1.5
15    /// cond. 2). Verify the model attestation directly; we don't trust the
16    /// gateway attestation (that is `nearai.py`'s weaker path).
17    pub model_attestations: Vec<ModelAttestation>,
18}
19
20/// One serving node's attestation of a model.
21#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
22pub struct ModelAttestation {
23    pub model_name: String,
24    /// The attested signing identity (ECDSA address) an L1.5 chat signature
25    /// must recover to.
26    pub signing_address: String,
27    pub signing_algo: String,
28    /// Intel TDX quote, hex-encoded.
29    pub intel_quote: String,
30    /// NVIDIA GPU attestation payload, a JSON document carried as a string
31    /// (`{"arch":"HOPPER","evidence_list":[…]}`), forwarded to NRAS.
32    pub nvidia_payload: String,
33    /// The client nonce echoed back, bound into `report_data`.
34    pub request_nonce: String,
35    pub info: AttestationInfo,
36    /// The dstack event log. Replayed into RTMR3 and compared to the quote so
37    /// the `info` fields it records (app-id, compose-hash, …) are anchored to
38    /// the genuine TEE measurement, not merely cloud-asserted (spec §5.1).
39    pub event_log: Vec<DstackEvent>,
40}
41
42/// One dstack measured-boot event. Each event with `imr == 3` extends RTMR3 by
43/// `RTMR3 = sha384(RTMR3 ‖ digest)`; `event` names it (`"app-id"`,
44/// `"compose-hash"`, …) and `event_payload` is the measured value.
45#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
46pub struct DstackEvent {
47    pub imr: u32,
48    pub digest: String,
49    pub event: String,
50    pub event_payload: String,
51}
52
53/// The dstack `info` block. Source of the policy-pinning fields (spec §1.5
54/// Decision 8). These map to the ported DCAP policy's pins (see
55/// [`crate::near::dcap::model_identity`]).
56#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
57pub struct AttestationInfo {
58    /// dstack app id — the **workload id** the policy allowlists.
59    pub app_id: String,
60    /// `sha256(app_compose)` — one of the **image digests** the policy
61    /// allowlists, and the target of the compose↔mr_config binding (Task 3).
62    pub compose_hash: String,
63    /// Guest OS image hash — another **image digest** the policy allowlists.
64    pub os_image_hash: String,
65    /// dstack key-provider block, a JSON string `{"name":"kms","id":"<hex>"}`.
66    /// The `id` is the **KMS root** public key (a P-256 DER SPKI) the policy
67    /// pins (spec §1.5 cond. 1).
68    pub key_provider_info: String,
69    pub tcb_info: TcbInfo,
70}
71
72/// The dstack `tcb_info` sub-block. We model only `app_compose` — the raw
73/// compose document whose `sha256` must equal `compose_hash` (Task 3's
74/// compose↔mr_config binding). The base registers (`mrtd`, `rtmr0..2`) reported
75/// here are **self-declared cloud metadata and are not trusted**: the verdict
76/// path asserts the *quote's* decoded base registers against the policy's pinned
77/// reference bundle ([`crate::AciDcapVerifierPolicy::accepts_base_measurements`],
78/// issue #567), and binds `rtmr3` via the event-log replay
79/// ([`crate::event_log_binds_info`]).
80#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
81pub struct TcbInfo {
82    pub app_compose: String,
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    const FIXTURE: &str = include_str!("../../tests/fixtures/near_report.json");
90
91    #[test]
92    fn deserializes_live_near_model_report() {
93        let report: AttestationReport =
94            serde_json::from_str(FIXTURE).expect("fixture should deserialize");
95
96        assert_eq!(report.model_attestations.len(), 1);
97        let m = &report.model_attestations[0];
98
99        assert_eq!(m.model_name, "zai-org/GLM-5.1-FP8");
100        assert_eq!(
101            m.signing_address,
102            "0xbb4d2e7ffe98eefcd9690e2139be41e92b95e333"
103        );
104        assert_eq!(m.signing_algo, "ecdsa");
105        assert_eq!(
106            m.request_nonce,
107            "9a01356cb451dc2c3c0ce9a195245a0be984a3f73617f55f87913fc2f059cba7"
108        );
109        // Intel TDX quote is hex; NVIDIA payload is a JSON string for NRAS.
110        assert!(m.intel_quote.starts_with("040002008100"));
111        assert!(m.nvidia_payload.contains("\"arch\":\"HOPPER\""));
112        // compose hash drives the compose↔mr_config binding (Task 3).
113        assert_eq!(
114            m.info.compose_hash,
115            "c445f29994165e94e85bdfc4824f4bcba89b0a883f45e7912f1bfd7c2634a698"
116        );
117    }
118}