Solana checkout addresses
How each Solana checkout gets a fresh address from a public key, the derivation to reproduce it, and moving the funds with only the recovery phrase.
Every Solana checkout pays to a fresh address that no other payment uses. 402pay derives these addresses from a public key your browser shares, as EVM, Tron and Bitcoin checkouts do from an account's extended public key, so it never holds a key that can move your funds. This page publishes the derivation, so anyone can reproduce it.
What they are
When you create or import a wallet, your browser derives a Solana checkout key from the recovery phrase and shares only its public half: a public key and a chain code. 402pay derives checkout address n from it for checkout n. Only the recovery phrase makes the private keys behind these addresses, so only you can move what they hold. The checkout key can't spend anything, but it links every checkout address to your wallet, as any extended public key does.
Where funds show
- The main address,
m/44'/501'/0'/0', is the one Solana wallet apps show when you restore your recovery phrase, and the one Receive shows in the dashboard. - Checkout addresses sit on a branch of their own,
m/402'/501'/0', whose children use public (soft) derivation. Wallet apps don't scan it, so they don't show these funds. - Move them with Send in the dashboard, which signs for checkout addresses in your browser, or with nothing but your recovery phrase on the Solana recovery page.
The derivation
402pay Solana checkout keys, version 1, is BIP32-Ed25519 (Khovratovich and Law, the V2 scheme of Cardano's ed25519-bip32) from a SLIP-0010 root. Below, B is the Ed25519 base point, l its group order, 2^252 + 27742317777372353535851937790883648493, numbers are read and written little-endian, x[a..b] is bytes a up to b, and || joins bytes.
seedis the BIP39 seed of the recovery phrase, its words in lowercase and one space apart, with no passphrase.- SLIP-0010 derivation for Ed25519 of
seedalongm/402'/501'/0', all three steps hardened, gives a 32-byte keykand a 32-byte chain codec. Purpose 402' keeps the branch apart from them/44'/501'accounts wallet apps show. h = SHA-512(k),kL = h[0..32]andkR = h[32..64]. ClampkL:kL[0] &= 0xF8,kL[31] &= 0x1F,kL[31] |= 0x40. The root public key isA = kL * B.- The checkout key your browser shares is
base58(A || c): 64 bytes in Bitcoin's base58 alphabet, with no checksum. A valid one decodes to exactly 64 bytes, is at most 88 characters with no whitespace, and itsAis a canonical encoding of a point in the prime-order subgroup that isn't of small order, what libsodium'scrypto_core_ed25519_is_valid_pointaccepts. - Child
i, for0 <= i < 2^31:Z = HMAC-SHA512(key = c, data = 0x02 || A || le32(i)),ZL = Z[0..28]andZR = Z[32..64]. Its public key isA_i = A + (8 * ZL) * B, and checkout addressiisbase58(A_i). Its private key, which only the phrase can make, iskL_i = kL + 8 * ZL(a 256-bit addition, not reduced) andkR_i = (kR + ZR) mod 2^256, andkL_i * Bmust equalA_i. - To sign message
Mwith childi:r = SHA-512(kR_i || M) mod l,R = r * B,x = SHA-512(R || A_i || M) mod landS = (r + x * (kL_i mod l)) mod l. The signature isR || S, and it verifies under standard Ed25519 verification (RFC 8032), strict checks included.
Indexes
- Solana index 0 is the main address's slot, so child 0 of the checkout key is never handed out.
- Checkout
n, from 1 up, pays to childn. Indexes count up per wallet and never go back, so an address never serves two payments. Card payments that settle on Solana take one too. - To find funds, scan from child 0.
address_counts.solanaonGET /walletis one more than the highest Solana index handed out, so it says how far to look. - A child whose public key would be the identity point is skipped and never handed out. The chance of one is about 2^-250.
Test vectors
From the published BIP39 test phrase, which is never a wallet to send funds to. Independent implementations give the same values. The message to sign is hex, the bytes of "402pay".
{ "phrase": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", "main_address": "HAgk14JpMQLgt6rVgv7cBQFJWFto5Dqxi472uT3DKpqk", "checkout_key": "sHz8kwCJhANstUZXuSv3SDDFbcVmqpH2ojjvY1VNvCPSTcqm9rx2r65ECEryDo3oxWpZCv9mMmi4twijuvbf7F7", "children": [ { "index": 0, "address": "FFWTFNVcavC2RVTDsjNHfJwPAZuSaKhvABNk7goBWkTX" }, { "index": 1, "address": "EgsxatR8QNRxCBkf6ZBf3Z9uoaeN23yBhFWn2sk91h3E" }, { "index": 2, "address": "ERRm1NNCfSnBaf9YzrSJwKVDYR5LfREkaRarxGWbyJpf" }, { "index": 2147483647, "address": "7FMbTxB3Ke1NFknB1SCZJ2crt4pwmSTz3WNPSmzhtqB9" } ], "signature": { "index": 1000, "address": "HP3xenGuaNHgbeLhrZ4W7th5nqxCxwscYNHLi2KtSU5p", "message": "343032706179", "signature": "1095be7d64ba627ddd82081fc35a7f76e00a64a289656949ea2e0db101905f6f40cc8fb2ea410ae779bf8a90e67673bb5e81b18ecab3f277b876da13ba796d06" }}Recover with your own code
You don't need 402pay's code to move these funds. Anything that follows the steps above finds the same addresses and makes the same keys: any Ed25519 library that signs with an expanded key (kL, kR) can sign for a checkout address, and BIP32-Ed25519 libraries, such as Cardano's ed25519-bip32, derive the same children from the root key (kL, kR, c). The signature is a standard Ed25519 signature, which Solana validators accept like any other, so a transfer from a checkout address is an ordinary Solana transaction.
- Derive the main address and the checkout key from your recovery phrase.
- Derive checkout addresses from child 0 up, and read each one's SOL and its USDC and USDT associated token accounts from any Solana RPC.
- For each funded address, sign a transaction with its child key that moves its tokens and SOL where you choose, with an address that holds SOL, such as the main address, paying the fee.
Next steps
- Wallet and deposits The three kinds of wallet, how each payment lands in yours, and who holds the keys.
- Security best practices Narrow keys, rotations without downtime, a locked-down webhook endpoint and a secure account.
- Supported networks Every coin and network checkout accepts, and the confirmations each one needs.