Skip to content

Sign receipts with Ed25519 by default - #8

Open
rishvaiyer wants to merge 4 commits into
mainfrom
agent/contextseal-ed25519-default
Open

Sign receipts with Ed25519 by default#8
rishvaiyer wants to merge 4 commits into
mainfrom
agent/contextseal-ed25519-default

Conversation

@rishvaiyer

Copy link
Copy Markdown
Owner

What

Makes asymmetric receipt signing the shipped default. Flips ED25519_ENABLED_BY_DEFAULT to true in src/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 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.
  • test/signing.test.mjs: assert the new default; exercise the legacy HMAC path via an explicit enabled: false.

Verification

  • node --test174/174 pass
  • npm run lint → clean
  • Demo boot, no key → boots, ed25519, ephemeralKey: true, /api/signing-key 200
  • Prod boot, no CONTEXTSEAL_SIGNING_KEYrefuses to boot (CONTEXTSEAL_SIGNING_KEY is required)
  • Prod boot, stable key → boots, ephemeralKey: false, stable keyId

Deploy 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

unevil-warden-scallion-princess-no-rollback and others added 4 commits August 17, 2026 16:32
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>
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