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;
}