Skip to main content

KeyVault and key management

Every account on Quantum has an on-chain KeyVault — a per-account, mutable set of authorized keys (up to 256) managed by the native KeyVault precompile at fixed address 0x1000. Each key entry is structurally PQ-safe: the primary field is mandatory and must be a scheme on the protocol's approved-primary list (today: [ML-DSA-44]). The cosigner field is optional. PQ security is enforced by the shape of the data, not by a policy flag.

KeyEntry shape

Each account's KeyVault holds up to 256 KeyEntry records. The table below shows the fields of a KeyEntry and three representative examples.

FieldTypeExample (key 0)Example (key 1)Example (key 2)
idu32012
primaryScheme + pubkeyML-DSA-44(pk1)ML-DSA-44(pk2)ML-DSA-44(pk4)
cosignerOptional scheme + pubkeyNone (PQ-only)P256(pk3) (passkey cosigner)ECDSA(pk5) (institutional cosigner)
permissionFullAccess or ScopedFullAccessFullAccessScoped — contracts: [0xDEX...], methods: ["swap(bytes)"], allowance: 1000 QETH, expiry: 1735689600

Storage model. KeyVault uses keccak-hashed slot addressing (not sequential) for EVM storage layout. Public keys are stored as their keccak-256 hashes (not raw bytes) to keep storage costs manageable for large PQ keys. The raw pubkey lives in chunked storage indexed by hash.

Permission levels

Quantum supports exactly two permission levels per key:

  • FullAccess (byte 0) — unrestricted. Can sign any transaction and perform any operation including addKey / removeKey. Auth-method updates (updateKeyAuth) are self-scoped: only the authenticated key lane can update its own primary or cosigner material (auth_key_id == target_key_id).
  • Scoped (byte 1) — restricted to specific contracts (up to 64 addresses), methods (up to 64 selectors), a native value allowance (max wei per tx), and an expiry timestamp (0 = no expiry). A trading desk gets a key that can only call specific DeFi contracts with a spending cap, while the treasury FullAccess key stays in the Mithril vault.

Cosigner policy is orthogonal. Any key, FullAccess or Scoped, can independently adopt or remove cosigner hedging via explicit updateKeyAuth lifecycle operations. Cosigner downgrade (removing an active cosigner) requires the current cosigner to have signed the transaction.

Lifecycle operations (reference)

Key management operations are standard CALLs to the KeyVault precompile at 0x1000, processed inside the EVM execution pipeline. Each lifecycle operation is submitted as a dedicated single-call transaction.

OperationWho can invokeAuth requiredConstraints
bootstrapKey()Any sender with no existing keysPrimary signature only (from init fields)key_count == 0; key_id must be 0; creates FullAccess primary-only; cosigner init fields must be absent
addKey(...)FullAccess holderFullAccess (byte 0)Lifecycle intent required; key_id must not exist; primary scheme must be approved; max 256 keys
removeKey(uint32)FullAccess holderFullAccess (byte 0)Lifecycle intent required; key must exist; cannot remove last FullAccess key (lockout protection)
updateKeyAuth(...)Key holder (any permission)Self-scoped: auth_key_id == target_key_idLifecycle intent required; primary scheme must be approved; cosigner downgrade requires cosigner auth
getKeys(address)AnyoneNone (read-only)Callable in STATICCALL

Safety invariants

  1. Lockout protection. The last FullAccess key cannot be removed. full_access_count is checked at removeKey time.
  2. Bootstrap isolation. Bootstrap is primary-only. Cosigner activation requires a subsequent explicit updateKeyAuth.
  3. Lifecycle intent binding. Mutations are bound to the authenticating sender and target key_id via transient storage. Nested contract calls cannot trigger mutations — only direct top-level KeyVault calls can.
  4. Cross-key auth mutation forbidden. updateKeyAuth is self-scoped.
  5. Cosigner downgrade guard. Removing an active cosigner requires the current cosigner's signature.
  6. Primary scheme governance. All primary schemes must appear on the CryptoSwitchboard's approved_primary list.
  7. Key count cap. Max 256 keys per account.
  8. Scoped item caps. Max 64 contracts and 64 methods per scoped key.

Permissions and authorization matrix

CapabilityFullAccess (0)Scoped (1)
Sign arbitrary transactions
Call any contractOnly allowed contracts
Call any methodOnly allowed selectors
Transfer any native valueUp to allowance per tx
Use after expiry✅ (no expiry)❌ (rejected after expiry timestamp)
Invoke addKey
Invoke removeKey
Invoke updateKeyAuth (self)✅ (own key lane only)
Invoke updateKeyAuth (other)

Cosigner policy matrix

Current stateDesired stateRequirements
No cosignerAdd cosignerupdateKeyAuth with cosigner tuple; self-scoped auth
Active cosignerChange cosignerupdateKeyAuth with new cosigner; self-scoped + current cosigner sig
Active cosignerRemove cosignerupdateKeyAuth with cosigner_scheme=0, empty pubkey; self-scoped + current cosigner sig

Scheme placement rules

SchemeAs primaryAs cosignerNotes
ML-DSA-44 (0x01)✅ (if on approved list)Default approved primary; post-quantum
P256 (0x02)Classical; cosigner-only (passkeys / mobile)
ECDSA (0x03)Classical; cosigner-only (hardware wallets)
SLH-DSA (0x04)✅ (if on approved list)Post-quantum; rejected as cosigner (verification stubbed in v1)

Design rationale

Scoped allowance scope. Because the KeyVault is evaluated pre-EVM, the allowance parameter in Scoped permissions enforces limits on the native L1 asset only (QETH value + gas fees), not ERC-20 token amounts. Token restrictions are enforced via the contracts and methods allowlists.

Bootstrap isolation. Bootstrap is always primary-only. The cosigner init fields must be absent. Cosigner activation is a subsequent explicit updateKeyAuth step. This isolation prevents one-shot account creation from inadvertently locking in the cosigner before the user has confirmed the device that holds it.

Lifecycle intent mechanism. During pre-EVM validation, the node writes the validated key_id and permission into a transient storage slot on the KeyVault precompile. During EVM execution, the precompile reads that slot — not msg.sender — to determine the outer transaction's authorization level. A malicious contract called by a Scoped key cannot internally CALL the precompile to escalate privileges because the transient slot does not match.

Contract-controlled accounts. Accounts can remove all FullAccess keys and retain only Scoped keys. This converts the account into a lockbox interactable only through specific methods with spending limits — useful for treasury vaults, escrow patterns, and protocol-owned accounts.

Key rotation. Key rotation is AddKey(new) → verify it works → RemoveKey(old). Each operation is a separate transaction. The address, assets, token approvals, and all on-chain references stay the same. If a new signature scheme replaces ML-DSA-44 on the switchboard's approved-primary list, users add a key with the new scheme and remove the old one — no address change, no asset migration.