Summary
The dkgPubKey and enclaveCommKey lengths the kernel submits during DKG registration do not match the requirements of DKG.sol::register on the chain side. As a result, any chain running the current dkg/dev contracts cannot complete DKG registration — every validator's register reverts during gas estimation and DKG never bootstraps.
Background: how we ran into this
We were validating the DKG resharing fixes (chain side piplabs/story #838 off-by-one + participants-root, with the companion kernel-side PR #72 "wait for finalization stage") on a 4-node devnet, with the chain on branch test/v190-regress3 and the kernel on dd7eca07 (= #67 + #72).
It peeled apart in layers:
-
Symptom: on every setup, DKG never bootstrapped — every validator's register reverted during gas estimation and the chain just churned (the DKG round counter kept incrementing but no active round ever appeared).
-
First layer (code commitment): the initial revert was SGXAttestationReportValidator: Code commitment does not match. That turned out to be a devnet setup-script bug: the GenerateAlloc forge script reverted due to an owner/prank mismatch, but its non-zero exit code was swallowed by a … | tail pipeline, so the script silently reused a several-days-old local-alloc.json and wrote a previous kernel build's code commitment into genesis. Fixed in piplabs/story-cdr-e2e PR #239 (fail-fast on forge failure + force regeneration of the alloc).
-
Second layer (this issue): with the setup fixed, the setup deployed the current dkg/dev contracts for the first time. The code-commitment error disappeared and was immediately replaced by DKG: DKG public key must be 64 bytes — the kernel↔contract key-length mismatch described here.
-
Why it went unnoticed: every previously "successful" setup had actually been running pre-#831 contracts (no 64/65 length checks) because of the same GenerateAlloc failure, so the 32-byte ed25519 public key slipped through the whole time. Only after fixing the setup and deploying the up-to-date contracts did this long-masked incompatibility surface.
The mismatch
| key |
kernel submits |
contract requires |
result |
dkgPubKey |
32 bytes (edwards25519 compressed point) |
== 64 |
❌ wrong curve / length |
enclaveCommKey |
64 bytes (secp256k1 uncompressed, 0x04 prefix stripped) |
== 65 |
❌ missing the prefix byte |
Evidence
Contract side (piplabs/story, introduced by #831 9567630), contracts/src/protocol/DKG.sol:
require(enclaveInstanceData.dkgPubKey.length == 64, "DKG: DKG public key must be 64 bytes");
require(enclaveInstanceData.enclaveCommKey.length == 65, "DKG: Enclave communication key must be 65 bytes");
Kernel side, service/dkg_generate_key.go:
edPubBz, err := edPub.MarshalBinary() // edwards25519 -> 32 bytes
...
reportData, err := calculateReportData(...,
edPubBz, // dkgPubKey -> 32 bytes
ecrypto.FromECDSAPub(secpPub)[1:], // enclaveCommKey -> secp256k1 uncompressed (65) minus prefix = 64 bytes
)
...
DkgPubKey: edPubBz, // 32 bytes
CommPubKey: ecrypto.FromECDSAPub(secpPub)[1:], // 64 bytes
Observed devnet log (register, round 3):
err="...execution reverted: DKG: DKG public key must be 64 bytes"
dkg_pub_key=f3cfb902…a109d23 (64 hex = 32 bytes)
comm_pub_key=20516989…b199ea (128 hex = 64 bytes)
Status of PR #68
PR #68 (feat/impl-tdx-backend) adds length assertions to calculateReportData in service/utils.go — dkgPubKeySize = 64 / enclaveCommKeySize = 65 (the comment notes these "match the require()s in DKG.sol::register") — but it does not change the actual key generation in dkg_generate_key.go (still edPubBz / secpPub[1:]). So with PR #68 the failure simply moves earlier, to a kernel-side dkgPubKey must be 64 bytes, got 32; the underlying mismatch is unchanged.
Deeper design conflict
The kernel's DKG itself runs on edwards25519 (service/dist_key_gen.go uses SuiteEd25519), so dkgPubKey is inherently 32 bytes, while the contract requires 64. This needs a protocol-level decision:
- either the contract reverts
dkgPubKey back to 32 bytes (keep ed25519), or
- the DKG public key migrates to a 64-byte representation (e.g. secp256k1
X‖Y).
enclaveCommKey is simpler: the kernel just needs to stop stripping the 0x04 prefix ([1:]) to produce the 65 bytes the contract expects.
Impact
- On any chain running the current
dkg/dev contracts, DKG registration always fails and the chain cannot bootstrap.
Ask
Decide the target curve/length for dkgPubKey, and make the kernel's key generation consistent with the 64 / 65-byte requirements of DKG.sol::register.
Summary
The
dkgPubKeyandenclaveCommKeylengths the kernel submits during DKG registration do not match the requirements ofDKG.sol::registeron the chain side. As a result, any chain running the currentdkg/devcontracts cannot complete DKG registration — every validator'sregisterreverts during gas estimation and DKG never bootstraps.Background: how we ran into this
We were validating the DKG resharing fixes (chain side
piplabs/story#838 off-by-one + participants-root, with the companion kernel-side PR #72 "wait for finalization stage") on a 4-node devnet, with the chain on branchtest/v190-regress3and the kernel ondd7eca07(= #67 + #72).It peeled apart in layers:
Symptom: on every setup, DKG never bootstrapped — every validator's
registerreverted during gas estimation and the chain just churned (the DKG round counter kept incrementing but no active round ever appeared).First layer (code commitment): the initial revert was
SGXAttestationReportValidator: Code commitment does not match. That turned out to be a devnet setup-script bug: theGenerateAllocforge script reverted due to an owner/prank mismatch, but its non-zero exit code was swallowed by a… | tailpipeline, so the script silently reused a several-days-oldlocal-alloc.jsonand wrote a previous kernel build's code commitment into genesis. Fixed inpiplabs/story-cdr-e2ePR #239 (fail-fast on forge failure + force regeneration of the alloc).Second layer (this issue): with the setup fixed, the setup deployed the current
dkg/devcontracts for the first time. The code-commitment error disappeared and was immediately replaced byDKG: DKG public key must be 64 bytes— the kernel↔contract key-length mismatch described here.Why it went unnoticed: every previously "successful" setup had actually been running pre-#831 contracts (no 64/65 length checks) because of the same
GenerateAllocfailure, so the 32-byte ed25519 public key slipped through the whole time. Only after fixing the setup and deploying the up-to-date contracts did this long-masked incompatibility surface.The mismatch
dkgPubKey== 64enclaveCommKey0x04prefix stripped)== 65Evidence
Contract side (
piplabs/story, introduced by #8319567630),contracts/src/protocol/DKG.sol:Kernel side,
service/dkg_generate_key.go:Observed devnet log (register, round 3):
Status of PR #68
PR #68 (
feat/impl-tdx-backend) adds length assertions tocalculateReportDatainservice/utils.go—dkgPubKeySize = 64/enclaveCommKeySize = 65(the comment notes these "match the require()s in DKG.sol::register") — but it does not change the actual key generation indkg_generate_key.go(stilledPubBz/secpPub[1:]). So with PR #68 the failure simply moves earlier, to a kernel-sidedkgPubKey must be 64 bytes, got 32; the underlying mismatch is unchanged.Deeper design conflict
The kernel's DKG itself runs on edwards25519 (
service/dist_key_gen.gousesSuiteEd25519), sodkgPubKeyis inherently 32 bytes, while the contract requires 64. This needs a protocol-level decision:dkgPubKeyback to 32 bytes (keep ed25519), orX‖Y).enclaveCommKeyis simpler: the kernel just needs to stop stripping the0x04prefix ([1:]) to produce the 65 bytes the contract expects.Impact
dkg/devcontracts, DKG registration always fails and the chain cannot bootstrap.Ask
Decide the target curve/length for
dkgPubKey, and make the kernel's key generation consistent with the 64 / 65-byte requirements ofDKG.sol::register.