Skip to content

Commit d88d2c6

Browse files
committed
refactor: use single event_type for both v1 and v2
Previously v1 and v2 used different event_type values (0x08000001 vs 0x08000002) to let verifiers distinguish formats. Now that version is serialized on RuntimeEvent and TdxEvent via serde, a single event_type (0x08000001) suffices — version is carried explicitly. Changes: - Drop DSTACK_RUNTIME_EVENT_TYPE_V2 constant - cc_event_type() always returns DSTACK_RUNTIME_EVENT_TYPE - Add version field to TdxEvent (serde default V1, #[codec(skip)] for scale compat) - V2 canonical JSON now includes explicit "version": 2 field for self-describing content - Serde skips version when V1 to keep existing JSON outputs unchanged Scale backward compat: legacy V0 attestations contain only v1 events, so #[codec(skip)] defaulting to V1 on decode is correct.
1 parent 3320eb2 commit d88d2c6

4 files changed

Lines changed: 46 additions & 40 deletions

File tree

cc-eventlog/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
pub use dstack_types::EventLogVersion;
66
pub use runtime_events::{
7-
canonical_event_json, replay_events, RuntimeEvent, DSTACK_RUNTIME_EVENT_TYPE_V2,
7+
canonical_event_json_v2, replay_events, RuntimeEvent, DSTACK_RUNTIME_EVENT_TYPE,
88
};
99
pub use tdx::TdxEvent;
1010

cc-eventlog/src/runtime_events.rs

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ use std::io::Write;
1212

1313
use ez_hash::{Hasher, Sha256, Sha384};
1414

15-
/// The event type for dstack runtime events (v1).
15+
/// The event type for dstack runtime events.
1616
/// This code is not defined in the TCG specification.
1717
/// See https://trustedcomputinggroup.org/wp-content/uploads/PC-ClientSpecific_Platform_Profile_for_TPM_2p0_Systems_v51.pdf
18+
///
19+
/// V1 and V2 use the same event type; the digest format is distinguished by
20+
/// `EventLogVersion` (carried on `RuntimeEvent`/`TdxEvent` or inferred from
21+
/// the v2 canonical JSON content).
1822
pub const DSTACK_RUNTIME_EVENT_TYPE: u32 = 0x08000001;
19-
/// The event type for dstack runtime events (v2, JSON canonical content).
20-
/// V2 events use JCS (RFC 8785) canonical JSON as the digest input, enabling
21-
/// relying parties to define fine-grained trust policies on individual event claims.
22-
pub const DSTACK_RUNTIME_EVENT_TYPE_V2: u32 = 0x08000002;
2323
/// The path to the userspace TDX event log file.
2424
pub const RUNTIME_EVENT_LOG_FILE: &str = "/run/log/dstack/runtime_events.log";
2525

@@ -112,7 +112,7 @@ impl RuntimeEvent {
112112
/// Compute the digest of the event.
113113
///
114114
/// - V1: `SHA(event_type_le || ":" || event_name || ":" || payload)`
115-
/// - V2: `SHA(canonical_json({"event":"...","event_type":134217730,"payload":"hex..."}))`
115+
/// - V2: `SHA(canonical_json({"event":"...","event_type":134217729,"payload":"hex...","version":2}))`
116116
pub fn digest<H: Hasher>(&self) -> H::Output {
117117
match self.version {
118118
EventLogVersion::V1 => H::hash([
@@ -123,31 +123,30 @@ impl RuntimeEvent {
123123
&self.payload,
124124
]),
125125
EventLogVersion::V2 => {
126-
let canonical =
127-
canonical_event_json(&self.event, DSTACK_RUNTIME_EVENT_TYPE_V2, &self.payload);
126+
let canonical = canonical_event_json_v2(&self.event, &self.payload);
128127
H::hash([canonical.as_bytes()])
129128
}
130129
}
131130
}
132131

132+
/// The event type used when extending RTMR. Always `DSTACK_RUNTIME_EVENT_TYPE`.
133+
/// Version is distinguished via `EventLogVersion`, not the event type.
133134
pub fn cc_event_type(&self) -> u32 {
134-
match self.version {
135-
EventLogVersion::V1 => DSTACK_RUNTIME_EVENT_TYPE,
136-
EventLogVersion::V2 => DSTACK_RUNTIME_EVENT_TYPE_V2,
137-
}
135+
DSTACK_RUNTIME_EVENT_TYPE
138136
}
139137
}
140138

141-
/// Construct JCS (RFC 8785) canonical JSON for a runtime event.
139+
/// Construct the JCS (RFC 8785) canonical JSON used as the v2 digest input.
142140
///
143-
/// Uses `serde_jcs` for deterministic serialization per RFC 8785,
144-
/// including alphabetical key ordering and canonical number/string formatting.
145-
/// The payload is hex-encoded for human readability.
146-
pub fn canonical_event_json(event: &str, event_type: u32, payload: &[u8]) -> String {
141+
/// The JSON includes an explicit `version: 2` field so the content is
142+
/// self-describing for relying parties that don't know dstack's event schema.
143+
/// Keys and number/string formatting are handled by `serde_jcs` per RFC 8785.
144+
pub fn canonical_event_json_v2(event: &str, payload: &[u8]) -> String {
147145
let obj = serde_json::json!({
148146
"event": event,
149-
"event_type": event_type,
147+
"event_type": DSTACK_RUNTIME_EVENT_TYPE,
150148
"payload": hex::encode(payload),
149+
"version": 2,
151150
});
152151
serde_jcs::to_string(&obj).unwrap_or_default()
153152
}
@@ -195,11 +194,10 @@ mod tests {
195194
vec![0xab, 0xcd],
196195
EventLogVersion::V2,
197196
);
198-
let canonical =
199-
canonical_event_json(&event.event, DSTACK_RUNTIME_EVENT_TYPE_V2, &event.payload);
197+
let canonical = canonical_event_json_v2(&event.event, &event.payload);
200198
assert_eq!(
201199
canonical,
202-
r#"{"event":"compose-hash","event_type":134217730,"payload":"abcd"}"#
200+
r#"{"event":"compose-hash","event_type":134217729,"payload":"abcd","version":2}"#
203201
);
204202
let digest = event.digest::<Sha384>();
205203
let expected = Sha384::hash([canonical.as_bytes()]);
@@ -225,8 +223,9 @@ mod tests {
225223

226224
#[test]
227225
fn v2_event_type() {
226+
// v2 uses the same event_type as v1 — version is carried separately
228227
let event = RuntimeEvent::new("test".to_string(), vec![], EventLogVersion::V2);
229-
assert_eq!(event.cc_event_type(), DSTACK_RUNTIME_EVENT_TYPE_V2);
228+
assert_eq!(event.cc_event_type(), DSTACK_RUNTIME_EVENT_TYPE);
230229
}
231230

232231
#[test]
@@ -255,11 +254,7 @@ mod tests {
255254

256255
#[test]
257256
fn canonical_json_escapes_special_chars() {
258-
let canonical = canonical_event_json(
259-
"event\"with\\special\nchars",
260-
DSTACK_RUNTIME_EVENT_TYPE_V2,
261-
&[0xff],
262-
);
257+
let canonical = canonical_event_json_v2("event\"with\\special\nchars", &[0xff]);
263258
// Verify it's valid JSON
264259
let parsed: serde_json::Value = serde_json::from_str(&canonical).unwrap();
265260
assert_eq!(

cc-eventlog/src/tcg.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@ impl TryFrom<TcgEvent> for TdxEvent {
370370
digest,
371371
event: Default::default(),
372372
event_payload: value.event.into(),
373+
version: Default::default(),
373374
})
374375
}
375376
}

cc-eventlog/src/tdx.rs

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
// SPDX-License-Identifier: Apache-2.0
44

55
use anyhow::Result;
6+
use dstack_types::EventLogVersion;
67
use scale::{Decode, Encode};
78
use serde::{Deserialize, Serialize};
89

910
use crate::{
10-
runtime_events::{RuntimeEvent, DSTACK_RUNTIME_EVENT_TYPE, DSTACK_RUNTIME_EVENT_TYPE_V2},
11+
runtime_events::{RuntimeEvent, DSTACK_RUNTIME_EVENT_TYPE},
1112
tcg::TcgEventLog,
1213
};
1314

@@ -16,9 +17,9 @@ use crate::{
1617
/// and the raw event data. The IMR index is zero-based, unlike the TCG event log format
1718
/// which is one-based.
1819
///
19-
/// As for RTMR3:
20-
/// - V1 (event_type 0x08000001): digest = `sha384(event_type_le || ":" || event || ":" || payload)`
21-
/// - V2 (event_type 0x08000002): digest = `sha384(canonical_json({"event":"...","event_type":134217730,"payload":"hex..."}))`
20+
/// For dstack runtime events (`event_type == DSTACK_RUNTIME_EVENT_TYPE`), the digest is:
21+
/// - V1: `sha384(event_type_le || ":" || event || ":" || payload)`
22+
/// - V2: `sha384(canonical_json({"event":"...","event_type":134217729,"payload":"hex...","version":2}))`
2223
#[derive(Clone, Debug, Serialize, Deserialize, Encode, Decode)]
2324
pub struct TdxEvent {
2425
/// IMR index, starts from 0
@@ -33,6 +34,17 @@ pub struct TdxEvent {
3334
/// Event payload
3435
#[serde(with = "serde_human_bytes")]
3536
pub event_payload: Vec<u8>,
37+
/// Event log version (for dstack runtime events).
38+
/// Skipped by scale codec for binary compat with legacy attestations
39+
/// (which only ever contain V1 events).
40+
/// Serde skips serialization when V1 so existing JSON outputs stay clean.
41+
#[serde(default, skip_serializing_if = "is_v1")]
42+
#[codec(skip)]
43+
pub version: EventLogVersion,
44+
}
45+
46+
fn is_v1(v: &EventLogVersion) -> bool {
47+
matches!(v, EventLogVersion::V1)
3648
}
3749

3850
impl TdxEvent {
@@ -43,6 +55,7 @@ impl TdxEvent {
4355
digest: vec![],
4456
event,
4557
event_payload,
58+
version: EventLogVersion::default(),
4659
}
4760
}
4861

@@ -56,6 +69,7 @@ impl TdxEvent {
5669
digest: Vec::new(),
5770
event: self.event.clone(),
5871
event_payload: self.event_payload.clone(),
72+
version: self.version,
5973
}
6074
} else {
6175
Self {
@@ -64,6 +78,7 @@ impl TdxEvent {
6478
digest: self.digest.clone(),
6579
event: self.event.clone(),
6680
event_payload: Vec::new(),
81+
version: self.version,
6782
}
6883
}
6984
}
@@ -77,37 +92,32 @@ impl TdxEvent {
7792

7893
pub fn is_runtime_event(&self) -> bool {
7994
self.event_type == DSTACK_RUNTIME_EVENT_TYPE
80-
|| self.event_type == DSTACK_RUNTIME_EVENT_TYPE_V2
8195
}
8296

8397
pub fn to_runtime_event(&self) -> Option<RuntimeEvent> {
8498
if !self.is_runtime_event() {
8599
return None;
86100
}
87-
use dstack_types::EventLogVersion;
88-
let version = if self.event_type == DSTACK_RUNTIME_EVENT_TYPE_V2 {
89-
EventLogVersion::V2
90-
} else {
91-
EventLogVersion::V1
92-
};
93101
Some(RuntimeEvent {
94102
event: self.event.clone(),
95103
payload: self.event_payload.clone(),
96-
version,
104+
version: self.version,
97105
})
98106
}
99107
}
100108

101109
impl From<RuntimeEvent> for TdxEvent {
102110
fn from(value: RuntimeEvent) -> Self {
103111
let event_type = value.cc_event_type();
112+
let version = value.version;
104113
let digest = value.sha384_digest().to_vec();
105114
TdxEvent {
106115
imr: 3,
107116
event_type,
108117
digest,
109118
event: value.event,
110119
event_payload: value.payload,
120+
version,
111121
}
112122
}
113123
}

0 commit comments

Comments
 (0)