Skip to content

feat(nitro): automated enclave key self-registration (register CLI + --auto-register) - #938

Merged
piohei merged 13 commits into
mainfrom
feat/nitro-worker-self-register
Aug 3, 2026
Merged

feat(nitro): automated enclave key self-registration (register CLI + --auto-register)#938
piohei merged 13 commits into
mainfrom
feat/nitro-worker-self-register

Conversation

@agentotto

@agentotto agentotto Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

What

Automates on-chain registration of the nitro enclave's generated secp256k1 signing key, so operators no longer have to run the manual cast sequence described in the deploy runbook. Follow-up to the self-registration gap noted in #937.

Three entry points, one shared implementation:

  1. world-chain-prover-nitro register — one-shot CLI (dev/local).
  2. nitro-worker register — same flow on the long-running worker binary (this is what runs in the alphanet pod, so it's what just execs).
  3. nitro-worker run --auto-register (AUTO_REGISTER=true) — startup hook: the worker registers its key before it starts leasing jobs.

Plus a just proof-register-key <env> recipe (Phase 4) that execs nitro-worker register in the running worker pod, resolving the registry address from the deployment file.

How the flow works

register::register_enclave_key (Linux + enclave feature):

  1. Fetches a public_key-embedding attestation from the enclave over vsock (NitroProver::get_public_key_async — the PublicKey request, not the bare get-attestation).
  2. Builds the registerKey(attestationTbs, signature, attestationSigHints) calldata via the new pure register::build_registration_calldata, which reuses:
    • cose::decode_attestation_tbs — reconstructs the exact COSE_Sign1 TBS bytes + P-384 signature (matches on-chain NitroValidator.decodeAttestationTbs),
    • attestation::leaf_cert_pubkey_xy — extracts the leaf cert P-384 key,
    • p384_hints::collect_hints — the existing hint generator (called directly, no shelling out).
  3. Submits registerKey to NitroEnclaveKeyRegistry with an alloy provider + PrivateKeySigner, waits for the receipt, and confirms isKeyRegistered.
  4. Idempotent: pre-checks isKeyRegistered and also treats a KeyAlreadyRegistered revert as success.

registerKey is not owner-gated, so any funded key works (authorization is the attestation + the owner-approved PCR allowlist) — the CLIs take REGISTER_PRIVATE_KEY and fall back to PRIVATE_KEY.

Code changes

  • proofs/nitro/src/cose.rs (new) — pure COSE_Sign1 TBS/signature decoder + unit tests.
  • proofs/nitro/src/register.rs (new) — pure calldata builder + gated async self-registration flow (alloy #[sol(rpc)] binding for the registry) + unit test.
  • proofs/nitro/src/attestation.rs — add leaf_cert_pubkey_xy.
  • proofs/nitro/src/bin/p384_hints.rs — reuse the shared cose decoder (removes a duplicate CBOR parser).
  • proofs/nitro/Cargo.toml — alloy provider/contract/signer/url as optional deps tied to the enclave feature.
  • proofs/prover-nitroregister subcommand.
  • proofs/nitro/workerregister subcommand + run --auto-register startup hook.
  • Justfileproof-register-key recipe (Phase 4).
  • docs/proof/proof-cli.md — document the new subcommand + wrappers.

Testing

Validated locally (Linux, enclave feature):

  • cargo check -p world-chain-proof-nitro --features enclave — clean.
  • cargo test -p world-chain-proof-nitro --features enclave --lib — 24 passed, including the new cose::* and register::* tests.
  • cargo check -p world-chain-prover-nitro -p world-chain-nitro-worker — clean (both binary crates).

No live enclave / L1 was available in the dev environment, so the end-to-end vsock→registerKey→confirm path (register_enclave_key) is exercised only via the pure calldata unit test, not against real hardware/chain. The alloy provider/contract/signer usage mirrors the existing defender/proposer binaries.

Follow-ups

Co-authored-by: Otto otto@toolsforhumanity.com


Note

Medium Risk
Submits real L1 registerKey transactions and handles funding keys in deployment scripts; mistakes could leave workers unable to prove or leak keys if misconfigured, though authorization remains on-chain via attestation and PCR allowlist.

Overview
Adds automated on-chain registration of the Nitro enclave’s generated secp256k1 signing key, replacing manual cast steps so NitroProofVerifier will accept that enclave’s proofs.

A shared register_enclave_key flow fetches a public-key attestation over vsock, builds registerKey(attestationTbs, signature, attestationSigHints) via new cose::decode_attestation_tbs, leaf_cert_pubkey_xy, and p384_hints, then submits to NitroEnclaveKeyRegistry with retries and idempotent isSignerRegistered checks. Entry points: world-chain-prover-nitro register, nitro-worker register, and nitro-worker run --auto-register.

just proof-register-key (Phase 4) execs registration in the worker pod (secrets via stdin); proof-setup now stops after phases 0a–3b and points operators at Phase 4 or auto-register. Docs updated for the new CLI.

Reviewed by Cursor Bugbot for commit 85bf6ef. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread proofs/nitro/src/register.rs
Comment thread Justfile Outdated
Comment thread proofs/nitro/worker/src/cmd/run.rs Outdated
// Optionally self-register the enclave's generated signing key on-chain before leasing
// any jobs. Without a registered key the proofs this worker submits would not verify.
if args.auto_register {
use world_chain_proof_nitro::register::{

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.

Why it is conditionally included here? What about moving it to top?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed — moved to the module-level use at the top of run.rs (with the other world_chain_* imports) in 7d10cda. It was a leftover local import; there's no reason to gate it since the whole file is already #![cfg(target_os = "linux")].

Comment thread proofs/nitro/worker/src/cmd/run.rs Outdated
agentotto Bot pushed a commit that referenced this pull request Jul 31, 2026
- register.rs: declare KeyAlreadyRegistered/KeyRevokedPermanently/InvalidPublicKey
  on the sol! interface so alloy can decode the reverts; make the idempotency
  race-check decode-independent by re-querying isKeyRegistered instead of matching
  the error string. Validate non-empty l1_rpc/registry/key up front.
- worker run.rs: move the register import to module top (no conditional import).
- Justfile proof-register-key: pass secrets (incl. funding key) over stdin instead
  of argv, with proper single-quote escaping, so keys don't leak into container
  argv / kubectl audit logs and metacharacters can't break out.

Co-authored-by: Otto <otto@toolsforhumanity.com>
Comment thread proofs/nitro/src/register.rs
@agentotto

agentotto Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

Addressed all review comments in 7d10cda (replied inline on each thread):

  • KeyAlreadyRegistered detection (Bugbot): declared the custom errors on the sol! interface, and made the idempotency race-check decode-independent by re-querying isKeyRegistered instead of string-matching the revert.
  • Private key in pod exec (Bugbot): proof-register-key now pipes the export …; exec nitro-worker register script to kubectl exec -i … -- sh -s over stdin, with each value single-quote-escaped — no secrets in argv/audit logs, no metacharacter break-out.
  • Conditional import (@piohei): moved the register import to the module top of run.rs.
  • Empty-string validation (@piohei): added non_empty checks for the L1 RPC URL, registry address, and key in the shared register_enclave_key, so all three entry points get precise errors.

Verified: cargo check -p world-chain-proof-nitro --features enclave is clean, and the Justfile parses (just --summary).

Resolves E0283/E0284 (ambiguous PartialEq for .parse()) in the new
enclave_signer_address unit test; fmt.

Co-authored-by: Otto <otto@toolsforhumanity.com>
Comment thread Justfile
Comment thread Justfile
Comment thread proofs/prover-nitro/src/main.rs
- register.rs: wrap registerKey submit in a bounded retry loop (5 attempts,
  backoff) so multi-replica --auto-register survives funding-account nonce
  races and transient RPC errors; each attempt re-checks isSignerRegistered.
- worker run.rs: drop --register-l1-rpc / REGISTER_L1_RPC_URL; registration
  reuses --l1-rpc / L1_RPC_URL.
- prover-nitro: hex_to_pcr now strips an optional 0x prefix (parity with the
  worker's build_expected_pcrs).
- Justfile: proof-register-key gains a Running-container guard before exec'ing
  and piping the funding key; clarify that proof-setup runs phases 0a-3b only
  and Phase 4 (register) is separate / handled by --auto-register.

Co-authored-by: Otto <otto@toolsforhumanity.com>
Comment thread proofs/nitro/src/register.rs
Comment thread Justfile
…egistry

- register.rs: only retry when the tx never mined (send/receipt error, e.g. a
  funding-account nonce race). A mined revert (unapproved PCR set, revoked
  signer, cold CertManager, bad hints) is deterministic, so bail immediately
  instead of resubmitting and burning gas.
- Justfile: resolve the registry address with jq '// empty' so a missing key
  yields "" (caught by the required-var check) instead of the literal "null".

Co-authored-by: Otto <otto@toolsforhumanity.com>
Comment thread proofs/nitro/src/register.rs Outdated
A transient RPC failure on the post-receipt registration check was being
unwrap_or(false)'d and turned into a permanent bail, aborting --auto-register
even when the key was already on-chain. Now the check's Result is matched
explicitly: Ok(true) => success, Ok(false) => deterministic fail-fast, Err =>
retry. The pre-check is likewise tolerant of RPC errors (proceed instead of
aborting).

Co-authored-by: Otto <otto@toolsforhumanity.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a3125d8. Configure here.

Comment thread proofs/nitro/src/register.rs
Classify registerKey send errors: a revert surfaced at estimation carries
revert data (as_revert_data) and is deterministic (unapproved PCRs, revoked
signer, cold CertManager, bad hints) -> fail fast; a transport/nonce error has
no revert data -> retry. Makes the send-error path consistent with the already
fail-fast mined-revert path.

Co-authored-by: Otto <otto@toolsforhumanity.com>
@piohei
piohei merged commit 75e4099 into main Aug 3, 2026
15 checks passed
@piohei
piohei deleted the feat/nitro-worker-self-register branch August 3, 2026 11:30
agentotto Bot pushed a commit that referenced this pull request Aug 4, 2026
The 'known gap' section is now implemented (PR #938); replace it with an
'Automated self-registration' section (register CLI / --auto-register /
just proof-register-key) and a 'Kubernetes deployment (alphanet auto-register)'
section that captures the rationale trimmed out of the crypto-apps values:
production-mode enclave, probe-based keep-alive (jq + trap + sleep infinity),
the funding-key provisioning chain (infra Terraform -> kube-ops -> application
secret -> /etc/secrets), and PCR verification.

Co-authored-by: Otto <otto@toolsforhumanity.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants