Guide
Sécurité
Ce contre quoi nous nous défendons, comment les clés sont conservées aujourd'hui et les promesses que nous ne faisons volontairement pas.
Cette page est disponible en anglais pour le moment.
The model
- Bounded. The allowance certificate fixes a cap (funded plus credit), a per-payment limit, a maximum number of payments and an expiry. Country packs and KYC tiers keep those numbers low.
- Detected. Every payment carries a sequence number, a running total and the hash of the previous payment. Two payments that claim the same place in the chain are a fork, and settlement flags it.
- Recovered. A detected fraud revokes the allowance, opens a fraud case and emits
fraud.detected. Any shortfall is taken from the holder's wallet before the risk pool.
Threat model
| Threat | Mitigation |
|---|---|
| Same allowance spent twice at different merchants | Low caps; fork detection at settlement; revocation; recovery from the holder, then the risk pool |
| Replay to the same merchant | One-time request nonce, and the merchant rejects payments it already holds |
| Replay to a different merchant | Every payment is signed over the merchant ID |
| Counter rollback on the device | The hash chain turns a rollback into a fork |
| Payment forged by a merchant | Needs the payer's device private key |
| Forged allowance | Needs the partner's issuer private key; merchants check it against cached issuer keys |
| Stolen unlocked phone | PIN before signing; limited remaining allowance; the allowance can be revoked |
| Revoked or expired allowance | Checked offline against the last synced list and the certificate's expiry. Risk inside the sync window is accepted and bounded |
| Device clock set wrong | Device clocks are not trusted; merchants allow 5 minutes of skew, and payments are ordered by sequence number, not time |
What a merchant checks offline
verifyBundle runs on the merchant's device with no network. It rejects a payment if any of these fail:
- The issuer key is known and the allowance signature is valid.
- The allowance is inside its validity window and not on the cached revocation list.
- The payer's signature is valid for the device key named in the allowance.
- The payment names this merchant and answers this request (nonce, amount, currency).
- The amount is within the per-payment limit, the running total within the cap, and the count within the maximum.
- The payment links correctly to the ones this merchant already holds from the same allowance, with no fork.
A merchant only sees its own payments. A double-spend across two merchants passes both offline checks and is caught at settlement. That is the bounded risk.
How keys are held
| Key | Where it lives today |
|---|---|
| Payer device key (ECDSA P-256) | Generated in the browser as a non-extractable WebCrypto key and stored in IndexedDB. It can sign but cannot be exported |
| App PIN | Never stored. PBKDF2-SHA-256 with 150,000 iterations and a random salt. Five wrong tries lock signing for five minutes |
| Partner issuer keys (ECDSA P-256) | On the server, encrypted at rest with AES-256-GCM under PV_MASTER_KEY |
| Partner API keys | Shown once as pv_<prefix>_<secret>. Only a SHA-256 hash of the secret is stored, compared in constant time |
| Webhook secret | Used to sign each delivery with HMAC-SHA-256 |
See Webhooks for how to verify signatures.
Known limits of the reference implementation
The reference wallet is a web app. That is useful for pilots and has limits a native SDK will remove:
- A browser does not guarantee that a non-extractable key is backed by secure hardware, and it offers no device attestation. Code running in the wallet's origin can ask the key to sign.
- The PIN gates the wallet's signing flow. It is not bound into the key itself.
- The offline queue in IndexedDB is not encrypted beyond what the operating system provides.
- Issuer keys are sealed with a single master key. Production deployments should move them to a KMS or HSM.
- Development shortcuts (email sign-in without Supabase, a fixed master key) are refused when
NODE_ENVisproduction. - No independent security audit has been completed yet.