v135 · payments

Cross-Device Replay Demo

Browser-bound keys are non-syncing: they live on the device that created them. This demo enrolls Device A with the backend, creates a payment assertion, then sends a replay attempt signed by Device B's browser-bound key so the server can reject it.

checking WebCrypto…
Secure Payment Confirmation browser-bound keys bind the payment assertion to the specific device hardware. Even if an attacker steals the raw assertion bytes, they cannot forge a valid signature from a different device — the server rejects it because the public key on file is from Device A, not Device B.

Device keys

Device A (legitimate)

Public key (registered with bank):

Device B (attacker)

Public key (different device, NOT registered):

Attack scenario

Steps

How the server detects the replay

// Server-side verification (simplified) async function verifyPaymentAssertion(assertion, storedPublicKey) { const { challenge, deviceSignature } = assertion; // The server verifies the signature using the PUBLIC KEY // it registered with Device A at enrollment time. // An assertion signed by Device B will fail — different key pair. const valid = await crypto.subtle.verify( { name: 'ECDSA', hash: 'SHA-256' }, storedPublicKey, // ← Device A's public key, registered at enrollment deviceSignature, // ← bytes signed by Device B's PRIVATE key (stolen/replayed) challenge ); // valid === false for cross-device replay // The attacker's Device B key pair is completely separate. // No amount of raw assertion bytes helps without Device A's private key. return valid; }