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

How to Design a Proof-of-Solvency Protocol for Your Custody Service

This guide provides a technical blueprint for implementing a proof-of-solvency protocol to cryptographically verify that client assets are fully backed, using Merkle trees for privacy and client-side verification.
Chainscore © 2026
introduction
ARCHITECTURE GUIDE

How to Design a Proof-of-Solvency Protocol for Your Custody Service

A technical guide for developers building transparent, verifiable asset attestations for custodial services using modern cryptographic techniques.

A Proof-of-Solvency (PoS) protocol is a cryptographic system that allows a custodial service to prove it holds sufficient assets to cover all client liabilities without revealing individual client balances. The core challenge is balancing transparency with privacy. Modern designs, inspired by protocols like those from Kraken and Binance, typically involve two components: a Proof of Reserves (PoR) to attest to total assets held, and a Proof of Liabilities (PoL) to attest to total client obligations. The fundamental guarantee is that Total_Reserves >= Total_Liabilities, proven in a publicly verifiable manner.

The first step is implementing the Proof of Reserves. This involves cryptographically attesting to the custody service's on-chain holdings. A common method is for the service to sign a message with the private keys of its cold and hot wallet addresses at a specific block height, publishing these signatures alongside the attested balances. For a more robust and privacy-preserving attestation, consider using Merkle Sum Trees, as detailed in the Bulletproofs paper. Here, each leaf is a tuple (balance, nonce) and each node stores the sum of its children's balances, allowing you to prove total reserves while hiding individual wallet amounts.

Next, you must construct the Proof of Liabilities to account for all client deposits. This is more complex, as you cannot simply publish a list of client balances. The standard solution is to build a Merkle Tree where each leaf is a hash of (client_id, balance). By providing a user with their Merkle proof, they can verify their inclusion in the total liability sum. To prevent the service from omitting liabilities, you must use a Merkle Sum Tree for liabilities as well, enabling anyone to verify that the published root commitment corresponds to the correct total. Users can then cryptographically audit that their balance is included correctly.

The final, critical step is the solvency proof itself: demonstrating that the committed reserves are greater than or equal to the committed liabilities. This requires a zero-knowledge proof (ZKP), such as a zk-SNARK, to show that for the reserve Merkle sum root R_root and liability Merkle sum root L_root, the relation R_total >= L_total holds. This proof convinces verifiers of solvency without revealing the individual sums that constitute each total. Frameworks like Circom or Halo2 can be used to construct this circuit. The entire protocol output is the published roots, the ZKP, and the necessary data for user inclusion proofs.

For practical implementation, your system's architecture should include: an auditor client that generates the Merkle trees and proofs off-chain, a verifier contract (often on a transparent chain like Ethereum) to validate the ZKP on-chain, and a user verification portal. Regularly scheduled attestations (e.g., monthly) are crucial. Remember, while a PoS protocol significantly enhances trust, it is not a substitute for real-time auditing or insurance. It is a cryptographic snapshot that provides verifiable evidence of custodial health at a specific point in time.

prerequisites
FOUNDATION

Prerequisites and System Requirements

Before building a proof-of-solvency protocol, you must establish the technical and operational foundation. This section details the essential components, from cryptographic primitives to infrastructure, required for a secure and verifiable implementation.

A proof-of-solvency protocol is a cryptographic system that allows a custodian to prove it holds sufficient assets to cover all client liabilities without revealing individual client balances. The core prerequisite is a deep understanding of zero-knowledge proofs (ZKPs) and cryptographic commitments. You must be proficient with libraries like circom for circuit design or arkworks for backend proving systems. Familiarity with Merkle trees is essential, as they are the standard data structure for committing to a set of user balances. The protocol's security rests on the soundness of these cryptographic building blocks.

Your technical stack must support heavy computational workloads. Proof generation, especially for ZK-SNARKs, is computationally intensive and requires a server with significant CPU and RAM resources. For a custody service with 100,000 user accounts, generating a single proof could take minutes on a high-end machine. You'll need infrastructure for off-chain proof computation and a reliable method for publishing the resulting proof and commitment (the Merkle root) on-chain. This typically involves a dedicated proving server, a secure key management system for the prover's keys, and integration with a blockchain node for data publication.

Accurate and tamper-evident data sourcing is critical. The protocol requires a single, authoritative source of truth for user liabilities (the sum of all client balances) and asset holdings. This often means integrating directly with your custody service's core settlement database and the blockchain nodes for the assets you custody (e.g., Bitcoin Core, Geth for Ethereum). You must design a secure data pipeline that can generate a snapshot of this data, compute the commitments, and feed it into the proving system without being manipulated. Any flaw in data integrity invalidates the entire proof.

On-chain verification is the final component. You need to deploy verifier smart contracts on a public blockchain (like Ethereum or a Layer 2). These contracts contain the verification key for your ZK circuit and the logic to check the published proof against the published Merkle root. Ensure your chosen blockchain has the necessary precompiles for cryptographic operations (e.g., the BN256 pairing for Groth16). The cost of on-chain verification gas fees is a key system requirement; optimizing your ZK circuit to minimize constraints directly reduces operational costs.

Beyond pure cryptography, operational security is paramount. You must establish strict access controls and audit logs for the proving system. The process of taking the data snapshot, generating the proof, and publishing it should be automated but monitored. Consider using trusted execution environments (TEEs) like Intel SGX for the most sensitive computations to provide hardware-backed attestation of code integrity. Finally, prepare comprehensive documentation for users and auditors explaining how to independently verify the proof using only the public information on-chain.

core-principles
CORE CRYPTOGRAPHIC PRINCIPLES

How to Design a Proof-of-Solvency Protocol for Your Custody Service

A technical guide for custody service operators on implementing cryptographic proof-of-solvency to verify asset holdings and build user trust without compromising privacy.

A proof-of-solvency protocol cryptographically demonstrates that a custodian holds sufficient assets to cover all client liabilities. It's a critical trust mechanism in a trustless environment, moving beyond opaque balance sheets. The core challenge is to prove the inclusion of client funds in the custodian's aggregate reserves while maintaining client privacy and preventing data linkage. Modern designs, inspired by proposals from exchanges like Kraken and BitMEX, typically combine two proofs: a Proof of Liabilities (PoL) and a Proof of Reserves (PoR). The protocol is considered sound if the verified total reserves are greater than or equal to the sum of all verified client liabilities.

The Proof of Liabilities cryptographically commits to each user's balance. A common method uses a Merkle sum tree, a variant of a Merkle tree where each leaf node contains a hash of a user ID and their balance, and each non-leaf node stores the sum of its children's balances. The root of this tree commits to all liabilities and their total. To prove inclusion, you provide a user with their leaf hash and a Merkle path to the root, allowing them to verify their balance is included in the published total. Advanced schemes use zk-SNARKs or Bulletproofs to hide individual balances within the tree while still proving the sum is correct.

The Proof of Reserves demonstrates control over the assets claimed. For on-chain assets like Bitcoin or Ethereum, this involves the custodian cryptographically signing a message with the private keys controlling their reserve addresses at a specific block height. The signed message, combined with the publicly verifiable blockchain state, proves ownership of those UTXOs or account balances. For a multi-asset custodian, this requires proofs across multiple chains. The critical step is attesting to the same point in time; the liability Merkle root and the reserve signatures must be anchored to the same blockchain block hash to prevent time-based manipulation.

Designing the system requires careful architecture. First, select commitment schemes: Merkle sum trees (simpler) or zero-knowledge accumulators (more private). Second, establish a scheduled attestation cycle (e.g., monthly). In each cycle: 1) Snapshot user balances and generate the liability root, 2) Sign messages from all reserve wallets referencing this root and a recent block hash, 3) Publish the root, block hash, signatures, and wallet addresses. Third, build a public verifier tool. This is often a web application that lets any user input their ID and balance to receive an inclusion proof and independently verify the custodian's signatures against the blockchain.

Key cryptographic considerations include leaf entropy to prevent brute-force revelation of the user list. Each leaf should be hash(user_id, balance, salt) where the salt is a secret per user. The total liability is still calculable via the sum tree. Wallet transparency is another concern; using a single reserve address simplifies proof but creates a target. Using many addresses improves security but complicates the signing process. Third-party audits can enhance trust, where an auditor receives the full user list (with consent) to verify the Merkle root construction matches the internal ledger, providing an extra layer of assurance beyond the public cryptographic proof.

Implementation tools are increasingly accessible. For Ethereum reserves, you can use the eth-proof-of-solvency library or the EIP-4973 standard for Account Bound Tokens representing liabilities. For zero-knowledge proofs, frameworks like Circom and snarkjs can compile circuits for balance privacy. The final protocol must be transparent, reproducible, and non-interactive. Users should be able to verify their inclusion and the reserve proof without trusting the custodian's software. By implementing this, a custody service moves from claiming solvency to proving it, significantly reducing counterparty risk and aligning with the cryptographic ethos of self-sovereign verification.

protocol-components
PROOF-OF-SOLVENCY DESIGN

Key Protocol Components

A proof-of-solvency protocol requires several cryptographic and blockchain-based components to verify a custodian's assets exceed its liabilities.

06

Cryptographic Audit & Open Source

The protocol's security depends on its implementation. The entire codebase—especially the zk-circuit logic and Merkle tree library—must be open source and audited.

  • Engage multiple specialized firms for audits (e.g., Trail of Bits, OpenZeppelin).
  • Publish all audit reports publicly.
  • Use battle-tested libraries like circom or halo2 for circuit development to reduce risk.
3+
Recommended Audits
step-1-merkle-tree
CORE DATA STRUCTURE

Step 1: Constructing the Liability Merkle Tree

The foundation of any proof-of-solvency system is a verifiable, tamper-proof record of customer liabilities. This step details how to build the primary data structure that enables this: the liability Merkle tree.

A liability Merkle tree is a cryptographic accumulator that commits to all customer account balances at a specific point in time (the snapshot block). Each leaf in the tree represents a single customer's liability, typically hashed from a structured data string like keccak256(userId + balance). The tree's root hash becomes a short, public fingerprint of your total obligations. This design provides data privacy—individual balances are hidden—while enabling cryptographic proof that a specific balance is included in the total.

To construct the tree, you must first define the data schema for a leaf. A common and secure pattern is to hash a concatenated string of the user's unique identifier (like an internal account ID or a hash of their deposit address) and their balance, formatted as a fixed-point integer to avoid precision errors. For example, a leaf for user 0xabc... with 1.5 ETH (assuming 18 decimals) would be leaf = keccak256(abi.encodePacked(userId, 1500000000000000000)). Using abi.encodePacked ensures consistent serialization. Avoid using plain email addresses or names as identifiers to protect user privacy.

After generating leaves, you build a standard binary Merkle tree. If the number of leaves isn't a power of two, you must pad with zero-value leaves. The root is calculated by recursively hashing pairs of child nodes: node = keccak256(leftChild + rightChild). Libraries like OpenZeppelin's MerkleProof can handle this logic. The final root must be published on-chain or in a verifiable data store. Crucially, the process and all code used to generate the tree should be open-source, allowing anyone to verify the root was computed correctly from the claimed liabilities.

This step's output is the Merkle root and the complete set of Merkle proofs (one per user). Each proof is the sequence of sibling hashes needed to recompute the root from a user's leaf. You will provide these proofs to users in the next step. The integrity of the entire protocol hinges on this root accurately reflecting the true sum of all leaf balances. Any error or omission here invalidates the subsequent solvency proof.

step-2-inclusion-proofs
PROOF GENERATION

Step 2: Generating Client Inclusion Proofs

This step involves cryptographically proving that a specific client's assets are included in the global liability commitment, enabling verifiable solvency.

After establishing the Merkle root of all client liabilities in Step 1, you must generate a Merkle proof for each client. This proof is a cryptographic receipt that demonstrates, beyond doubt, that their individual balance is correctly included in the total liability snapshot. The proof consists of the authentication path—the sequence of sibling hashes needed to recompute the root from the client's leaf. You deliver this proof to the client, who can independently verify it against the publicly posted root.

The technical implementation requires your backend to traverse the Merkle tree. For a client with leaf index i, you collect the sibling hash at each level up to the root. In a binary Merkle tree, the path is determined by the binary representation of the index. Here's a simplified Python example using keccak256 for hashing:

python
import hashlib
def keccak256(data):
    return hashlib.sha3_256(data).digest()

def generate_proof(leaves, index):
    proof = []
    current_index = index
    current_level = leaves
    while len(current_level) > 1:
        sibling_index = current_index ^ 1  # Flip last bit to get sibling
        if sibling_index < len(current_level):
            proof.append(current_level[sibling_index])
        # Move to parent level
        current_index = current_index // 2
        current_level = [keccak256(l + r) for l, r in zip(current_level[0::2], current_level[1::2] + [b''])]
    return proof

For the proof to be trustworthy, the data in the client's leaf must be non-malleable. A standard approach is to hash a structured message like keccak256(abi.encodePacked(clientAddress, balance, nonce)). The nonce (e.g., a timestamp or serial number) prevents replay attacks across different snapshots. This ensures each proof is unique to a specific audit period. Clients must be able to access the current root, often published on-chain or via a signed API endpoint, to serve as the single source of truth for verification.

Consider privacy implications. A simple Merkle tree can leak information if the tree structure is known. Using a sorted Merkle tree, where leaves are ordered by hash, can help obscure individual positions. For enhanced privacy, protocols like zk-SNARKs can prove inclusion without revealing the leaf's index or neighboring hashes, though this adds significant computational complexity. The choice depends on your service's transparency requirements and threat model.

Finally, integrate proof generation into your custody service's audit cycle. This is typically an automated process triggered after each liability snapshot. Proofs can be delivered via email, through a client portal, or as on-chain transactions. Document the verification process for clients, providing sample code in languages like JavaScript or Python so they can independently check the cryptographic integrity of their inclusion proof against the published root hash.

step-3-total-commitment
CRYPTOGRAPHIC PROOF

Step 3: Committing to Total Assets and Signing

This step involves cryptographically proving the total assets under custody and generating a verifiable signature from the custodian.

After generating the Merkle root of all user liabilities, the custodian must now commit to their total assets. This is the cryptographic statement that the sum of all user balances (liabilities) is backed by the custodian's on-chain holdings (assets). The commitment is created by hashing the total asset value, typically in a base unit like wei for Ethereum, alongside the liability Merkle root. A common construction is total_assets_commitment = keccak256(abi.encodePacked(merkle_root, total_assets_value)). This binds the two values together, preventing later manipulation.

The custodian must then sign this commitment with their private key. This signature acts as a non-repudiable attestation. Anyone can verify that the entity controlling the custodian's signing key attested to the specific merkle_root and total_assets_value at a point in time. The signed message should include a timestamp or block number to prevent replay attacks. In practice, you would use a library like OpenZeppelin's ECDSA to produce a signature over the commitment hash, resulting in a (v, r, s) tuple or a packed 65-byte signature.

Here is a simplified Solidity example for generating the commitment and a conceptual signing step off-chain:

solidity
// Off-chain computation (e.g., in Node.js with ethers.js)
import { ethers } from 'ethers';

function generateCommitment(merkleRoot, totalAssets) {
  // Encode and hash the commitment
  const commitmentHash = ethers.utils.keccak256(
    ethers.utils.defaultAbiCoder.encode(
      ['bytes32', 'uint256'],
      [merkleRoot, totalAssets]
    )
  );
  // Sign the hash with the custodian's private key
  const wallet = new ethers.Wallet(CUSTODIAN_PRIVATE_KEY);
  const signature = await wallet.signMessage(ethers.utils.arrayify(commitmentHash));
  return { commitmentHash, signature };
}

The final output of this step is the signed commitment, which will be published for verification. This data package includes: the merkle_root, the total_assets_value, the signature, and the custodian's public address or verification key. This package is the core of the proof-of-solvency claim. It allows any verifier to check two things: that the signature is valid for the given custodian address and commitment hash, and later, that the total_assets_value can be validated against real on-chain data.

A critical consideration is asset aggregation. The total_assets_value must represent the sum of all custodial wallets' holdings for the specific assets covered by the liability Merkle tree. This often requires querying balances across multiple addresses and chains, using standardized price oracles for consistent valuation, and snapshotting these values at the same block height used for user balances. Inconsistencies in timing or valuation methodology between assets and liabilities create gaps that undermine the proof's integrity.

With the signed commitment generated, the protocol moves to the final verification stage. The published data enables independent auditors and users to cryptographically verify that the custodian's attested liabilities are fully backed, completing the trust-minimized audit loop without exposing individual user balances.

TECHNIQUES

Proof-of-Solvency Method Comparison

Comparison of technical approaches for proving custody service solvency, detailing trade-offs in privacy, cost, and verification.

Feature / MetricMerkle Tree (Proof of Reserves)ZK-SNARKs (Zero-Knowledge Proofs)Trusted Setup (Proof of Liabilities)

Client Privacy

Proof Size

~1 KB per client

~1-10 KB constant

~100 bytes per client

On-Chain Verification Cost

$5-20 per audit

$50-200 per audit

$1-5 per audit

Prover Computation Time

< 5 minutes

2-6 hours

< 1 minute

Requires Trusted Setup

Reveals Total AUM

Audit Frequency Feasibility

Daily

Weekly/Monthly

Real-time

Cryptographic Assumption

Collision-resistant hash

Knowledge-of-exponent

Multi-party computation

step-4-client-verification
IMPLEMENTATION

Step 4: Building Client Verification Tools

This guide details how to build the client-side verification tools that allow users to independently verify their inclusion in your Proof-of-Solvency protocol.

The core value of a Proof-of-Solvency (PoS) system lies in its verifiability. After generating the Merkle tree and publishing the root hash on-chain, you must provide users with the tools to confirm their assets are included. This requires building a public verification portal or providing an open-source script. The portal's primary function is to take a user's provided account identifier (e.g., client_id), fetch their leaf data and the corresponding Merkle proof from your published data, and cryptographically verify it against the on-chain root hash.

The verification process follows a standard cryptographic check. The client provides their hashed leaf data H(leaf). Your tool supplies the sibling hashes (the Merkle proof) along the path from the leaf to the root. The algorithm recursively hashes H(leaf) with each sibling hash, moving up the tree, until it computes a final root hash. This computed root is then compared to the merkleRoot stored in your on-chain smart contract. A match proves the user's data was part of the committed dataset. Here's a simplified Python example using web3.py and merkletools: computed_root = merkle_tools.make_merkle_tree_from_hashes([leaf_hash] + proof_hashes); assert computed_root == contract.functions.merkleRoot().call().

For transparency, the verification tool should also decode and display the leaf data for the user. This means parsing the concatenated string client_id|total_balance_usd|timestamp from the leaf's pre-image hash. Displaying the total_balance_usd and timestamp allows the user to confirm the snapshot reflects their holdings at a specific point in time. This step builds critical trust, as users can see the exact data you committed about them. Ensure your data publication method (IPFS, dedicated API) is reliable and the fetched proofs are immutable between the proof generation and verification stages.

Advanced implementations can enhance the user experience and security. Consider generating a unique verification URL for each client, pre-populated with their client_id and a signed message from your custody service's private key. This signature proves the URL was legitimately generated by you. Furthermore, you can implement privacy-preserving verification using Zero-Knowledge Proofs (ZKPs). Instead of sending the raw Merkle proof, a ZK-SNARK can generate a proof that the user's leaf exists in the tree without revealing the leaf's content or their position, though this adds significant implementation complexity.

Finally, document the verification process thoroughly and open-source your client tools. Provide clear instructions for technical users to run the verification locally. This auditability is a cornerstone of a trustworthy PoS system. By empowering users with independent, cryptographic verification, you move beyond marketing claims to providing mathematically-grounded proof of solvency, significantly enhancing your custody service's credibility in the market.

operational-considerations
IMPLEMENTATION

Operational Considerations and Attestation Schedule

A proof-of-solvency protocol requires a robust operational framework to ensure its integrity and trustworthiness over time. This section covers key execution details and the critical cadence of attestations.

The core of your protocol's trust is the attestation schedule. This defines how frequently you prove solvency to users and the public. Common frequencies are monthly or quarterly, but more frequent attestations (e.g., weekly) provide stronger, near real-time assurances. The schedule must be published and immutable, often recorded on-chain via a smart contract or a public commitment. Adherence to this schedule is non-negotiable; missing a scheduled attestation is a major red flag that can trigger mass withdrawals and severely damage trust. Consider using automated systems or on-chain timelocks to enforce publication deadlines.

Operational security for the attestation process is paramount. The system generating the Merkle tree and proof must be air-gapped or exist in a highly secure, audited environment separate from your live custody servers. This prevents a compromise of operational keys from allowing the generation of fraudulent proofs. The process typically involves: taking a cryptographic snapshot of all customer liabilities at a specific block height, generating the Merkle root off-chain, and then publishing the root and necessary proof data to a verifiable public location, such as a blockchain or a decentralized storage network like IPFS or Arweave.

Transparency in data presentation is crucial for user verification. You must provide a public tool, often a verifier webpage, where any user can independently confirm their inclusion in the proof. This tool uses the published Merkle root and allows a user to input their account ID and the cryptographic proof (the Merkle path) to verify their funds are included in the total liabilities. The design should be open-source and auditable. Additionally, publishing the total proof-of-reserves figure (e.g., "We hold 100,000 ETH in these verifiable addresses") alongside the total liabilities allows anyone to check the solvency ratio at a glance.

Your protocol must account for dynamic liabilities like staking rewards, airdrops, or transaction fees accrued between attestations. Clearly communicate to users that the proof is for a specific point in time. One approach is to use a commitment scheme where you commit to a future attestation root in advance. For advanced implementations, consider zero-knowledge proofs like zk-SNARKs to prove solvency without revealing individual customer balances or the total liability amount, enhancing privacy while maintaining verifiability. However, this adds significant implementation complexity.

Finally, establish clear procedures for key management and disaster recovery. Who can trigger the attestation? How are the signing keys for the attestation secured (e.g., multi-sig, HSMs)? What is the process if the primary attestation system fails? Documenting and following these procedures rigorously is part of demonstrating operational excellence and is as important as the cryptographic proof itself for establishing long-term trust with your user base.

DESIGN & IMPLEMENTATION

Proof-of-Solvency Protocol FAQ

Technical questions and answers for developers building or auditing a proof-of-solvency system for a crypto custody service.

The core cryptographic proof is a Merkle tree commitment of user account balances. The protocol cryptographically proves that the total sum of all user balances (liabilities) is less than or equal to the total assets held by the custodian. This is typically done by:

  • Constructing a Merkle Tree: Each leaf is a commitment to a user's account ID and balance, often hashed as H(user_id, balance).
  • Publishing the Merkle Root: This root is published on-chain or to a public data availability layer, acting as a commitment to the entire set of liabilities at a specific block height.
  • Generating Inclusion Proofs: Users can request a Merkle proof to verify their specific balance is included in the published root.

The critical step is that the custodian must also prove knowledge of the private keys for the wallets holding the reserve assets, usually via a signed message from those addresses.