11 MARKETSUSDG10 S GRIDNEXT FILL 00:10
//11_ Docs

Q-ACCOUNTS

//01_ WHAT IT IS

A Q-Account is a second trading account, a small contract that the exchange treats like any trader. It has no ECDSA owner. The only thing that can move what it holds is a Winternitz one-time signature made with a key kept in your browser, and the contract checks that signature itself.

Wallet keys are ECDSA keys. A large enough quantum computer running Shor's algorithm could recover one from its public key, which is on chain once the wallet has sent a transaction. Winternitz signatures are made of keccak-256 hash chains. The best known quantum attack on a hash is Grover's search, which leaves 128 bits of security. It is the same family as SLH-DSA, the hash-based standard NIST published in 2024 (FIPS 205).

//02_ OPENING ONE

  1. 01
    In the app, open the Q-Account tab and press Create a Q-Account. Your browser draws a 32-byte secret from its secure random source and shows it as 24 words.
  2. 02
    Write the 24 words down, in order. They are the only way to recover the account: nobody can reset it, not us and not your wallet.
  3. 03
    Choose Q-Account as the account you trade with, then deposit from the Account tab. Your wallet signs a USDG permit for the factory and sends one transaction that creates the account, moves the USDG into its exchange balance and gives it 0.00004 ETH for its one-click key.

The account address is fixed by its first key (CREATE2 with salt keccak256(seed, keyHash)), so it is known before it exists. Later deposits go through fundWithPermit. USDG sent straight to the address is moved into trading by sweep(), which anyone may call: it can only move funds into the account's own exchange balance.

//03_ ONE SIGNATURE

The parameters: w = 16, n = 32 bytes, keccak-256, 67 chains of 15 steps. Each step is a distinct hash:

Chain step
F(seed, adrs, x) = keccak256(seed || adrs || x),  adrs = ("PHQK" << 224) | (key << 32) | (chain << 8) | step
  1. 01
    The digest binds everything: keccak256(TYPEHASH, chainId, account, keyIndex, nextKeyHash, gasMin, keccak256(calls)).
  2. 02
    Its 64 hex digits di pick a position on chains 0 to 63. The checksum c = sum(15 - d_i), at most 960, is written as 3 more digits for chains 64 to 66. Raising any digit lowers the checksum, so a forger would need to go down a chain, which means inverting keccak-256.
  3. 03
    Chain i of key k starts at a secret sk = keccak256("paperhands.q.sk" || master || k || i). The signature word is that secret hashed di times. 67 words, 2,144 bytes.
  4. 04
    The contract hashes each word the remaining 15 - d_i times and hashes the 67 ends together. The result must equal keyHash, the hash of key k's public key that the account stores.
  5. 05
    Then it stores nextKeyHash and moves to key k + 1. The next key was part of the signed digest, so nobody can swap it.

A Winternitz key is safe for one signature only. The contract enforces it by rotating on every verified signature. The app enforces the other half: it keeps each signed batch until it lands and refuses to sign a second one with the same key. If a batch does not land, the same batch is sent again.

//04_ WHAT NEEDS THE QUANTUM KEY

ACTIONSESSION KEYQUANTUM KEY
requestOpen, requestCloseyes, for the Q-Accountyes
withdrawnoyes
collectClaims, claimRewardsnoyes
authorizeSession, revokeSessionnoyes
any transfer out of the accountnoyes

A withdrawal is one signed batch of two calls: withdraw(amount) on the exchange, then a USDG transfer to your wallet. The batch runs all or nothing. If it fails on chain (more than your available balance, say), the key is still spent and nothing moves; you sign the next batch with the next key.

//05_ WHO PAYS THE GAS

The keeper relays signed batches and pays their gas, at https://paperhands-keeper.fly.dev/q/relay. The signature is the only authority: the relay cannot change a byte of the batch, and anyone may relay the same payload. The relay serves accounts of this factory, batches that call the exchange, USDG and the token, with hourly limits. When it refuses or is down, the app asks your wallet to send the batch; the wallet then pays the gas and nothing else.

A batch carries a signed gas floor, gasMin, so nobody can relay it with too little gas to make it fail.

//06_ CONTRACTS

The factory has no owner and holds nothing. It deploys accounts, funds them in one transaction, and counts the post-quantum signatures they verify (signatureCount()). Each account stores its seed, its current key hash and its key number, and exposes execute(calls, nextKeyHash, gasMin, sig), sweep() anddigest(...). Neither contract can be upgraded.

//07_ LIMITS

//The key lives in this browser_
In localStorage under ph.q.<wallet>, never sent anywhere. Malware on your device could read it.
//Lose the words, lose the account_
Without the 24 words and this browser, nothing can move the funds again.
//One device at a time_
Two devices signing with the same key at once could reveal two signatures of one key.
//No external audit_
Tested with unit tests, fuzzing and a mainnet fork against the live exchange.

Credits: hands from Michelangelo, The Creation of Adam (detail), public domain, via Wikimedia Commons. Marks of the markets and integrations belong to their owners (sources, credits).