Quantum Shielddocs

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, nextLeaf becomes ℓ + 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:

  • key becomes newKey, nextLeaf returns to 0, and epoch rises 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.

On this page