Insights

How the XRPL.to Wallet Protects Your Keys

Keys are generated, encrypted and used entirely inside your browser — no seed ever touches our servers. One rule shapes the whole design: the cryptographic parameters are fixed at the top. If a device is too slow to meet them, unlocking takes longer. We never scale the security down to fit the hardware.

128-bit
seed entropy — the XRPL format maximum
AES-256-GCM
authenticated encryption at rest
600,000
PBKDF2 iteration floor — never less
4,000,000
calibrated ceiling on fast hardware

Key generation

Entropy comes from crypto.getRandomValues() — the OS random generator via the Web Cryptography API. No Math.random(), no time-seeded fallback, anywhere. We draw 256 bits; the XRPL family seed format encodes 128 of them — the protocol's structural maximum, shared by every XRPL wallet (ripple-keypairs generateSeed takes exactly 16 bytes). Enumerating a 128-bit keyspace is not a hardware problem; it is a thermodynamics problem.

256 bits drawn from the OS CSPRNG
1
0
1
1
0
1
0
0
1
1
1
0
0
0
1
0
1
1
0
1
0
0
1
1
1
0
0
1
0
1
1
0
■ 128 bits — encoded into the seed■ 128 bits — discarded (format holds no more)
sEd…ed25519 family seed — address verified against the seed before anything is stored

What a stored seed looks like

A seed is written to disk exactly one way: AES-256-GCM inside IndexedDB, key derived from your password with PBKDF2-HMAC-SHA-256. GCM is authenticated encryption (NIST SP 800-38D) — one flipped bit and decryption returns nothing, never a plausible-but-wrong seed. The derived key is non-extractable: the browser will not hand its raw bytes to any JavaScript, including ours. localStorage never holds a seed.

one stored wallet blob — widths not to scale
1 B
4 B
16 B
12 B
versioniterationssaltIV (nonce)ciphertext + 16 B auth tag

Salt and nonce are freshly random on every write — never reused. The header rides outside the ciphertext, but it can't cheat: a blob claiming fewer than 600,000 iterations is rejected before key derivation is even attempted.

The floor and the ceiling

The OWASP recommendation for PBKDF2-HMAC-SHA-256 is 600,000 iterations. That is our minimum. On first use the wallet times a probe, calibrates how many iterations fit in a 250 ms unlock, and re-checks weekly — capped at 4,000,000. A fast desktop doesn't get a faster unlock; it gets a harder key derivation for the same quarter-second.

600,000 — the floor. No device goes below it.4,000,000 — the capslow device: all 600,000 run — unlock just takes >250 msfast device: unlock stays ~250 ms,iterations climb insteaddevice speed →PBKDF2 iterations per unlock

Slow hardware waits — it doesn't get weaker crypto

Calibration only ever moves up from the floor. A low-end phone still runs all 600,000 iterations; its unlock simply takes longer than 250 ms. We price the defense against the attacker's hardware, not the slowest phone's — whoever steals your encrypted blob gets to choose their GPUs. The same policy applies to platforms: the wallet hard-requires HTTPS and crypto.subtle. A browser that can't provide them is unsupported. The bar does not move.

The Coldcard test

Three days before this article, on July 30, 2026, an attacker swept ~594 BTC (≈$38M) in about 25 minutes from Coldcard hardware wallets — devices built for exactly one job: keeping keys safe. Coinkite's advisory and the hotfix changelog put the effective seed entropy at ~40 bits on Mk3 and ~72 bits on later models — instead of 128. The flaw shipped in March 2021 and sat in production firmware for over five years. And it didn't stop at one sweep: by August 2, Galaxy Research counted three attack waves totalling 1,367 BTC (≈$88.6M) across 4,585 addresses.

The firmware is open source, so the fix commit shows the mechanism precisely. The board's hardware-TRNG file never exported the global rng_get() symbol — so the linker silently bound RNG calls to MicroPython's fallback software PRNG ("yasmarang"), compiled into every build for boards without hardware RNG. Its entire seed:

pad = *(uint32_t *)MP_HAL_UNIQUE_ID_ADDRESS ^ SysTick->VAL;
n   = RTC->TR;   // wall-clock time
d   = RTC->SSR;  // sub-second counter

A public chip ID, a tick counter, and the time. Nothing errored — the hardware TRNG sat unused while every seed came from an enumerable keyspace. The hotfix comment says it plainly: "this selected some PRNG code we really didnt want." The fix is the right one: the fallback is now compiled to an empty object so any reference breaks the build, and a symbol check asserts the hardware path is linked on every release.

Coldcard, pre-hotfixThis wallet
Randomness sourcehardware TRNG — silently replaced by a linked-in software PRNGOS CSPRNG via crypto.getRandomValues()
Fallback pathcompiled into every build, waitingnone exists — strong or throw
Effective entropy~40 bits (Mk3) · ~72 bits (Mk4/Q)128 bits
How failure surfacessilent for five yearsloud — refuses to run

This is not a hot-wallet-beats-hardware-wallet story — a Coldcard resists threats a browser never will. The lesson is narrower and harder: key generation is the single point of failure everywhere, an air gap cannot save a seed that was born weak, and no one can tell a weak seed from a strong one by looking at it. The only defense is what this page keeps repeating — the weak path must not exist, and where it cannot exist, it cannot be silently chosen.

Around the ciphertext

HTTPS secure contextno WebCrypto, no wallet — there is no fallback cipher
Brute-force lockoutsescalating delays across three storage layers
Device bindingthe ciphertext refuses to decrypt on another machine
AES-256-GCM · PBKDF2 ≥ 600kthe blob itself — tamper and the auth tag fails
seed
1–4 no delay
5th 60 s
6th 5 min
7th 15 min
8th 1 h
9+ 2 h

Failed attempts, counted in memory, localStorage and IndexedDB at once — worst case wins, so clearing one layer doesn't reset the clock.

  • Cached credentials are XOR-masked with a random pad and expire after 15 minutes — plaintext passwords never sit in the heap.
  • A revealed seed starts blurred and wipes itself on a countdown, on tab switch, and on unmount — and lives outside React state, invisible to DevTools.
  • A portable blob format exists for exactly one flow: the explicit QR transfer between your own devices.

The transaction layer

Every trustline the platform creates sets NoRipple — an open rippling path is an actively exploited way to drain same-currency tokens without a signature. Swap safety values (DeliverMin, slippage) are computed server-side from live ledger state, and transactions are simulated before submission so a failing transaction aborts instead of burning a fee. More in Sandwich Attacks on XRPL.

What this means for you

  • Your seed never leaves your device — there is nothing server-side to breach.
  • Your password is the last line — the iteration floor makes each guess expensive; a strong password makes the math unwinnable.
  • Back up your seed — device binding means a lost device is a lost wallet unless you exported it. The nudge in the wallet is load-bearing.
  • Old hardware works, slowly — a sluggish unlock is the security budget being spent. There is no setting to lower it.