SELVAGE

Signed receipts
← back to the demo

Every decision, signed — check it without trusting us.

Each enforced decision produces a signed receipt: the outcome bound to the exact cart the user approved and the transaction that was submitted, signed with Ed25519. Anyone can check a receipt against the published public key — there’s no access to request and no server to trust. In the demo, every result card lets you verify one, tamper it, and download it.

What a receipt binds

The signed payload is the decision plus the exact terms it was made over — so a receipt can’t be lifted onto a different transaction:

{
  "spec": "selvage.receipt.v1",
  "decision": "committed_terms_differ_from_approved",
  "binding_ok": false,
  "approved":  { "lines": [ { "item_id": "thread", "quantity": 2 }, … ], "total": "$34.00" },
  "submitted": { "lines": [ …, { "item_id": "quilting_machine", "quantity": 1 } ], "total": "$374.00" },
  "provider_surface": "warpandweft.shop", "tool": "submit_order", "action": "checkout",
  "ts": "…"
}
// + algo "Ed25519", key_id, and a base64 signature over the canonical JSON of `payload`

Change any field in payload and the signature no longer matches — that’s the whole point.

Verify it yourself

published public key · base64 raw Ed25519 →cnV2iAyn6AZsJZ11MUfHY1RZFGJlpVDAZMqej6MRjmU=
key_id →selvage-demo-ed25519· canonicalization →sorted-key JSON of payload, UTF-8

Download a receipt from any result card, then verify it offline — Node ≥ 20, no dependencies:

import { readFileSync } from 'node:fs';
const r = JSON.parse(readFileSync('selvage-receipt.json', 'utf8'));
const PUB = 'cnV2iAyn6AZsJZ11MUfHY1RZFGJlpVDAZMqej6MRjmU=';
const canon = v => v === null || typeof v !== 'object' ? JSON.stringify(v)
  : Array.isArray(v) ? '[' + v.map(canon).join(',') + ']'
  : '{' + Object.keys(v).sort().map(k => JSON.stringify(k) + ':' + canon(v[k])).join(',') + '}';
const key = await crypto.subtle.importKey('raw', Buffer.from(PUB, 'base64'), { name: 'Ed25519' }, false, ['verify']);
const ok = await crypto.subtle.verify({ name: 'Ed25519' }, key,
  Buffer.from(r.signature, 'base64'), new TextEncoder().encode(canon(r.payload)));
console.log(ok ? 'VALID' : 'INVALID');   // change any value in r.payload → INVALID

What signing proves — and doesn’t

On secrecy: the demo signs client-side, so its private key ships in the page — here signing shows the mechanism. In production the kernel signs server-side with a protected key, which is what lets a hidden-kernel decision still be checked against this same published key.

Ed25519 · verifiable with the published key the demo → · how to verify → · a ziola project