Security
Security model
The assumption the vault rests on, the construction, what it protects against, and the NIST context.
The assumption
A vault authorises spending by checking that revealed values hash to a stored commitment. The one assumption is that SHA-256 cannot be inverted. Grover’s algorithm takes a 256-bit preimage search to roughly operations. No signature over any keypair is checked anywhere on the path that moves value.
Everything else, including the wallet that pays fees and the owner_hint a vault records, is outside the trust boundary. If the program ever checked a signature against owner_hint, the vault would be exactly as breakable as the wallet it replaced.
The construction
The lock is plain Winternitz: 34 unkeyed SHA-256 chains of length 255, two of them carrying a checksum, with the chain endpoints hashed under a domain tag into one 32-byte commitment. The argument that a forgery requires a preimage is given in full in The lock.
It is the same family as the hash-based schemes NIST has standardised, which are built from Winternitz-style one-time signatures (LM-OTS, WOTS+). It is not WOTS+ itself: WOTS+ adds per-step masks so that its proof rests on second-preimage resistance rather than collision resistance. metaspace’s lock keeps the chains plain and the checksum a subtraction so that the construction can be checked by reading it.
Properties
| Property | Where it is enforced |
|---|---|
| A signature authorises exactly one withdrawal. | The digest covers the vault, nonce, amount, destination and next commitment. WithdrawalRequest::digest. |
| The checksum chains constrain the walk direction. | chain_steps: raising a message byte lowers the checksum. |
| A commitment is never spent twice. | finalize_withdrawal rotates and increments the nonce in the instruction that pays. |
| A vault cannot rotate to a commitment it already has. | open_withdrawal, CommitmentReused. |
| A vault cannot rotate to zero. | init_vault and open_withdrawal, ZeroCommitment. |
| No instruction that moves value checks a signer. | finalize_withdrawal has no signer at all. |
| Finalisation is permissionless and safe. | Destination constrained to request.destination; rent to request.payer. |
| The vault stays rent exempt. | finalize_withdrawal computes the minimum from Rent and refuses to go below it. |
| One pending withdrawal per vault. | The request PDA is seeded on the vault’s current nonce. |
| A live request cannot be cancelled by a stranger. | cancel_withdrawal requires expiry while the nonce still matches. |
| Chains arrive in order and are never replaced. | push_signature enforces the cursor on chains_filled. |
Boundaries
Two properties depend on the client rather than the program, and the program cannot check them.
- The next secret is derived before the current one is revealed. The client derives chain secrets deterministically from the seed. See Recovery words.
- One signature per vault state, ever. A client must never sign two different digests against the same commitment, including across retries. See Rotation.
And the lock does not protect against:
- Theft of the seed. Whoever holds the twelve words holds the vault.
- A compromised client that signs a digest the owner did not intend.
- Loss of the seed. There is no recovery path that bypasses the commitment.
NIST context
NIST IR 8547, in its initial public draft of November 2024, lists EdDSA at 128 bits of security as disallowed after 2035. Ed25519, which every Solana wallet uses, is EdDSA at that strength.
SP 800-208 approves LMS and XMSS. FIPS 205 specifies SLH-DSA. All three are hash-based, built from Winternitz-style one-time signatures, and NIST states that their security relies only on the hash function. metaspace’s lock is the same family. These pages claim lineage, not certification.