feat(dkg): add TDXValidationHook for TDX attestation#831
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Adds the devnet helper kit used to bring up the 4-validator SGX+TDX chain that exercises PR #831 + story-kernel PR #68: - contracts/script/dcap-devnet/*.sh -- DCAP collateral upload, SGX hook rewire, full devnet bring-up scripts (real V3QuoteVerifier). - contracts/script/dcap-devnet/add-alloc-extras.py -- alloc patcher that injects MockAutomataDcap bytecode and other non-Create3 prereqs. - contracts/script/dcap-devnet/p256verifier_bytecode.txt -- pre-built P256Verifier bytecode for inline alloc injection. - .claude/devnet-test/DEVNET_RUNBOOK.md -- long-form runbook for the SGX + DCAP devnet path (force-tracked under .claude). - .claude/devnet-test/TDX_DEVNET_RUNBOOK.md -- TDX delta runbook on top of the SGX runbook. - GenerateAlloc.s.sol -- USE_DEPLOYER_AS_OWNER toggle (default true) so the deployer key can call whitelistEnclaveType / approvePlatform / scheduleUpgrade directly without scheduling through the TimelockController. forge tests assume timelock ownership and will fail with this flag on, expected for devnet-only configuration. This commit is intentionally devnet-only and is NOT meant to be merged into the PR branch; it lives on dkg/hans-temp-test and is rebased on top of dkg/impl-tdx-val-hook for each E2E test cycle.
Adds the devnet helper kit used to bring up the 4-validator SGX+TDX chain that exercises PR #831 + story-kernel PR #68: - contracts/script/dcap-devnet/*.sh -- DCAP collateral upload, SGX hook rewire, full devnet bring-up scripts (real V3QuoteVerifier). - contracts/script/dcap-devnet/add-alloc-extras.py -- alloc patcher that injects MockAutomataDcap bytecode and other non-Create3 prereqs. - contracts/script/dcap-devnet/p256verifier_bytecode.txt -- pre-built P256Verifier bytecode for inline alloc injection. - .claude/devnet-test/DEVNET_RUNBOOK.md -- long-form runbook for the SGX + DCAP devnet path (force-tracked under .claude). - .claude/devnet-test/TDX_DEVNET_RUNBOOK.md -- TDX delta runbook on top of the SGX runbook. - GenerateAlloc.s.sol -- USE_DEPLOYER_AS_OWNER toggle (default true) so the deployer key can call whitelistEnclaveType / approvePlatform / scheduleUpgrade directly without scheduling through the TimelockController. forge tests assume timelock ownership and will fail with this flag on, expected for devnet-only configuration. This commit is intentionally devnet-only and is NOT meant to be merged into the PR branch; it lives on dkg/hans-temp-test and is rebased on top of dkg/impl-tdx-val-hook for each E2E test cycle.
Adds the devnet helper kit used to bring up the 4-validator SGX+TDX chain that exercises PR #831 + story-kernel PR #68: - contracts/script/dcap-devnet/*.sh -- DCAP collateral upload, SGX hook rewire, full devnet bring-up scripts (real V3QuoteVerifier). - contracts/script/dcap-devnet/add-alloc-extras.py -- alloc patcher that injects MockAutomataDcap bytecode and other non-Create3 prereqs. - contracts/script/dcap-devnet/p256verifier_bytecode.txt -- pre-built P256Verifier bytecode for inline alloc injection. - .claude/devnet-test/DEVNET_RUNBOOK.md -- long-form runbook for the SGX + DCAP devnet path (force-tracked under .claude). - .claude/devnet-test/TDX_DEVNET_RUNBOOK.md -- TDX delta runbook on top of the SGX runbook. - GenerateAlloc.s.sol -- USE_DEPLOYER_AS_OWNER toggle (default true) so the deployer key can call whitelistEnclaveType / approvePlatform / scheduleUpgrade directly without scheduling through the TimelockController. forge tests assume timelock ownership and will fail with this flag on, expected for devnet-only configuration. This commit is intentionally devnet-only and is NOT meant to be merged into the PR branch; it lives on dkg/hans-temp-test and is rebased on top of dkg/impl-tdx-val-hook for each E2E test cycle.
1f9cfc1 to
14bb38c
Compare
Wiz Scan Summary
To detect these findings earlier in the dev lifecycle, try using Wiz Code VS Code Extension. |
9f296ee to
5f8f10a
Compare
On-chain validator for Intel TDX V4/V5 attestation quotes used by the DKG flow. Mirrors SGXValidationHook in structure (Ownable2Step + Pausable) with TDX-specific quote field offsets. Identity model (schema v3): - Binary identity = keccak256(RTMR3), whitelisted via EnclaveTypeData.codeCommitment (the same DKG slot SGX uses for MRENCLAVE). RTMR3 is self-extended by the kernel exactly once at startup with SHA-384(/proc/self/exe), so its post-extend value SHA-384(0x00..00 || SHA-384(elf)) is fully determined by the running ELF. - Platform identity = keccak256(MRTD || RTMR0 || RTMR1 || RTMR2), approved via the hook's approvePlatform admin. MRTD + RTMR0 pin SKU/TDVF, RTMR1 captures OVMF + bootloader vintage, RTMR2 pins the TD initrd + cmdline. Why not keccak256(RTMR2) for the binary half: RTMR2 measures the TD initrd + kernel cmdline, both boot-image properties decided at TD launch by the firmware/boot chain. The userspace Go binary is loaded from the rootfs after boot and is never fed into RTMR2 by any link of the boot chain — so a keccak256(RTMR2) rule would commit to a boot-image artifact, not to the running ELF. RTMR3 is the first software-defined register the TD payload can claim, so the kernel extends it explicitly for the binary half. Decomposing the commitment along the operator / cloud boundary lets binary releases and cloud firmware vintage rotations evolve independently — N kernel binaries × M platform vintages collapse from N×M into N + M governance rows. REPORT_DATA[0:32] carries the kernel-bound caller commitment from DKG.register and is compared against expectedDataCommitment, mirroring SGX's instance binding. Contract changes: - TDXValidationHook (new). _computeBinaryCommitment reads RTMR3 at offset 520; _computePlatformCommitment assembles the 192-byte preimage from MRTD (offset 184) + the contiguous RTMR0/RTMR1/RTMR2 block (offsets 376..519). - ITDXValidationHook (new). Admin surface: setAutomataValidationAddr, approvePlatform, revokePlatform, isPlatformApproved. - IAutomataDcapAttestationFee: 1-arg overload (PR #816 pattern) so standard TCB transitions take effect automatically. - DeployTDXValidationHook script + GenerateAlloc opt-in toggle for TDX devnet bring-up. DKG.sol / IDKG.sol are unchanged — ABI/storage compatible with the live deployment. To pass register() an attacker must produce a TDX quote where RTMR3 hashes to an approved binary commitment AND MRTD || RTMR0 || RTMR1 || RTMR2 hashes to an approved platform commitment. The platform half catches initrd / rootfs forgeries (which shift RTMR1 and RTMR2); RTMR3's append-only nature plus the kernel claiming it before any operator code runs catches userspace tampering. Tests: - 30 TDXValidationHookTest cases pass (including decomposition tests for same-binary-different-platform and same-platform-different-binary). - 64 DKGTest cases continue to pass (no regression). - E2E on devnet with real Automata DCAP: TDX register-tx 0x1ff101c150caa3b1506908123374ded76af1cc35d779d728016e1f9f7c55cd39 at block 12015, status=1, with both AttestationFee + DKG.Registered logs emitted.
GitHub Actions runners are intermittently failing to download the SHAs
that the v5 moving tag of actions/setup-go and the v2 moving tag of
pnpm/action-setup currently resolve to:
##[error]An action could not be found at the URI
'https://codeload.github.com/actions/setup-go/tar.gz/40f1582b...'
The same SHAs are fetchable from outside the runner network, so this
is a runner-side cache miss against the resolved v5 / v2 commits.
Bumping to v6 and v4 forces resolution to fresh SHAs. No behavioural
change — both v6 setup-go and v4 pnpm/action-setup are drop-ins for
our usage.
5f8f10a to
5fe4b9b
Compare
|
The new Highest concern: Deployment risk: Deployment script sentinel: The CI action upgrades ( Coverage note: the Review iteration 3 · Commit 61fe8be · 2026-05-29T06:02:27Z |
| // enclaveType=2 with the binary commitment above, then calls approvePlatform for each | ||
| // configured platform commitment. Operators flip this to true on TDX-capable devnets; | ||
| // it is false by default so mainnet/SGX-only deployments are unaffected. | ||
| bool private constant DKG_INCLUDE_TDX = true; |
There was a problem hiding this comment.
Removed the constant and make the script set TDXValidationHook contract by default in ddf3d7d
Remove the DKG_INCLUDE_TDX gate so setTDXValidationHook is always invoked.
# Conflicts: # contracts/src/interfaces/external/IAutomataDcapAttestationFee.sol
…844) ## Problem `DKG.sol::register` pins `dkgPubKey` to 64 bytes and `enclaveCommKey` to 65 bytes, but the kernel produces — and the consensus client verifies — a **32-byte** ed25519 `dkgPubKey` and a **64-byte** secp256k1 `commKey` (uncompressed, `0x04` prefix stripped). Every `register` therefore reverts on the length check, so DKG cannot bootstrap on any chain running the current contracts. The strict checks were added in #831 (previously the fields were only required to be non-empty), which is why this surfaced only after the up-to-date contracts were actually deployed. ## Fix Require `dkgPubKey == 32` and `enclaveCommKey == 64` to match what the kernel emits and the CL verifies. Both fields stay fixed-length, so the `abi.encodePacked` report-data preimage remains injective and `calculateReportData` still mirrors it. issue: piplabs/story-kernel#73
Summary
On-chain validator for TDX V4/V5 attestation quotes used by the DKG flow. Mirrors
SGXValidationHookin structure (Ownable2Step + Pausable) with TDX-specific quote field offsets.Identity model (v3 — RTMR3-bound binary, RTMR2-included platform)
Split along the operator / cloud boundary, where "operator-controlled" means the running Go binary:
keccak256(RTMR3), whitelisted viaEnclaveTypeData.codeCommitment(same path as SGXMRENCLAVE). RTMR3 is self-extended by the kernel exactly once at startup withSHA-384(/proc/self/exe)(see story-kernel #68), so its post-extend valueSHA-384(0x00..00 || SHA-384(elf))is fully determined by the running ELF.keccak256(MRTD || RTMR0 || RTMR1 || RTMR2), approved via the hook'sapprovePlatformadmin. RTMR2 moves into the platform half because it measures TD initrd + kernel cmdline — both boot-image properties decided at TD launch by the firmware/boot chain, never the userspace Go binary loaded from disk after boot.Why not
keccak256(RTMR2)for the binary half: on every direct-launch cloud TD measured, RTMR2 contains the TD initrd + cmdline digest. The story-kernel Go binary is loaded from the rootfs after boot and is never fed into RTMR2 by any link of the boot chain — so akeccak256(RTMR2)rule would commit to a boot-image artifact, not to the running ELF. The v3 split corrects this by claiming RTMR3 explicitly for the userspace binary half.This decouples binary release governance from cloud firmware vintage rotation, so each axis updates independently.
REPORT_DATA[0:32]carries the kernel-bound caller commitment fromDKG.registerand is compared againstexpectedDataCommitment, mirroring SGX's instance binding.Onboarding flow
A TDX enclave type is enabled in two steps:
DKG.whitelistEnclaveType(enclaveType, { codeCommitment = keccak256(RTMR3), validationHookAddr = tdxProxy }).TDXValidationHook.approvePlatform(keccak256(MRTD || RTMR0 || RTMR1 || RTMR2), label).Contract changes
TDXValidationHook(new)._computeBinaryCommitmentreadsRTMR3at offset 520;_computePlatformCommitmentassembles the 192-byte preimage from MRTD (offset 184) + the contiguous RTMR0/RTMR1/RTMR2 block (offsets 376..519).ITDXValidationHook(new). Admin surface:setAutomataValidationAddr,approvePlatform,revokePlatform,isPlatformApproved.IAutomataDcapAttestationFee: adds 1-arg overload (PR feat(dkg): let SGXValidationHook follow standard TCB via Automata verifier #816 pattern) so standard TCB transitions take effect automatically.DKG.sol/IDKG.solare unchanged — ABI/storage compatible with the live deployment.Adversary cost — both halves must succeed
To pass
register()an attacker must produce a TDX quote where:RTMR3hashes to an approved binary commitment, andMRTD || RTMR0 || RTMR1 || RTMR2hashes to an approved platform commitment.The RTMR3 self-extend can only push values into RTMR3 (RTMRs are append-only). An attacker binary
B'that self-extends RTMR3 withSHA-384(honest_B)to forge the binary commitment still has to be loaded by the boot chain — which measures the kernel image + initrd into RTMR1 and the initrd + cmdline into RTMR2. Either modification shifts the platform commitment off any honest entry, and the platform-approval check fails.