feat: persist + expose proven kernel signing address at registration (evidence #52) - #230
Draft
LamaSu wants to merge 4 commits into
Draft
feat: persist + expose proven kernel signing address at registration (evidence #52)#230LamaSu wants to merge 4 commits into
LamaSu wants to merge 4 commits into
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
This was referenced Jul 10, 2026
Owner
Author
|
Superseded by feat/optionc-machinelog (Option C tagged Ed25519-default keys, bulletin #306) — do not merge standalone. |
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.
Why
The oracle authenticates kernel-signed machine logs (evidence primitive #52,
machine.execution_logsettlement) against a kernel's real secp256k1 signing address. Today registration threw that address away and stored a random value (kernel.facade.ts:313 publicKey: 0x${randomBytes(32)}), so the oracle had nothing to verify against — the root blocker of the machine-log money path.What
Persist the kernel's REAL signing address at registration, with proof-of-possession, and expose it on the kernel DTO.
@pcc/store): new nullablesigning_addresscolumn onshop_kernels(drizzle schema + idempotentsafeAddColumnfor existing DBs).$inferInsertauto-derives — no repo change.@pcc/gateway)register(): recovers the signer fromsigningProof(viemrecoverMessageAddressover a kernelId-bound message) and persistssigningAddressonly if it matches the claimed address. Absent / mismatched / malformed / replayed proof →null. Fail closed — never persist an unproven signer (a wrong one could clear settlement and release escrow).KernelDTO.signingAddress: string | null(checksummed when proven, else null) via the populator → flows toGET /api/kernels/:id(oracle fetch) andGET /api/kernels.@pcc/kernel):KernelKeychain.buildRegistrationProof(kernelId)returns{ signingAddress, signingProof };kernelSigningProofMessage()is the single source of truth for the message format, imported by the gateway verifier so signer and verifier never drift.Pinned contract (honored)
GET /api/kernels/:kernelId→KernelDTOincludessigningAddress: string | null.Security invariant
pcc-kernel-signing-key:${kernelId}→ a signature captured for one kernel cannot register another kernel's address.null.Tests (all watched pass)
kernel-signing-address.test.ts— 7/7: valid → persisted + returned by/:idand list; mismatch / absent / garbage / cross-kernel replay → null; no-fields → null; set-once blocks overwrite. Proofs from a real viem test account.kernel-registration-proof.test.ts— 4/4: round-trip recover, kernelId binding, per-keychain distinctness.@pcc/storestore+device-registry 61/61.@pcc/store+@pcc/kernelbuild clean.Follow-ups (out of scope)
buildRegistrationProofinto the live log-signing process'sPOST /api/kernelscall (no existing TS daemon self-registers; pcc-node is Python/Ed25519, not the secp256k1 kernel key).🤖 Generated with Claude Code