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
Node.js Version 16 or later
Build the program
Clone the repository
git clone https://github.com/Privacy-Cash/privacy-cash.git
cd privacy-cash
Navigate to the program directory
Build the Anchor program
This compiles the Solana program and generates the IDL (Interface Definition Language) file.
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.
SOL tests
SPL tests
Mint checked tests
Unit tests
Test native SOL private transactions: 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
Test SPL token private transactions: This runs the test suite at tests/spl_tests.ts for SPL token deposits and withdrawals. Test mint-checked SPL token transactions: npm run test:mint-checked
This runs the test suite at tests/spl_mint_test.ts with additional mint validation. Run Rust unit tests: This executes the Rust-level unit tests for the program logic.
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:
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' })
];
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 );
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:
Build with devnet feature
anchor build -- --features devnet
Prepare the program keypair
rm target/deploy/zkcash-keypair.json
cp zkcash-keypair.json target/deploy/zkcash-keypair.json
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