Skip to content

feat(kms): self-init device-auth de-root (reproducible) + BLS PoP /pop for staked registration#172

Merged
jhfnetboy merged 4 commits into
mainfrom
feat/kms-deroot-selfinit-and-pop
Jul 10, 2026
Merged

feat(kms): self-init device-auth de-root (reproducible) + BLS PoP /pop for staked registration#172
jhfnetboy merged 4 commits into
mainfrom
feat/kms-deroot-selfinit-and-pop

Conversation

@jhfnetboy

@jhfnetboy jhfnetboy commented Jul 10, 2026

Copy link
Copy Markdown
Member

Two CC-24/CC-25/CC-37 follow-ups. Note: the /pop endpoint 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.sh so B-board/community nodes de-root automatically: dedicated non-root kms (in tee/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 /health liveness) + auto-rollback. Handoff KEEPER_SIGNER_URL fixed to the loopback base + KEEPER_ID added.

2. BLS proof-of-possession /pop — SDK canonical convention (CC-37)

For the staked validator registerWithProof. The authoritative PoP is SDK core buildDvtPop (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 like publicKey ignored; X-Signer-Token = shared BLS signer token) { publicKey, popPoint, popSig } (camelCase — the exact keys DVT/SDK parse).
  • TA bls::sign_pop signs the node's OWN 128B EIP-2537 pubkey under BLS_DST (BLS_SIG_…_POP_). RFC self-PoP.
  • Anti-oracle: the message is the node's own pubkey (caller supplies none), so /pop can'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.
  • proto 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} under AASTAR_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

  • GOLDEN: standalone blst sign_pop(0x…c0ffee) == SDK buildDvtPoppublicKey + popPoint + popSig byte-identical. Locked as bls.rs #[test] sign_pop_matches_sdk_builddvtpop.
  • proto 43 tests + TA aarch64-unknown-optee + CA aarch64-linux-gnu green.
  • Contract cross-checked against DVT #214 (3beba03): request/token/DST/message/response keys all align.

Activation (coordinated — not in this PR)

/pop needs a TA reflash on the A-board (TA changed). Then SDK popSigner/DVT register-node.mjs → registerWithProof for a KMS-TEE key-less node (that tx = cross-repo acceptance).

https://claude.ai/code/session_015cWRdv3oPjoQ21PEjwo9m5

…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.
@github-actions

Copy link
Copy Markdown

Security Audit Report

Date: Fri Jul 10 17:30:21 UTC 2026
Commit: 6985131

Cargo Audit Results

�[1m�[33mwarning�[0m: profiles for the non root package will be ignored, specify profiles at the workspace root:
package:   /home/runner/work/AirAccount/AirAccount/kms/host/Cargo.toml
workspace: /home/runner/work/AirAccount/AirAccount/Cargo.toml
�[1m�[92m    Updating�[0m crates.io index
�[1m�[92m     Locking�[0m 0 packages to latest compatible versions
�[1m�[92mnote�[0m: pass `--verbose` to see 43 unchanged dependencies behind latest
�[0m�[0m�[1m�[32m    Fetching�[0m advisory database from `https://github.com/RustSec/advisory-db.git`
�[0m�[0m�[1m�[32m      Loaded�[0m 1159 security advisories (from /home/runner/.cargo/advisory-db)
�[0m�[0m�[1m�[32m    Updating�[0m crates.io index
�[0m�[0m�[1m�[31merror:�[0m not found: Couldn't load Cargo.lock
Caused by:
  -> I/O operation failed: I/O operation failed: entity not found
  -> I/O operation failed: entity n

... [查看完整报告](artifacts)

… + 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 clestons left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#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 undefinedregisterWithProof(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.)
@jhfnetboy

Copy link
Copy Markdown
Member Author

Thanks — the cross-repo contract catch was worth it. Verdict after checking both current heads:

Your DST / message / input points were against the pre-ee62aeb operator version (the PR body at the time still described it). Commit ee62aeb had already reworked /pop to the SDK canonical pubkey-self-PoP: signs the node's own 128B EIP-2537 pubkey under BLS_SIG_…_POP_ (not operator/AASTAR_DVT_POP_), request is { node_id } (no operator), golden-verified byte-identical to SDK buildDvtPop.

But you were right about the real residual: response field naming. ee62aeb still emitted snake_case { public_key, pop_point, pop_signature }, while DVT #214 register-node.mjs reads { popPoint, popSig } and SDK's DvtPop is { publicKey, popPoint, popSig }. That would have broken the integration. Fixed in c61afd8 (serde rename to camelCase).

Full contract now cross-checked against DVT #214 (3beba03): request {node_id,publicKey}→my {node_id?} (extra field ignored) ✅ · X-Signer-Token shared token ✅ · DST BLS_SIG_…_POP_ ✅ · message hashToCurve(publicKey) ✅ · response {publicKey,popPoint,popSig} ✅. And the anti-oracle design (KMS signs bound pubkey, returns popPoint too) is preserved.

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 /pop call — that's the acceptance evidence to come. de-root part unchanged. Please re-review.

@github-actions

Copy link
Copy Markdown

Security Audit Report

Date: Fri Jul 10 18:02:29 UTC 2026
Commit: 6464d55

Cargo Audit Results

�[1m�[33mwarning�[0m: profiles for the non root package will be ignored, specify profiles at the workspace root:
package:   /home/runner/work/AirAccount/AirAccount/kms/host/Cargo.toml
workspace: /home/runner/work/AirAccount/AirAccount/Cargo.toml
�[1m�[92m     Locking�[0m 0 packages to latest compatible versions
�[1m�[92mnote�[0m: pass `--verbose` to see 43 unchanged dependencies behind latest
�[0m�[0m�[1m�[32m    Fetching�[0m advisory database from `https://github.com/RustSec/advisory-db.git`
�[0m�[0m�[1m�[32m      Loaded�[0m 1159 security advisories (from /home/runner/.cargo/advisory-db)
�[0m�[0m�[1m�[32m    Updating�[0m crates.io index
�[0m�[0m�[1m�[31merror:�[0m not found: Couldn't load Cargo.lock
Caused by:
  -> I/O operation failed: I/O operation failed: entity not found
  -> I/O operation failed: entity not found

Cargo Deny Results

�[

... [查看完整报告](artifacts)

@clestons clestons left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#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.

@jhfnetboy jhfnetboy merged commit 1c1bdc1 into main Jul 10, 2026
6 of 7 checks passed
@jhfnetboy jhfnetboy deleted the feat/kms-deroot-selfinit-and-pop branch July 10, 2026 18:07
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 10, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants