mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-09-27 17:58:47 +00:00
plan: correct the unlinkability claim to no stored reference
This commit is contained in:
@@ -81,7 +81,7 @@ Stripe / BTCPay ─POST─▶│ │ web_orders ◀───
|
||||
- **Flavor** — an Android build flavor, `google` or `foss`, spelled as Gradle spells it.
|
||||
- `BadgePrice` always names the protocol catalog row (`Badges/Service.hs`); the clients' fetch-state wrapper is `BadgePriceState` (G4, G5).
|
||||
|
||||
**Linkage between an order and a purchase.** They are joined only through the code, `code = crockford32(HMAC-SHA256(codeSecret, orderId))` truncated to 95 bits plus a check character (decision 9, B3), and `@codes` is keyed by `SHA256(code)`. No row stores both an `orderId` and a `purchaseKey`, so the database alone does not link a browser session to a SimpleX profile. A holder of `codeSecret` can derive the link at will, which puts the operator inside the trust boundary for this property; unlinkability from the operator is not claimed, and `codeSecret` is guarded accordingly (§7, H5).
|
||||
**Linkage between an order and a purchase.** They are joined only through the code, `code = crockford32(HMAC-SHA256(codeSecret, orderId))` truncated to 95 bits plus a check character (decision 9, B3), and `@codes` is keyed by `SHA256(code)`. No row stores both an `orderId` and a `purchaseKey`, so no *stored reference* links a browser session to a SimpleX profile. That is weaker than unlinkability: a settlement-time correlation between `codes.created_at` and `web_orders.settled_at`, helped by the literal `batch = 'web'` label, links the two without `codeSecret` at all (§9, B10). The cryptographic claim — an `orderId` cannot be turned into a code without the secret — is unaffected. A holder of `codeSecret` can derive the link at will, which puts the operator inside the trust boundary for this property; unlinkability from the operator is not claimed, and `codeSecret` is guarded accordingly (§7, H5).
|
||||
|
||||
**End to end:**
|
||||
|
||||
@@ -1309,7 +1309,7 @@ Deferred. All remain defined in the protocol and either return `bad_request` or
|
||||
| A code is a bearer secret to a paid good | 95 bits of entropy, which is the load-bearing control; the checksum is rejected before any database lookup; a global failure budget on `purchaseBadge` and a per-signer throttle that only shapes honest retries (B5); per-IP limits on the HTTP endpoints (H1) |
|
||||
| `orderId` is equivalent to the code: anyone holding it can read the code from `/api/order` | `orderId` is 128 random bits, never logged (H4), never in a `Referer` (D4), and absent from the Stripe `cancel_url` (F1). `code` is withheld once the code is redeemed or revoked (F4, H2), or `codeDisclosureDays` after `max(settled_at, unredeemed_at)` (E4); `codes unredeem` reopens the window (H2). E6 tells the user the URL is equivalent to the code. H5 covers the reverse-proxy access log, which the service does not control |
|
||||
| A `codeSecret` leak makes the code for any known `orderId` offline-derivable | Both secrets are backed up (H5); `orderId` is itself a 128-bit secret, so an attacker needs both. Rotating `codeSecret` destroys code recomputation for every past order (B3), so H5 documents it as a break-glass action to be taken only once every outstanding order is past `codeDisclosureDays`, not as routine hygiene |
|
||||
| The operator can link a card payment to a SimpleX profile | Only with `codeSecret`, which derives the code from any `orderId` and joins it to `redeemed_purchase_id`. No row stores both identifiers (A3, §3), so a database copy without `codeSecret` does not link them. Unlinkability from the operator is not claimed |
|
||||
| The operator can link a card payment to a SimpleX profile | Only with `codeSecret`, which derives the code from any `orderId` and joins it to `redeemed_purchase_id`. No row stores both identifiers (A3, §3), and B10 asserts the two-hop `web_orders → invoices → payments → badge_purchases` join is empty. But a database copy alone still correlates an order to a purchase by settlement timestamp and the `batch = 'web'` label, with no `codeSecret` — so what holds is "no stored reference", not unlinkability. E3/H5 own the mitigation (§9, B10). Unlinkability from the operator is not claimed |
|
||||
| A user is charged in a store and receives nothing | The store purchase action is removed until store evidence is verified (G0, decision 6, §6); iOS and Play users reach a badge only through a redemption code |
|
||||
| A webhook is missed | Every settlement path is idempotent and monotonic toward `paid` (E3); an unprocessed event is reprocessed on retry (D0, E3); H3 reconciles from provider state |
|
||||
| Apple or Google reject the linked-out purchase | Decision 6: the redirect ships only on desktop and Android `foss` |
|
||||
|
||||
Reference in New Issue
Block a user