Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
LABS
Guides

Setting Up a Privacy-Preserving Invoice and Payment System

A developer tutorial for building a B2B payment system that hides invoice details on-chain while enabling verification for involved parties and accounting integration.
Chainscore © 2026
introduction
B2B PAYMENTS GUIDE

Setting Up a Privacy-Preserving Invoice and Payment System

This guide explains how to build a private B2B payment system using zero-knowledge proofs and confidential assets to protect transaction amounts and counterparty identities.

Traditional B2B payments on public blockchains like Ethereum expose sensitive financial data, including invoice amounts and business relationships, to competitors and the public. A privacy-preserving system addresses this by leveraging cryptographic primitives such as zero-knowledge proofs (ZKPs) and confidential assets. These technologies allow parties to prove the validity of a transaction—like sufficient funds or a correct invoice total—without revealing the underlying data. This is critical for businesses that require auditability and compliance without sacrificing confidentiality.

The core component is a zk-SNARK circuit that validates payment logic. For an invoice payment, the circuit would verify that: the payer's confidential balance is greater than the invoice amount, the payment signature is valid, and the new encrypted balances for both parties are correctly computed. Developers can implement this using frameworks like Circom or Halo2. The invoice itself can be represented as a hashed commitment, with details stored off-chain and accessed via a decentralized storage protocol like IPFS or Arweave, linked by a content identifier (CID).

A practical architecture involves a smart contract acting as a confidential payment verifier. It doesn't hold balances directly but verifies ZK proofs submitted by users. When Party A pays Party B, they generate a proof off-chain using their private keys and the encrypted invoice commitment. They then submit this proof to the verifier contract. The contract checks the proof and updates a Merkle tree or a state root that represents the current set of valid, encrypted balances, leaving no transactional metadata on-chain. This pattern is used by protocols like Aztec Network and Manta Network.

For development, start by defining the business logic in a circuit. A simple Circom template for a private payment might include signals for secretBalance, invoiceAmount, and newBalance. The circuit ensures newBalance == secretBalance - invoiceAmount and that invoiceAmount matches the hashed commitment. Use a library like snarkjs to generate and verify proofs. The smart contract verifier, typically written in Solidity or Cairo, imports a verifier key to check proof validity with a function like verifyPayment(bytes calldata _proof, bytes32 _invoiceCommitment).

Key challenges include managing private keys for encryption (often using stealth address schemes), ensuring off-chain data availability for auditors, and handling regulatory compliance through selective disclosure. Tools like Tornado Cash Nova (for assets) or Semaphore (for identity) offer building blocks. The system must also account for gas costs of proof verification, which can be high on Ethereum L1, making Layer 2 solutions like zkRollups or dedicated privacy chains a consideration for scalability.

To deploy, integrate with a front-end that handles key management via wallets (e.g., MetaMask with Snap for ZKP) and proof generation. The flow is: 1) Payer fetches invoice CID, 2) Generates proof locally, 3) Submits proof to verifier contract, 4) Recipient's client detects the state update and decrypts their new balance. For further reading, explore the Aztec docs on Noir, or the Zcash protocol for foundational concepts in shielded transactions. This setup provides the privacy of traditional finance with the audit trail and finality of blockchain.

prerequisites
FOUNDATION

Prerequisites and System Architecture

Before building a privacy-preserving payment system, you need the right tools and a clear architectural blueprint. This section covers the essential software, libraries, and high-level design patterns required to implement confidential transactions.

The core prerequisite is a development environment for zero-knowledge (ZK) cryptography. You will need Node.js (v18 or later) and a package manager like npm or yarn. The most critical dependency is a ZK proving system library. For Ethereum-based systems, Circom is the standard for writing arithmetic circuits, paired with snarkjs for proof generation and verification. An alternative is Halo2 (used by Zcash and Polygon zkEVM), which offers more advanced features but has a steeper learning curve. You should also be familiar with a smart contract language like Solidity for deploying the verification contracts on-chain.

The system architecture separates the on-chain verifier from the off-chain prover. The off-chain component, often a dedicated server or client-side application, handles sensitive data: it generates the cryptographic proof that a transaction is valid without revealing the underlying details (invoice amount, payer identity). This proof is then sent to a lightweight on-chain smart contract—the verifier—which uses a pre-compiled verification key to confirm the proof's validity in constant time, typically for a fixed gas cost. This separation ensures computational heavy lifting happens off-chain, keeping transaction fees manageable.

For invoice privacy, a common pattern uses Pedersen Commitments or zk-SNARKs to hide transaction amounts and parties. The system must maintain a private Merkle tree (or similar data structure) off-chain to track balances confidentially. Each user's commitment (a hash of their balance) is a leaf in this tree. When a payment occurs, the prover generates a proof demonstrating: 1) the payer's commitment exists in the tree, 2) the new commitments for sender and receiver are calculated correctly, and 3) no funds were created or destroyed. The on-chain contract only receives the proof and the new root of the Merkle tree, updating the global state without leaking any transaction details.

Key management is another architectural pillar. Users need a way to generate and manage private viewing keys and spending keys. A viewing key, derived from a secret, allows a user to decrypt their own transaction history on the client side. The spending key is used to authorize transactions. These keys should never leave the user's device. Libraries like libsodium or ethers.js secure wallets are essential for key generation and management. The architecture must ensure these keys are never transmitted to the server; all proof generation should be possible client-side or within a trusted execution environment.

Finally, consider the data availability and persistence layer. The private Merkle tree state and transaction histories need to be stored reliably. Options include a centralized database (simpler, but a trust assumption), a decentralized storage network like IPFS or Arweave (with encryption), or an optimistic or zk-rollup style data availability committee. The choice impacts the system's trust model and resilience. For a fully trust-minimized system, the architecture should aim for the state data to be available so users can reconstruct their history independently, aligning with principles of self-sovereign data.

key-concepts
PRIVACY ENGINEERING

Core Cryptographic Components

These cryptographic primitives form the foundation for building private payment systems, enabling selective disclosure and verifiable computation without exposing sensitive data.

step-1-commitment
FOUNDATIONS

Step 1: Creating Cryptographic Invoice Commitments

This step establishes the privacy-preserving core of the system by generating a cryptographic commitment that hides invoice details on-chain.

A cryptographic commitment is a fundamental building block for privacy. It allows a payer to publicly commit to the details of an invoice—such as the amount, due date, and recipient—without revealing those details on-chain. Think of it as a sealed envelope: you can prove you created a specific envelope (the commitment) without showing its contents. This is achieved using a cryptographic hash function like SHA-256 or Poseidon, which produces a deterministic, fixed-size output (a hash) from any input data.

To create a commitment, you first serialize the invoice data into a structured format. A typical invoice object includes fields like invoiceId, amount, currency, dueDate, and payeeAddress. This data is then combined with a randomly generated nonce (a secret number used once). The commitment is computed as commitment = hash(invoiceData + nonce). The nonce is crucial; without it, someone could easily guess common invoice amounts and brute-force the original data from the hash. Only the hash is published to the blockchain, creating an opaque reference to the invoice.

Here is a conceptual JavaScript example using a hash function:

javascript
const crypto = require('crypto');
// 1. Define invoice data and secret nonce
const invoiceData = JSON.stringify({
  invoiceId: 'INV-2024-001',
  amount: '1000',
  currency: 'USDC',
  payee: '0x742d35Cc6634C0532925a3b844Bc9e...'
});
const nonce = crypto.randomBytes(32).toString('hex'); // 256-bit secret
// 2. Create the commitment hash
const commitment = crypto.createHash('sha256')
  .update(invoiceData + nonce)
  .digest('hex');
console.log('Commitment:', commitment);

The payer must securely store the original invoiceData and the nonce. This tuple (invoiceData, nonce) is called the witness or opening, which will be needed later to prove the commitment's validity without revealing it prematurely.

This commitment serves as the public anchor for all subsequent steps. It can be recorded on a blockchain (e.g., Ethereum, Arbitrum) or within a decentralized storage system. The on-chain record acts as an immutable, timestamped proof that a specific invoice commitment was created at a certain time. Later, during payment, the payer will use a zero-knowledge proof (ZKP) to demonstrate they possess a valid witness for this commitment and that the payment satisfies the hidden terms, all without leaking the sensitive invoice details to the public ledger.

step-2-confidential-payment
IMPLEMENTATION

Step 2: Enabling Confidential Payment Channels

This guide details the technical implementation of a privacy-preserving invoice and payment system using zero-knowledge proofs and smart contracts.

A confidential payment channel requires a system for generating and verifying invoices without revealing the transaction amount or recipient on-chain. This is achieved using zero-knowledge proofs (ZKPs). The core component is a ConfidentialInvoice smart contract that stores a cryptographic commitment—specifically a Pedersen commitment—to the payment amount. The payer generates this commitment off-chain by combining the actual amount with a secret blinding factor, creating commitment = amount * G + blinding_factor * H, where G and H are elliptic curve generator points. Only the hash of this commitment is stored on-chain, keeping the details private.

The invoice lifecycle begins when the recipient (payee) creates a payment request. They generate a unique invoice ID and the amount commitment off-chain, then call the createInvoice(bytes32 commitmentHash) function on the contract. The contract emits an event containing the invoice ID and commitment hash, which serves as a public, non-revealing record. The payer listens for this event. To fulfill the invoice, the payer must construct a zk-SNARK proof (e.g., using the Circom framework or Halo2) that demonstrates two things without revealing the inputs: 1) They know the secret blinding factor and amount that correspond to the published commitment hash. 2) The amount is exactly what was agreed upon in the off-chain negotiation.

Submitting the payment involves calling the contract's settleInvoice(uint256 invoiceId, bytes calldata zkProof) function with the generated proof. The contract verifies the proof against the stored commitment hash using a pre-deployed verifier contract. If valid, it marks the invoice as paid and updates the internal balance ledger for the involved parties. All sensitive data—the actual amount and blinding factor—remains confidential. For developers, a typical implementation stack includes Circom for circuit design, snarkjs for proof generation, and a Solidity verifier. The zk-SNARKs tutorial by 0xPARC provides essential background for building these circuits.

Key security considerations include ensuring the off-chain negotiation channel is secure (using encrypted messaging) and protecting against front-running. Since the commitment hash is public, a malicious actor could see an invoice and attempt to settle it first. To mitigate this, invoices can include a payer-specific public key or a short timelock during which only the intended payer's signature is accepted. Furthermore, the blinding factor must be a cryptographically secure random number; reuse can compromise privacy. Audit the zk-SNARK circuit to ensure it correctly constrains the amount to prevent overflow or negative values.

This architecture enables confidential recurring payments, payroll, or B2B transactions. The on-chain contract only sees hashes and proof validity, while all financial details are kept between the transacting parties. The next step involves integrating this with a state channel or rollup to enable fully private, high-throughput off-chain payment networks, moving the bulk of transaction processing away from the base layer while maintaining cryptographic settlement guarantees.

step-3-zk-reconciliation
PRIVACY ENGINE

Step 3: Proving Payment with Zero-Knowledge Proofs

This step details how to generate and verify a zero-knowledge proof to confirm a payment occurred without revealing the transaction details, completing the private invoice workflow.

With the invoice commitment stored on-chain and the payment made off-chain, the payer must now generate a zero-knowledge proof (ZKP). This proof cryptographically demonstrates two things: that they know a valid secret (the payment preimage) that hashes to the public payment_hash from Step 1, and that this secret was used to generate a valid transaction on the specified payment rail (e.g., Lightning Network). The proof is generated client-side using a zk-SNARK circuit, such as one written in Circom or Noir, which encodes the logic of the hash function and payment validation.

The circuit's public inputs, which are revealed to the verifier, are minimal and privacy-preserving. They typically include only the public invoice_id (to identify the specific obligation) and the resulting nullifier. The nullifier is a unique identifier derived from the secret preimage; it prevents double-spending by ensuring the same payment proof cannot be submitted twice. The circuit's private inputs, which remain hidden, are the payer's secret preimage and any necessary transaction details required to prove a valid payment was executed according to the invoice terms.

To generate the proof in practice, a developer would use a proving system like Groth16 or PLONK. For a Circom circuit, the process involves compiling the circuit, performing a trusted setup to generate proving/verification keys, and then using a library like snarkjs to create the proof. The code snippet below outlines the core proving step after the setup phase:

javascript
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
  { preimage: secretPreimage, publicHash: invoiceHash },
  "circuit_compiled.wasm",
  "proving_key.zkey"
);

The publicSignals output contains the public inputs, including the nullifier, for the verifier to check.

The generated proof and public signals are then submitted to a verifier smart contract on-chain. This contract, which holds the verification key from the trusted setup, runs the verifyProof function. The function checks the cryptographic proof against the public inputs and the contract's stored invoice commitment. If valid, it confirms that a hidden, valid payment corresponding to the invoice has been made. The contract then marks the invoice as paid, often emitting an event and storing the nullifier to prevent replay attacks.

This architecture enables powerful privacy properties. The business receiving payment (the verifier) gains cryptographic assurance of payment without learning the payer's on-chain address, the payment amount, or the exact timing from the public ledger. The system's security rests on the soundness of the zk-SNARK construction and the correctness of the circuit logic. Developers must rigorously audit the circuit to ensure it correctly validates the intended payment conditions without unintended side-channels.

step-4-accounting-integration
SYSTEM ARCHITECTURE

Step 4: Integrating with Accounting Software and Banks

This guide details how to connect your on-chain privacy-preserving payment system to traditional financial rails, enabling automated reconciliation and compliance.

The core challenge in integrating a privacy-preserving system with legacy accounting software is data reconciliation. Your off-chain database holds the plaintext details of invoices and counterparties, while the blockchain only records encrypted or zero-knowledge proof-based transaction hashes. The integration layer must map these two data sets without exposing sensitive information. A common pattern involves using a secure, permissioned API that allows your accounting software (like QuickBooks or Xero) to query payment statuses using a unique, non-sensitive identifier generated for each invoice, while the settlement proof is verified on-chain.

For bank integrations, you typically interact with banking APIs or use a payment processor like Stripe or Plaid to facilitate fiat on/off-ramps. When a user pays an invoice with fiat, the system must trigger the release of the on-chain payment or asset. This is often done through a webhook system. Your bank's API sends a payment confirmation to your secure backend server, which then executes a smart contract function to release funds or mark the invoice as paid. It's critical to implement idempotency checks here to prevent duplicate on-chain actions from repeated webhook calls.

Here is a simplified Node.js example of a webhook handler that verifies a bank payment and calls a contract. It uses the ethers.js library and assumes the use of a relayer to preserve payer privacy.

javascript
app.post('/bank-webhook', async (req, res) => {
  const { invoiceId, bankTxId, amount } = req.body;
  // 1. Verify payment with your bank's API
  const isValid = await verifyBankPayment(bankTxId, amount);
  if (!isValid) return res.status(400).send('Invalid payment');

  // 2. Fetch the corresponding on-chain invoice
  const invoice = await getInvoiceFromDB(invoiceId);
  const contract = new ethers.Contract(invoice.contractAddr, ABI, relayerSigner);

  // 3. Execute settlement, e.g., releasing escrowed funds
  const tx = await contract.settleInvoice(invoice.zkProof, invoice.nullifier);
  await tx.wait();
  res.status(200).send('Settlement triggered');
});

Security for these bridges is paramount. Your integration server becomes a trusted oracle. To minimize risk, implement multi-signature requirements for releasing large payments, rigorous audit logs for all reconciliation events, and regular attestations that the off-chain database and on-chain state are synchronized. Tools like Chainlink Functions or API3 can be used to create more decentralized oracle setups for payment verification, reducing the reliance on a single backend server.

Finally, ensure your system generates audit-friendly reports. While transaction details remain private, you must produce a verifiable trail for accounting purposes. This can be achieved by providing your accounting software with hashes of invoice data, zero-knowledge proof identifiers, and corresponding public on-chain transaction IDs. This allows auditors to cryptographically verify that the reported financials are consistent with the immutable blockchain ledger without learning sensitive business details.

PROTOCOL ANALYSIS

Comparison of Privacy Technologies for Payments

A technical comparison of leading privacy-enhancing technologies for building confidential payment systems.

Feature / MetricTornado CashAztec ConnectZcash (Sapling)Railgun

Privacy Model

Mixing Pools

ZK-SNARK Private Rollup

ZK-SNARK Shielded Pools

ZK-SNARK Private State

Base Chain

Ethereum

Ethereum (L2)

Zcash / EVM via EIP

EVM Chains (Ethereum, BSC, Polygon)

Withdrawal Delay

~30 min (challenge period)

< 5 min (rollup proof)

~2.5 min (block time)

< 2 min (proof generation)

Avg. Tx Cost (Mainnet)

$50-150

$5-15 (L2 gas)

$0.5-2.0

$10-30

Smart Contract Support

Multi-Asset Support

ETH, ERC-20s

ETH, ERC-20s

ZEC, ZEC Assets

ETH, ERC-20s, NFTs

Audit Status

Multiple audits (pre-sanctions)

Formally verified circuit

Multiple audits, academic review

Multiple audits, bug bounty

Relayer Requirement

PRIVACY-PRESERVING PAYMENTS

Common Implementation Issues and Fixes

Developers often encounter specific challenges when building systems that hide transaction amounts and participants. This guide addresses frequent technical hurdles and their solutions.

On-chain proof verification failures are often due to mismatched proving/verifying keys or incorrect public inputs. The most common causes are:

  • Mismatched Circuit: The proof was generated for a different circuit (e.g., different constraints) than the one the verifier contract expects. Ensure the proving key, verification key, and verifier smart contract are all compiled from the exact same source code (e.g., Circom file).
  • Incorrect Public Inputs: The array of public inputs (like a commitment hash or nullifier) passed to the verifier must be in the exact order and format the circuit expects. A single byte out of place will cause a revert.
  • Gas Limit Issues: Complex proofs may exceed the block gas limit. Optimize your circuit or consider using a verifier with cheaper elliptic curve operations (e.g., the Groth16 verifier from the snarkjs library).

Debugging Step: Use snarkjs to verify the proof off-chain first with the same verification key and public inputs you plan to use on-chain. This isolates the issue from blockchain-specific problems.

DEVELOPER FAQ

Frequently Asked Questions

Common technical questions and troubleshooting for building a privacy-preserving invoice and payment system using zero-knowledge proofs and blockchain.

A privacy-preserving payment system allows users to transact on a public blockchain while keeping the transaction amount, sender, and receiver confidential. It typically uses zero-knowledge proofs (ZKPs) like zk-SNARKs or zk-STARKs.

Core Mechanism:

  1. A user locks funds in a smart contract (e.g., on Ethereum).
  2. To make a payment, they generate a ZK proof that they own valid, unspent funds without revealing which ones.
  3. They create a cryptographic commitment (like a hash) representing the new transaction details (amount, recipient).
  4. The smart contract verifies the ZK proof and the commitment, then releases the funds to the new owner, who can later claim them with a secret key.

This process breaks the on-chain link between the original deposit and the final withdrawal, ensuring privacy. Protocols implementing this include Tornado Cash (for simple transfers) and Aztec (for private smart contracts).

conclusion
SYSTEM OVERVIEW

Conclusion and Next Steps

This guide has walked through building a system that leverages zero-knowledge proofs for private invoice generation and confidential payment settlement.

You have now implemented a functional prototype of a privacy-preserving financial system. The core components include a zk-SNARK circuit (e.g., using Circom) that validates invoice data without revealing it, a smart contract (on Ethereum or a compatible L2) that verifies proofs and manages state, and a frontend client that orchestrates proof generation and transaction submission. This architecture ensures that sensitive details like invoice amounts, client identifiers, and payment terms remain confidential on-chain, visible only to the involved parties off-chain.

To extend this system, consider integrating with existing DeFi primitives. The settlement contract could automatically route verified payments through a decentralized exchange (DEX) aggregator like 1inch to convert assets, or deposit funds into a lending protocol like Aave to generate yield for the recipient. For enterprise use, you could develop an oracle service that attests to real-world payment confirmations (like bank transfers) as a private input to the circuit, bridging traditional finance and on-chain settlement.

The next critical phase is security auditing. Begin with static analysis of your Circom circuits using tools like Circomspect to identify common vulnerabilities. Engage a specialized firm for a formal review of both the circuit logic and the verifier contract. Additionally, implement a robust testing suite with foundry or hardhat, including fuzz tests for the contract and comprehensive proof generation tests with edge-case inputs to ensure circuit reliability.

Further development should focus on scalability and user experience. Explore recursive proof systems (like Plonky2) to batch multiple invoice verifications into a single proof, drastically reducing on-chain gas costs. For the client, abstract the complexity of proof generation by implementing a background service worker or leveraging a proof co-processor network like Brevis or Risc Zero to handle the computational load, making the application viable for mobile users.

Finally, the principles demonstrated here—selective disclosure and computational integrity—are foundational for a new class of Web3 applications. You can adapt this framework for private voting, confidential payroll, compliant DAO treasuries, or any system where transactional privacy is required without sacrificing auditability. The source code for the circuits and contracts in this guide is available on the Chainscore Labs GitHub for further exploration and contribution.

How to Build a Private B2B Invoice and Payment System | ChainScore Guides