Skip to content

Commit 55e7191

Browse files
ArkForgeclaude
andcommitted
fix(spec): payment_evidence → provider_payment (alignement code)
Corrige l'incohérence documentation/code : tous les modules Python (proxy.py, receipts, tests) utilisent `provider_payment` mais SPEC.md référençait `payment_evidence`. Mise à jour des 10 occurrences dans SPEC.md et du note field dans test-vectors.json. Ajout Sigstore Rekor comme 3e témoin dans : - Section 7 (table témoins indépendants) - Section 7.1 "Transparency log (Sigstore Rekor)" (nouveau) - Champ optionnel `transparency_log` dans la spec des champs - Note : transparency_log est post-chain-hash (n'affecte pas la formule) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 76d4161 commit 55e7191

2 files changed

Lines changed: 36 additions & 12 deletions

File tree

SPEC.md

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ A conformant proof is a JSON object. The following fields are **required**:
101101
}
102102
```
103103

104-
**Note:** proofs without `payment_evidence` may use `spec_version: "1.1"` (backward compatible). Proofs with `payment_evidence.receipt_content_hash` use `spec_version: "2.0"`.
104+
**Note:** proofs without `provider_payment` may use `spec_version: "1.1"` (backward compatible). Proofs with `provider_payment.receipt_content_hash` use `spec_version: "2.0"`.
105105

106106
### Payment variants
107107

@@ -121,7 +121,7 @@ All variants produce a valid chain hash. The `payment.transaction_id` value is u
121121
|-------|------|-------------|
122122
| `spec_version` | string | Proof format version (`"1.1"` or `"2.0"`). Informational for auditors |
123123
| `upstream_timestamp` | string | Upstream service's HTTP `Date` header (RFC 7231 format). **Included in chain hash** when present |
124-
| `payment_evidence` | object | External receipt verification (see section 2.1). `receipt_content_hash` **included in chain hash** when present |
124+
| `provider_payment` | object | External receipt verification (see section 2.1). `receipt_content_hash` **included in chain hash** when present |
125125
| `arkforge_signature` | string | Ed25519 signature of the chain hash. Format: `ed25519:<base64url_without_padding>` |
126126
| `arkforge_pubkey` | string | Ed25519 public key used for signing. Format: `ed25519:<base64url_without_padding>` |
127127
| `verification_url` | string | URL to verify and view the proof (e.g. `https://arkforge.fr/trust/v1/proof/<proof_id>`) |
@@ -134,6 +134,7 @@ All variants produce a valid chain hash. The `payment.transaction_id` value is u
134134
| `upstream_status_code` | int | HTTP status code returned by the upstream service |
135135
| `disputed` | bool | Whether this proof has been disputed. Set by the dispute system |
136136
| `dispute_id` | string | Reference to the dispute record (e.g. `disp_a1b2c3d4`). Set when disputed |
137+
| `transparency_log` | object | Sigstore Rekor entry. **Post-chain-hash metadata, does not affect chain hash formula.** See section 7.1 |
137138

138139
## 2. Chain hash algorithm
139140

@@ -156,7 +157,7 @@ chain_hash = SHA256(request_hash + response_hash + transaction_id + timestamp +
156157
| `buyer_fingerprint` | `parties.buyer_fingerprint` | `SHA256(api_key)` — hash of the raw API key string |
157158
| `seller` | `parties.seller` | Target domain (e.g. `arkforge.fr`) |
158159
| `upstream_timestamp` | `upstream_timestamp` | Upstream service's HTTP `Date` header (optional — only included when the field is present and non-null) |
159-
| `receipt_content_hash` | `payment_evidence.receipt_content_hash` | SHA-256 hex digest of raw receipt bytes (optional — only included when present). Strip the `sha256:` prefix before concatenation |
160+
| `receipt_content_hash` | `provider_payment.receipt_content_hash` | SHA-256 hex digest of raw receipt bytes (optional — only included when present). Strip the `sha256:` prefix before concatenation |
160161

161162
### Concatenation
162163

@@ -169,7 +170,7 @@ input = request_hash + response_hash + transaction_id + timestamp + buyer_finger
169170
# With upstream_timestamp:
170171
input += upstream_timestamp
171172
172-
# With receipt_content_hash (from payment_evidence.receipt_content_hash, stripped of "sha256:" prefix):
173+
# With receipt_content_hash (from provider_payment.receipt_content_hash, stripped of "sha256:" prefix):
173174
input += receipt_content_hash
174175
175176
chain_hash = sha256(input.encode("utf-8")).hexdigest()
@@ -179,7 +180,7 @@ chain_hash = sha256(input.encode("utf-8")).hexdigest()
179180

180181
Each optional component is discriminated by **presence of its field** in the proof JSON:
181182
- `upstream_timestamp`: if the field is absent or null, do not append. If present and non-null, append after `seller`.
182-
- `receipt_content_hash`: if `payment_evidence.receipt_content_hash` is absent or null, do not append. If present, strip the `sha256:` prefix and append after `upstream_timestamp` (or after `seller` if `upstream_timestamp` is absent).
183+
- `receipt_content_hash`: if `provider_payment.receipt_content_hash` is absent or null, do not append. If present, strip the `sha256:` prefix and append after `upstream_timestamp` (or after `seller` if `upstream_timestamp` is absent).
183184

184185
Do **not** use `spec_version` for this decision (avoids string comparison pitfalls like `"1.10" < "1.9"`).
185186

@@ -191,7 +192,7 @@ A proof MAY include external payment evidence — an independently fetched recei
191192

192193
```json
193194
{
194-
"payment_evidence": {
195+
"provider_payment": {
195196
"type": "stripe",
196197
"receipt_url": "https://pay.stripe.com/receipts/payment/...",
197198
"receipt_fetch_status": "fetched",
@@ -218,7 +219,7 @@ A proof MAY include external payment evidence — an independently fetched recei
218219

219220
### Chain hash impact
220221

221-
When `payment_evidence.receipt_content_hash` is present, its value (with the `sha256:` prefix stripped) is appended to the chain hash input. This binds the external receipt to the proof — modifying the receipt content after the fact invalidates the chain hash.
222+
When `provider_payment.receipt_content_hash` is present, its value (with the `sha256:` prefix stripped) is appended to the chain hash input. This binds the external receipt to the proof — modifying the receipt content after the fact invalidates the chain hash.
222223

223224
### What payment evidence proves vs. does not prove
224225

@@ -280,7 +281,7 @@ TIMESTAMP=$(echo "$PROOF" | jq -r '.timestamp')
280281
BUYER=$(echo "$PROOF" | jq -r '.parties.buyer_fingerprint')
281282
SELLER=$(echo "$PROOF" | jq -r '.parties.seller')
282283
UPSTREAM=$(echo "$PROOF" | jq -r '.upstream_timestamp // empty')
283-
RECEIPT_HASH=$(echo "$PROOF" | jq -r '.payment_evidence.receipt_content_hash // empty' | sed 's/sha256://')
284+
RECEIPT_HASH=$(echo "$PROOF" | jq -r '.provider_payment.receipt_content_hash // empty' | sed 's/sha256://')
284285

285286
# 2. Recompute chain hash
286287
# Linux:
@@ -351,7 +352,7 @@ pub.verify(b64url_decode(sig_b64), chain_hash.encode("utf-8"))
351352

352353
### What the signature covers vs. does not cover
353354

354-
**Covered** (via the chain hash): `hashes.request`, `hashes.response`, `payment.transaction_id`, `timestamp`, `parties.buyer_fingerprint`, `parties.seller`, `upstream_timestamp` (if present), `payment_evidence.receipt_content_hash` (if present).
355+
**Covered** (via the chain hash): `hashes.request`, `hashes.response`, `payment.transaction_id`, `timestamp`, `parties.buyer_fingerprint`, `parties.seller`, `upstream_timestamp` (if present), `provider_payment.receipt_content_hash` (if present).
355356

356357
**Not covered** (mutable metadata): `identity_consistent`, `timestamp_authority` status, `transaction_success`, `upstream_status_code`, `disputed`, `dispute_id`. These fields are informational and may change after proof creation.
357358

@@ -367,13 +368,36 @@ A proof MAY be corroborated by independent witnesses:
367368
|---------|---------------|--------------|-------------|
368369
| **Ed25519 Signature** | Proof was issued by ArkForge | Verify `arkforge_signature` with `arkforge_pubkey` | All plans |
369370
| **RFC 3161 Timestamp** | Proof existed at claimed time | Verify `.tsr` file via `openssl ts -verify` | All plans |
371+
| **Sigstore Rekor** | Chain hash registered in append-only public log | See section 7.1 | All plans |
370372
| **Stripe** | Payment occurred | Check `payment.transaction_id` on Stripe dashboard or API | Pro plan only |
371-
| **External Receipt** | Receipt content at time of proof | Fetch `payment_evidence.receipt_url`, hash content, compare to `receipt_content_hash` | When `payment_evidence` is present |
373+
| **External Receipt** | Receipt content at time of proof | Fetch `provider_payment.receipt_url`, hash content, compare to `receipt_content_hash` | When `provider_payment` is present |
372374

373-
Free tier proofs have 2 witnesses (Ed25519, RFC 3161). Pro proofs add Stripe as a 3rd witness. Proofs with external payment evidence add the receipt as an additional witness.
375+
Free tier proofs have 3 witnesses (Ed25519, RFC 3161, Sigstore Rekor). Pro proofs add Stripe as a 4th witness. Proofs with external payment evidence add the receipt as an additional witness.
374376

375377
No witness is required for chain hash verification. Each adds an independent layer of trust.
376378

379+
### 7.1 Transparency log (Sigstore Rekor)
380+
381+
Rekor is an append-only public transparency log operated by the Linux Foundation under the Sigstore project. When present, `transparency_log` contains:
382+
383+
```json
384+
{
385+
"provider": "sigstore-rekor",
386+
"status": "verified",
387+
"uuid": "24296fb...",
388+
"log_index": 12345678,
389+
"integrated_time": 1709500000,
390+
"log_url": "https://rekor.sigstore.dev/api/v1/log/entries/24296fb...",
391+
"verify_url": "https://search.sigstore.dev/?logIndex=12345678"
392+
}
393+
```
394+
395+
If Rekor is unavailable at proof creation time, `status` is `"failed"` and the proof remains valid (all other witnesses are unaffected).
396+
397+
**Important**: `transparency_log` is post-chain-hash metadata. It is populated after the chain hash is computed and **does not affect the chain hash formula**. Verifiers MUST NOT include `transparency_log` in chain hash recomputation.
398+
399+
**Independent verification**: Visit `verify_url` or fetch `log_url` directly to confirm the chain hash was registered in the public log without relying on ArkForge.
400+
377401
## 8. Test vectors
378402

379403
See [`test-vectors.json`](test-vectors.json) for machine-readable test cases.

test-vectors.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"algorithm": "SHA-256",
44
"canonical_json_rule": "json.dumps(data, sort_keys=True, separators=(\",\", \":\"))",
55
"chain_formula": "SHA256(request_hash + response_hash + transaction_id + timestamp + buyer_fingerprint + seller [+ upstream_timestamp] [+ receipt_content_hash])",
6-
"note": "transaction_id = payment.transaction_id from the proof JSON (pi_*, crd_*, or free_tier). buyer_fingerprint = SHA256(api_key). All concatenation uses raw strings, no separator. upstream_timestamp is appended only when present and non-null in the proof JSON. receipt_content_hash is appended only when payment_evidence.receipt_content_hash is present (stripped of sha256: prefix). In test vector inputs, the field is named payment_intent_id for historical reasons.",
6+
"note": "transaction_id = payment.transaction_id from the proof JSON (pi_*, crd_*, or free_tier). buyer_fingerprint = SHA256(api_key). All concatenation uses raw strings, no separator. upstream_timestamp is appended only when present and non-null in the proof JSON. receipt_content_hash is appended only when provider_payment.receipt_content_hash is present (stripped of sha256: prefix). In test vector inputs, the field is named payment_intent_id for historical reasons.",
77
"vectors": [
88
{
99
"name": "minimal_transaction",

0 commit comments

Comments
 (0)