Security & scope
What Winternitz protects, what it doesn't, the risks we identified, and how each one is handled.
Audit status
Winternitz is a prototype and has not been audited. Do not use it with real funds.
What is quantum-safe#
Authorising your account. Forging a transaction from your account would require reversing keccak256, which quantum computers cannot do in any practical time.
What is not#
- Ethereum and Robinhood Chain consensus. Validators and the chain itself still use classical cryptography. Changing that is the job of protocol upgrades, not wallets.
- The bundler's transaction. The bundler or relayer submits your operation in an ordinary ECDSA transaction. A quantum attacker who controls a bundler could delay or censor your operations, but could not forge them or move your funds.
- Your device. Whoever has your seed controls your account. Both the web wallet and the extension store it encrypted with your password (PBKDF2 and AES-256-GCM) and keep the decrypted copy in memory only while unlocked. Malware on your device, or a malicious script on the wallet's own page, could still capture it while the wallet is unlocked.
- Network connections. TLS to RPC endpoints is outside this project.
Scope of the project#
In scope: the TypeScript WOTS library, the Solidity verifier, the ERC-4337 account with automatic key rotation, the CREATE2 factory, the CLI, a simple web UI, a testnet deployment, and the gas benchmark and documentation.
Out of scope: mainnet deployment and a security audit; making Ethereum consensus quantum-safe; social recovery, multisig and hardware wallets; and a Merkle multi-key scheme (XMSS). These are not part of the prototype; XMSS and the audit are the first and fifth priorities in Future work.
Risks and mitigations#
The biggest risk is a WOTS key being used twice, because that can open a path to stealing funds.
| Risk | Impact | Mitigation | Status |
|---|---|---|---|
| A key is reused because the local key index is out of sync | Critical: funds could be stolen | Always read keyIndex from the chain before signing; refuse on mismatch; write-ahead journal; block new sends while one is pending | Implemented and tested |
| The 2.1 KB signature exceeds the bundler's verification gas | Operation isn't included | Explicit verificationGasLimit floors (400k, 650k with deployment); tested against a local Alto bundler | Implemented |
| Gas cost much higher than ECDSA | Less practical | Accepted for the prototype; measured, with larger w and ZK compression as follow-ups | Measured: see benchmarks |
| Bug in the verifier or rotation | Loss of funds | Testnet only; differential tests against TypeScript; fuzz tests on every byte | Implemented |
| Misunderstanding the security scope | Overclaiming | This page, and the same statement in the README and app | Done |
Guarantees enforced on-chain#
- A signature must be exactly 2,176 bytes; the next key must be non-zero and different from the current one.
- The signed message covers the entire UserOperation (via
userOpHash, which includes the chain ID and EntryPoint) and the next key. - A failed check returns
SIG_VALIDATION_FAILEDwithout changing any state. - A key is consumed as soon as validation succeeds, even if the transaction's call later reverts, so a revealed key is never valid again.
- Only the EntryPoint (or the account itself) can make the account execute, withdraw its deposit, or act in any way. There is no owner key.
Stuck operations#
If a signed operation can never be included (for example, fees rise far above its maxFeePerGas), signing it again at a higher fee would reuse the key. Instead, the wallet replaces it with a recovery key: a second one-time key chain, derived from the same seed, that can authorise one replacement with the same nonce. The account then skips the stuck key entirely, so no key ever signs twice.
If the original operation lands anyway, the recovery key has been revealed without being used. The wallet detects this and asks you to rotate the recovery key, which is an ordinary transaction.
Known limitation#
The key journal is per device, so two devices sharing a seed should not both send at the same time.