# ProxyPay open payment request v1

Editorially adapted for publication from the reviewed v1 specification; normative technical content is unchanged.

Status: RC0 public conformance draft, synthetic/no-money evidence only. This format is open and independent of either company's private service or source. A request is a signed message; it is not a wallet session, payment guarantee, POS receipt, customer identity, transaction, or permission to move money.

## Wire and signature contract

The wire form is UTF-8 JSON whose exact bytes must equal RFC 8785 JSON Canonicalization Scheme (JCS) output. V1 limits the wire to 4,096 bytes, JSON nesting to four levels, and pre-parsing top-level membership to 32 names. The valid top-level object contains exactly the sixteen fields in `schema.json`; unknown and prohibited fields fail closed. `signature` is canonical unpadded base64url Ed25519 over the UTF-8 JCS bytes of the other fifteen claims. Implementations explicitly reject duplicate top-level JSON names before re-canonicalization; they do not rely on a JSON parser's last-name-wins behavior. Strings must be Unicode scalar values; display fields must be NFC-normalized, trimmed, printable, and bounded.

All money uses a positive canonical decimal integer string in the token's base units. There are no floats, signs, decimals, exponents, whitespace, zero, or leading zeros. A wallet must verify the exact network, native USDC mint, and six decimals from its independent trusted configuration before formatting. It must never infer decimals from the request.

`request_id`, `payment_reference`, and `nonce` are independently random and contain no customer, member, or transaction-record data. The checked vector freezes values produced independently with Node's cryptographic RNG. `payment_reference` is a random 32-byte Solana public key used only for public transaction correlation and is distinct from the recipient, mint, and known program identifiers. It is text-length bounded to 32–44 base58 characters before numeric decoding. `recipient` is the exact merchant-controlled Solana address. Address-purpose and merchant-control evidence remain external production gates; a syntactically valid base58 key is not enough for production.

## Required validation order

0. Validate local policy before reading the request: `now` is a real instant; the issuer allowlist contains 1–8 exact canonical HTTPS identifiers; network is a v1 enum; mint is a bounded 32-byte base58 key; decimals are exactly six; and all four caller limits are positive safe integers no greater than the v1 ceilings (15 amount digits, 600 seconds validity, 30 seconds skew, and 300 seconds metadata cache). A caller may tighten but never loosen these ceilings.
1. Enforce the 4,096-byte wire ceiling. For byte input, decode UTF-8 strictly.
2. Lexically enforce depth four, at most 32 top-level names, and no duplicate top-level JSON name.
3. Parse JSON and require one top-level object.
4. Re-canonicalize and require exact wire-byte equality. Outer whitespace is therefore rejected and must not be trimmed by paste, scan, or deep-link routing.
5. Reject unknown/prohibited fields, then missing fields, then non-string fields.
6. Validate claims in schema order: version `1`; exact allowlisted HTTPS issuer; bounded key ID; random UUIDv4 request ID; bounded 32-byte base58 payment reference; safe merchant/location display; bounded 32-byte recipient; configured network; exact configured mint and its syntax; independently verified `D = 6`; canonical positive integer and digit bound; strict timestamps; validity ordering, future skew, expiry, duration; nonce; then non-sensitive display reference.
7. Validate the entire issuer metadata runtime value against the exact bounded shape before semantic lookup: exact top-level and key members, no more than eight keys, bounded strings, `kty = OKP`, `crv = Ed25519`, canonical 32-byte JWK `x`, known `active`/`revoked` status, and status-consistent `revoked_at`.
8. Require exact issuer equality, cache freshness and interval, exactly one matching key, immediate revocation rejection, and an active key window.
9. Validate the canonical signature encoding and verify Ed25519 over unsigned canonical claims.
10. Atomically reserve `request_id`, `payment_reference`, and `nonce` through expiry. Duplicate or exhausted replay storage fails closed. An invalid request must not consume a replay reservation.
11. Display every critical field and require a separate explicit customer approval. Funding success cannot approve or submit a transfer.

This order is normative precedence for multi-invalid inputs. The first failing stage determines the internal code; tests pin policy-over-wire, size-over-syntax, duplicate-name-over-canonicalization, canonicalization-over-field shape, claims-over-metadata, metadata-over-signature, and signature-over-replay examples. Clients may disclose a coarser public error than the internal taxonomy.

## Issuer metadata and rotation

The verifier interface consumes immutable metadata matching `issuer-metadata-schema.json`: exact `issuer`, `fetched_at`, `expires_at`, and at most eight keys. Before selecting a key, the verifier validates every key entry and requires globally unique `key_id` values, including among nonselected rotation keys. Each key is an exact public JWK with `key_id`, `kty: "OKP"`, `crv: "Ed25519"`, canonical unpadded 32-byte `x`, explicit `active` or `revoked` status, strict canonical `not_before`/`not_after` instants, and `not_after` strictly later than `not_before`. `revoked_at` is forbidden for `active` and required for `revoked`. Unknown status or members fail closed. A revoked key is rejected immediately even if its cryptographic signature verifies. Rotation publishes the new key before use, overlaps active verification windows only as documented, and removes old active use after the overlap. A duplicate key ID, unknown key, stale cache, future fetch time, malformed interval, unavailable metadata, or cache beyond the local bound fails closed. This draft defines no network client, callback, or private endpoint.

Production metadata hosting, HTTPS identity, denial-of-service controls, incident response, key ceremony, hardware protection, and revocation availability require independent review.

## Error taxonomy

- Configuration/preflight: `POLICY_INVALID`, `WIRE_TOO_LARGE`, `WIRE_DEPTH_EXCEEDED`, `ENVELOPE_SHAPE_LIMIT`, `DUPLICATE_JSON_NAME`.
- Parse/canonicalization: `MALFORMED_UTF8`, `MALFORMED_JSON`, `ENVELOPE_OBJECT`, `NON_CANONICAL_WIRE`, `CANONICALIZATION_*`.
- Shape/privacy: `UNKNOWN_FIELD`, `PROHIBITED_FIELD`, `MISSING_FIELD`, `FIELD_TYPE`, unsafe display/reference and malformed identifier/address errors.
- Policy/time: unsupported version, issuer/network/mint/decimals mismatch, malformed/overflow amount, malformed/not-yet-valid/expired/overlong validity.
- Metadata/key: object/unknown/missing/type/key-count shape errors; exact JWK/status/revocation errors; issuer mismatch; malformed/future/stale/unbounded cache; unknown/ambiguous/inactive/revoked key.
- Signature: malformed signature/public key or `SIGNATURE_INVALID`.
- Replay: duplicate request/reference/nonce or bounded-store capacity.

Customer identity, phone/email, loyalty data, line-item or product data, point-of-sale vendor identifiers, memo, wallet session, callback, private endpoint, or receipt data are explicitly prohibited fields. The protocol carries amount, recipient, reference and expiry only. No error or support export should echo their values.

## Published conformance material

This page publishes the specification, `schema.json`, `issuer-metadata-schema.json`, and a fixed set of synthetic conformance fixtures (envelopes and issuer metadata) under `/protocol/v1/`, each with its exact SHA-256 hash printed alongside it. The fixtures are deterministic, no-money bytes used to test that a verifier accepts a valid request and rejects each specific way one can be invalid; none of them touch a real network, a real issuer, or real funds, and no published file contains private key material.

A wallet that independently implements this specification, without importing another party's private implementation or contacting another party's private service, MUST NOT present a merchant display name carried in a request as independently verified unless it has itself verified it.

## Explicitly open gates

The RC0 corpus demonstrates deterministic local parsing, policy, signature, metadata, replay, and display behavior only. Production issuer identity/key management, merchant-recipient proof, provider compatibility, physical-device UAT, accessibility audit, on-chain submission/finality, and production security assessment remain external work.
