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}