feat(spec,gateway): Option C — algorithm-tagged registered signing keys (Ed25519-default) - #235
Merged
Conversation
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
enabled auto-merge
July 10, 2026 17:49
This was referenced Jul 10, 2026
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reshapes #230 off secp256k1-only to algorithm-tagged registered keys, Ed25519-default (fleet-native). Gateway
verifySigningProofbranches ed25519/secp256k1, persists taggedsigningKey, serves on KernelDTO, declaws the randomBytes publicKey. Supersedes #230. 40/40. Design: ai/research/pcc-machinelog-keymodel.md.