Move tokens between accounts with witness-derived keypair authentication.
ownPublicKey() is not authentication. Anyone reading the chain can copy
a public key and supply it as ownPublicKey() in a new transaction. To
authenticate the caller, prove knowledge of a witness-held secret whose hash
matches a stored public key — the Access Control
pattern. This page builds the transfer function on top of that pattern.
To call transfer, the caller must produce a ZK proof that they know a
UserSecretKey whose hash matches the on-chain UserPublicKey they’re
spending from. The on-chain balance is keyed by the hash of the secret;
reading the chain gives an attacker the hash, not the preimage.Compare with the broken pattern (do not use):
// ❌ Authentication does NOT work this wayconst sender = left<ZswapCoinPublicKey, ContractAddress>(ownPublicKey());
ownPublicKey() returns whatever value the prover claims to know. An attacker
reads sender from the chain, supplies it as their own ownPublicKey(), and
produces a valid proof. See Access Control.
This contract publishes the full participant graph._balances is
keyed by UserPublicKey, every transfer discloses both sender and to,
and the inserted amount is also disclosed. Anyone reading the ledger sees
the complete history of who paid whom and how much.This is the account-model privacy profile. For payments where senders,
recipients, and amounts must be private, use Midnight’s shielded token
primitives (sendShielded, mintShieldedToken, etc.) — they’re built on
UTXOs and ZK commitments and don’t leak the participant graph. The
account-model pattern shown here is appropriate for registries where
participants are intentionally public (game leaderboards, public reward
programs, etc.).