Skip to content

feat(spec,gateway): Option C — algorithm-tagged registered signing keys (Ed25519-default) - #235

Merged
LamaSu merged 7 commits into
masterfrom
feat/optionc-machinelog
Jul 10, 2026
Merged

feat(spec,gateway): Option C — algorithm-tagged registered signing keys (Ed25519-default)#235
LamaSu merged 7 commits into
masterfrom
feat/optionc-machinelog

Conversation

@LamaSu

@LamaSu LamaSu commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Reshapes #230 off secp256k1-only to algorithm-tagged registered keys, Ed25519-default (fleet-native). Gateway verifySigningProof branches ed25519/secp256k1, persists tagged signingKey, serves on KernelDTO, declaws the randomBytes publicKey. Supersedes #230. 40/40. Design: ai/research/pcc-machinelog-keymodel.md.

LamaSu and others added 7 commits July 9, 2026 20:45
Kernel's real secp256k1 signing address, proven at registration via
EIP-191 proof-of-possession. Same address that signs machine-log
entries, so the oracle can authenticate kernel-signed logs for
settlement (evidence primitive #52). Nullable/fail-closed; added to
the drizzle schema and via idempotent safeAddColumn for existing DBs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMoShvxX49Xyt4Mi5evDnB
KernelKeychain.buildRegistrationProof(kernelId) returns the
{ signingAddress, signingProof } pair a kernel sends at registration:
an EIP-191 signature by the kernel signing key over the kernelId-bound
message. kernelSigningProofMessage() is the single source of truth for
the message format, imported by the gateway verifier so signer and
verifier never drift. Binding to kernelId blocks cross-kernel replay.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMoShvxX49Xyt4Mi5evDnB
…ster

register() now recovers the signer from signingProof (viem
recoverMessageAddress over the kernelId-bound message) and persists
signingAddress ONLY when it matches the claimed address; absent /
mismatched / malformed / replayed proof -> null (fail closed). Update
path is set-once: a generic upsert never overwrites an already-proven
signer. KernelDTO exposes signingAddress: string | null via the
populator, flowing to GET /api/kernels/:id (oracle fetch) and
GET /api/kernels. 7/7 proof-of-possession tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMoShvxX49Xyt4Mi5evDnB
Assert the proof emits the real signing address, recovers to it under
the bound message (gateway acceptance path), fails to recover under a
different kernelId (replay binding), and differs per keychain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMoShvxX49Xyt4Mi5evDnB
…lizer (Option C)

Adds the tagged RegisteredSigner union ({algorithm:ed25519,publicKey} |
{algorithm:secp256k1,address}) and normalizeRegisteredSigner — the canonical
fail-closed parser the gateway registry serves on KernelDTO.signingKey and the
oracle #52 verifier matches log kernelSignatures against. EIP-55 checksums via
@noble/hashes keccak (existing spec dep, no viem). Exported through
evidence/verifiers. 14 unit tests incl. a canonical EIP-55 vector.

implementer-m1-spec-gateway
…ption C)

Additive columns signing_key_algorithm + signing_key_public_key alongside the
existing signing_address, via safeAddColumn (idempotent for old DBs). Lets the
kernel registry prove + persist BOTH signing families: secp256k1 keeps its EVM
address in signing_address; ed25519 stores the raw 32-byte pubkey ("0x"+64hex)
in signing_key_public_key. signing_key_algorithm is the tag the oracle #52
verifier branches on. Same fail-closed, set-once semantics as signing_address.

implementer-m1b-gateway-finish

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMoShvxX49Xyt4Mi5evDnB
…Option C)

Generalizes #230's single secp256k1 proof-of-possession path to the tagged
RegisteredSigner model. KernelFacade.verifySigningProof now branches on
signingKeyAlgorithm: secp256k1 (default, unchanged — recoverMessageAddress)
or ed25519 (verifyEd25519Signature over the same kernelId-bound challenge),
and signerToColumns maps the proven signer to the three persisted columns.
The populator builds KernelDTO.signingKey (ed25519 pubkey | secp256k1 address
| null) while keeping signingAddress as the secp256k1 compat alias the merged
oracle #13 reader consumes. Set-once guard preserved across both families.

Adds 11 ed25519 proof-of-possession tests mirroring the #230 secp256k1 suite:
valid -> served as signingKey:{algorithm:ed25519,publicKey}; wrong-key / absent
/ garbage-sig / malformed-pubkey / cross-kernel-replay -> null (fail closed);
set-once holds incl. a cross-family hijack attempt; a secp256k1 kernel is still
served as the tagged secp256k1 signingKey. Gateway build clean; 11 new + 7 #230
secp256k1 tests green.

implementer-m1b-gateway-finish

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMoShvxX49Xyt4Mi5evDnB
@LamaSu
LamaSu enabled auto-merge July 10, 2026 17:49
@LamaSu
LamaSu merged commit 472b2bc into master Jul 10, 2026
5 checks passed
LamaSu added a commit that referenced this pull request Jul 12, 2026
… critical)

Adversarial review (sol / Codex GPT-5.6, reviewed by Opus) of the #235/#247
signing-identity path found 5 findings; this fixes all, preserving the
session-key delegation model.

- F1 (critical): per-job session-key evidence now validates. The SDK transmits
  the principal-signed session-key delegation (sessionKeyAuthorization) in the
  bundle; the gateway verifies it via @pcc/verifier SessionKeyService against the
  REGISTERED principal, enforcing action + contract scope, before checking the
  bundle signature. Previously session-signed bundles always failed the direct
  registered-key check.
- F2 (critical): POST /api/kernels removed from the method-blind public allowlist
  (GET stays public). Binding a signing proof to an existing kernel requires the
  authenticated actor to own it (ForbiddenError otherwise); creates are owned by
  the authenticated actor, not a body-nominated address.
- F3: SET-ONCE signer binding is an atomic CAS (bindSignerIfUnregistered, WHERE
  signer columns IS NULL). Race-loss re-reads and accepts only an idempotent
  same-signer proof, else a loud ConflictError.
- F4: buildEd25519RegistrationProof takes an explicit
  {algorithm, privateKey, expectedPublicKey} and fails closed when the derived
  key != expected — no silent binding of ambiguous/dev 32-byte secrets.
- F5: the irreversible SET-ONCE bind is the final network step and fails loud
  when the gateway reports a different/absent signer.

Tests added/updated: registration-auth (hijack), delegation round-trip,
CAS race, ephemeral-key rejection, fail-loud ordering.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016MqxjtNMrzb7M23dChjizW
LamaSu added a commit that referenced this pull request Jul 13, 2026
feat(kernel-sdk): register #235 Ed25519 signing proof via POST /api/kernels
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.

1 participant