@@ -868,6 +868,40 @@ impl TdxAttestationExt for AttestationV1 {
868868}
869869
870870impl AttestationV1 {
871+ /// Convert a V1 dstack attestation back to the legacy SCALE schema.
872+ ///
873+ /// This is only lossless for the original dstack stack with V1 runtime
874+ /// events. Pod payloads and newer event encodings must remain on the V1
875+ /// msgpack wire format.
876+ pub fn try_into_legacy ( self ) -> Result < Attestation > {
877+ let Self {
878+ platform, stack, ..
879+ } = self ;
880+ let StackEvidence :: Dstack {
881+ report_data,
882+ runtime_events,
883+ config,
884+ } = stack
885+ else {
886+ bail ! ( "dstack-pod attestation cannot be represented by the legacy schema" ) ;
887+ } ;
888+ if runtime_events
889+ . iter ( )
890+ . any ( |event| !matches ! ( event. version, EventLogVersion :: V1 ) )
891+ {
892+ bail ! ( "non-V1 runtime events cannot be represented by the legacy schema" ) ;
893+ }
894+ Ok ( Attestation {
895+ quote : platform_into_legacy_quote ( platform) ,
896+ runtime_events,
897+ report_data : report_data
898+ . try_into ( )
899+ . map_err ( |_| anyhow ! ( "stack.report_data must be 64 bytes" ) ) ?,
900+ config,
901+ report : ( ) ,
902+ } )
903+ }
904+
871905 /// Decode the VM config from the external or embedded config.
872906 pub fn decode_vm_config < ' a > ( & ' a self , config : & ' a str ) -> Result < VmConfig > {
873907 decode_vm_config_with_fallback ( config, self . stack . config ( ) )
@@ -2886,6 +2920,40 @@ mod tests {
28862920 assert ! ( matches!( upgraded. stack, StackEvidence :: Dstack { .. } ) ) ;
28872921 }
28882922
2923+ #[ test]
2924+ fn v1_dstack_with_v1_events_converts_losslessly_to_legacy ( ) {
2925+ let mut legacy = dummy_tdx_attestation ( [ 0x5a ; 64 ] ) ;
2926+ legacy. runtime_events . push ( cc_eventlog:: RuntimeEvent :: new (
2927+ "legacy-event" . into ( ) ,
2928+ vec ! [ 1 , 2 , 3 ] ,
2929+ cc_eventlog:: EventLogVersion :: V1 ,
2930+ ) ) ;
2931+ let converted = legacy. clone ( ) . into_v1 ( ) . try_into_legacy ( ) . unwrap ( ) ;
2932+ assert_eq ! ( converted. report_data, legacy. report_data) ;
2933+ assert_eq ! ( converted. runtime_events. len( ) , 1 ) ;
2934+ assert ! ( matches!(
2935+ converted. into_versioned( ) ,
2936+ VersionedAttestation :: V0 { .. }
2937+ ) ) ;
2938+ }
2939+
2940+ #[ test]
2941+ fn v1_conversion_rejects_lossy_legacy_projection ( ) {
2942+ let pod = dummy_tdx_attestation ( [ 0x5b ; 64 ] )
2943+ . into_v1 ( )
2944+ . into_dstack_pod ( "payload" . into ( ) ) ;
2945+ assert ! ( pod. try_into_legacy( ) . is_err( ) ) ;
2946+ let mut v2 = dummy_tdx_attestation ( [ 0x5c ; 64 ] ) . into_v1 ( ) ;
2947+ if let StackEvidence :: Dstack { runtime_events, .. } = & mut v2. stack {
2948+ runtime_events. push ( cc_eventlog:: RuntimeEvent :: new (
2949+ "v2-event" . into ( ) ,
2950+ vec ! [ 4 , 5 , 6 ] ,
2951+ cc_eventlog:: EventLogVersion :: V2 ,
2952+ ) ) ;
2953+ }
2954+ assert ! ( v2. try_into_legacy( ) . is_err( ) ) ;
2955+ }
2956+
28892957 #[ test]
28902958 fn versioned_v0_projects_to_v1 ( ) {
28912959 let projected = dummy_tdx_attestation ( [ 5u8 ; 64 ] ) . into_versioned ( ) . into_v1 ( ) ;
0 commit comments