Accounts, leaves and epochs
How the vault turns a one-time signature scheme into an account that can sign for as long as it likes.
An account is its first key
An account is named by a bytes32: the first key its owner generates. Nothing is registered. shield credits shares
to any name, and the first signed operation proves somebody holds the key behind it.
struct Account {
bytes32 key; // the current key; zero until the first key change, when the account's name is its key
uint64 nextLeaf; // the lowest leaf of the current key still accepted
uint64 epoch; // how many times the key has changed
}keyOf(account) returns the stored key, or the account's name while no change has happened.
Leaves only go forward
A hash-based leaf may sign one message. The vault enforces the on-chain half of that rule with a counter:
- An operation signed with leaf
ℓis accepted only ifℓ ≥ nextLeaf. - When it lands,
nextLeafbecomesℓ + 1.
Leaves do not have to be used in strict order, but they can never go back. A signature that was made and never submitted dies as soon as a later one lands. There is no nonce to manage and no replay: the leaf is the nonce.
Changing the key
An operation may carry newKey. When it lands:
keybecomesnewKey,nextLeafreturns to 0, andepochrises by one.- The account's name, shares and history are untouched.
The vault rejects a change to the key the account already has (KeyUnchanged): the same key again would start its
leaves over.
Epochs
Every signed digest includes the account's epoch:
digest = keccak256(abi.encode(OP_DOMAIN, chainId, vault, epoch, keccak256(abi.encode(op))))with OP_DOMAIN = keccak256("QuantumVault.Op.v1"). A signature is therefore valid on one chain, for one vault, in one
epoch. Signatures made by any earlier key stay dead forever, even if that key were ever installed again.
What an owner signs
struct Op {
bytes32 account; // the account spending
bytes32 newKey; // if non-zero, the account's key from now on
bytes32 to; // the account receiving transferShares
uint256 transferShares;
uint256 exitShares; // leave the vault as the token
uint256 gasFee; // shares burned: their tokens stay, a donation to every account
address recipient; // receives the exit, or what exitTarget leaves of it
address exitTarget; // if non-zero, the contract that handles the exit
bytes exitData; // parameters for exitTarget
address caller; // if non-zero, only this address may submit
uint256 minCallGasLimit; // ERC-4337: the least gas the execution step may be given
uint48 deadline; // if non-zero, void after this time
}One operation can transfer, exit and change the key at once. Amounts are in shares.
The wallet's half of the rule
The vault sees only signatures that reach it. The wallet keeps the record of every leaf that has signed, and writes it
to durable storage before a signature leaves the signer. In the SDK this is LeafSigner:
- A digest that was already signed gets the same leaf and the same signature back, so resubmitting or repricing an operation never consumes a new leaf.
- A new digest gets the next leaf above both the wallet's record and the vault's counter.
- A wallet restored from its secret, with no record, starts a margin of leaves above the vault's counter
(
skipWhenUnknown; the app uses 8).
Capacity
The app forges trees of height 8: 256 signatures per key, built in the browser by a background worker. The vault accepts any height up to 20 (1,048,576 signatures per key). Since a key change costs one signature, an account's lifetime is unbounded at any height.