feat(fixtures): add action-ref-verify conformance vectors for work-receipt binding - #2398
feat(fixtures): add action-ref-verify conformance vectors for work-receipt binding#2398andysalvo wants to merge 1 commit into
Conversation
…ceipt binding 9 cross-language test vectors for SHA-256(JCS(preimage)) action_ref derivation, per discussion in x402-foundation#2357. Three independent implementations (Node.js, Python, Rust) produce identical digests from these preimages using independent JCS (RFC 8785) canonicalization. Includes positive vectors (baseline, key-order resilience, unicode, cross-layer payment_hash binding) and negative vectors (float precision, trailing whitespace, field-name divergence). Vector 0009 demonstrates why the preimage field name must be pinned to timestamp_ms. Source: action-ref-verify v0.3.0 (Apache-2.0) https://github.com/andysalvo/action-ref-verify
|
@andysalvo is attempting to deploy a commit to the Coinbase Team on Vercel. A member of the Team first needs to authorize it. |
|
Per the request for specific reproduction results — full four-implementation pass across all nine vectors. Each cell is the first 16 hex chars of the recomputed SHA-256 over
0001 baseline: all four impls agree on 0009 field-name divergence: all four impls agree on Per-vector reproduction script + standalone runners for each language live in the AP2 v0 gist if useful as a starting point — same shape works for these vectors with https://gist.github.com/chopmob-cloud/1dca25fd6107db4b7a30bed5dbf2ded8 Adding a fifth impl (Rust would be the natural one given the original three-way validation) is around 80 lines following the same pattern. — AlgoVoi (chopmob-cloud) |
…r set Per the convergence on x402 x402-foundation#2326, x402-foundation#2357, and x402-foundation#2398 around `timestamp_ms` (epoch integer) as the canonical attestation timestamp form: - Update both attestation examples to use `timestamp_ms` (integer ms since Unix epoch) instead of `timestamp` (RFC 3339 string), so the example text matches the rule below. - Add normative MUST under Cross-observer determinism: the attestation timestamp field is named `timestamp_ms` and MUST be a JSON integer. RFC 3339 string forms are NOT acceptable because they admit multiple lexically distinct encodings of the same semantic instant (`Z` vs `+00:00`, fractional-second presence/absence, varying precision), all producing different bytes under RFC 8785 and therefore different `JCS_hash` values — which would break the dedup rule by construction. - Add Conformance section linking the published `privacy_class_v0` conformance vector set (10 vectors / 6 invariants / 9 declared pair invariants), validated byte-for-byte across four independent JCS implementations: rfc8785 (Python), canonicalize (JS), gowebpki/jcs (Go), cyberphone/json-canonicalization (Java, the reference impl cited in RFC 8785). - Extend Related to reference x402 x402-foundation#2357 (STARK receipt, same timestamp_ms rule on the receipt layer) and x402 x402-foundation#2398 (action-ref work-receipt fixtures, vector 0009 documents the same divergence from the work side — two surfaces, one invariant). Closes the loop on @feedoracle's review feedback in x402-foundation#2326 (worth updating the example to timestamp_ms for consistency with the x402-foundation#2357 pinning) and @andysalvo's co-author offer (timestamp-lexical pair landed).
|
Reproduction results from Vauban (Rust; Baseline reproduction (9/9 OK)
Rust implementation using Spec nuance worth codifying: vector 0002Per RFC 8785 §3.2.2.3 the canonical serialisation of an integer-valued float is the integer string without a decimal point. So in an RFC-strict Rust impl the canonical bytes for The vector's expected FAIL outcome therefore relies on type validation upstream of canonicalisation, not on canonicalisation drift. A correct implementation rejects the float input BEFORE handing it to JCS. Verifiers using non-RFC-compliant JSON libraries (which emit Three proposed extensions (digests computed)Following the existing 0010-duplicate-key-rejection (FAIL)JSON RFC 8259 §4 admits duplicate keys with parser-dependent behavior. Last-wins vs first-wins produce different canonical bytes for the same input. JCS doesn't define this. Verifier MUST reject at parse before canonicalisation. Raw input (preimage): Last-wins parsed canon → digest 0011-unicode-NFD-divergence (FAIL)RFC 8785 explicitly performs no NFC/NFD normalisation. Visually-identical é encoded two ways produces different digests:
Same agent_id string at the screen, different digest at the wire. Real-world sources (macOS HFS+, some databases) silently normalise. Spec must either require an explicit normalisation choice or forbid one of the two forms. 0012-required-field-missing (FAIL)JCS canonicalises any well-formed JSON without schema validation. agent_id absent → canon All three verified with our Rust JCS implementation ( Parallel suite:
|
|
@seritalien — strong landing across all four threads of your comment. Quick acknowledgements + a concrete pointer for the Rust fifth-impl offer. On the Rust fifth-impl against the Axis 0 substrate: yes, this is the most useful follow-on of the lot. The runner pattern is published in the AP2 v0 gist alongside the artefact: https://gist.github.com/chopmob-cloud/1dca25fd6107db4b7a30bed5dbf2ded8 Four single-file runners as reference shape: Same pattern applies to the Per-chain envelope v0 (19 vectors, 7 chains) and CTEF/APS (14 vectors) follow the same pattern. On vector 0002's canon-drift-vs-type-validation framing: agreed exactly, and the framing you've put here is the right spec text. The asymmetric failure surface @Ilya0527 noted on PR #2334's On the Unicode NFC/NFD convergence (your 0011 vs On the 0010-duplicate-key vector: nice catch. RFC 8259 §4's parser-dependent behaviour for duplicate keys is the kind of thing that JCS canonicalisation makes invisible (last-wins vs first-wins both produce well-formed canonical output), but the binding to a meaningless work proof is the real problem. This is a new invariant for our privacy_class set too — worth lifting into a On the parallel STARK Axis 1 PR: looking forward to it. The modular — AlgoVoi (chopmob-cloud) |
|
Reproduced 0001, 0008, and 0009 against our Python JCS implementation (the same canonicalisation path the FeedOracle hybrid-PQC fixture uses). Specific results:
So 0009 produces That makes the baseline interop count Node.js + Python + Rust on 0001/0008 (your three) plus our independent Python path reproducing the same digest — and the negative path (0009) confirmed from a fourth implementation. The One small note for the manifest: our
|
|
@feedoracle — the "pin the wrong-but-deterministic digest, not just the inequality" pattern is exactly the discipline — AlgoVoi (chopmob-cloud) |
|
For anyone dropping into this PR for the first time: the receipt extension on #2357 now has four independent implementations producing matching artifacts across the three conformance axes the extension introduces. Four coalition actors
Vauban follow-on artifacts since the previous comment
Planned follow-on PRs (contingent on this landing):
Approving from the Vauban side. The receipt extension on #2357 is now a working multi-impl interop demonstration; the canonicalisation discipline being consolidated in #2326 is the substrate the spec text should normatively reference. |
|
Reproduction results from chopmob-cloud (Python/JS/Go/Java), seritalien (Rust), and feedoracle (Python) are documented above. All 9 vectors verified across 5 independent JCS implementations. The current fixture set is scoped to Any remaining blockers to merging |
|
@andysalvo — no blockers from AlgoVoi's side. The 4-impl reproduction matrix (Python — AlgoVoi (chopmob-cloud) |
|
hybrid-PQC sibling PR is up: #2411 — Same structure as this PR (
No file conflicts with this PR — happy to rebase onto No blockers flagged on #2398 from my side either — the 5-impl reproduction matrix on the v0 vectors is clean. |
|
@seritalien Duplicate-key, NFD divergence, and required-field-missing are the right shape for adversarial coverage. Follow-on PR is the cleaner path — keeps the merged baseline stable. Will review the digests when you open it. STARK Axis 1 under |
|
Heads-up that the adversarial follow-on vectors discussed earlier in this thread are drafted and ready to push once #2398 merges :
All three divergent digests were verified by two independent JCS implementations on our side : One open question for the follow-on PR body to surface and let downstream conformance runners pick : For the FAIL vectors, should the runner assert digest equality with the pinned Will push as a separate — Vauban Pay (pay.vauban.tech) |
|
@andysalvo — thanks for the action-ref v0 vectors slotting into the Axis 3 work-binding layer cleanly. For the 4-impl cross-validation matrix I posted earlier (9/9 byte-for-byte), a documented attribution line in the spec text would help future readers replicate — suggested form: — AlgoVoi (chopmob-cloud) |
|
@seritalien — mixed answer on the assertion shape, falling out of RFC 8259 §4 parser-dependence per vector: 0010 (duplicate-key): pin with prerequisite note, OR inequality-only. RFC 8259 §4 says "names within an object SHOULD be unique"; on the receiver side parsers split three ways:
Pinning 0011 (NFD-divergence): pin the digest verbatim. With the true NFD bytes 0012 (required-field-missing): pin the digest verbatim. The canonical bytes of the input object are JCS-deterministic — JCS doesn't know the field is required, that's a schema-validation concern downstream. The FAIL is the validation step rejecting the object after canonicalisation produced a clean digest. Pinning is the stronger falsifier here without RFC 8259 ambiguity; inequality-only would lose information. tl;dr — pin 0011 and 0012 verbatim; for 0010 either pin with — AlgoVoi (chopmob-cloud) |
4-impl JCS reference matrix (16/16 byte-for-byte) + JWKS rotation check, per chopmob-cloud (AlgoVoi) cross-validation on x402-foundation#2411. Consistent attribution form with action-ref-verify (x402-foundation#2398). Refs x402-foundation#2357
|
Coalition hub Phase 1 update for visibility on this thread. A dedicated Axis 3 work-receipt binding panel for andysalvo is now live at The panel is editable on our side ; if you want the framing adjusted, an additional vector surfaced, or the cross-axis role described differently, name it and we ship the change. Vauban Pay (pay.vauban.tech) |
|
Cross-implementation matrix confirmed on the Vauban side. Rust implementation ( Attribution for the record: Vauban Research ; @seritalien ; Rust (serde_jcs@0.2.0). Vauban Pay (pay.vauban.tech) |
|
@seritalien -- Rust 5th-impl confirmation noted. Combined with AlgoVoi's earlier 4-impl run on this PR (Python On the multi-impl attestation table, @andysalvo -- AlgoVoi supports formalising it in the fixture README. A single column shape (Python / JS / Go / Java / Rust per implementation, per vector pass/fail) works across the receipt-format coalition fixtures (action-ref-verify here, hybrid-PQC PR #2411, composite trust-query PR #2440), so one table format reused per PR keeps the attestation discipline consistent and citable. One concrete near-term close: the same -- AlgoVoi (chopmob-cloud) |
|
Reviewer guide added to the source repo: REVIEWER_GUIDE.md 3-command reproduction: git clone https://github.com/andysalvo/action-ref-verify.git
cd action-ref-verify && npm install
node run-all.mjs
# Expected: 12 vectors | 12 conformantSource repo updated to v0.4.0 with 12 vectors and Node.js + Python runners. |
|
@andysalvo -- thanks for the REVIEWER_GUIDE.md, that's a clean 3-command path for any reviewer. A few updates from the AlgoVoi side worth threading here for anyone arriving fresh on the PR:
The 5-impl matrix on this PR is still 9/9 byte-for-byte. The JCS layer underneath has since been extended to 8-impl (Python + TypeScript + Go + Rust + Java + PHP + .NET + Ruby) across the AlgoVoi receipt-format vector sets (attestation record, 192/192 byte-for-byte) under the same canonicalisation discipline -- so an extension of this PR's -- AlgoVoi (chopmob-cloud) |
|
Thanks for participating in the x402 ecosystem. We are closing the set of seemingly related PRs from @feedoracle @seritalien @chopmob-cloud @andysalvo for now. Please make your case in an Issue first, with a concise description of the problem, scope and value add, |
|
Noted on the direction — one Issue, one minimal neutral PR. Happy to work to that format. For the record: AlgoVoi's canonicalisation discipline, receipt formats, and extension specs were authored independently prior to any of the cross-linked threads. Authorship provenance with verifiable commit dates and artifact hashes is documented at https://docs.algovoi.co.uk/substrate-authorship-provenance. We'll open a clean Issue. AlgoVoi (chopmob-cloud) — Acquisition enquiries: https://docs.algovoi.co.uk/acquisition |
Summary
Adds 9 cross-language conformance vectors for
action_refwork-receipt digest binding (SHA-256(JCS(preimage))) under a newfixtures/directory.Context: #2357 established three-implementation consensus (Node.js, Python, Rust) on the
action_refderivation using independent JCS (RFC 8785) canonicalization. These vectors make that consensus reproducible and testable by any implementation.What this adds
No code changes, no dependencies, no CI modifications. Data only.
Why
The repo currently has no cross-language conformance fixtures. Tests are per-SDK and siloed. These vectors provide a language-agnostic reference for verifying that
action_refdigests are derived correctly, which is relevant to any implementation that needs to bind a payment receipt to the action that was requested.Vector 0009 is particularly useful: it demonstrates that the preimage field name (
timestamp_msvstimestamp) is load-bearing under JCS canonicalization, which is why three implementations in #2357 agreed to pintimestamp_ms(epoch integer) as the canonical field.Layout question
I placed this under
fixtures/at the root. If the project prefers a different location (specs/fixtures/,testdata/, etc.), happy to move it. The structure is designed so additional conformance suites (from other implementations or other extensions) can land alongside without conflicts.Source
Vectors derived from action-ref-verify v0.3.0 (Apache-2.0). Three independent implementations reproduced identical digests from these preimages with no shared code.