# Proof of Liabilities Deep Dive
**TL;DR**: Proof of Liabilities is one of two protocols used in Proof of Reserves. This post walks through the construction of such a protocol using a Merkle Sum Tree, which allows an exchange to commit to its liabilities without revealing customer information or business-critical data like the exchange’s number of users, individual, and total liabilities. As a public company, Coinbase already proves its reserves through rigorous accounting supported by a strong control environment. On-chain accounting is the future, but more work needs to be done before it’s ready to be implemented at scale. To that end, Coinbase is exploring crypto-native ways to provide proof of reserves.
---
# Introduction
In our [previous blog post](https://www.coinbase.com/blog/proof-of-reserves-grant), we delved into the collaboration between Coinbase and [Silver Sixpence](https://silversixpence.io), highlighting our commitment to advancing the state of Proof-of-Reserves (PoR) through privacy-preserving and crypto-native solutions. Silver Sixpence is building an open-source software library & web application for PoR which aims to enhance the transparency, reliability, and accessibility of exchange solvency data.
Generally, Proof of Reserves (PoR) refers to an accounting procedure that exchanges undertake to show that they have enough assets to cover their total user liabilities; it allows the exchange’s customers to verify (in a trustless manner) that the exchange is indeed solvent. PoR consists of 2 steps: Proof of Assets (PoA) and Proof of Liabilities (PoL). In PoA, the exchange demonstrates control over the private keys of specific on-chain addresses, while in PoL the exchange commits to the sum total of assets owed to each user (the liabilities). The essence of PoR lies in comparing the total sum of liabilities to the total sum of assets, with the ultimate goal of ensuring a positive balance sheet (assets - liabilities > 0). **In this post** we will focus on **PoL**, and PoA will be explored in a subsequent article. To simplify the text, we focus on PoL for a single digital asset, although the principles can be easily extended to multiple.
A working implementation of the PoL protocol can be found [here](https://crates.io/crates/dapol). The protocol allows exchanges to perform PoL while keeping hidden a) the number of users of the exchange, b) each user’s identifying information & individual balance, and c) the total sum of the exchange’s liabilities. Below is a snippet of the benchmark data for the implementation (full benchmark results can be found [in the repo](https://github.com/silversixpence-crypto/dapol#benchmarks)).
The tree build and memory metrics are for a tree of height 32, built using an r7a EC2 instance with 128 cores:
<table>
<tr>
<td>Number of users
</td>
<td>10M
</td>
<td>50M
</td>
<td>125M
</td>
<td>250M
</td>
</tr>
<tr>
<td>Tree build time
</td>
<td>3.5m
</td>
<td>15m
</td>
<td>34m
</td>
<td>1h
</td>
</tr>
<tr>
<td>Tree build mem
</td>
<td>81GB
</td>
<td>323GB
</td>
<td>653GB
</td>
<td>667GB
</td>
</tr>
</table>
This table shows proof generation time and size metrics:
<table>
<tr>
<td>Tree height
</td>
<td>16
</td>
<td>24
</td>
<td>32
</td>
<td>40
</td>
<td>48
</td>
</tr>
<tr>
<td>Proof generation time
</td>
<td>207ms
</td>
<td>393ms
</td>
<td>547ms
</td>
<td>823ms
</td>
<td>1.9s
</td>
</tr>
<tr>
<td>Proof size
</td>
<td>2.6kB
</td>
<td>3.6kB
</td>
<td>4.5kB
</td>
<td>5.4kB
</td>
<td>6.3kB
</td>
</tr>
</table>
# What is PoL?
At its core, PoL navigates the delicate balance between safeguarding user data and providing verifiable proof of an exchange’s liabilities. The exchange stores its users’ asset balances securely on private servers. This ensures privacy but also presents a challenge for validation, as the exchange and the user are the only parties aware of the specific details of their asset balances.
PoL protocols aim to establish a trusted verification mechanism between the exchange and users, without requiring any third party oversight. Current PoL protocols tend to align on a high-level design, comprising three main stages:
1. The exchange cryptographically commits to its liabilities at some historic timestamp by publishing data on a public bulletin board (PBB) (e.g. a blockchain). Once the commitment is made, it cannot be altered or reversed later.
2. At any time after the commitment is made, a user of the exchange can use the commitment to verify that their balance at that time was correctly recorded by the exchange.
3. Should discrepancies arise, users are empowered to raise their concerns and challenge the exchange.
It's crucial to acknowledge that the effectiveness of this system hinges on user participation. An exchange may be able to get away with some under-accounting of its liabilities if some users don’t verify that their balances are “included” (as in step 2). However, if a diverse and random subset of users take part in the verification process, it becomes extremely challenging for the exchange to manipulate its liabilities. (A detailed statistical analysis of this can be found in the [Generalized Proof of Liabilities](https://eprint.iacr.org/2021/1350) paper by Chalkias & Ji, section 5.)
Since we can't make user balances public, we need to find a way to hide specific user information while still allowing everyone to check for their liabilities. For this, we use a **Merkle Tree**.
# Merkle Trees
A [Merkle Tree](https://en.wikipedia.org/wiki/Merkle_tree) is a binary tree used for secure data verification. Each node in the tree contains a **hash**, a string of bytes generated by a [cryptographic hash function](https://en.wikipedia.org/wiki/Cryptographic_hash_function) from some input data.
$$
\text{Hash} = \textbf{hash_function}(\text{data})
$$
A secure cryptographic hash function has the following useful properties:
1. **Collision resistance:** It is computationally infeasible to find two different input data that generate the same hash.
2. **Pre-image resistance:** If you only have the hash, it is computationally infeasible to find any input data that generates it.
A Merkle tree must be built from bottom to top, starting with the nodes on the bottom layer (known as **leaf nodes**). The hashes in the leaf nodes relate to some external data that the Merkle Tree is modeling:
$$
\text{Leaf node hash} = \textbf{hash_function}(\text{external data})
$$
The nodes in the bottom layer are **children** to the nodes in the layer above, who are the **parents** (picture a family tree). The child nodes are grouped into pairs. Each node’s partner in the pair is known as its **sibling**. Each parent node is constructed from a pair using the merge function:
<!--
The quads here are to center the image. HackMD does not support the 'align' tag.
-->
$\quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad$<img src="https://hackmd.io/_uploads/rJCKpR_HR.png" width="150" align="center" >
The merge function typically uses the same hash function as was used to construct the leaf nodes, except this time the input is a concatenation of the hashes of the child nodes:
$$
\text{Parent node hash} = \textbf{hash_function} (\text{left child hash} | \text{right child hash})
$$
After all the nodes in the 2nd layer have been constructed one can construct the 3rd layer using the same process, but with the children being the nodes in the 2nd layer and the parents the nodes in the 3rd. This process repeats olayer by layer, halving the number of nodes each step, until we reach the tree’s summit with a singular node called the **root**. See the figure below for an illustration of this process. (Hash values in this figure, and in the figures below, are _not_ obtained through a hash function.)

Merkle Trees bring certain advantages that make them beneficial for use in PoL:
1. **Public Commitment**: Only the root node, encapsulating the entirety of the underlying data, needs to be published. Due to the pre-image resistance of the hash function we can be sure that the underlying data stays hidden. (Actually, we need to take some precautions to make sure that the data remains truly hidden as you will see below.)
2. **Inclusion Proof**: After the public commitment, the tree’s creator can generate a proof confirming that a specific piece of data was used to construct the tree, without revealing any other data used in its construction. Note that node hashes _will_ be exposed in an inclusion proof, but the pre-image resistance again keeps the underlying data hidden.
3. **Data Integrity**: The tree’s creator cannot fabricate a proof for arbitrary data that was not used to construct the tree. This is dependent on the collision resistance of the hash function.
### The Merkle Sum Tree
The most popular PoL protocols use a variation of the Merkle Tree called a **Merkle Sum Tree** (MST). An MST has the appearance of a regular Merkle Tree but each node contains an additional **integer** **value**. Also, the merge function is different in that the input to the hash function at any node is the concatenation of children’s hashes _and_ the integer value at that node, where the value itself is the _sum_ of children's values. Using this new value field to represent an exchange’s liabilities to users gives us a PoL tree.
$\quad \quad \quad \quad \quad \quad \quad \quad \quad$<img src="https://hackmd.io/_uploads/rkkGRCdH0.png" width="400" align="center" >
### Maxwell-Todd Tree
Maxwell & Todd first described the usage of an MST for PoL in a [Bitcoin Wizards IRC chat](https://web.archive.org/web/20170106070229/https:/people.xiph.org/~greg/bitcoin-wizards-fraud-proof.log.txt) in March 2013. Todd later spoke about MSTs at the Bitcoin 2013 conference, and in 2014 Wilcox explored the idea further in [a blog post](https://web.archive.org/web/20171124195504/https://iwilcox.me.uk/2014/proving-bitcoin-reserves). This sparked a deeper conversation on the [Bitcoin forum](https://bitcointalk.org/index.php?topic=595180.0) and [YCombinator](https://news.ycombinator.com/item?id=7277865) involving Maxwell, Todd & Buterin, amongst others.
The construction of a Maxwell-Todd tree closely mirrors that of a Merkle Sum Tree. It begins at the bottom level, with each user being represented by a leaf node. The user’s balance is the value of the leaf node, and their account information is the input to the hash function.
$\quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad$<img src="https://hackmd.io/_uploads/Bym40ROB0.png" width="300" align="center" >
The nonce adds extra security and is necessary if the user IDs are not long enough (see [restricted pre-image space attacks](https://en.wikipedia.org/wiki/Preimage_attack#Restricted_preimage_space_attacks) for more details). Next, the internal nodes of the tree are built from the leaf nodes, layer by layer, using the merge function as described in the previous section.

Any layers with an uneven number of nodes are made even using a padding mechanism (padding nodes have a value of 0 and a hash of random data). The final structure of the MST can be categorized as either a '**perfect** binary tree' if the user count aligns with a power of two, or a '**full** binary tree' if not (see [here](https://en.wikipedia.org/wiki/Binary_tree#Types_of_binary_trees) for more details on the various types of binary trees).
### Inclusion Proofs
Inclusion proofs allow the creator of a Merkle Tree to prove that some piece of data was used to construct the tree. In the case of a Maxwell-Todd Tree, an exchange is able to prove to a user that their balance is correctly represented in the tree. Thanks to the collision-resistance property of our hash function, it is computationally infeasible for the exchange to produce a false proof that will pass verification.
Recall that every user is represented by a leaf node in the tree. A user’s **path** is the set of nodes that forms the unique route up the tree from their leaf node to the root node. To verify that their balance is correctly represented, the user first constructs their own leaf node using their account ID, nonce, and asset balance at the time of the PoR attestation. Next, they request an inclusion proof from the exchange. The exchange responds with the nodes that are _siblings_ to the nodes along the user’s path.
The user constructs their own path, from bottom to top, by invoking the merge function at each layer. Since the merge function requires 2 child nodes as input, and the user starts with just one node (their leaf node), they require additional nodes to build their path. The sibling nodes sent by the exchange are exactly these extra nodes! At the end (top) of the path, the user will obtain a root node, which they compare to the published root node. They also check that every node has a value greater than or equal to zero (otherwise the exchange can cheat by adding fake leaves with negative balances, reducing their liability sum).
If there is a mismatch between the root nodes, or one of the values is negative, then the verification fails, otherwise it is successful.

*[image source](https://drive.google.com/file/d/1MGz2T-4rkH777gxc5pLqiaHBwISTqdby/view?usp=sharing )*
In the diagram above:
* The blue node is the leaf node that the user constructs themselves, using their account ID, nonce and balance.
* The nodes surrounded by dotted diamonds represent the sibling nodes, collectively forming the inclusion proof. These nodes are sent by the exchange to the user.
* The nodes surrounded by dashed circles make up the user’s path and are calculated by the user, using the green and blue nodes as input to the merge function.
* The clear nodes are not involved with the process at all.
_Note: the user associated with the lowest green node (with value 19) is known as the **sibling user**, since they are neighbors to the user making the request._
# Security Concerns
While the Maxwell-Todd Tree offers an efficient method for implementing PoL, a critical security flaw was identified by [Hu, Zhang and Guo](https://eprint.iacr.org/2018/1139.pdf) in 2019. This flaw, if exploited, allows an exchange to significantly understate its liabilities without being detected. Here is how it works:
1. **Manipulating the Merge Function**: the exchange uses a different merge function to construct the tree. Instead of calculating the parent value as the **sum** of its children’s values, it uses the **maximum** value of the two. The parent hash is calculated as before, using the (newly defined) parent value.
$\quad \quad \quad \quad \quad \quad \quad \quad \quad$<img src="https://hackmd.io/_uploads/ryxw7ytB0.png" width="400" align="center" >
A tree built using this merge function will have a root node with a value that is equal to the **maximum** liability of all the leaf nodes, as opposed to the **sum** of them. In fact, every node’s value will be less than what it should be, except for the leaf nodes.

*[image source](https://drive.google.com/file/d/1IhH0X3rnEDdJcMTsX45xHgvByJB87NwT/view)*
2. **Tailoring Inclusion Proofs**: The inclusion proofs provided to the users are altered in such a way that they still verify correctly when the user employs the intended merge function for validation. This means that even though the exchange has understated its liabilities, users are unable to detect the discrepancy. Here are two examples of inclusion proofs for the malicious tree that still verify correctly:
<table>
<tr>
<td>
<img src="https://hackmd.io/_uploads/ryU0X1tHA.png" width="" alt="alt_text" title="Maxwell Tree altered inclusion proof 1">
</td>
<td>
<img src="https://hackmd.io/_uploads/rJRg4ytSA.png" width="" alt="alt_text" title="Maxwell Tree altered inclusion proof 2">
</td>
</tr>
<tr>
<td><a href=https://drive.google.com/file/d/1g8KSpEnTMW5m6vjK2prec9nwZ24Tp20a/view>image source</a></td>
<td><a href=https://drive.google.com/file/d/15wJi2UyaiUh224028TK7yewBMOiPnOrX/view>image source</a></td>
</tr>
</table>
### Addressing the Flaw: The Maxwell+ Tree
Hu, Zhang and Guo proposed a fix too. Their solution involves an adjustment to the merge function: the input to the hash function now includes the values of both child nodes, rather than just the sum of the node's children’s values.
The Maxwell-Todd Tree with this fix is called the **Maxwell+ Tree**.
$\quad \quad \quad \quad \quad \quad \quad \quad \quad$<img src="https://hackmd.io/_uploads/SkWS41YSC.png" width="400" align="center" >
By incorporating the values of the child nodes directly into the hash, any attempt to manipulate the merge function would result in a detectable discrepancy in the hash values.
# Privacy
The Maxwell-Todd / Maxwell+ tree offers certain privacy benefits over simply publishing a flat list of all leaf nodes, which would expose the total number of users and their individual balances to the public. The tree keeps the individual leaf nodes hidden from the public since the root node does not expose any information about individual users. However, there are some issues.
Firstly, the root node exposes the total liability sum of the exchange. And, secondly, any user receiving an inclusion proof can deduce:
1. The balance of one other user: their sibling user in the tree (they cannot tell what the account ID is, since this information is hidden behind the hash function).
2. The cumulative balances of other sets of users in the tree, depending on where they are located. These balances correspond to larger and larger sets as one goes up the tree.
3. The height of the tree, which can be used to get a good estimate of the total number of users: $\text{Estimated total users}=2^{\text{tree height}}$
It’s crucial for the exchange to authenticate users requesting an inclusion proof to mitigate data leakage, ensuring that each user only receives their specific inclusion proof (if any). However, even with authentication, users could collude amongst themselves and learn a lot more than one would expect.
### Attempting to Hide the Total User Count
One approach to enhance privacy is to make the tree **sparse**. The idea here is to arbitrarily increase the height of the tree to throw off the estimate calculation above. Since the number of user leaf nodes is fixed, and the number required to fill the bottom layer of the tree grows with the height, we will end up with a partially-filled bottom layer. Users are randomly mapped to a location on the bottom layer, and padding nodes are added so that each leaf node has a sibling. Now that we have pairs of nodes, we use the merge function to construct the next layer. The process of adding pads to “lonely” nodes is repeated at each layer so that its parent layer can be constructed. In the figure below, purple nodes represent the padding nodes.

*[image source](https://drive.google.com/file/d/1_MByHFTt6YcX3ndcDJmr0w7njWTZOFg3/view?usp=sharing)*
However, this method has its limitations too. The value of 0 in the padding nodes, which is visible to anyone with the right inclusion proof, can leak information about the tree's structure. This will allow for a better estimate on the number of users than the original formula can give through
$$
\sum_{i=1}^{m}2^{\text{height} - x_i}
$$
where $\{x_1, ... ,x_m\}$ are the heights of all padding nodes present in an inclusion proof.
### Tracking users across multiple PoLs
Privacy considerations become more complex when dealing with multiple PoLs. Over time, an exchange would produce many PoLs, each representing a snapshot of the exchange's liabilities at a given time. To prevent users from being tracked across different PoLs, each user must have a unique nonce for each PoL. Although this makes tracking significantly harder, it is not a foolproof method. An educated guess based on the comparison of leaf nodes’ values across trees can sometimes reveal which leaf nodes belong to the same user, particularly if the values are close or identical and no other similar values are present.
The authentication process makes such attacks more difficult to carry out because _only_ a valid user (not just anyone) can request _only_ their inclusion proof (not another user’s). Another method to mitigate the risks is to shuffle the leaf nodes, which decreases the likelihood of a user having the same sibling user across different trees.
### Better Privacy
A popular method to address the privacy issues with Maxwell-Todd / Maxwell+ tree is to split each user's balance into multiple smaller denominations and assign each to its own leaf node, shuffling them along the bottom layer and then building the tree as before. Each user will now have multiple paths up the tree.

*[image source](https://drive.google.com/file/d/1EtHhh_75lxxnhIR7-mZTv8F4pRnRrUoD/view)*
The leaf node hash function must be given an index as part of the input so that each hash is unique in a user's leaf node set. From the outside, it won’t be possible to tell which set of leaf nodes is associated with a particular user and so the user's balance is obfuscated. To keep the total number of users private, the number of splits must not be the same for every user. If this were the case, then a user could gain a good estimate on the total number of users of the exchange from their inclusion proof via the following formula:
$$
\text{Estimated total users}=\frac{2^{\text{tree height}}}{\text{number of splits}}
$$
The more we split, the more obfuscated user balances become, with the extreme case being that each user is split until each of their leaf nodes has a small fixed value (say, the least number of satoshis one can buy on the exchange). Added privacy through this method, however, comes at a performance cost since more splits generate a larger tree. Note that this method of hiding the total number of users is better than the sparse tree because the two trees can have the same height but the pad-counting formula does not apply to this tree.
It's not known who first proposed this method, but Kostas Chalkias spoke about it [at the ZKProof Standards conference](https://www.youtube.com/watch?v=EH40KY5P5Y0) in 2019. We call this version of the tree the **Maxwell++ Tree.**
### Near-Perfect Privacy
We can do better than Maxwell++: Enter the magical [Pedersen commitment](https://link.springer.com/chapter/10.1007/3-540-46766-1_9). A Pedersen commitment encrypts a number (the **secret**) using another number (the **blinding factor**), which must be kept hidden for the commitment to be secure. Pedersen commitments are **homomorphic**, i.e, a commitment to the sum of two secrets could be generated by “adding” the commitments to the individual secrets. So if we have two Pedersen commitments $P_1$ and $P_2$ which commit to secrets $s_1$ and $s_2$, then $P_1 + P_2$ commits to $s_1+s_2$. Now, if the balance at every leaf node is stored as a Pedersen commitment then we can still build the tree using the existing method because the merge function only requires that we can add node values.
Replacing all the leaf node values with Pedersen commitments helps us solve two privacy issues:
1. No user or third party can know the total liability sum of the exchange (value at the root is a commitment to the liability).
2. Users cannot know the balance of their sibling user, or any other subset of users.
The verification process has not been compromised in any way because each user simply needs to commit to their balance with the same blinding factor the exchange used, and they can verify an inclusion proof with the same algorithm (a small difference is that Pedersen commitments would be summed in a way different from integer values). So, along with their nonce value, a user must be able to retrieve their blinding factor from the exchange.
Pedersen commitments introduce a problem though. Since values are committed now, users won’t be able to check if values in their path are negative when they verify their inclusion proof. Some further machinery is required to get us a valid protocol – and that is zero-knowledge (ZK) range proofs. A ZK range proof shows cryptographically that the secret in a Pedersen commitment is non-negative, without revealing any information about it. When the user requests an inclusion proof, the exchange must provide a range proof for each sibling node’s value showing that it is non-negative, along with the sibling nodes. The user will have to verify each of the range proofs before considering an inclusion proof valid. (The proofs do not contain any secret / private information so others can verify them on behalf of the user.)
The tree now looks like this:

*[image source](https://drive.google.com/file/d/1PcKlsUCAOSmgr93vPEhOuEwLECD__nde/view)*
Note that the node values look similar to the hashes now, but this is simply because the commitments have been encoded to hexadecimal, which is also the default encoding of a hash function’s output.
This type of tree was [first proposed by Camacho](https://www.slideshare.net/philippecamacho/protocols-for-provable-solvency-38501620) in 2014. It had the same security issue as the Maxwell-Todd tree, which was fixed by Chalkias, Lewi, Mohassel and Nikolaenko in their [Distributed Auditing Proofs of Liabilities](https://eprint.iacr.org/2020/468) (DAPOL) paper. The paper also described a method to hide the exchange's total number of users through using the padding node method described above. The problem with this method before was that the pad nodes revealed information about the structure of the tree. But since every node's value is hidden behind a Pedersen commitment, this information is not attainable anymore. We now have a tree with the following properties:
* Every user's ID & balance is hidden from all but the user and the exchange.
* The total liability sum of the exchange is hidden from all but the exchange.
* Every user can calculate an upper bound on the total number of users of the exchange, but the exchange can make this value arbitrarily large by increasing the height of the tree. (Note that the best lower bound that can be calculated from a user’s perspective is 1.)
There are, however, some trade-offs:
1. Computing sums for Pedersen commitments is more expensive than regular integers.
2. Generating and verifying range proofs adds extra computation cost to the inclusion proofs.
The DAPOL paper had some minor issues, which were fixed in a follow-up paper in 2021 titled [Generalized Proof of Liabilities](https://eprint.iacr.org/2021/1350) (DAPOL+). The latter paper also gave rigorous definitions for privacy and security, which were mathematically proved to be held by the tree.
This is the final version of the tree and it is known as the **DAPOL+ Tree**.

*[image source](https://drive.google.com/file/d/1Kkja8aqQbI-1JtkCQ6Qj_6b4aedoNqJv/view)*
# Other approaches to PoL
There are some other PoL protocols worth mentioning here. Some of them use a Merkle Sum Tree but in a slightly different way, like **Summa** and **Proven**. Others, like **Provisions** and the idea Vitalik put forward, take a different approach.
### Summa
[Summa](https://github.com/summa-dev) (2023) uses a regular MST, but produces inclusion proofs differently in order to hide customer & exchange data. When a user requests their inclusion proof, the exchange constructs the Merkle proof inside of a ZK-SNARK and shares the SNARK proof with the user. The path information is thus seen only by the exchange. Since the MST tree is simpler than the DAPOL+ one, it is much quicker to build (exact numbers are not available), and the inclusion proofs are slightly smaller (1.6kB vs 3kB for a tree of height 20) but take a bit longer to generate (0.38s vs 0.277s for the same tree). Details of the protocol can be found in their recent blog posts ([part 1](https://mirror.xyz/privacy-scaling-explorations.eth/_1Y6ExFD_Rs3oDxwx5_kWAj_Tl_L9c0Hm7E6SVJei0A) & [part 2](https://mirror.xyz/privacy-scaling-explorations.eth/f2ZfkPXZpvc6DUmG5-SyLjjYf78bcOcFeiJX2tb2hS0)).
### Proven
Similarly to Summa, [Proven](https://www.proven.tools) (2022-2023) uses ZK-SNARKs. Instead of calculating just the inclusion proof inside of the SNARK, they calculate both PoA and PoL inside of the SNARK. The SNARK keeps the customer & exchange data hidden. Benchmarks are not publicly available, but one can learn more about their protocol in [this Stanford Blockchain talk](https://www.youtube.com/watch?v=ChX8MuRVzmw).
### Provisions
The [Provisions paper](https://eprint.iacr.org/2015/1008) (2015) proposes both PoA and PoL protocols. Although the PoL protocol does not utilize an MST, it still hides user & exchange data via Pedersen commitments and range proofs. Instead of publishing just the root node of an MST, an exchange publishes Pedersen commitments to **all** of its users’ balances, which means that the size of the committed value scales linearly with the number of users (hundreds of gigabytes are required to be published for 100M users, as opposed to kilobytes for DAPOL+). PoL construction time is also much larger than DAPOL+ (hundreds of hours vs ~1 hour for 100M users).
### Vitalik’s proposal
In [a blog article](https://vitalik.ca/general/2022/11/19/proof_of_solvency.html) published in 2022, Vitalik described a Proof of Reserves protocol. The PoL part of the protocol uses [the KZG polynomial commitment scheme](https://www.iacr.org/archive/asiacrypt2010/6477178/6477178.pdf) for the exchange to commit to their liabilities, which hides exchange & user data. Benchmarks for this scheme are not publicly known.