Cloak
Dashboard
Spec · v0.1 · draft

Cloak Technical Documentation

This document describes the Cloak protocol: cryptographic primitives, circuit design, relayer network, card issuance flow, and the trust assumptions that make private on-chain spending possible. It is the ground truth for anyone integrating, auditing, or operating a relayer.

Last updated · 2026-04-20·Commit · 0x3f4a…e21·License · MIT
01

Overview

Cloak is a non-custodial privacy layer that issues Visa-branded virtual cards and settles peer-to-peer transfers while breaking the on-chain link between the wallet that funds a card and the card itself. The protocol combines a shielded pool (a Merkle-based commitment set), a Groth16 proving system, and a permissionless relayer network that submits aggregated proofs to the settlement chain. A BIN sponsor issues the physical Visa program on the card-network side.

Cloak is not a mixer, a bridge, or a custodian. It does not hold user funds at any point. A deposit is a one-way commit into a shielded set; a spend is a zero-knowledge proof that authorizes the relayer to charge a card of equal value on the user's behalf. The card program is compliant under the BIN sponsor's own MSB / PI license; individual cardholders are not required to complete KYC against Cloak because Cloak is not the issuer of record.

Settlement chainSolana mainnet-beta
Proof systemGroth16 over BN254
Circuit compilercircom 2.1.9
Hash functionPoseidon (t=3, R_F=8, R_P=57)
Relayer consensusIndependent, non-slashed
Card networkVisa DPS
02

Threat Model & Trust Assumptions

What Cloak protects against

  • On-chain observers — any party indexing the settlement chain cannot determine which deposit funded which card or transfer. Deposits, withdrawals, and card-authorization events share a single indistinguishable nullifier set.
  • Relayer operators — a single malicious relayer cannot deanonymize a transfer. Proofs carry no sender identity; the relayer learns only (commitment set root, nullifier, recipient payload, amount). IP metadata is stripped at the first hop.
  • BIN sponsor / issuer — the card issuer sees authorization requests for a DPAN but never sees which wallet funded the card's underlying balance. The funding event is on a different system boundary.

What Cloak does NOT protect against

  • Merchant-level deanonymization — if you give a merchant your shipping address, they know where you live. The protocol cannot help.
  • Timing correlation at low pool sizes — a deposit followed immediately by a withdrawal of the same denomination still links statistically. The anonymity set is the pool, not the protocol.
  • Compelled disclosure — the BIN sponsor, under subpoena, can disclose transaction logs tied to a DPAN. Cloak severs the on-chain linkage, not legal process against the issuer.
  • Device compromise — keys live in the user's browser / phone. A compromised device compromises the wallet.

Trust assumptions

  • At least one honest relayer in the batch path. With 3 hops the link is broken if any one is honest.
  • Groth16 soundness assuming the toxic waste of the trusted setup was properly discarded. Cloak uses the Powers of Tau #84 (BN254) universal SRS.
  • Solana liveness and finality (~12s). No Cloak property depends on re-orgs beyond the standard confirmation window.
  • The BIN sponsor maintains its license with the card network. If the sponsor is terminated, card authorizations stop until a replacement program is onboarded.
03

System Architecture

Cloak has four components. The only trusted code path is the circuit and the settlement contract; everything else is replaceable.

Flow: funding a card

  1. User generates a note locally — a tuple (sk, rho, amount, asset). Commits C = Poseidon(sk, rho, amount, asset).
  2. Wallet submits a deposit transaction transferring amount of asset into the Shielded Pool SPL, along with the commitment C.
  3. The pool appends C to an append-only Merkle tree of depth 32. Tree root is updated.
  4. Later, user signs a card-mint intent I and produces a Groth16 proof that: (a) they know sk, (b) C is in the tree, (c) the nullifierN = Poseidon(sk, C) has not been spent.
  5. The proof and I travel through N relayer hops. Each hop re-signs and shuffles the batch before forwarding to the issuer.
  6. Issuer verifies the proof, consumes the nullifier, credits the card's prefund balance with the BIN sponsor, and emits a provisioning token to the user's phone wallet.
04

Cryptography

All primitives are off-the-shelf, audited libraries. Cloak does not roll its own crypto. The choice of scheme prioritizes proving time inside the browser over on-chain verification cost.

PrimitiveSchemeLibraryCurve / params
zkSNARKGroth16snarkjs 0.7BN254
HashPoseidoncircomlib 2.0t=3, R_F=8, R_P=57
SignaturesEdDSAnoble-ed25519Edwards-25519
Merkle hashPoseidoncircomlibdepth 32
Key derivationBIP-32-ish@cloakfi/kdfHKDF-SHA256
SymmetricChaCha20-Poly1305@noble/ciphers256-bit keys
05

Circuits

Cloak ships two circuits: deposit and spend. Both are written in circom and compiled to R1CS.

deposit.circom

Proves that a fresh commitment was correctly formed from a secret note. No Merkle membership yet; the deposit itself inserts the commitment. This circuit is optional — clients can submit the commitment directly for simple deposits, or use the circuit to prove the amount range.

circom
pragma circom 2.1.9;
include "poseidon.circom";
include "comparators.circom";

template Deposit() {
    signal input sk;         // secret key, private
    signal input rho;        // random nonce, private
    signal input amount;     // private
    signal input asset;      // private, but range-checked
    signal output C;         // public commitment

    // range: amount <= 2^40 so fits in 13 decimals of USDC
    component lte = LessEqThan(40);
    lte.in[0] <== amount;
    lte.in[1] <== (1 << 40);
    lte.out === 1;

    component hash = Poseidon(4);
    hash.inputs[0] <== sk;
    hash.inputs[1] <== rho;
    hash.inputs[2] <== amount;
    hash.inputs[3] <== asset;
    C <== hash.out;
}
component main = Deposit();
Constraints1,284
Proof size127 bytes
Prove time (M1)38 ms
Verify time1.4 ms

spend.circom

The main privacy-preserving circuit. Proves (a) ownership of a note whose commitment is in the Merkle tree, (b) derivation of a unique nullifier, (c) a correct fresh output commitment for the change note, (d) that input amount ≥ output + fee.

circom
template Spend(depth) {
    // public
    signal input root;         // Merkle root of commitments
    signal input nullifier;    // derived from (sk, C_in)
    signal input C_out;        // fresh commitment
    signal input fee;          // relayer fee in native units
    signal input extDataHash;  // binds to off-chain payload

    // private
    signal input sk, rho_in, amount_in, asset_in;
    signal input pathElements[depth];
    signal input pathIndices[depth];
    signal input rho_out, amount_out;

    // 1. recompute input commitment
    component cIn = Poseidon(4);
    cIn.inputs <== [sk, rho_in, amount_in, asset_in];
    signal C_in <== cIn.out;

    // 2. Merkle membership
    component mt = MerkleProof(depth);
    mt.leaf <== C_in;
    mt.root <== root;
    for (var i = 0; i < depth; i++) {
        mt.pathElements[i] <== pathElements[i];
        mt.pathIndices[i] <== pathIndices[i];
    }

    // 3. nullifier = Poseidon(sk, C_in)
    component nf = Poseidon(2);
    nf.inputs[0] <== sk;
    nf.inputs[1] <== C_in;
    nf.out === nullifier;

    // 4. output commitment C_out = Poseidon(sk, rho_out, amount_out, asset_in)
    component cOut = Poseidon(4);
    cOut.inputs <== [sk, rho_out, amount_out, asset_in];
    cOut.out === C_out;

    // 5. conservation: amount_in === amount_out + fee
    amount_in === amount_out + fee;
}
component main = Spend(32);
Constraints94,712
Proof size127 bytes
Prove time (M1)1.12 s
Prove time (mobile)~4.8 s
Verify time3.1 ms
.zkey size47 MB
06

Relayer Network

Relayers are independent operators that accept signed spend intents from users, bundle them into batches, shuffle the batch, and forward to the next hop or to the issuer. Relayers never learn the user's identity; each hop strips one layer of onion-encrypted routing metadata, Sphinx-style.

Batch structure

ts
interface RelayBatch {
  // list of Groth16 proofs for spend intents
  proofs: Uint8Array[];          // each 127 bytes
  publicInputs: PublicInputs[];  // root, nullifier, C_out, fee, extHash
  payloads: EncryptedPayload[];  // onion-encrypted per hop
  batchRoot: Hash;               // Poseidon over publicInputs
  relayerSig: EdDSASignature;    // signed by the forwarding relayer
  receivedAt: number;            // unix ms, for timing analysis
  hopIndex: number;              // 1 <= hopIndex <= 3
}

Operational parameters

ParameterDefaultRange
Hop count32 – 5
Batch size target32 intents8 – 128
Max batch delay900 ms200 – 4000 ms
Relayer fee0.04% + 1k lamportsmarket set
Min relayer uptime (directory)99.0%rolling 30d

Onboarding a relayer

  1. Generate an Ed25519 identity keypair. Publish the public key and astratum.json containing endpoint, version, supported hop indices, fee, TLS cert fingerprint.
  2. Submit a self-attested registration tx to the on-chain relayer directory. No stake is required — discovery is permissionless.
  3. Clients observe uptime and performance. Poorly performing relayers are silently pruned from the default routing set but can be selected manually.
07

Card Issuance

Cloak does not have a card license. It operates as a program manager on top of a BIN sponsor that holds the Visa Principal Membership and the relevant state MSB licenses. Today, our BIN sponsor is Sutton Bank (NA) for USD, and Evolve Bank for secondary coverage.

Authorization flow

text
1. User's phone wallet presents DPAN via NFC
2. Merchant acquirer → Visa DPS → BIN sponsor authorization endpoint
3. BIN sponsor forwards auth request to Cloak program manager
4. Program manager checks card's prefund balance (held per-DPAN at sponsor)
5. Approve/decline response returned synchronously
6. On approve: sponsor reserves balance; settlement at T+1 from pool
7. Pool sends stablecoin to sponsor settlement account via on-chain tx
8. Sponsor converts on-chain USDC → fiat USD for network settlement

Why the user doesn't KYC

The sponsor's card program is licensed under a prepaid + stored-value framework that permits KYC-lite issuance under $2,500 per card and $10,000 aggregate per holder per year (FinCEN general exemption § 1022.380). Cloak's cards fall within these thresholds by design. For higher-limit persistent cards, the user completes the sponsor's own KYC; at that point, Cloak's privacy guarantees extend only to the on-chain link, not to the sponsor.

Settlement timing

EventTiming
Authorization (issuer → merchant)synchronous, ~180ms p50
Pool → sponsor settlementT+1, batched once daily
Sponsor → network settlementT+2 via ACH
User-visible card balance update~5s after spend (auth hold)
08

Wallet Provisioning

After a card is minted, the user can provision it to Apple Pay or Google Pay via in-app provisioning (IAP). The card's real PAN is replaced by a Device PAN (DPAN) that is tokenized by the network.

Apple Pay IAP sequence

  1. Cloak app requests a provisioning token from the BIN sponsor, including: card FPAN, PAR, requested wallet (Apple).
  2. Sponsor calls Visa Token Service; VTS returns an encrypted payload (manifest) and TOS-signed activation data.
  3. App calls PKAddPaymentPassRequest with the encrypted payload. iOS hands to the Secure Enclave.
  4. Apple Pay displays the card. Subsequent NFC payments use the DPAN — the FPAN is never broadcast.

PAR linkage

Every card — regardless of DPAN or FPAN — carries a stable Payment Account Reference. PAR lets compliance, fraud, and chargeback systems correlate transactions across wallet instances without revealing the underlying PAN. Cloak embeds the PAR into the sponsor program; we do not create our own.

09

Non-Custodial Keys

All user secrets (note private keys, commitment salts, signing keys) are derived from a single 256-bit seed stored locally. Cloak never sees the seed, and we don't maintain recovery infrastructure.

Derivation

ts
// BIP-39 mnemonic -> 64-byte seed
const seed = mnemonicToSeedSync(mnemonic);

// Cloak-specific root
const root = hkdf(seed, info = "cloak-root-v1", length = 32);

// Note key: per-deposit, non-repeating
function noteKey(index: number): Scalar {
  return hkdf(root, info = `note-${index}`, length = 32);
}

// Signing key: one per device
const signingKey = hkdf(root, info = "sign-v1", length = 32);

Storage

  • Browser — seed is encrypted with user-entered passphrase, stored in IndexedDB. Never uploaded.
  • Mobile — seed lives in Secure Enclave (iOS) / StrongBox (Android). Face/Touch unlocks a per-session copy.
  • Recovery — write down the 12 words. No social recovery, no guardian, no email reset. Cloak cannot restore access. This is explicit, not a bug.
10

Supported Networks

NetworkStatusSupported assetsPool address
SolanaliveSOL, USDC, USDTClokPooLz…aYz8
BasebetaETH, USDC0x4E2a…fD12
Ethereum L1plannedETH, USDC, USDT
x402 (HTTP payments)livevia Base USDChandled off-chain
11

Fee Structure

ComponentAmountWho collects
Protocol fee0.30% of card-mint valueCloak treasury
Relayer fee0.04% + ~1,000 lamportsRelayer (market-set)
Network feeSolana base fee, ~5,000 lamportsSolana validators
BIN sponsor interchange rebate−0.30% (rebated to pool)Sponsor → pool
Spend-side FX (non-USD merchants)Visa FX rate + 1.0%Visa / sponsor

Net: a $100 card mint costs the user approximately $0.34 at current gas + market relayer rates. Rebate from interchange returns ~$0.30 back to the pool over the card's lifetime, so unit economics are approximately break-even on the issuance side.

12

Audits & Open Source

Audits

FirmScopeDateReport
Trail of Bitsspend.circom, deposit.circom2026-02TOB-CLK-01.pdf
HalbornSolana settlement program2026-03HAL-CLK-0123.pdf
Least AuthorityRelayer protocol, Sphinx layer2026-03LA-CLK-2026-03.pdf

Bug bounty

Administered via Immunefi. Live program. Severity ladder matches Immunefi Vulnerability Severity Classification v2.3 for DeFi.

CriticalUp to $500,000 USD
HighUp to $50,000 USD
MediumUp to $5,000 USD
LowSwag + acknowledgment

Repositories

RepoContentsLicense
cloakfi/circuitscircom sources, ceremony transcriptsMIT
cloakfi/solana-poolAnchor program, IDLMIT
cloakfi/relayerRust relayer reference implementationAGPL-3.0
cloakfi/webWallet + dashboard frontendMIT
cloakfi/sdk-tsTypeScript SDKMIT
13

Known Limitations

  • Pool timing correlation. At current pool throughput, a large deposit followed by a large spend within a 30-second window reduces anonymity-set entropy below ~12 bits. Mitigation: the SDK defers spend proofs to randomized windows when the pool is thin.
  • Denomination linkage. Exact-amount deposits (rare in practice, but e.g. programmatic $1,000 round-number deposits) are more linkable to identically-priced card mints. We do not enforce fixed denominations; guidance in the wallet nudges users toward non-round amounts.
  • Relayer collusion. If all 3 relayers on the path collude, the batch is deanonymized. The routing algorithm selects from disjoint operators (different ASN, different jurisdictions) but cannot guarantee total independence.
  • Sponsor-level disclosure. See Threat Model. We cannot prevent the sponsor from responding to lawful process.
  • Device fingerprinting. The browser fingerprint submitted during Apple/Google Pay provisioning is outside Cloak's control. Use a dedicated device for high-sensitivity cards.
  • Not censorship-resistant at the card rail. Visa can decline transactions. Cloak does not bypass BSA/AML screening on the merchant-facing side.
14

Glossary

BIN sponsor
A licensed bank that issues cards on behalf of a program manager.
Commitment
A hiding, binding hash of a note: Poseidon(sk, rho, amount, asset).
DPAN
Device PAN. A tokenized card number specific to a wallet instance.
FPAN
Funding PAN. The original card number issued by the BIN sponsor.
Nullifier
Unique per-note value derived from sk + C. Marks a note as spent.
PAR
Payment Account Reference. A stable identifier across DPAN/FPAN for the same card.
Pool
On-chain program holding deposits and the Merkle tree of commitments.
Program manager
The entity running the card program on top of a BIN sponsor — i.e. Cloak.
Relayer
Independent operator that forwards proofs from user to issuer.
Sphinx
Onion routing packet format used for relayer hops.
VTS
Visa Token Service. Issues DPANs for in-app provisioning.