golden-vector-provenance
v0.10.0
Published
Reproducible provenance for computed answers: a canonical SHA-256 fixed point (RFC 8785 JCS) that anyone can re-derive for free, so you recompute the answer instead of trusting the server.
Maintainers
Readme
Golden-Vector Provenance (GVP)
Every answer ships a hash anyone can re-derive for free — so you don't trust the server, you recompute the math.
GVP is a small, open convention for reproducible provenance on computed answers. An answer travels
with a canonical SHA-256 fixed point over {endpoint, inputs, result, method, dataVintage}. Anyone
holding the answer can recompute it and check the hash. No key exchange, no callback to the issuer, no
trust in us.
Optional layers add an Ed25519 issuer attestation (who issued this, and when) and an on-chain identity anchor. They never assert that the answer is correct — correctness is established only by reproduction.
- Canonicalization is RFC 8785 (JCS) — so implementing GVP means calling a JCS library you already have, not writing serialization code.
- Apache-2.0 (code) / CC-BY-4.0 (spec). Implement it freely; no permission or notification needed.
npm install golden-vector-provenanceimport { gvpHash } from 'golden-vector-provenance';
const responseHash = gvpHash({
endpoint: '/v1/self-employment-tax',
inputs: { netProfit: 80000, filingStatus: 'single' },
result: { selfEmploymentTax: 11303.64 },
method: 'Schedule SE: net earnings = profit x 0.9235; SS 12.4% to wage base; Medicare 2.9%',
dataVintage: '2026.0',
});
// -> "sha256:7236fd58598c29a6d8ebf7721a83201c363f5d88755b3c39242b3a4355982352"
// attach this to your response. That's L1. (The full self-employment-tax
// conformance vector in vectors/expected.json hashes differently — it carries
// more input/result fields; the hash is a fixed point over exactly what you pass.)That is the whole of L1. If you already emit a signed receipt (x402, ACTA, or your own), adding
responseHash to it upgrades "you paid for this URL" into "and here is proof of what came back."
Drop-in middleware (Express / Hono)
If you'd rather not hash by hand, the middleware export attaches responseHash to every JSON
response under the standard extensions["response-provenance"] envelope. You supply
buildFixedPoint, which returns the spec's fixed point {endpoint, inputs, result, method, dataVintage} — all five members required, no extras (spec §2.1).
import express from 'express';
import { expressProvenance } from 'golden-vector-provenance/middleware';
const app = express();
app.use(expressProvenance((req, body) => ({
endpoint: req.path,
inputs: req.query,
result: body.result,
method: body.method,
dataVintage: body.dataVintage,
})));
// now res.json({...}) also carries extensions["response-provenance"].responseHashimport { honoProvenance } from 'golden-vector-provenance/middleware';
app.use(honoProvenance((c, body) => ({ /* same shape */ })));Root-level APIs (no result wrapper): pass { resultCarriage: "body" } as the second argument to
expressProvenance / honoProvenance / attachProvenance and build the fixed point with
fixedPointFromBody(body, { endpoint, inputs, method, dataVintage }). The answer stays at the root,
the block declares resultCarriage: "body", and a verifier recovers result as the body minus
extensions. Same fixed point, same hash as the "member" default (the x402#3304 carriage table).
Provenance never breaks serving: if buildFixedPoint returns falsy or throws, the response is emitted
unchanged (Express records the error on res.locals.gvpError). The emitted block also lists which
fields were hashed (fixedPoint) — but that list is the spec constant, not a self-declared description. All five members are required and the set is closed, so a seller cannot shrink the fixed point (e.g. drop inputs) and stay internally consistent: a verifier always recomputes over the five spec members, and endpoint+inputs being required means the hash binds the question as well as the answer. The block also carries fixedPointVersion: "GVP-FixedPoint/1" — the name of the frozen rule set that defined those five members, emitted beside the hash (never inside it, so no hash changes). On a bare L1 response this field is informational and unauthenticated — nothing signs a bare response, so it can be stripped or forged. It is bound at L2: under signed-payload shape GVP-Attestation/2 it sits inside the Ed25519-signed payload, so stripping or forging it fails the signature (tools/check-l2-binding.mjs proves the four-case table). GVP-FixedPoint/1 will never change — a different member set would be a new identifier. (All of these came out of three rounds of review on x402-foundation/x402#3234.)
What GVP proves — and what it doesn't
| Claim | GVP | How | |---|---|---| | The answer can be independently recomputed from the stated inputs and method | Yes | L1 fixed point + free re-derivation | | The answer has not been altered since issuance | Yes | any change alters the hash | | A specific issuer produced this record at a stated time | Optional | L2 Ed25519 attestation | | That identity is bound to an on-chain handle | Optional | L3 anchor (e.g. ERC-8004) | | The answer is correct | No | a signature proves a server ran, not that it was right — an LLM can sign a confident hallucination. GVP makes the answer re-derivable so a consumer can decide for themselves |
This distinction is the point of the standard, and it is deliberate. GVP is not a fact-checker, an oracle, an endorsement, or a certification.
GVP-FixedPoint/2 — dataVintage is now machine-comparable
/1 typed dataVintage only as <string>. JCS canonicalizes the JSON, not the semantics of a
value, so two honest implementations describing the same data year as "July 2026", "2026-07",
"2026.0" or an epoch int all produced well-formed, conformant, mutually irreproducible hashes.
That was not hypothetical — this repo's own vectors used "2026.0" while its reference service
published "July 2026". One project, two representations, neither wrong under the old text.
Under GVP-FixedPoint/2, dataVintage MUST be an ISO 8601 calendar date at reduced precision —
YYYY, YYYY-MM, or YYYY-MM-DD, matching ^[0-9]{4}(-[0-9]{2}(-[0-9]{2})?)?$, and a real date at
that precision. Nothing else. Precision is significant: "2026", "2026-07" and "2026-07-01" are
three vintages and hash differently. Lexical order equals chronological order, so a verifier can
compare vintages without parsing. Spec §2.1.3; vectors vectors/expected-v2.json; gate
tools/check-fixedpoint-2.mjs (11 vectors + 7 rejection cases, and it re-proves /1 is frozen).
/1 is unchanged and still valid. It is frozen; receipts declaring it verify under its rules. A
migration to /2 re-hashes and declares fixedPointVersion: "GVP-FixedPoint/2" — and because that
identifier sits inside the signed payload at L2, the migration is detectable rather than silent. That
is the whole reason the identifier exists.
Closure: the result MUST be a function of the fixed point
An issuer MUST NOT emit a responseHash for a response whose result depends on anything not in the
fixed point — a clock, a live feed, mutable server state. Such an endpoint is out of scope for GVP.
This exists to make a finding falsifiable — which is not the same as attributable, and the difference matters. A MUST NOT does not make a violation impossible; it makes it a violation. So a failed re-derivation means the artifact was altered or the issuer emitted a hash it was not permitted to emit. Both are defects, and a verifier does not need to decide which — which is the point, because deciding would require seeing the issuer's internals that a third party cannot see. A finding should be stated as that disjunction rather than picking a branch. "The data moved" stops being a defence and becomes an admission of the second branch.
There is a third branch, and it belongs to the verifier: its own canonicalization may be wrong. JCS
is exactly where that happens quietly — number formatting, non-ASCII escaping, surrogate-pair key
ordering — and such a verifier publishes accusations against clean artifacts that look identical to the
other two from outside. That is not a clause in the finding (a finding that hedged "or our arithmetic
may be broken" would be unusable); it is excluded before a finding may be stated. Spec §8.1: a party
MUST NOT state a re-derivation failure unless its own canonicalizer passes the L1 vectors, the
independent-implementation JCS gate, and the vectors of the rule set it is checking. It may report
that it was unable to verify — a claim about itself, not about the issuer, and the two MUST NOT be
conflated. All of it runs offline, so the precondition costs one test run rather than a relationship
with the party being audited. Time-varying data is not excluded; hidden time-varying data is. Lift the rate into
inputs or into the dataVintage axis and it is conformant again. Spec §2.1.2.
Both of the above came from @seancrecord (scvd.store conformance desk) reviewing this spec for a conformance-check implementation, 2026-08-24.
Conformance levels
- L1 — Reproducible: the response carries the canonical hash and a free re-derivation path.
- L2 — Signed: L1 + an Ed25519 issuer attestation with a key-id (old keys stay verifiable).
- L3 — Anchored: L2 + an on-chain identity binding (e.g. ERC-8004).
L1 is the layer that matters. L2 and L3 are conveniences that other standards already provide; if you have a signed-receipt mechanism, keep it and just carry the L1 hash inside it.
Where this sits next to other standards
GVP is designed to be carried by existing receipt formats, not to compete with them. The agent ecosystem has largely converged on Ed25519 + RFC 8785 JCS for signed receipts — which is exactly why GVP uses the same primitives, and why its hash drops into any of them.
| Standard | Binds | Binds the response body? |
|---|---|---|
| x402 Signed Offers & Receipts | resourceUrl, payer, network, issuedAt, txHash | No |
| IETF draft-farley-acta-signed-receipts | decision-maker, tool, policy result, timestamp | No |
| Microsoft agent-governance-toolkit receipts | policy hashes, pre/post-execution signatures | No — attests policy/execution, not answer re-derivation |
| GVP L1 | endpoint, inputs, result, method, dataVintage | Yes, + free re-derivation |
Rail-agnostic by construction. The fixed point binds the question and the answer, and says
nothing about how — or whether — payment settled. x402 receipts are the first carrier, but an AP2
mandate, an ACP order, a card-network token receipt, or a response nobody paid for can carry the same
responseHash member unchanged. If the agent-payments rails consolidate differently than today's
map suggests, the provenance layer doesn't move. That claim is executable, not rhetorical:
examples/plain-http/ is an issuer and a verifier on Node's built-in http
with no payment protocol anywhere in the directory, reproducing a published vector and all four
verification outcomes (npm run check:plain-http, part of npm test).
Two companion notes locate GVP in the wider stack:
docs/RECOMPUTE-NOT-TRUST.md— the five trust models for machine-produced answers (signatures, TEE, staking, zkML, recompute), what each proves, and the honest boundary of the recompute class GVP belongs to.docs/REGULATORY-BRIDGE.md— how GVP artifacts map to the evidence classes named by the EU AI Act, ISO/IEC 42001, NIST AI RMF, and (reportedly) AI insurance underwriting. Not legal advice, and it says so.docs/COMPLIANCE-MEMO-ART50-ART12.md— the one-page version for audit and compliance vendors: what a receipt gives you under EU AI Act Article 50(2) (complements synthetic-content marking; does not satisfy it alone) and Article 12 (the direct fit: a re-derivable record instead of a log line), with the dates and the boundary stated.docs/CARRIAGE-PROFILES.md— where the hash travels: the x402 extension envelope (C1), the originalprovenanceblock x402toll.com still emits (C2), and theresponse_provenanceextension field for signed compliance-receipt profiles (C3). Same bytes under every carriage.audit/README.md—gvp-audit, the closure audit for sellers: point it at the bodies you actually served (or free surfaces) and it reports hidden-input signals, the carriage that fits, format violations, vintage candidates, and whether an existingresponseHashre-derives. It finds violations; it never claims a route is closed.npm i -g golden-vector-provenancethengvp-audit bodies ./served --md report.md.docs/MEASURED-NOT-CLAIMED.md— explainer: what the x402 counter episode (a headline metric that was a claim, not a measurement) means one layer down, at the response, and exactly what a re-derivable hash does and does not prove.
Canonicalization
Canonical JSON in GVP is RFC 8785 (JCS). GVP defines no bespoke serialization; RFC 8785 governs.
Use any verified implementation — canonicalize (npm), erdtman/java-json-canonicalization, or the
cyberphone/json-canonicalization ports (Go, .NET/C#, Python 3), all listed in RFC 8785 Appendix G;
json-canon is a further Go implementation.
GVP adds exactly three restrictions on top: non-finite numbers (NaN, ±Infinity) are rejected, object keys must be unique, and nesting depth should be bounded (the references reject depth > 100).
See spec/gvp-0.2.md §2.2 for the normative text, an informative restatement, and
the evidence below.
Interop is proven, not asserted
Two implementations only interoperate if they (a) serialize byte-for-byte identically before
hashing and (b) produce mutually-verifiable signatures. Number formatting and non-ASCII escaping are
where naive JSON serializers diverge (JS integer 1500 vs Python float 1500.0; ensure_ascii
escaping non-ASCII). This repo proves all of it empirically before relying on the prose:
npm install
npm test # L1 hashes + L2 signatures + the JCS gate
node tools/check-js.mjs # JS: reproduce L1 hashes + verify L2 signatures
node tools/check-jcs.mjs # JS canonicalization vs an INDEPENDENT RFC 8785 impl
py -3 ref/py/gvp.py vectors/expected.json # Python: reproduce every hash byte-for-byte
uv run --with cryptography ref/py/attest.py # Python: verify JS's signatures AND reproduce them
# byte-identically (Ed25519 is deterministic)
node tools/gen-vectors.mjs # regenerate L1 vectors from vectors/canonicalization.jsongen-vectors.mjs can also cross-check the reference against a second implementation you point it at
via GVP_CALC_CORE=/path/to/impl.mjs (must export canonicalJson(value)); unset, it just regenerates
the vectors and runs from a clean clone with no external paths.
Status (verified 2026-08-20):
- L1 + L2 conformance: 8 vectors — integral floats, negatives, non-ASCII UTF-8, control-char
escaping, nested arrays/objects, empty containers, and the ECMAScript number edge cases (
0.00001,1e15,1e16,1e21,5e-324) — pass in both references at L1 and L2 with 0 failures. - JCS equivalence gated:
tools/check-jcs.mjsreports 0 divergences across 39 comparisons againstcanonicalizev2.1.0 (an independent implementation listed in RFC 8785 Appendix G) — the 39 = 8 L1 canonicalizations + 8 L1 hash re-derivations + 8 L2 attestation payloads + 15 adversarial probes covering key ordering (ASCII, mixed case, non-ASCII keys, shared prefixes), number edges,-0, safe-integer bounds, raw UTF-8, emoji/surrogate pairs, control characters, unescaped solidus, empty containers and nested arrays. The JS↔Python byte agreement carries the result to Python. - Both references implement the full ECMAScript
Number→Stringalgorithm, so they agree on any finite double, not just typical values. - The JS reference's canonical bytes match those of the deployed service at
x402toll.com (verified by the maintainer against that codebase; see the
GVP_CALC_COREcross-check above), so hashes issued by that service re-derive under this reference.
Independent third-party cross-check
Everything above is run by this repo. The strongest evidence is the check somebody else ran, with their own code, against vectors they fetched rather than ones we handed them:
whawk46/x402-jcs-crosscheck — npm install && npm test
reproduces the full table (reported 2026-08-23, in the review of
x402-foundation/x402#3234):
L1 vectors/expected.json 8/8 hashes match
L2 /1 attestation.json 8/8 canonical bytes identical · 8/8 signatures verify
L2 /2 attestation-v2.json 8/8 canonical bytes identical · 8/8 signatures verify
-> 24/24Plus the negative controls, each of which MUST fail — and does:
fixedPointVersion stripped -> fails correctly
forged to GVP-FixedPoint/2 -> fails correctly
downgraded to the /1 shape -> fails correctly
CONTROL: issuer tampered -> fails correctlyTwo properties make it evidence rather than a mirror, both their choices, not ours:
- Vectors are pinned, not vendored — fetched at commit
6111a5f9and checked against recorded sha256 digests, so it exercises our published vectors, not a copy that could drift. - Their canonicalizer is a dependency, not a copy — a reader exercises the implementation that actually ships, and signatures are verified over their bytes, not ours.
What it does not cover, stated in their README and repeated here rather than buried: this is evidence about canonicalization, not about their signing path, which refuses fractional numbers by design — the region our vectors live in. Note the distinction this repo keeps: an independent verification is not the same claim as adoption. (For the merged external adoption, see Status and governance below.)
Licensing
LICENSE is the verbatim Apache-2.0 text, so automated scanners classify this repository
correctly. The project is dual-licensed and always has been — reference code (ref/, tools/,
schema/, middleware/) Apache-2.0; the specification (spec/, this README) CC-BY-4.0 — with the
verbatim texts in LICENSE-APACHE-2.0 and LICENSE-CC-BY-4.0 and the split explained in
LICENSING.md. Implementing GVP requires no permission and no notification.
The test key is a test key
vectors/attestation-key.json contains a fixed throwaway private key, published deliberately so
anyone can reproduce the L2 attestation vectors byte-for-byte. It is not, and has never been, a
production signing key — verified: it derives public key MCowBQYDK2VwAyEA/UETLdE5..., which differs
from the live issuer key published at x402toll.com/.well-known/receipt-pubkey.json.
Layout
spec/gvp-0.2.md the normative specification (RFC 2119 MUST/SHOULD/MAY)
schema/receipt.schema.json JSON Schema (draft 2020-12) for a Verification Receipt
schema/revocation-list.schema.json JSON Schema for the signed, append-only revocation list
index.mjs package entry point (re-exports L1 + L2)
ref/js/gvp.mjs JS reference: canonicalize() + gvpHash() (L1)
ref/js/attest.mjs JS reference: Ed25519 attest()/verifyAttest() (L2)
ref/py/gvp.py Python reference: canonicalize() + gvp_hash() + a vectors runner (L1)
ref/py/attest.py Python reference: Ed25519 attest/verify + cross-lang proof (L2)
vectors/ conformance vectors + generated expected.json / attestation.json
tools/check-js.mjs JS conformance check (L1 + L2)
tools/check-jcs.mjs RFC 8785 equivalence gate (vs an independent implementation)
tools/gen-vectors.mjs generate L1 hashes (optional GVP_CALC_CORE cross-check vs a 2nd impl)
tools/gen-attestation.mjs generate L2 attestation vectors with the fixed test key
docs/RECOMPUTE-NOT-TRUST.md category note: the five trust models and where GVP sits
docs/REGULATORY-BRIDGE.md GVP artifacts mapped to EU AI Act / ISO 42001 / NIST AI RMF evidence classes
docs/COMPLIANCE-MEMO-ART50-ART12.md one-page memo for audit/compliance vendors: Art. 50(2) + Art. 12 fit and boundary
docs/CARRIAGE-PROFILES.md the three carriages (x402 envelope / provenance block / compliance-receipt field), same hash
docs/MEASURED-NOT-CLAIMED.md explainer: the x402 counter episode and what a re-derivable hash does and does not prove
audit/cli.mjs gvp-audit: closure audit for sellers (bodies dir or free-surface fetch; never pays)
audit/lib/ analyze (heuristics, format, vintage, re-derivation) / diff / report / load / fetch
audit/test.mjs 82-check suite: units + CLI end-to-end on generated fixtures (no network)
examples/plain-http/ issuer + verifier on node:http with no payment rail — the rail-agnostic claim, executable
LICENSE Apache-2.0 (code) / CC-BY-4.0 (spec) — full texts includedStatus and governance
GVP v0.2 is a draft specification, authored and maintained by its original author. It is not ratified by any standards body. It is single-author as a specification — nobody else has co-authored the spec text.
It is no longer without external implementation. As of 2026-08-27:
- First external implementation, merged to their production main. The
SCVD conformance desk
(source) — an independent evidence
observatory for agentic commerce, which competes with the issuers it audits — merged a GVP
responseHashre-derivation check into the main branch behind its/api/conformance/v1surface (PR #270, #274). The check runs on their own independent RFC 8785 implementation, and their §8.1 gate confirms it reproduces every published GVP vector — two canonicalizers, written independently by parties with reason to disagree, producing identical bytes. Their maintainer states the check runs in production (x402#3234, 2026-08-30). Observability note (checked 2026-08-31): the deployed public API doc does not yet expose the provenance block, so "running in production" is their statement, not independently observable from outside; the independently observable public credit is the defect-vocabulary entry below. This distinction is kept here on purpose — the repo's standard for its own claims is the standard it asks of everyone else's. - A defect class sourced from the work.
nonce-unbound-from-settlementis registered in that desk's public defect vocabulary, sourced by this project and registered by them — the first outside-sourced class in their register. - A fourth reproduction, from a separate RFC 8785 implementation (2026-09-19). goun7 (Tamga)
ran the x402 extension's normative worked vector through the JCS implementation built for their
receipt ledger: 134 canonical bytes and
81ea1f22…, exact; their verifier rejects an extra or a missing member asunverifiablebefore hashing (their report on x402#3304; tooltools/x402_response_provenance.pyin goun7/tamga-protocol, Apache-2.0). Re-run here the same day from a clean clone:vectorprinted the same 134 bytes and hash, andverifyon a fixed point with an extra member returnedunverifiable. - Three independent cross-checks reproduced the published vectors with their own code:
whawk46/x402-jcs-crosscheck (24/24, vectors pinned
not vendored); the SCVD desk (§8.1, above); and giskard09, who on 2026-09-15 reconstructed the
normative worked vector of the x402 extension text by hand, hashed it with a third-party RFC 8785
library to a byte-exact match, and hashed two fresh preimages end to end (review on
x402-foundation/x402#3304).
Correction 2026-09-15, at giskard09's request: an earlier version of this line credited
giskard09/argentum-core with "7 preimages, one anchored on Base mainnet at block 49623528". That
conflated their 2026-08-24 run of this canonicalizer over 14 of their own
action_refpreimages (canonicalization agreement, not a check of this fixed point) with a separate repository's anchored manifest that was offered as a shape for aresponseHashleg and never built. Neither was a verification of GVP's vectors; the line above describes what was.
The reference deployment x402toll.com computes GVP hashes under this rule set;
its canonicalization is byte-identical to this reference (verified above). Its served /v1/spec
description predates this document and is expected to be realigned to it — this repository is the
authoritative specification where they differ.
Honest boundary, kept in every artifact: reproduction is not co-authorship, one merged check is
not a ratified standard, and a maintainer's statement of production use is not independent
observability (see the note above). What is true and checkable is that the single thing this most
needed — an external implementation, written and merged by a party with no incentive to flatter it —
now exists on their production main. The
open proposal to carry responseHash inside the x402 signed receipt is at
x402-foundation/x402#3234.
If you implement it, or want a field like responseHash in a receipt format you maintain, please open
an issue.
Implementation checklist ("a stranger can implement it from the repo alone")
- [x] Canonicalization + hash (L1), JS + Python references, byte-agreement proven.
- [x] Canonicalization is normatively RFC 8785 (JCS) — implementable with an off-the-shelf library.
- [x] JCS equivalence gated against an independent implementation (
tools/check-jcs.mjs, 0 divergences). - [x] Normative
spec/gvp-0.2.md(MUST/SHOULD/MAY over canonicalization, hash, receipt, attestation, revocation, conformance). - [x] Receipt schema
schema/receipt.schema.json. - [x] Revocation-list mechanism (spec §7) +
schema/revocation-list.schema.json. - [x] Ed25519 attestation (L2) in both references — cross-implementation verify + byte-identical signatures.
- [x] Conformance suite (L1 + L2 golden vectors) passing in both references.
- [x] Full verbatim Apache-2.0 + CC-BY-4.0 licence texts, copyright holder set.
- [x] Publishable npm package (
golden-vector-provenance) withnpm testgreen from a clean install. - [x] Published: public repo + npm
golden-vector-provenance(0.2.0 → 0.4.0). - [ ] Revocation-list reference generator/verifier (schema + spec are done; a small signed-list signer/checker would round it out).
