Skip to main content
PrivacyPoolSDK is the client-side library that handles all cryptographic preparation for private transactions on Stellar/Soroban. It derives stealth addresses from wallet signatures, constructs private coins, builds Merkle witnesses, generates Groth16 proofs, and serializes everything into the byte format the PrivacyPoolsContract expects. The SDK does not communicate with the Arcane backend, manage audit cases, or touch auditor permissions — those responsibilities belong to the Arcane Auditing Portal.

Install

npm install @auditable/privacy-pool-zk-sdk
The SDK requires Node.js >=19.0.0 or a browser environment that exposes the Web Crypto API (crypto.subtle). Older Node.js versions and environments without crypto.subtle are not supported.

Initialize

Pass the WASM binary, circuit WASM, and proving key (zkey) to PrivacyPoolSDK.init(). In a browser application, fetch these files from your asset server:
import { PrivacyPoolSDK } from "@auditable/privacy-pool-zk-sdk";

const sdk = await PrivacyPoolSDK.init({
  wasmBinary: await fetch("/pkg/client_sdk_wasm_bg.wasm").then((r) => r.arrayBuffer()),
  circuitWasm: await fetch("/assets/main.wasm").then((r) => r.arrayBuffer()),
  zkey: await fetch("/assets/main_final.zkey").then((r) => r.arrayBuffer()),
});
PrivacyPoolSDK.init() is async and resolves to an initialized SDK instance. Hold on to this instance for the lifetime of your page or session — creating multiple instances is unnecessary.

SDK capabilities

Stealth addresses

Derive a deterministic stpl1 Bech32 private address from a Stellar wallet signature. Share this address with depositors so they can encrypt coins to you.

Coin creation

Generate new private coins, coins bound to a recipient via an ECDH shared secret, or coins for a deposit flow with a pre-shared hex secret.

Merkle witness

Read the current commitment tree from the contract and build the Merkle inclusion witness required for the ZK circuit.

Proof generation

Produce a Groth16 proof attesting to the validity of a withdrawal or general transaction without revealing private inputs.

Soroban serialization

Serialize a proof and public signals into the hex-encoded byte formats that PrivacyPoolsContract.transact accepts.

Nullifier helpers

Compute nullifierHash = Poseidon(nullifier) off-chain to check whether a coin has already been spent.

ECDH helpers

Generate ephemeral key pairs and compute ECDH shared secrets on the BabyJubJub curve for the deposit-side private address flow.

API reference

Stealth addresses

PrivacyPoolSDK.buildStealthAddressSignMessage(stellarAddress: string): string Builds the message a user must sign with their Stellar wallet to derive their stealth address. This is a static method — you can call it before the SDK is initialized. sdk.generateStealthAddressFromStellarSignature(signature: string): Promise<string> Hashes the provided wallet signature with SHA-256, derives a BabyJubJub scalar, and encodes the resulting public point as a stpl1 Bech32 stealth address.
const message = PrivacyPoolSDK.buildStealthAddressSignMessage(stellarAddress);
const signature = await wallet.signMessage(message);
const stealthAddress = await sdk.generateStealthAddressFromStellarSignature(signature);
// "stpl1…"

Coin creation

sdk.generateCoin(): Promise<Coin> Generates a new private coin with a random nullifier and secret. Use this for the output side of a transfer or when the recipient will recover the coin out-of-band. sdk.generateCoinWithSharedSecret(recipientStealthAddress: string): Promise<CoinWithEphemeral> Generates a coin whose sharedSecret is derived from an ECDH exchange with the recipient’s stpl1 stealth address. The depositor’s ephemeral public key is stored on-chain alongside the commitment so the recipient can recover the coin. sdk.generateCoinForDepositWithSharedHex(sharedSecretHex: string): Promise<Coin> Generates a deposit coin from a pre-computed shared secret provided as a hex string. Use this when you have already performed the ECDH exchange externally.
// Deposit to a known recipient
const { coin, ephemeralPublicKey } = await sdk.generateCoinWithSharedSecret(recipientStealthAddress);

// Or create a standalone coin
const coin = await sdk.generateCoin();

// Or create a coin from a pre-computed shared secret hex
const coin = await sdk.generateCoinForDepositWithSharedHex(sharedSecretHex);

Merkle witness

sdk.buildWithdrawMerkleWitness(params: WitnessParams): Promise<MerkleWitness> Builds the Merkle inclusion proof for a coin you intend to spend. You must supply the full list of commitments currently stored in the contract (fetched via get_commitments()) and the tree depth (fetched via get_merkle_depth()).
const witness = await sdk.buildWithdrawMerkleWitness({
  coin: coinToSpend,
  commitments: poolCommitments,
  treeDepth: poolTreeDepth,
});

Proof generation

sdk.proveWithdrawal(params: WithdrawalParams): Promise<ProofResult> Generates a Groth16 proof for a withdrawal transaction — spending a private input coin and releasing funds to a public Stellar address. sdk.proveTransaction(params: TransactionParams): Promise<ProofResult> Generates a Groth16 proof for a general transaction with explicit control over input coins, output coins, and public deposit/withdrawal amounts.
// Withdrawal
const proofResult = await sdk.proveWithdrawal({
  coin: coinToSpend,
  witness,
  publicWithdrawAddress: destinationStellarAddress,
  outputCoins: [],
});

// General transaction
const proofResult = await sdk.proveTransaction({
  inputCoins: [coinToSpend],
  outputCoins: [newCoin],
  witness,
  publicDepositAmount: 0n,
  publicWithdrawAmount: withdrawAmount,
  publicWithdrawAddress: destinationStellarAddress,
});

Soroban serialization

sdk.proofToHex(proof: Proof): string Serializes a Groth16 proof into the hex-encoded byte string expected by the proof_bytes argument of transact. sdk.publicToHex(publicSignals: PublicSignals): string Serializes public circuit signals into the hex-encoded byte string expected by the pub_signals_bytes argument of transact.
const proofBytes = sdk.proofToHex(proofResult.proof);
const pubSignalBytes = sdk.publicToHex(proofResult.publicSignals);

// Pass these directly to PrivacyPoolsContract.transact(from, proof_bytes, pub_signals_bytes, encoded)

Nullifier helpers

sdk.calculateNullifierHash(nullifier: string): Promise<string> Computes Poseidon(nullifier) and returns the nullifier hash as a decimal string. Use this to check whether a coin has already been spent before building a proof.
const nullifierHash = await sdk.calculateNullifierHash(coin.nullifier);
const isSpent = await contract.is_nullifier_hash_consumed(nullifierHash);

ECDH helpers

sdk.ecdhEphemeralPublicKeyFromScalarHex(scalarHex: string): Promise<EphemeralPublicKey> Derives the BabyJubJub public key corresponding to a given ephemeral scalar (hex-encoded). Use this on the depositor side to produce the public key that gets stored on-chain with the commitment. sdk.ecdhSharedKey(privateScalarHex: string, otherPublicKey: EphemeralPublicKey): Promise<SharedKey> Computes the ECDH shared secret between a private scalar and a BabyJubJub public key. Use this on the recipient side to recover the shared secret from the depositor’s ephemeral public key stored on-chain.
// Depositor side: generate ephemeral key pair
const ephemeralScalarHex = generateRandomScalarHex();
const ephemeralPublicKey = await sdk.ecdhEphemeralPublicKeyFromScalarHex(ephemeralScalarHex);

// Recipient side: recover shared secret
const sharedKey = await sdk.ecdhSharedKey(recipientPrivateScalarHex, ephemeralPublicKey);

What the SDK does NOT handle

PrivacyPoolSDK handles only the cryptographic and serialization steps on the client side. It does not:
  • Manage disclosure cases or audit reports
  • Control auditor permissions or case assignments
  • Communicate with the Arcane backend APIs
  • Index or interpret on-chain events
All of those functions are provided by the Arcane Auditing Portal. Your application submits transact to the Soroban contract; Arcane handles everything that happens after the event is emitted.