Level: Intermediate | Prerequisites: Familiarity with TEE attestation, TDX quotes, and SHA256 hashing | Code: Python
Introduction
When you deploy an application inside a dstack Confidential VM (CVM), you verify that it runs your intended configuration by comparing the hardware-signed TDX quote to a known reference value. If you have been using RTMR3 event log replay for this, there is a simpler approach: MR-CONFIG-ID, a 48-byte field set directly in the TDX quote, designed specifically for application identity verification.
What is MR-CONFIG-ID?
MR-CONFIG-ID is the mrconfigid field in the Intel TDX quote — a 48-byte value set by QEMU before the CVM boots. The guest cannot modify it.
It comes in two formats:
V1 — Compose hash only
Used when there is no KMS binding (key_provider_id is empty):
Byte 0 1 33 48
┌────┬────────────────────────────────┬──────────────────┐
│0x01│ compose_hash (SHA-256, 32B) │ padding (00×15) │
└────┴────────────────────────────────┴──────────────────┘V2 — Full configuration binding
Used when a KMS is involved:
Byte 0 1 33 48
┌────┬────────────────────────────────┬──────────────────┐
│0x02│ Keccak256(compose_hash || │ padding (00×15) │
│ │ app_id || kp_type || kp_id) │ │
└────┴────────────────────────────────┴──────────────────┘Where:
compose_hash= SHA-256 ofapp-compose.jsonapp_id= 20-byte application identifierkp_type= key provider type:0= none,1= local SGX,2= KMS,3= TPMkp_id= key provider identity (KMS root CA public key, SGX MR enclave measurement, or empty)
Why not just use RTMR3?
RTMR3 contains the full boot transcript: ten events extended by the guest agent during startup. Here is what is in there:
| # | Event | Payload | Stable as a reference value? |
| 1 | system-preparing | Empty marker | — |
| 2 | app-id | 20-byte app identifier | ✅ (if config unchanged) |
| 3 | compose-hash | SHA-256 of app-compose.json | ✅ (if config unchanged) |
| 4 | instance-id | SHA256(seed || app_id), seed generated on first boot and persisted | ✅ (if config unchanged) |
| 5 | boot-mr-done | Empty marker | — |
| 6 | mr-kms | KMS measurement returned at runtime | ❌ (varies per boot: KMS probe) |
| 7 | os-image-hash | OS image hash from KMS | ✅ (if config unchanged) |
| 8 | key-provider | JSON: KMS type + identity | ✅ (if config unchanged) |
| 9 | storage-fs | "zfs" or "ext4" | ✅ (if config unchanged) |
| 10 | system-ready | Empty marker | — |
Event #6, mr-kms, makes RTMR3 unsuitable as a stable reference value. The CVM probes multiple KMS instances at startup and records the measurement of whichever one it connects to. This value changes across boots even when your application configuration is identical. Because of this, you cannot pre-compute a reference value for RTMR3 from your application manifest. Your verifier must receive the full event log, replay all ten events to confirm integrity, then extract and check each field individually.
Measurement vs Reference Value
A quick note on terminology used throughout this tutorial:
| Term | Meaning |
| Measurement | The value returned by the hardware — e.g. RTMR3 as quoted in the TDX quote, or mrconfigid from the quote |
| Reference value | The known good value you compute offline — what the measurement should be if the CVM is running your expected configuration |
Verification means comparing the measurement to your reference value. MR-CONFIG-ID lets you do this in one step.
Verification flow
With RTMR3 event log replay
1. Receive the TDX quote + runtime event log from the CVM
2. Verify the quote signature (dcap-qvl, Intel root CA chain)
3. Extract the quoted RTMR3 from the TDREPORT (measurement)
4. Replay all 10 events:
rt = 0
for each event:
digest = SHA384(0x08000001 || ":" || event_name || ":" || payload)
rt = SHA384(rt || digest)
5. Compare replayed rt to quoted RTMR3 — if match, event log is authentic
6. Extract compose-hash, key-provider from event log; verify each against reference valuesWith MR-CONFIG-ID
1. Receive the TDX quote from the CVM
2. Verify the quote signature
3. Extract mrconfigid from the quote (measurement), compare to your pre-computed reference valueImplementation
Core verification function
import hashlib
def verify_mr_config_id(
measurement: bytes, # 48-byte mrconfigid from TDX quote
compose_hash: bytes, # SHA-256 of your app-compose.json
app_id: bytes, # 20-byte application identifier
kp_type: int, # 0=none, 1=local, 2=kms, 3=tpm
kp_id: bytes, # KMS root CA pubkey, SGX MR, or b''
) -> bool:
"""Compare the measurement (mrconfigid from quote) to the reference value."""
if len(measurement) != 48:
raise ValueError(f"mrconfigid must be 48 bytes, got {len(measurement)}")
# Backward compatibility: pre-0.5.2 images set mrconfigid to all zeros
if measurement == b'\\x00' * 48:
return verify_via_rtmr3(quote) # fallback to RTMR3 replay
version = measurement[0]
if version == 1:
reference_value = b'\\x01' + compose_hash + b'\\x00' * 15
elif version == 2:
data = compose_hash + app_id + bytes([kp_type]) + kp_id
keccak = hashlib.sha3_256(data).digest()
reference_value = b'\\x02' + keccak + b'\\x00' * 15
else:
raise ValueError(f"Unknown mr_config_id version: {version}")
return measurement == reference_valueExtracting mrconfigid from a TDX quote
from dcap_qvl.quote import Quote
def get_mr_config_id(quote_bytes: bytes) -> bytes:
"""Extract the 48-byte mrconfigid measurement from a TDX quote."""
quote = Quote.parse(quote_bytes)
return quote.report.as_td10().mr_config_idBuilding the reference value
1. compose_hash
The SHA-256 hash of your app-compose.json. If you deploy via Phala Cloud, get it from the provision API:
curl -s -X POST "<https://cloud.phala.network/api/v1/cvms/provision>" \\
-H "Authorization: Bearer $PHALA_API_TOKEN" \\
-H "Content-Type: application/json" \\
-d '{
"compose_file": {
"manifest_version": 2,
"name": "my-app",
"runner": "docker-compose",
"docker_compose_file": "...",
"allowed_envs": ["API_KEY"],
"kms_enabled": true,
"gateway_enabled": true
}
}' | jq -r '.compose_hash'If you provide your own pre_launch_script, you can pre-compute it locally:
import hashlib, json
from collections import OrderedDict
def compute_compose_hash(app_compose: dict) -> str:
d = OrderedDict(sorted(app_compose.items()))
s = json.dumps(d, separators=(",", ":"), ensure_ascii=False)
return hashlib.sha256(s.encode("utf-8")).hexdigest()2. app_id
The 20-byte application identifier, returned in the provision API response as app_id.
3. KMS binding
# KMS backend:
kp_type = 2
kp_id = bytes.fromhex(kms_root_ca_pubkey_hex)
# Local key provider (no external KMS):
kp_type = 1
kp_id = bytes.fromhex(sgx_mr_enclave_hex)
# No key provider:
kp_type = 0
kp_id = b''Complete end-to-end example
import hashlib
from dcap_qvl.quote import Quote
# ── Reference values (pre-computed, e.g. from CI or provision API) ──
COMPOSE_HASH = bytes.fromhex("a1b2c3d4...")
APP_ID = bytes.fromhex("deadbeef...")
KP_TYPE = 2
KP_ID = bytes.fromhex("feedface...")
def build_reference_value(compose_hash, app_id, kp_type, kp_id):
data = compose_hash + app_id + bytes([kp_type]) + kp_id
keccak = hashlib.sha3_256(data).digest()
return b'\\x02' + keccak + b'\\x00' * 15
REFERENCE = build_reference_value(COMPOSE_HASH, APP_ID, KP_TYPE, KP_ID)
def verify(quote_bytes: bytes) -> bool:
# (1) Verify DCAP signature chain
if not dcap_qvl_verify(quote_bytes):
return False
# (2) Read the measurement from the quote
quote = Quote.parse(quote_bytes)
measurement = quote.report.as_td10().mr_config_id
# (3) Compare measurement to reference value
return measurement == REFERENCE
# Usage
attestation_quote = receive_from_cvm()
if verify(attestation_quote):
print("Application identity verified.")
else:
print("Verification failed.")When to use which
| Scenario | RTMR3 | MR-CONFIG-ID |
| Verify application identity (compose-hash + KMS binding) | — | ✅ |
| Audit the full boot transcript of a specific instance | ✅ | — |
| Pre-compute reference value in CI | ❌ | ✅ |
| Verify across multiple CVM instances with the same config | ❌ | ✅ |
| Instance-level traceability (which instance produced this quote) | ✅ | — |
Summary
MR-CONFIG-ID is what it says: the configuration identifier set into the hardware measurement. It contains only the information relevant to application identity — compose_hash, app_id, and KMS binding — and nothing else. Because it excludes mr-kms (which varies per boot), the same configuration always produces the same value. You can build the reference value offline from your application manifest and KMS public key, without any interaction with a running CVM.
