Skip to main content
The withdrawal flow is where Abyss’s privacy guarantees are actually realized. Unlike deposits, which are publicly observable by necessity, withdrawals are designed to be cryptographically unlinkable to any prior on-chain activity. The protocol enforces this unlinkability not through heuristics or delay, but through zero-knowledge proofs that replace identity with knowledge. Conceptually, a withdrawal performs the inverse of a deposit, but without symmetry at the graph level:
(Commitment, Secret, Proof)

(Recipient Address, Amount)
The critical distinction is that the protocol never learns which commitment is being spent, only that some valid commitment exists and that the withdrawal is authorized.

V.2.1 Withdrawal Preconditions

To initiate a withdrawal, a user must possess:
  • The original secret_key
  • An unused withdrawal index i
  • A Merkle root corresponding to a valid pool state
No address-level permission is required. The withdrawal can be initiated by any address, including relayers, merchants, or third parties acting on behalf of the user.

V.2.2 Nullifier Generation

Each withdrawal consumes a unique nullifier derived deterministically from the secret:
nullifier_i := H(secret_key || i)
This nullifier serves as a one-time marker that prevents double-spending. It reveals nothing about the commitment or the secret itself, but it is globally unique per withdrawal index. On-chain, the protocol maintains:
NullifierSet := { N₁, N₂, … }
Any attempt to reuse a nullifier results in a hard revert.

V.2.3 Proof Construction

The client constructs a zero-knowledge proof asserting the following statement:
I know a secret_key such that:
  1. commitment(secret_key) ∈ MerkleTree(root)
  2. nullifier_i = H(secret_key || i)
  3. nullifier_i ∉ NullifierSet
  4. withdrawal_amount ≤ remaining_balance(secret_key)
The proof’s public inputs include:
  • Merkle root
  • Nullifier
  • Withdrawal amount
  • Recipient address
The witness includes:
  • Secret key
  • Merkle path
  • Internal balance state
This separation ensures that validity is proven without disclosure.

V.2.4 On-Chain Verification and Settlement

The withdrawal contract verifies the proof:
function withdraw(
    Proof proof,
    bytes32 root,
    bytes32 nullifier,
    uint256 amount,
    address recipient
) external {
    require(validRoot[root]);
    require(!nullifierUsed[nullifier]);
    require(verifyProof(proof, publicInputs));
    nullifierUsed[nullifier] = true;
    transferV(recipient, amount);
}
If verification succeeds:
  • Funds are released
  • The nullifier is permanently burned
  • No state links are introduced

V.2.5 Privacy Properties

From an observer’s perspective:
  • The withdrawal could correspond to any commitment in the pool
  • Timing does not imply linkage
  • Amount does not imply provenance
The recipient learns only that they were paid. They cannot infer the payer’s balance, deposit history, or prior activity.

V.2.6 Failure Modes

Withdrawals fail if:
  • The proof is invalid
  • The nullifier is reused
  • The amount exceeds remaining balance
  • The Merkle root is stale or invalid
There are no partial withdrawals or ambiguous outcomes.

V.2.7 Implications

This flow enables Abyss to function as:
  • A private payment rail
  • A settlement layer for exchanges
  • A privacy-preserving payout mechanism
All without accounts, sessions, or persistent identity.