01·5 min read
core protocol

how it works

A private transaction in KIRITE passes through three stages: deposit, mix, and withdraw. Each stage strips a different layer of metadata from the transaction.

step 1: deposit

The user deposits assets into the Shield Pool. Before the transaction is broadcast:

  1. The client generates a Twisted ElGamal ciphertext of the deposit amount.
  2. A range proof is computed to prove the amount is non-negative and within bounds, without revealing the value.
  3. The encrypted amount + proof are submitted to the Solana program.
  4. The on-chain program verifies the proof and accepts the deposit.
key insight

The chain never sees the deposit amount. It only verifies a mathematical proof that the amount is valid. The encryption happens entirely in the user's browser.

// client-side encryption (simplified)
const ciphertext = twistedElGamal.encrypt(
  amount,       // plaintext amount
  publicKey,    // recipient's public key
  randomness    // fresh randomness per transaction
);

const rangeProof = bulletproofs.prove(
  amount,
  randomness,
  { bits: 64 }  // prove amount fits in u64
);

// only ciphertext + proof go on-chain
await program.deposit(ciphertext, rangeProof);

step 2: mix

Inside the Shield Pool, deposits become indistinguishable. The pool operates as a multi-asset anonymity set:

  • All deposits of the same token type are pooled together.
  • Deposits are time-locked — they cannot be withdrawn immediately, preventing timing correlation attacks.
  • The lock window is variable — randomized per deposit to prevent fixed-interval analysis.
  • As the pool grows, the anonymity set expands, making individual transactions harder to trace.

anonymity set dynamics

The privacy guarantee of the Shield Pool is directly proportional to the size of the anonymity set. For a pool with n deposits of similar value:

privacy_score = 1 - (1/n)

n = 10   → 90% unlinkability
n = 100  → 99% unlinkability
n = 1000 → 99.9% unlinkability

configurable privacy levels

Users choose their own privacy-speed tradeoff at withdrawal:

leveldelayprivacyuse case
instant~0slowspeed over privacy
standard~10 minmediumrecommended for most users
maximum1 hr+highestlarge amounts, maximum anonymity

Longer delays allow more deposits to enter the pool, expanding the anonymity set and making timing correlation exponentially harder.

step 3: withdraw

When the selected privacy window expires, the user can withdraw to a stealth address:

  1. The user generates an ephemeral keypair — a fresh, one-time address that has never appeared on-chain.
  2. A withdrawal proof is generated, proving the user has a valid deposit without revealing which one.
  3. Assets are transferred to the stealth address.
  4. The recipient scans the on-chain registry to detect and claim the funds using their private key.
// stealth address generation (simplified)
const ephemeral = Keypair.generate();
const sharedSecret = ecdh(ephemeral.secretKey, recipient.publicKey);
const stealthPubkey = deriveStealthAddress(
  recipient.publicKey,
  sharedSecret
);

// recipient scanning
const detected = scanRegistry(recipient.secretKey);
// → returns stealth addresses the recipient can claim

end-to-end flow

01user walletencrypt amount + generate proof
02shield pooltime-locked mixing
03stealth addressrecipient scans & claims

what an observer sees

without kirite
amount: 14,000 USDC
sender: 7xK9..3fQ2
receiver: 9mR2..xK7P
link: deterministic
with kirite
amount: encrypted
sender: 1 of n depositors
receiver: stealth address
link: broken
metadatawithout kiritewith kirite
transaction amountfully visibleencrypted (ciphertext only)
sender addressfully visibleone of n pool depositors
recipient addressfully visibleone-time stealth address
sender-receiver linkdeterministicbroken by pool mixing
timing correlationexact timestamprandomized time-lock