How ZKP-based Proof of Reserves (PoR) Work
- Asset Audit
Definition and Description of Circuit
The BatchCreateUser circuit is used to operate the account tree and collect the data of each account. The circuit is defined as follows:
-
Inputs:
(1).Public Inputs:
group_commitment: Total Committed Hashrate
(2) Secret Inputs:
preSmt_root: The root value of the Merkle tree for the current batch of users
nextSmt_root: The root value of the Merkle tree after the current batch of users perform operations
pre_cex_commitment: The commitment value (hash) of all assets on the Cex for the current batch of users
next_cex_commitment: The commitment value (hash) of all assets on the Cex after the current batch of users perform operations
Pre_cex_asset: List of all cryptocurrencies on the Cex for the current batch of users
totalCex_Asset: The total Cex assets obtained by summing all USDT-converted cryptocurrencies
Uesr_Instructions: Information table for a group of users
△asset: List of all cryptocurrencies of the user
△totalEquity: User's USDT-converted net assets
△totalDebt: User's USDT-converted net debt
△account_index: Index of user
△account_idhash: The hash value of user leaf node
△account_proof: Proof path of user leaf node
-
Constraints
(1) group_commitment must be equal to the hashes calculated from preSmt_root, nextSmt_root, pre_cex_commitment, next_cex_commitment.
(2) Pre_cex_commitment must be calculated from Pre_cex_asset, and the two hash values must be equal.
(3) preSmt_root must be equal to the preSmt_root value of the first element in Uesr_Instructions.
(4) nextSmt_root must be equal to the nextSmt_root value of the last element in Uesr_Instructions.
(5) Calculate totalCexEquity and totalCexDebt through totalCex_Asset, both in the range [0, 2 ^ 64], and ensure that equit is greater than Debt.
(6) The next_cex_commitment must be calculated from after_asset_list, and the two next_cex_commitments must be equal.
(7) Each user's assets must be greater than their liabilities, and each user's USDT-converted net assets must be greater than 0.
(8) The root hash calculated by the user leaf node hash combined with the Merkle path must be equal to nextSmt_root
(9) nextSmt_root must be equal to preSmt_root for the next batch of users.
When all users are operated, we will get a series of proofs: [proof_0, proof_1, ..., proof_n], a set of commitments: [group_commitment_0, group_commitment_1, ..., group_commitment_n] and [cex_commitment_0 , cex_commitment_1, ..., cex_commitment_n], and a set of root hashes: [SMT_root_0, SMT_root_1, ..., SMT_root_n]. All this information will be made available for download and verification by any user.
How to Calculate Witness
The witness service is used to generate a witness for the prover service. As the input for the prover to generate proofs, the final witness input data generated is stored in MySQL. When generating the witness, considering the huge number of users and assets, we calculated the user data in batches and stored the asset data of a single user in the form of a sparse vector to save storage space. When reading the current batch of witness data from the MySQL table, we performed vector densification processing to align the data dimensions for subsequent calculations. An example of the data used to calculate the witness is as follows (this data is the snapshot data of user assets taken out at a certain time every month):
Index |
UID |
Btc_balance |
Eth_balance |
… |
Total_Equity |
Total_Debt |
0 |
Id1 |
0.00000032 |
0 |
|
0.008672 |
0 |
1 |
Id2 |
0.00000031 |
0 |
|
0.009716 |
0 |
2 |
Id3 |
0 |
0 |
|
0.00108 |
0 |
The balance columns indicate the amount of the user’s net assets in the corresponding currency. Total_Equity indicates the total USDT-converted value of the user's assets in all currencies. Total_Debt indicates the total USDT-converted value of the user's debts in all currencies. During the calculation, the total assets in a single currency of the exchange is obtained by accumulating the assets of the corresponding currency of the user. In the subsequent design of the proof circuit, the corresponding constraints are used to ensure the correctness of the calculation process to prevent cheating. Since the zero-knowledge proof algorithm adopted by the reserve proof can only be calculated on a finite field, we uniformly use a multiplication factor to expand and truncate the remaining precision to normalize the data for the processing of floating-point numbers.
When managing all user account data, the tree data structure of the sparse Merkle tree is used to manage the account data (as shown in the figure above). Unlike a standard Merkle tree, a sparse Merkle tree has ordered data (corresponding to the Index in the previous table). For example, users A and D with an Index of 0 and 3 must correspond to the 0th (leftmost) and 3rd leaf nodes (rightmost) at the bottom of the above figure. If users B and C with Index 1 and 2 have no asset data in the exchange (only registered but without assets), their position in the sparse Merkle tree remain fixed. Such a design is convenient for quickly verifying whether a specific user exists or does not exist in the proof process, making the proof scheme more rigorous.
In the calculation of witness data, frequent hash operations are required, and the complexity of the hash algorithm will introduce a large number of constraints in the zero-knowledge proof circuit. To speed up the proof calculation process while ensuring security, we have adopted Poseidon hash, a hash function friendly to zero-knowledge proof, in the calculation of digital commitments. Its structure is shown in the figure below. Optimizing the hash function can reduce the computational complexity to 1/8 of the original while maintaining proof security.
How to Perform Prover and Verifier
HTX uses the r1cs circuit and pk and vk files generated by the keygen program to generate the required proof files and save them in the database, allowing users to download and verify.
How to run: Make sure that the current working directory is under zkmerkleverify, i.e., under the upper directory of src.
(1) When only one host is used to enable the prover service, use the following command:
When prover is run correctly, it will display output:
(2) When multiple hosts are used to enable the prover service, all hosts should be guaranteed to be in the above-mentioned working directory, and use the same command:
When the command of all hosts ends, the following output will be
Use the following command
to check whether there is a prover program that has not been run. If there is, run it till it finishes. Once the program runs normally, the final output shows:
Instructions for Using Verifier Tool
HTX uses the verifier service for users self-verify PoR verification services and userproof verification services. Users uses the proof table and vk generated by the prover service to perform PoR verification and userproof verification. Specifically, PoR verification is our zero-knowledge asset proof verification, and userproof is our zero-knowledge Merkle tree verification. Users can download it from the following location:
Audit>>>
How to use:
For user verification, you need to download the audit data (public) and unzip the downloaded public file public-data.zip into a folder, which contains three public files (config.json, proof0.csv, and zkPoR500.vk.save), and save the folder in a directory. Then, download the executable program for your operating system from https://github.com/HTXapi/Tool-Go-MerkleVerify/releases/tag/2.0.0. Taking macOS as an example, unzip and save the executable program zkverifier-macos-x64.zip in the same directory, as shown in the image below.
You can perform PoR verification using the following command:
When launched on macOS for the first time, it may encounter the following error:
All you need to do is to enable “App Store and identified developers” in “Security & Privacy” under “System Preferences” as shown below.
Alternatively, you can enter the following command in the Terminal app on your Mac:
sudo spctl --master-disable
When the program is running, it will check for directories named “public-data” in the current directory and select one for reading. Then, it will output the directory’s path. (If it failed to find the most recent downloaded directory, you can delete previously downloaded ones.)
When verification is successful, it will display:
Before using Merkle verification, users need to download the audit data (private), user_config.json, by clicking the button shown below:
After downloading it, put it in the directory for PoR verification.
Users can perform Merkle verification using the following command:
When verification is successful, it will display:
To execute the entire process from the beginning, you can follow the steps outlined in README using the program on GitHub.
For specific mathematical principles, please refer to Why Do Exchanges Use Zero-Knowledge Proofs for Proof of Reserves?