Skip to content

Commit a8eb2e1

Browse files
authored
fix: align core, tempo-charge, and charge-intent specs with mppx implementation (#156)
1 parent ebce8ad commit a8eb2e1

File tree

2 files changed

+74
-8
lines changed

2 files changed

+74
-8
lines changed

specs/intents/draft-payment-intent-charge-00.md

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ author:
2525
normative:
2626
RFC2119:
2727
RFC3339:
28+
RFC4648:
2829
RFC8174:
30+
RFC8259:
2931
I-D.httpauth-payment:
3032
title: "The 'Payment' HTTP Authentication Scheme"
3133
target: https://datatracker.ietf.org/doc/draft-httpauth-payment/
@@ -114,13 +116,17 @@ failed).
114116

115117
The `request` parameter for a "charge" intent is a JSON object with
116118
shared fields defined by this specification and optional method-specific
117-
extensions in the `methodDetails` field.
119+
extensions in the `methodDetails` field. The `request` JSON MUST be
120+
serialized using JSON Canonicalization Scheme (JCS) and base64url-encoded
121+
without padding per {{I-D.httpauth-payment}}.
118122

119123
## Shared Fields
120124

121125
All payment methods implementing the "charge" intent MUST support these
122126
shared fields, enabling clients to parse and display payment requests
123-
consistently across methods.
127+
consistently across methods. Payment methods MAY elevate OPTIONAL fields
128+
to REQUIRED in their method specification (e.g., `recipient` and
129+
`expires` are REQUIRED for blockchain methods).
124130

125131
### Required Fields
126132

@@ -150,6 +156,7 @@ payment networks:
150156
| Format | Example | Description |
151157
|--------|---------|-------------|
152158
| ISO 4217 | `"usd"`, `"eur"` | Fiat currencies (lowercase) |
159+
| Token address | `"0x20c0..."` | On-chain token contract address |
153160
| Method-defined | (varies) | Payment method-specific currency identifiers |
154161

155162
Payment method specifications MUST document which currency formats they
@@ -213,8 +220,11 @@ for their implementation of the "charge" intent.
213220

214221
## Payload
215222

216-
The credential `payload` for a "charge" intent MUST contain proof that
217-
payment has been made or authorized. The proof type is method-specific:
223+
The credential structure follows {{I-D.httpauth-payment}},
224+
containing `challenge`, `payload`, and an optional `source` field
225+
identifying the payer. The `payload` for a "charge" intent MUST contain
226+
proof that payment has been made or authorized. The proof type is
227+
method-specific:
218228

219229
| Proof Type | Description | Example Methods |
220230
|------------|-------------|-----------------|
@@ -261,9 +271,11 @@ amounts.
261271

262272
## Recipient Verification
263273

264-
Clients SHOULD verify the payment recipient when possible. For methods
265-
that support recipient verification (e.g., known merchant addresses),
266-
clients SHOULD warn users about unknown recipients.
274+
Clients SHOULD verify the payment recipient when possible. Not all
275+
payment methods expose an explicit recipient (e.g., processor-based
276+
methods like Stripe route payments internally). For methods that do
277+
expose a recipient (e.g., blockchain addresses), clients SHOULD warn
278+
users about unknown recipients.
267279

268280
## Replay Protection
269281

@@ -281,6 +293,18 @@ The finality of a "charge" payment depends on the payment method:
281293
Servers SHOULD understand the finality guarantees of their accepted
282294
payment methods and adjust resource access accordingly.
283295

296+
## Transport Security
297+
298+
All Payment authentication flows MUST use TLS 1.2 or later per
299+
{{I-D.httpauth-payment}}. Payment credentials contain sensitive
300+
authorization data that could result in financial loss if intercepted.
301+
302+
## Currency Verification
303+
304+
Clients MUST verify the `currency` field matches their expectation
305+
before authorizing payment. Malicious servers could request payment
306+
in a different currency or token than expected.
307+
284308
# IANA Considerations
285309

286310
## Payment Intent Registration
@@ -291,3 +315,5 @@ registry established by {{I-D.httpauth-payment}}:
291315
| Intent | Description | Reference |
292316
|--------|-------------|-----------|
293317
| `charge` | One-time immediate payment | This document |
318+
319+
Contact: Tempo Labs (<contact@tempo.xyz>)

specs/methods/tempo/draft-tempo-charge-00.md

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ normative:
2828
RFC4648:
2929
RFC8174:
3030
RFC8259:
31+
RFC8785:
3132
I-D.httpauth-payment:
3233
title: "The 'Payment' HTTP Authentication Scheme"
3334
target: https://datatracker.ietf.org/doc/draft-ietf-httpauth-payment/
@@ -129,7 +130,9 @@ Fee Payer
129130
# Request Schema
130131

131132
The `request` parameter in the `WWW-Authenticate` challenge contains a
132-
base64url-encoded JSON object.
133+
base64url-encoded JSON object. The JSON MUST be serialized using JSON
134+
Canonicalization Scheme (JCS) {{RFC8785}} before base64url encoding,
135+
per {{I-D.httpauth-payment}}.
133136

134137
## Shared Fields
135138

@@ -138,6 +141,8 @@ base64url-encoded JSON object.
138141
| `amount` | string | REQUIRED | Amount in base units (stringified number) |
139142
| `currency` | string | REQUIRED | TIP-20 token address (e.g., `"0x20c0..."`) |
140143
| `recipient` | string | REQUIRED | Recipient address |
144+
| `description` | string | OPTIONAL | Human-readable payment description |
145+
| `externalId` | string | OPTIONAL | Merchant's reference (order ID, invoice number, etc.) |
141146

142147
Challenge expiry is conveyed by the `expires` auth-param in
143148
`WWW-Authenticate` per {{I-D.httpauth-payment}}.
@@ -148,6 +153,7 @@ Challenge expiry is conveyed by the `expires` auth-param in
148153
|-------|------|----------|-------------|
149154
| `methodDetails.chainId` | number | OPTIONAL | Tempo chain ID (default: 42431) |
150155
| `methodDetails.feePayer` | boolean | OPTIONAL | If `true`, server pays transaction fees (default: `false`) |
156+
| `methodDetails.memo` | string | OPTIONAL | A `bytes32` hex value. When present, the client MUST use `transferWithMemo` instead of `transfer`. |
151157

152158
**Example:**
153159

@@ -376,6 +382,22 @@ the transaction. The server verifies the transaction onchain:
376382
- Cannot be used with `feePayer: true` (client must pay their own fees)
377383
- Server cannot modify or enhance the transaction
378384

385+
## Transaction Verification {#transaction-verification}
386+
387+
Before broadcasting a transaction credential, servers MUST verify:
388+
389+
1. Deserialize the RLP-encoded transaction from `payload.signature`
390+
2. Verify the transaction contains a `transfer(recipient, amount)` or
391+
`transferWithMemo(recipient, amount, memo)` call matching the challenge request
392+
3. Verify the call target matches the `currency` token address
393+
4. Verify the `amount` matches the challenge request amount
394+
5. Verify the `recipient` matches the challenge request recipient
395+
6. If `methodDetails.memo` is present, verify the transaction uses
396+
`transferWithMemo` with the matching memo value
397+
For hash credentials, servers MUST fetch the transaction receipt and
398+
verify the emitted `Transfer` or `TransferWithMemo` event logs match
399+
the challenge parameters.
400+
379401
## Receipt Generation
380402

381403
Upon successful settlement, servers MUST return a `Payment-Receipt` header
@@ -391,6 +413,7 @@ The receipt payload for Tempo charge:
391413
| `reference` | string | Transaction hash of the settlement transaction |
392414
| `status` | string | `"success"` |
393415
| `timestamp` | string | {{RFC3339}} settlement time |
416+
| `externalId` | string | OPTIONAL. Echoed from the challenge request |
394417

395418
# Security Considerations
396419

@@ -448,6 +471,22 @@ Intents" registry established by {{I-D.httpauth-payment}}:
448471

449472
--- back
450473

474+
# ABNF Collected
475+
476+
~~~ abnf
477+
tempo-charge-challenge = "Payment" 1*SP
478+
"id=" quoted-string ","
479+
"realm=" quoted-string ","
480+
"method=" DQUOTE "tempo" DQUOTE ","
481+
"intent=" DQUOTE "charge" DQUOTE ","
482+
"request=" base64url-nopad
483+
484+
tempo-charge-credential = "Payment" 1*SP base64url-nopad
485+
486+
; Base64url encoding without padding per RFC 4648 Section 5
487+
base64url-nopad = 1*( ALPHA / DIGIT / "-" / "_" )
488+
~~~
489+
451490
# Example
452491

453492
**Challenge:**
@@ -460,6 +499,7 @@ WWW-Authenticate: Payment id="kM9xPqWvT2nJrHsY4aDfEb",
460499
intent="charge",
461500
request="eyJhbW91bnQiOiIxMDAwMDAwIiwiY3VycmVuY3kiOiIweDIwYzAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAiLCJyZWNpcGllbnQiOiIweDc0MmQzNUNjNjYzNEMwNTMyOTI1YTNiODQ0QmM5ZTc1OTVmOGZFMDAiLCJtZXRob2REZXRhaWxzIjp7ImNoYWluSWQiOjQyNDMxfX0",
462501
expires="2025-01-06T12:00:00Z"
502+
Cache-Control: no-store
463503
~~~
464504

465505
The `request` decodes to:

0 commit comments

Comments
 (0)