Skip to content

feat(core)!: speak the spec's PaymentPayload #75

Open

Description

@dhyaniarun1993

Updated 2026-11-04. Widened from "the two inner field renames" to the whole
PaymentPayload, after diffing our schemas against the spec. The original text
is in the edit history; the two renames are items 1 below.

The Canton scheme spec and core v2 define PaymentPayload at two
levels, and we diverge at both. A conforming client's payload does not parse here,
and ours does not parse there.

The envelope

ours spec
x402Version: 2 same ✅
scheme — moves inside accepted
network — moves inside accepted
payload same ✅
— accepted Required the chosen PaymentRequirements, inline
— resource Optional { url, description, mimeType }
— extensions Optional protocol extensions

accepted is the substantive one: the payer echoes back the accepts[] entry it
chose, so the facilitator verifies against what the client actually saw rather
than re-deriving it.

scheme and network are a relocation, not a rename. Their values were
corrected in #83 (exact, and the registered canton:<name> ids) and that work
is done — the fields simply stop being top-level, because accepted already
carries them. Nothing about the ids is outstanding here.

The inner payload

ours spec
preparedTransaction same ✅
preparedTransactionHash preparedTxHash rename
partySignature (hex) signature (base64) rename + re-encode
hashingSchemeVersion same ✅
— assetTransferMethod missing; MUST be "transfer-factory", required in both extra and payload
payer — not in the spec
publicKey — not in the spec
requirementsHash — not in the spec

signature is an encoding change, not a rename

The spec: "The payer's ed25519 signature (base64) over preparedTxHash."
Ours is hex. Renaming the field without re-encoding produces a payload that looks
conforming and fails every signature check.

Dropping payer / publicKey moves signature verification to the ledger

This is the item to think hardest about. Today:

// verify/canton-exact.ts
verifySignature(inner.preparedTransactionHash, inner.partySignature, inner.publicKey)

and the payer's ::<fingerprint> suffix is checked against a fingerprint derived
from publicKey. The spec has neither field. Rule 8 derives the payer from
the decoded prepared transaction's sender — our decoder already exposes
DecodedTransfer.sender, so that part is a straight swap — and the facilitator
wraps signature into the ledger's partySignatures for that party.

The consequence: with no publicKey on the wire there is no key to verify
against, so the signature is no longer checkable in /verify. It is validated
by the ledger at execute time instead. /verify keeps every other check (shape,
transfer binding, deadline, preapproval) but stops being able to reject a bad
signature up front. That is what the spec prescribes; it should be a deliberate
decision rather than a side effect of deleting a field.

Removing requirementsHash is safe because of transfer binding

requirementsHash is our mechanism for binding a payload to the requirements it
was signed against. The spec's mechanism is different: accepted carries the
requirements inline, and Rules 4–6 bind the decoded transfer to them — amount,
receiver, instrument. We already implement that (findTransferMismatch), so the
binding survives the removal. It would not if transfer binding were dropped too.

Not in scope

Everything inside accepted is #76 (requirements). This issue carries
accepted as a field; its contents are that issue's problem.

Suggested split

scope notes
1 preparedTxHash + signature renames, with the hex→base64 re-encode the original scope of this issue
2 assetTransferMethod in the payload additive
3 drop payer / publicKey; derive the payer from the decoded transfer decide the /verify trade-off first
4 drop requirementsHash depends on transfer binding staying
5 the envelope: accepted, resource, extensions; drop scheme / network largest; needs #76 first, since accepted is a PaymentRequirements

Blocks ChainSafe/canton-x402-facilitator#340. Context: ChainSafe/canton-x402-facilitator#316.

Note: the two renames in item 1 are missing from the conformance analysis in #316 — they were found by diffing our schemas against the spec directly.

Activity

  1. added
    Type: FeatureAdded to issues and PRs to identify that the change is a new feature.
    on Oct 29, 2026
  2. nikhilkumar1612 commented on Oct 31, 2026

    @nikhilkumar1612

    Hi @dhyaniarun1993, I’d be happy to work on this. Since I’ve already been working in the payment payload and verifier code for #56, this looks like a good follow-up. Can you please assign it to me if nobody else is working on it?

  3. dhyaniarun1993 commented on Nov 3, 2026

    @dhyaniarun1993
    MemberAuthor

    7. Hi @dhyaniarun1993, I’d be happy to work on this. Since I’ve already been working in the payment payload and verifier code for #56, this looks like a good follow-up. Can you please assign it to me if nobody else is working on it?

    @nikhilkumar1612 Thanks so much for reaching out and taking the initiative! This is one of the more time-sensitive issues on our board and also requires some internal discussion, so we'll be handling this one internally for now. Really appreciate your willingness to contribute and your continued interest in the project.

  4. nikhilkumar1612 commented on Nov 3, 2026

    @nikhilkumar1612

    No worries! thanks for the reply 😃. It'd be nice if there is a label dedicated for such issues. Would help me understand which issues are scoped for internal.

  5. dhyaniarun1993 commented on Nov 3, 2026

    @dhyaniarun1993
    MemberAuthor

    11. No worries! thanks for the reply 😃. It'd be nice if there is a label dedicated for such issues. Would help me understand which issues are scoped for internal.

    I agree that having a dedicated label for internally scoped issues would make things clearer for contributors. We will allocate some time for this. Thank you for the feedback.

  6. changed the title [-]feat(core): accept the spec's inner payload field names[/-] [+]feat(core)!: speak the spec's PaymentPayload[/+] on Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type: FeatureAdded to issues and PRs to identify that the change is a new feature.

Type

No type

Fields

Priority

High

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions