>05 // how it works

How it works

A walk through the Noxvault v3 spec. The program, the SDK, the relayer, the keeper and this site all follow the same document.

Why hashes

A normal Solana wallet is one ed25519 key. Its security rests on elliptic curves, and a large enough quantum computer running Shor's algorithm could work out the private key from the public one. Hash functions do not have that weakness: the best known quantum attack (Grover) only speeds up brute force, which 256-bit SHA-256 comfortably absorbs.

Noxvault guards funds with WOTS+ one-time signatures built only from SHA-256. No ed25519 key, not even yours, can move what is inside a vault. Your ordinary wallet can still be protected around the edges: see the Escape Hatch.

One address, many keys

A one-time key may sign only one message. A vault therefore holds trees of them: each signer tree has 256 one-time keys ("leaves") under a Merkle root, the idea behind XMSS. A vault has up to 3 signer trees and an optional recovery tree of 16 keys. One fixed vault address serves every action; the vault keeps a bitmap of used leaves, so each one works exactly once.

The vault address is a program address derived from a hash of the full config: the tree roots, the approval threshold, the recovery delay, the whitelist, the delay and instant limit, the heirs and the testament delay. Nobody, you included, can change a vault's rules after the fact. Want different rules? Make a new vault and migrate.

Your words

Everything starts from 24 BIP39 words, a 32-byte secret S. Vault number v = 0, 1, 2… lets one phrase run several vaults. Each tree gets its own sk_seed and pk_seed, hashed from S and v; a recovery tree uses v + 2³¹, so one phrase never produces the same keys in two roles. Building a signer tree is about 2.2 million SHA-256 calls: a few seconds in a background worker of your browser. The words never leave the tab.

What gets signed

Each leaf signs one action, bound to the program, the vault, the tree and the leaf number:

  • Withdraw SOL or a token to a destination.
  • Stake, Unstake, Collect stake: native staking from inside the vault.
  • Cancel the pending withdrawal or migration; Ping to prove the owner is alive.
  • Schedule / Cancel schedule, Budget / Cancel budget, Migrate.

Every action also names a fee and a fee_payee: lamports the vault pays when the action executes. That is how a relayer gets paid, so you never need a wallet. With fee 0 you send it yourself.

Approvals & multisig

A verified signature does not move funds by itself. It becomes an approval of the action's hash, recording which tree approved. An action executes (anyone may trigger it) once:

  • signer approvals reach the vault's threshold (1, 2 or 3 of up to 3 signer trees), or
  • the recovery tree approved a Cancel, Cancel schedule or Cancel budget, or
  • the recovery tree approved anything and the owner has been inactive for the recovery delay.

For a 2-of-3 vault, each co-signer keeps their own 24 words; the vault commits to all three roots. The first signer's approval waits on chain; the second signer approves exactly the same action and it executes. An approval that never completes can be discarded after 7 days. Signer approvals reset the vault's last_active clock.

Rules: whitelist, instant limit, delay

Up to 4 addresses can be whitelisted. A destination is restricted when a whitelist exists and it is not on it, or when there is no whitelist but an instant limit is set.

  • Instant limit: restricted SOL withdrawals execute at once while they stay within this many lamports per 24 h.
  • Delay: anything else restricted becomes the vault's single pending withdrawal, executable by anyone after the delay and cancellable by a signer or the recovery key before then. With delay 0, restricted withdrawals are refused outright.
  • Tokens to a restricted destination always wait (or are refused).

A thief with your words then cannot drain the vault quietly: they can take at most the instant limit, and everything else shows up as a pending withdrawal you can see and stop.

Recovery key

Optionally a vault has a recovery tree of 16 keys from a separate phrase, ideally held by someone else. It can cancel pending withdrawals, schedules and budgets at any time, and once the signers have been silent for the recovery delay (180 days by default) it can do everything, unrestricted. A Ping resets the clock. Manage it on /recovery.

Testament

Name up to 4 heirs with shares in basis points (summing to 100%) and a testament delay (365 days by default). When the owner has been inactive that long, anyone may distribute all SOL and every token to the heirs by share, and deactivate and collect stakes first. Keepers do this automatically. Any signer action, including a Ping, postpones it.

Schedules

One signature sets up recurring payments: amount every interval, count times, from start. Anyone can run a payment once it is due; the keeper does. A schedule to a restricted destination starts no earlier than the delay (refused with delay 0). Cancel it with a signer or the recovery key.

Session budgets: the daily-use path

Hash signatures are heavy for buying a coffee. A budget lets an ordinary ed25519 session key (for example your Phantom wallet) spend up to a limit per period from the vault until expires, and only to non-restricted destinations. That is the daily path: Phantom signs normal transactions, the vault caps what it can ever lose. Lose the session key and you lose at most what is left of the budget; cancel it with one hash signature.

Migration

Keys running out, or rules to change? Sign Migrate to a new Noxvault vault. It is restricted unless the new vault is whitelisted (then it waits out the delay and can be cancelled). Once it executes, anyone moves all SOL and every token to the new vault; the keeper finishes it.

Invoice addresses

inbox(vault, id) is a separate address per id (PDA["inbox", vault, id]). Anyone can pay SOL or tokens into it and anyone can sweep it into the vault. Give one per customer or payer: per-payer accounting, without publishing the vault address.

Tokens

Everything above works for SOL, SPL tokens and Token-2022 tokens: withdrawals, rules, schedules, budgets, testament, migration, invoices and the Escape Hatch. Token-2022 mints with transfer fees, transfer hooks, permanent delegates, confidential transfers or non-transferable tokens are refused before signing. A token whose issuer holds a freeze authority can still be frozen by that issuer.

Escape Hatch & registry

The vault is safe, but your ordinary wallet is still one ed25519 key. The Escape Hatch gives it a fire exit into your vault:

  • Link: the wallet signs register_link(vault). This is also a timestamped registry entry: proof, signed while ed25519 is still safe, that this wallet belongs to that vault. Changing the vault takes 30 days and is impossible after the canary trips.
  • Tokens: the wallet approves PDA["escape", wallet] as delegate of its token accounts. escape_token moves a delegated balance into the linked vault, and only after panic or a tripped canary.
  • SOL: the wallet pre-signs a durable-nonce transaction calling escape_sol and stores it in its link. Anyone may broadcast it, at any time; it can only move this wallet's SOL into this wallet's own vault. Using the nonce for anything else voids it; re-arm then.
  • Panic: the wallet itself can trigger the hatch at any time if it thinks its key is compromised.

Honest limits. It protects the moment it is triggered. It is a race: if an attacker breaks your key and moves first, the hatch is too late. Tokens received after approving need approving again. Keep most funds in the vault; the hatch is for what stays outside. Check any address with the scanner.

Quantum canary

PDA["canary"] watches a fixed ed25519 address found by hashing to the curve: SHA256("noxvault/canary/v1" || u32le(i)) for the first i that is a valid point (i = 0). Nobody knows its private key. It holds a bounty; trip_canary (anyone) succeeds only once the balance falls below the recorded baseline, which needs ed25519 to be broken. Then keepers evacuate every linked wallet. It is a public alarm, not a guarantee: an attacker may break your key without touching the bounty. See /canary.

Relayer & keeper

A relayer sends your signed action as fee payer and is repaid through its fee. A keeper runs the permissionless instructions: due schedules, pending withdrawals after their delay, testaments, migrations, the canary check and Escape Hatch evacuations. This site runs one as a daily cron; a long-running keeper can pass every minute (see bot/README.md).

Both are conveniences, not trust assumptions. Neither can change anything that was signed: one changed byte and the signature no longer verifies, and permissionless instructions only do what the rules already allow. The worst they can do is refuse or be slow; then anyone, you included, can send the same transactions.

On-chain flow

A signature (1,376 bytes) does not fit one transaction, so it is assembled in a buffer:

  • open_unlock: creates the buffer with the tree, leaf, pk_seed, auth path and action.
  • write_sigs: fills in the 34 chain values. The vault is named, so every signature byte is in the vault's public history.
  • verify: walks the chains to their ends, split over transactions to fit the compute limit.
  • approve: rebuilds leaf and root, burns the leaf, records the approval.
  • execute: runs the action once the approval rules are met. Anyone may call it.

Every step can be retried, and none can redirect funds: the action is inside the signed message.

One key, one message

If one leaf ever signs two different messages, an attacker can combine them to forge a third. The program refuses a used leaf, but a signature that was only partly published is still out there. So before signing, this app reads the vault's entire history, failed transactions included, and recognises every value your keys ever published. A leaf with an unfinished message can only complete that same message. If the history cannot be read, the app refuses to sign.

Offline & backups

Your words never need to touch an online device: sign on /offline and carry the result to /submit. /backup splits a phrase into Shamir shares or encrypts it into a file, locally. Back up every phrase (signers, recovery) separately.

Parameters

hashSHA-256, n = 32 bytes
Winternitz w256 (one digit = one byte)
chains32 message + 2 checksum = 34
signer treesup to 3, height 8 = 256 one-time keys each
threshold1 to the number of signer trees
recovery treeheight 4 = 16 one-time keys (optional)
signature34 × 32 + 32 pk_seed + 8 × 32 path = 1,376 bytes
whitelistup to 4 addresses
heirsup to 4, shares in basis points
link change30 days
stale approvaldiscardable after 7 days

The program stays upgradeable during testing. See risks.