feat(kms): self-init device-auth de-root (reproducible) + BLS PoP /pop for staked registration#172
Conversation
…gistration
Two CC-24/CC-25 follow-ups:
1) self-init encodes the device-auth hardening so B-board/community nodes de-root
automatically (not just the hand-hardened A-board): creates dedicated kms (in
tee/teepriv) + dvt (no TEE) users, migrates the kms deploy off /root, writes both
de-root drop-ins (kms: User=kms + DevicePolicy=closed + DeviceAllow=/dev/tee*; dvt:
User=dvt), health-gated with auto-rollback so it can never brick first boot. Also
fixes the handoff KEEPER_SIGNER_URL (emit the loopback BASE, not …/kms/sign — DVT
appends the path) and adds KEEPER_ID.
2) BLS proof-of-possession endpoint POST /pop {node_id, operator} -> {pop_signature}
for the staked validator path (registerWithProof). The TA signs the OPERATOR
address under POP_DST (AASTAR_DVT_POP_…) — an operator-bound message the KMS
chooses, NEVER a caller-supplied point, so /pop can't be a forgery oracle. New
proto BlsPopSign=34 + TA bls::sign_pop + ta_client + host route (loopback+token).
Builds: proto 43 tests, TA aarch64-unknown-optee, CA aarch64-linux-gnu all green.
Activating /pop on the A-board needs a TA reflash + DVT register-node.mjs sending the
operator (not pop_point) — coordinated rollout, not in this commit.
…ct_eq token Codex adversarial review fixes: - H1: the de-root health gate only checked :3100/health (static JSON liveness) — it passed even if kms-api couldn't open /dev/tee0. Now gated on a real BLS sign (tee_reachable) so a broken de-root rolls back instead of marking a dead signer done. - H2: dvt-in-tee-group is now actively removed (gpasswd -d), and the dvt drop-in gets DevicePolicy=closed (no DeviceAllow) so the network-facing node can never reach /dev/tee* even on a group leak. - M4: || true on the ExecStart extraction so a no-match can't abort harden under set -e. - L5: check_signer_token now uses ct_eq (constant-time), matching keeper/remove paths. - L6: register-bls-node.sh documented BOOTSTRAP-ONLY; staked path points to DVT register-node.mjs + the new /pop. CA rebuilds green; self-init syntax OK.
Security Audit ReportDate: Fri Jul 10 17:30:21 UTC 2026 Cargo Audit Results |
… + golden test Reviewing CC-37 (SDK) against ground truth showed my first /pop cut followed DVT's register-node.mjs convention (operator + AASTAR_DVT_POP_ DST), which diverges from the authoritative, on-chain-verified SDK buildDvtPop (live tx 0xa641534f). The contract _verifyPoP is message-agnostic so both pass mathematically, but the ecosystem standard is SDK's RFC self-PoP. Reworked to match it: - TA bls::sign_pop now signs the node's OWN 128B EIP-2537 pubkey under BLS_DST (…_POP_), returning the full DvtPop tuple (publicKey, popPoint, popSig). New encode_g1_eip2537 + hash_to_g2_eip2537 (blst_hash_to_g2, same RFC 9380 SSWU as @noble). - Safer than the operator variant: the message is the node's own pubkey (caller supplies NONE), so /pop is not a signing oracle; and a 128B pubkey can't collide with a 32B co-sign userOpHash, so a PoP can't be replayed as a co-sign sig despite the shared DST. - proto BlsPopSignInput drops operator (just key_id); output is the tuple. Host POST /pop {node_id?} -> {publicKey, popPoint, popSig}. - GOLDEN: standalone blst vs SDK buildDvtPop(0x…c0ffee) => publicKey + popPoint + popSig byte-identical. Locked as bls.rs #[test] sign_pop_matches_sdk_builddvtpop. proto 43 tests + TA aarch64 + CA aarch64 all green.
clestons
left a comment
There was a problem hiding this comment.
#172 — REQUEST CHANGES
Part 1 (de-root) is excellent. Part 2 (/pop) has a confirmed cross-repo wire-contract mismatch with its only consumer — DVT register-node.mjs (#214) — so CC-37 KMS-TEE staked registration will fail as shipped.
🔴 Blocking — /pop does not match the DVT consumer
I diffed both sides at their current heads:
| Axis | KMS #172 /pop (producer) |
DVT #214 register-node.mjs (consumer) |
|---|---|---|
| Request field | reads operator (PopSignReq{node_id, operator}) |
sends publicKey ({node_id, publicKey: node.pubEip2537}, line 155) — no operator |
| Response shape | {pop_signature, pop_signature_compact} — no popPoint |
parses {popPoint, popSig} (line 159, "CC-37 shape") |
| Signed message | sk·hashToG2(operator, …) |
expects popPoint = hashToG2(publicKey) returned by KMS |
| DST | AASTAR_DVT_POP_BLS12381G2_XMD:SHA-256_SSWU_RO_ |
comment asserts KMS uses BLS_SIG_…_POP_ |
Concretely: DVT POSTs {node_id, publicKey} → KMS #172 finds no operator field → 400 "operator must be a 20-byte address hex". Even if it parsed, KMS returns pop_signature but DVT reads popPoint/popSig → both undefined → registerWithProof(pubkey, undefined, undefined). Registration cannot succeed.
Both PRs even reference each other and disagree: KMS bls.rs says "POP_DST MUST equal DVT register-node.mjs (AASTAR_DVT_POP_…)", while DVT #214 says "KMS /pop uses BLS_SIG_…POP". They were mis-coordinated in opposite directions. (The live tx 0xe4e1de53… only proves the local-key path, which is self-consistent under any scheme — it does NOT validate the KMS-TEE wire contract.)
Reconcile on ONE contract before merge. Note: KMS #172's choice to sign a bound message (the operator, never a caller-supplied point) is the right anti-oracle property and worth preserving — but then the response must also return popPoint (the point KMS hashed+signed) so the consumer can put it in registerWithProof calldata, and DVT must send operator (not publicKey) and read {popPoint, popSig}. Whichever way it lands, the request field + response shape + DST + hashed message must be identical on both sides.
✅ Part 1 (de-root) — good, keep it
The reproducible de-root (dedicated kms/dvt users, dvt removed from tee/teepriv, DevicePolicy=closed, deploy off /root) directly closes the /dev/tee*-access gap I flagged on #168 — a network-facing DVT compromise can no longer invoke the sealed keeper/BLS TA directly. TA-backed health probe (real BLS sign) + auto-rollback so it can't brick first boot is the right safety. The handoff KEEPER_SIGNER_URL base fix + KEEPER_ID are correct.
CODE-track, security-critical. Wire mismatch verified by diffing both repos' current heads.
Cross-repo contract fix: DVT register-node.mjs (#214) reads { popPoint, popSig } and
SDK's DvtPop is { publicKey, popPoint, popSig } (camelCase). The response was emitting
snake_case (pop_point/pop_signature) → consumers would read undefined and fail. serde
rename to the exact camelCase keys the consumers parse. (The earlier REQUEST-CHANGES
review's DST/message/input points were against the pre-ee62aeb operator version; those
were already reworked to SDK's pubkey-self-PoP — this field-naming was the real residual.)
|
Thanks — the cross-repo contract catch was worth it. Verdict after checking both current heads: Your DST / message / input points were against the pre- But you were right about the real residual: response field naming. Full contract now cross-checked against DVT #214 ( Agree the live tx only proves the local-key path; the KMS-TEE path E2E is gated on the A-board TA reflash + SDK/DVT |
Security Audit ReportDate: Fri Jul 10 18:02:29 UTC 2026 Cargo Audit ResultsCargo Deny Results |
clestons
left a comment
There was a problem hiding this comment.
#172 — APPROVE (re-review)
The /pop wire-contract mismatch I flagged is fully resolved — the endpoint was reworked (beyond the camelCase commit message) to conform to the CC-37 contract DVT #214 + SDK #309 expect. Verified at the current head:
| Axis | Was (my REQUEST CHANGES) | Now |
|---|---|---|
| Request | {node_id, operator} |
{node_id?} — TA bls_pop_sign(key_id) uses the board's sealed key; DVT's extra publicKey field harmlessly ignored |
| Message | sk·hashToG2(operator) |
sk·hashToG2(publicKey, BLS_DST) |
| DST | AASTAR_DVT_POP_… |
BLS_DST = BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_ |
| Response | {pop_signature} |
{publicKey, popPoint, popSig} (camelCase) |
Now byte-aligned with DVT #214's consumer ({popPoint, popSig} = await res.json()) and SDK #309's DvtPop. A key-less node's node_state pubkey came from KMS gen-key, so KMS derives popPoint over the same sealed key it returns → e(pubkey, popPoint) == e(g1, popSig) verifies on-chain.
Part 1 (de-root — closes the /dev/tee* gap from #168) remains good. No findings.
Re-review; CC-37 /pop contract re-verified against KMS TA + DVT #214 + SDK #309.
Two CC-24/CC-25/CC-37 follow-ups. Note: the
/popendpoint was reworked mid-PR — see the "/pop convention" section; earlier commits (and any review of them) describe a superseded operator-based design.1. Reproducible device-auth de-root (self-init)
Encodes the A-board's hand-hardening into
aastar-kms-selfinit.shso B-board/community nodes de-root automatically: dedicated non-rootkms(intee/teepriv) +dvt(removed from tee groups +DevicePolicy=closed) users, deploy migrated off/root, both drop-ins, TA-backed health gate (a real BLS sign, not/healthliveness) + auto-rollback. HandoffKEEPER_SIGNER_URLfixed to the loopback base +KEEPER_IDadded.2. BLS proof-of-possession
/pop— SDK canonical convention (CC-37)For the staked validator
registerWithProof. The authoritative PoP is SDK corebuildDvtPop(golden vector + live on-chain tx). Final contract, aligned byte-for-byte with SDK and DVT register-node.mjs #214:POST /pop { node_id }(extra fields likepublicKeyignored;X-Signer-Token= shared BLS signer token) →{ publicKey, popPoint, popSig }(camelCase — the exact keys DVT/SDK parse).bls::sign_popsigns the node's OWN 128B EIP-2537 pubkey underBLS_DST(BLS_SIG_…_POP_). RFC self-PoP./popcan't be used to forge a signature on a chosen message; and a 128B pubkey can't collide with a 32B co-sign userOpHash, so a PoP can't be replayed as a co-sign sig despite the shared DST.BlsPopSign=34,encode_g1_eip2537+hash_to_g2_eip2537(blst, same RFC 9380 SSWU as @noble)./pop convention (supersedes earlier commits)
The first cut followed DVT register-node.mjs's then-current operator-based scheme (
{operator}underAASTAR_DVT_POP_, response{pop_signature}). After cross-checking the on-chain-verified SDK golden, it was reworked (ee62aeb) to the pubkey-self-PoP above, and the response keys fixed to camelCase (c61afd8). DVT #214 independently aligned register-node.mjs to the same pubkey/BLS_SIG_…_POP_convention.Verification
blstsign_pop(0x…c0ffee)== SDKbuildDvtPop→publicKey+popPoint+popSigbyte-identical. Locked asbls.rs #[test] sign_pop_matches_sdk_builddvtpop.aarch64-unknown-optee+ CAaarch64-linux-gnugreen.3beba03): request/token/DST/message/response keys all align.Activation (coordinated — not in this PR)
/popneeds a TA reflash on the A-board (TA changed). Then SDKpopSigner/DVT register-node.mjs →registerWithProoffor a KMS-TEE key-less node (that tx = cross-repo acceptance).https://claude.ai/code/session_015cWRdv3oPjoQ21PEjwo9m5