Sign receipts with Ed25519 by default - #8
Open
rishvaiyer wants to merge 4 commits into
Open
Conversation
Flip ED25519_ENABLED_BY_DEFAULT to true so the shipped default is asymmetric signing: the service holds the private key, the public key is published at /api/signing-key, and anyone can verify a receipt offline with the public key alone. HMAC is symmetric, so every verifier is also a forger and no outside reviewer can verify without the secret. It is retained only to verify receipts issued before this default flipped. ed25519Enabled() now lets an explicit CONTEXTSEAL_ED25519 value (1/0, true/false) override the compiled default in either direction, so a deployment can force the scheme on or off without a code edit. Production requires a stable CONTEXTSEAL_SIGNING_KEY: server.mjs already runs the signer with allowEphemeral false outside demo mode, so it now refuses to boot without a key rather than silently minting an ephemeral key that stops verifying after a restart. Demo/dev still allows an ephemeral key and reports that condition on /health and /api/signing-key. Tests updated to assert the new default and to exercise the legacy HMAC path via an explicit enabled:false. Full suite 174/174, lint clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add CONTRIBUTING.md establishing that any user-visible feature change must update the README in the same PR, with a checklist and a reviewer-blocking rule. Apply it here: the README still described HMAC as the default and Ed25519 as off-by-default optional, which the previous commit changed. Updated the signing summary, environment config (CONTEXTSEAL_SIGNING_KEY required outside demo mode), the verify-a-receipt run steps, and the repository map. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
What
Makes asymmetric receipt signing the shipped default. Flips
ED25519_ENABLED_BY_DEFAULTtotrueinsrc/signing.mjs.Why
HMAC-SHA256 is a symmetric MAC: verifying a receipt requires the same secret used to produce it, so every verifier is also a forger, and an outside reviewer cannot verify anything without being handed the signing key. That is the wrong property for a tamper-evident audit record.
Ed25519 splits those roles. The service holds the private key and signs; the public key is published at
GET /api/signing-key; anyone verifies a receipt offline with the public key alone (scripts/verify-receipt.mjs, dependency-free). A stranger can check the evidence without receiving anything that would let them forge it. A verify failure also distinguishes "signed by a different key than advertised" (wrong keyId) from "payload altered after signing" (tampered).Legacy HMAC verification is retained only so receipts issued before this flip still verify. Nothing signs with HMAC by default anymore.
Changes
src/signing.mjs:ED25519_ENABLED_BY_DEFAULT = true; doc comment rewritten to match.ed25519Enabled()now lets an explicitCONTEXTSEAL_ED25519value (1/0,true/false) override the compiled default in either direction, so a deployment can force the scheme on or off without a code edit.test/signing.test.mjs: assert the new default; exercise the legacy HMAC path via an explicitenabled: false.Verification
node --test→ 174/174 passnpm run lint→ cleaned25519,ephemeralKey: true,/api/signing-key200CONTEXTSEAL_SIGNING_KEY→ refuses to boot (CONTEXTSEAL_SIGNING_KEY is required)ephemeralKey: false, stable keyIdDeploy requirement (blocking)
Before deploying, set a stable
CONTEXTSEAL_SIGNING_KEY(PEM, or a 32-byte seed as hex/base64) in the service env. Outside demo mode the server now hard-fails boot without one, by design, rather than minting an ephemeral key that stops verifying after the next restart. Existing HMAC receipts in the ledger remain verifiable; new receipts sign under a fresh Ed25519 keyId.🤖 Generated with Claude Code