stealth-vault
Non-revocable custody for one-time Token-2022 Confidential Transfer accounts.
Program id 7oFAWawZCxDivMSa73hGh5V4XnhukLbrZQTA9Sq2r2pk (devnet).
Not on the active path. stealth-vault is the custody design for the Token-2022 Confidential Transfer flow, which is disabled in favour of the note model. The program is deployed and documented because it pioneered the recipient-only ownership gate that note-vault now uses. If you are integrating today, you want note-vault, not this.
The problem it solves
In a naive stealth scheme the one-time account owner is derived from the ECDH
shared secret, which the sender also knows, so the sender could sign as that
owner and reclaim an unclaimed payment. Token-2022 forbids changing a CT account's
owner (SetAuthority returns 0x22), so the fix is structural: the one-time CT
account is an ATA owned by a per-output PDA of this program, and every release
instruction requires a signature from the recipient-only owner
R = spend_pub + t*G. The sender can compute R but cannot sign as it (that needs
the recipient's spend scalar), so once funds land, only the recipient can move
them. Payments are final; there is deliberately no sender reclaim path.
PDA
| PDA | Seeds | Purpose |
|---|---|---|
| vault | ["stealth", R] | Owns the one-time CT account. Derived from the recipient-only key R. |
The program is stateless: release instructions take r as a signer and derive
the PDA from r.key() in the seeds, so a wrong signer simply fails the seeds
constraint. There is no stored state and nothing to migrate.
Instructions
init_stealth
Sender side. Creates, reallocates, and configures the one-time CT account owned by
the vault PDA for recipient-only owner r (passed as a non-signer here, since the
sender initializes). The caller must place the VerifyPubkeyValidity
instruction immediately after this one in the same transaction: Token-2022 resolves
the configure proof at offset +1 against the top-level instructions via the
sysvar.
Args: decryptable_zero_balance: [u8;36], maximum_pending_balance_credit_counter: u64.
Internally it does three hand-encoded Token-2022 CPIs: create the ATA (idempotent),
Reallocate for the ConfidentialTransferAccount extension, and ConfigureAccount
with proof_instruction_offset = +1.
apply_pending
Recipient side. Applies the pending balance on the stealth account. Only r can
sign; the PDA seeds bind to it.
Args: expected_pending_balance_credit_counter: u64,
new_decryptable_available_balance: [u8;36].
release_transfer
Recipient side. A confidential transfer from the stealth account to a destination. The three proof context-state accounts (equality, validity, range) are created and verified client-side exactly as a normal CT transfer; the program supplies them and signs the transfer with the vault PDA seeds.
Args: new_source_decryptable_available_balance: [u8;36],
auditor_ciphertext_lo: [u8;64], auditor_ciphertext_hi: [u8;64].
Why it is documented
The same recipient-only gate (require r: Signer, derive the PDA from r.key())
is what makes note-vault payments final. If the CT path is
ever re-enabled, this program is how live stealth outputs get non-revocable
custody without a SetAuthority shortcut.