Skip to main content

Overview

This guide will help you get Privacy Cash running quickly. You’ll learn how to build the program, run tests, and execute your first private transaction.
For production use, we recommend using the Privacy Cash SDK instead of building from source.

Prerequisites

Before you begin, ensure you have:

Solana CLI

Version 2.1.18 or later

Rust

Version 1.79.0 or compatible

Anchor

Version 0.31.1

Node.js

Version 16 or later
See the installation guide for detailed setup instructions.

Build the program

1

Clone the repository

git clone https://github.com/Privacy-Cash/privacy-cash.git
cd privacy-cash
2

Navigate to the program directory

cd anchor
3

Build the Anchor program

anchor build
This compiles the Solana program and generates the IDL (Interface Definition Language) file.
4

Verify the build

Check that the compiled program exists:
ls target/deploy/zkcash.so
You should see the compiled program artifact.

Run tests

Privacy Cash includes comprehensive test suites for SOL and SPL token transactions.
Test native SOL private transactions:
npm run test:sol
This runs the test suite at tests/sol_tests.ts which includes:
  • Double spend attack prevention
  • Deposit and withdrawal with fees
  • PDA recipient support
  • Nullifier verification
The integration tests use the localnet feature flag and spin up a local Solana validator automatically.

Understanding a private transaction

Here’s a breakdown of how Privacy Cash transactions work using code from the test suite:
1

Create UTXOs

For a deposit, create two output UTXOs (one with your amount, one empty):
const depositAmount = 20000;
const calculatedDepositFee = calculateDepositFee(depositAmount); // 0 for deposits

const inputs = [
  new Utxo({ lightWasm }),  // Empty input
  new Utxo({ lightWasm })   // Empty input
];

const outputAmount = (depositAmount - calculatedDepositFee).toString();
const outputs = [
  new Utxo({ 
    lightWasm, 
    amount: outputAmount, 
    index: globalMerkleTree._layers[0].length 
  }),
  new Utxo({ lightWasm, amount: '0' })
];
2

Generate the zero-knowledge proof

Create the proof input and generate the proof:
const inputNullifiers = await Promise.all(inputs.map(x => x.getNullifier()));
const outputCommitments = await Promise.all(outputs.map(x => x.getCommitment()));
const root = globalMerkleTree.root();
const extDataHash = getExtDataHash(extData);

const input = {
  root: root,
  inputNullifier: inputNullifiers,
  outputCommitment: outputCommitments,
  publicAmount: publicAmountNumber.toString(),
  extDataHash: extDataHash,
  inAmount: inputs.map(x => x.amount.toString(10)),
  inPrivateKey: inputs.map(x => x.keypair.privkey),
  inBlinding: inputs.map(x => x.blinding.toString(10)),
  mintAddress: inputs[0].mintAddress,
  inPathIndices: inputMerklePathIndices,
  inPathElements: inputMerklePathElements,
  outAmount: outputs.map(x => x.amount.toString(10)),
  outBlinding: outputs.map(x => x.blinding.toString(10)),
  outPubkey: outputs.map(x => x.keypair.pubkey),
};

const keyBasePath = path.resolve(__dirname, '../../artifacts/circuits/transaction2');
const {proof, publicSignals} = await prove(input, keyBasePath);
3

Submit the transaction

Format the proof and submit to the program:
const proofInBytes = parseProofToBytesArray(proof);
const inputsInBytes = parseToBytesArray(publicSignals);

const proofToSubmit = {
  proofA: proofInBytes.proofA,
  proofB: proofInBytes.proofB.flat(),
  proofC: proofInBytes.proofC,
  root: inputsInBytes[0],
  publicAmount: inputsInBytes[1],
  extDataHash: inputsInBytes[2],
  inputNullifiers: [inputsInBytes[3], inputsInBytes[4]],
  outputCommitments: [inputsInBytes[5], inputsInBytes[6]],
};

await program.methods
  .transact(
    proofToSubmit, 
    createExtDataMinified(extData), 
    extData.encryptedOutput1, 
    extData.encryptedOutput2
  )
  .accounts({
    treeAccount: treeAccountPDA,
    nullifier0: nullifier0PDA,
    nullifier1: nullifier1PDA,
    nullifier2: nullifier2PDA,
    nullifier3: nullifier3PDA,
    recipient: recipient.publicKey,
    feeRecipientAccount: FEE_RECIPIENT_ACCOUNT,
    treeTokenAccount: treeTokenAccountPDA,
    globalConfig: globalConfigPDA,
    signer: randomUser.publicKey,
    systemProgram: anchor.web3.SystemProgram.programId
  })
  .signers([randomUser])
  .rpc();
The zero-knowledge proof generation is computationally intensive. On a typical laptop, proof generation takes 5-15 seconds.

Deploy to devnet

Once you’ve tested locally, deploy to Solana devnet:
1

Build with devnet feature

anchor build -- --features devnet
2

Prepare the program keypair

rm target/deploy/zkcash-keypair.json
cp zkcash-keypair.json target/deploy/zkcash-keypair.json
3

Deploy to devnet

anchor deploy --provider.cluster devnet
Or use the verifiable build:
anchor deploy --verifiable --provider.cluster devnet
Verifiable builds allow anyone to verify that the deployed program matches the source code. This is important for security and trust.

Next steps

Installation guide

Detailed setup instructions for all dependencies

Privacy Cash SDK

Integrate Privacy Cash into your application

Program source code

Explore the Rust implementation

Test suites

Review comprehensive test examples

Build docs developers (and LLMs) love