Git Product home page Git Product logo

zkmerkle-proof-of-solvency's Introduction

zkmerkle-proof-of-solvency

Circuit Design

See the technical blog for more details about background and circuit design

How to run

Run third-party services

This project needs following third party services:

  • mysql: used to store witness, userproof, proof table;
  • redis: provide distributed lock for multi provers;
  • kvrocks: used to store account tree

We can use docker to run these services:

docker run -d --name zkpos-redis -p 6379:6379 redis

docker run -d --name zkpos-mysql -p 3306:3306  -v /server/docker_data/mysql_data:/var/lib/mysql -e MYSQL_USER=zkpos -e MYSQL_PASSWORD=zkpos@123 -e MYSQL_DATABASE=zkpos  -e MYSQL_ROOT_PASSWORD=zkpos@123 mysql

docker run -d --name zkpos-kvrocks -p 6666:6666 -v /server/docker_data/kvrocksdata:/var/lib/kvrocks apache/kvrocks

where /server/docker_data/ is directory in the host machine which is used to persist mysql and kvrocks docker data.

Generate zk keys

The keygen service is for generating zk related keys which are used to generate and verify zk proof. The BatchCreateUserOpsCounts constant variable in utils package represent how many users can be created in one batch. The larger the BatchCreateUserOpsCounts is, the longer it will take to generate zk-related keys and generate zk proof.

We choose 864 as the BatchCreateUserOpsCounts variable. It will take about 6 hours to generate zk-related keys, and 105 seconds to generate zk proof for one batch in a 128GB memory and 32 core virtual machine.

Run the following commands to start keygen service:

// make sure the BatchCreateUserOpsCounts in utils/constants.go is expected

cd src/keygen; go run main.go

After keygen service finishes running, there will be several key files generated in the current directory, like the following:

Total 26G
-rw-rw-r-- 1 ec2-user ec2-user  69M 12月 26 07:49 zkpor864.ccs.ct.save
-rw-rw-r-- 1 ec2-user ec2-user  16G 12月 26 07:51 zkpor864.ccs.save
-rw-rw-r-- 1 ec2-user ec2-user 1.8G 12月 26 07:51 zkpor864.pk.A.save
-rw-rw-r-- 1 ec2-user ec2-user 1.6G 12月 26 07:51 zkpor864.pk.B1.save
-rw-rw-r-- 1 ec2-user ec2-user 3.2G 12月 26 07:51 zkpor864.pk.B2.save
-rw-rw-r-- 1 ec2-user ec2-user  59M 12月 26 07:51 zkpor864.pk.E.save
-rw-rw-r-- 1 ec2-user ec2-user 1.9G 12月 26 07:52 zkpor864.pk.K.save
-rw-rw-r-- 1 ec2-user ec2-user  708 12月 26 07:52 zkpor864.vk.save
-rw-rw-r-- 1 ec2-user ec2-user 2.1G 12月 26 07:52 zkpor864.pk.Z.save

Generate witness

The witness service is used to generate witness for prover service.

witness/config/config.json is the config file witness service use. The sample file is as follows:

{
  "MysqlDataSource" : "zkpos:zkpos@123@tcp(127.0.0.1:3306)/zkpos?parseTime=true",
  "UserDataFile": "/server/data/20230118",
  "DbSuffix": "0",
  "TreeDB": {
    "Driver": "redis",
    "Option": {
      "Addr": "127.0.0.1:6666"
    }
  }
}

Where

  • MysqlDataSource: this is the mysql config;
  • UserDataFile: the directory which contains all users balance sheet files;
  • DbSuffix: this suffix will be appended to the ending of table name, such as proof0, witness0 table;
  • TreeDB:
    • Driver: redis means account tree use kvrocks as its storage engine;
    • Option:
      • Addr: kvrocks service listen address

Run the following command to start witness service:

cd witness; go run main.go

The witness service supports recovery from unexpected crash. After witness service finish running, we can see witness from witness table.

The performance: about 850 users witness generation per second in a 128GB memory and 32 core virtual machine.

Generate zk proof

The prover service is used to generate zk proof and supports running in parallel. It reads witness from witness table generated by witness service.

prover/config/config.json is the config file prover service uses. The sample file is as follows:

{
  "MysqlDataSource" : "zkpos:zkpos@123@tcp(127.0.0.1:3306)/zkpos?parseTime=true",
  "DbSuffix": "0",
  "Redis": {
    "Host": "127.0.0.1:6379",
    "Type": "node"
  },
  "ZkKeyName": "/server/zkmerkle-proof-of-solvency/src/keygen/zkpor864"
}

Where

  • MysqlDataSource: this is the mysql config;
  • DbSuffix: this suffix will be appended to the ending of table name, such as proof0, witness0 table;
  • Redis:
    • Host: redis service listen addr;
    • Type: only support node type
  • ZkKeyName: the key name generated by keygen service

Run the following command to start prover service:

cd prover; go run main.go

To run prover service in parallel, just repeat executing above commands.

Note: After all prover service finishes running, We should use go run main.go -rerun command to regenerate proof for unfinished batch

After the whole prover service finished, we can see batch zk proof in proof table.

Generate user proof

The userproof service is used to generate and persist user merkle proof. It uses userproof/config/config.json as config file, and the sample config is as follows:

{
  "MysqlDataSource" : "zkpos:zkpos@123@tcp(127.0.0.1:3306)/zkpos?parseTime=true",
  "UserDataFile": "/server/data/20230118",
  "DbSuffix": "0",
  "TreeDB": {
    "Driver": "redis",
    "Option": {
      "Addr": "127.0.0.1:6666"
    }
  }
}

Where

  • MysqlDataSource: this is the mysql config;
  • UserDataFile: the directory which contains all users balance sheet files;
  • DbSuffix: this suffix will be appended to the ending of table name, such as proof0, witness0 table;
  • TreeDB:
    • Driver: redis means account tree use kvrocks as its storage engine;
    • Option:
      • Addr: kvrocks service listen address

Run the following command to run userproof service:

cd userproof; go run main.go

After userproof service finishes running, we can see every user proof from userproof table.

The performance: about 10k users proof generation per second in a 128GB memory and 32 core virtual machine.

Verifier

The verifier service is used to verify batch proof and single user proof.

Verify batch proof

The service use config.json as its config file, and the sample config is as follows:

{
  "ProofTable": "config/proof.csv",
  "ZkKeyName": "config/zkpor864",
  "CexAssetsInfo": [{"TotalEquity":219971568487,"TotalDebt":9789219,"BasePrice":24620000000},{"TotalEquity":8664493444,"TotalDebt":122580,"BasePrice":1682628000000},{"TotalEquity":67463930749983,"TotalDebt":16127314913,"BasePrice":100000000},{"TotalEquity":68358645578,"TotalDebt":130187,"BasePrice":121377000000},{"TotalEquity":590353015932,"TotalDebt":0,"BasePrice":598900000},{"TotalEquity":255845425858,"TotalDebt":13839361,"BasePrice":6541000000},{"TotalEquity":0,"TotalDebt":0,"BasePrice":99991478},{"TotalEquity":267958065914051,"TotalDebt":501899265949,"BasePrice":100000000},{"TotalEquity":124934670143615,"TotalDebt":1422964747,"BasePrice":34500000}]
}

Where

  • ProofTable: this is proof csv file which can be exported by proof table;
  • ZkKeyName: the key name generated by keygen service;
  • CexAssetsInfo: this is published by CEX, it represents CEX's liability;

You can get CexAssetsInfo using dbtool command after witness service run finished. Run the following command to verify batch proof:

cd verifier; go run main.go

Verify user proof

The service use user_config.json as its config file, and the sample config is as follows:

{
  "AccountIndex": 9,
  "AccountIdHash": "0000041cb7323211d0b356c2fe6e79fdaf0c27d74b3bb1a4635942f9ae92145b",
  "Root": "29591ef3a9ed02605edd6ab14f5dd49e7dbe0d03e72a27383f929ef3efb7514f",
  "Assets": [{"Index":7,"Equity":123456000,"Debt":0}],
  "Proof": ["DrPpFsm4/5HntRTf8M3dbgpdrxq3Q8lZkB2ngysW2js=","G1WgD/CvmGApQgmIX0rE0BlSifkw6IfNwY9z2DnRazM=","HZm8N563lDMrx//oMjejlXKLzLrZQRqKZKyXwpDnT+I=","A+wqmnw0NfdgAyEieZRqKlczF48VOROxME36YBwLhmY=","BaLA62VkWlLE/FZTxnvx/lwU7WNfDxgdM2cBw27MAog=","EvkbbZF7Y/W8AxPc+49lmzzFNdyPl1QWRu1ZQB/gmz8=","EJzsNkrzgcB6LIctzoqWAetzLHO57vx00FxIlLKhwqg=","D3kGOz1Xcsi5hGXr02LOSC23L+lCOhq6FlXGiPYcYig=","DSAmcCy6GEl1DlRNpP05e4I9ScMCz/4DtJCKpserVL8=","L16LdkqEmjI/4uVNETm4ScZ5uA/Cho59zjbgN1OgI+k=","CaRuyDB3b3JeZlItZw8Txi7MSP5vyRpco0OtHRuO6RQ=","CkfKeaCV6rLcskjWE91vCAxTPKEymsDtJvc4r8aWytw=","JfAF+uLGIBAJklZrWRPjxmJ3lFHyau9oNL5dOwQ72kQ=","JKQm66yeIFuPJcm5OkTUDyohsL5CWFF+fFpW/NiS2O0=","KXs+zCQELaQRJzQoCnYm+G2lr5eKLqmWd3G0xeSPWgc=","AFL9l/1p5puhKsCtS7WTT/hExtl3hmRfXnTofVY9+3s=","A2lctbb1mGsAgbERpnu4/RKX/OkcU06dhU9l4wS7e8k=","AIB1T+2rEytbSyMLfyp1rNhS0RCmPYwITdjhb/34FVQ=","GQmXAAvuoPUvt4zDrQ9qaAcRV5t8TPFXAIAIuwv0vr0=","J8pP9A0l54qj9UfumcTXn3hVBYEh+iBH03newr296io=","LD/99JpY8ZZupXaoRt6p6wKDmQepQyVMsJ3s5rX+Q9g=","EOqvnEUbpnzF0HeEv0PdB/R3KMbNU15jYqwNaBJ/j7M=","KLZwBqvYBNFPBFVPQQOipuYd9bYdLEme+Y5UiDRV5Z8=","IXgr/ZWOF/rduJvkSjOABCUtj5C1+tXjHJ/AwR+f7MM=","MFzAvInNDNdT4+6kp2YKAUT4+sb8fdrha9BxXnwHR1Q=","DdvOYaMZDO/di7mBvQdEocS2CE8BH2bGmIDuSGBaHa8=","MEa67EULCikf5rusfcsUb/kWUfx7NPpHjzHKEo3imho=","KLRU4kqn5Fd3FtjapW9MTJBgWdMtAGKgt3OVLQNHh14="],
  "TotalEquity": 123456000,
  "TotalDebt": 0
}

Where

  • AccountIndex: account index used to verify;
  • AccountIdHash: account hash id which contains user info
  • Root: account tree root published by cex;
  • Assets: all user assets info;
  • Proof: user merkle proof which uses base64 encoding;
  • TotalEquity: user total equity which is calculated by all the assets equity multipy its corresponding price
  • TotalDebt: user total debt which is calculated by all the assets debt multipy its corresponding price

Run the following command to verify single user proof:

cd verifier; go run main.go -user

dbtool command

Run the following command to remove only kvrocks data:

cd src/dbtool; go run main.go -only_delete_kvrocks

Run the following command to delete kvrocks data and mysql:

cd src/dbtool; go run main.go -delete_all

Run the following command to get cex assets info in json format:

cd src/dbtool; go run main.go -query_cex_assets

Check data correctness

check account tree construct correctness

userproof service provides a command flag -memory_tree which can construct account tree in memory using user balance sheet.

Run the following command:

cd userproof; go run main.go -memory_tree

Compare the account tree root in the output log with the account tree root by witness service, if matches, then the account tree is correctly constructed.

Note: when userproof service runs in the -memory_tree mode, its performance is about 75k per minute, so 3000w accounts will take about ~7 hours

check mysql table consistency

Suppose the number of users is accountsNum, and batchNumber is 864, so the number of rows in witness and proof table is: (accountNum + 864 - 1) / 864. And the number of rows in userproof table equals to accountsNum.

When all services run finished, Check that the number of rows in table is as expected.

zkmerkle-proof-of-solvency's People

Contributors

bbarwik avatar dependabot[bot] avatar jonte-z avatar lightning-li avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

zkmerkle-proof-of-solvency's Issues

How is the property "each user's total net balance is greater than 0" guaranteed?

From the design, one property "each user's total net balance is greater than 0" needs to be guaranteed. So I modified the sample data to make one user's total net balance negative, but the witness and proof can still be generated, and the batch verification still passes.

The only warning is when generating the witness, it prints a message saying data wrong: total debt is bigger than equity and the invalid accounts number is 1.

Is my understanding wrong? Thanks in advance.

Lack of BasePrice range check allows for overflow which can be used to generate fake debt

Yesterday I discovered a bug in circuit which allows to bypass api.AssertIsLessOrEqual(totalUserDebt, totalUserEquity) assertion and generate fake user debt, which cannot by detected by 3th party.

It is possible to bypass it, because there is a bug which allows setting BasePrice to very high value, there is no CheckValueInRange for this parameter. However, BasePrice is public for everyone, so it would be easy to detect if it’s invalid or not. But there is a way to do it in a way that will be impossible to detect by other users.

Each proof is generated for batch of 864 users, then they’re linked with each other using the following poseidon hash:

// verify whether befo// verify whether beforeCexAssetsCommitment is computed correctly
for i := 0; i < len(b.BeforeCexAssets); i++ {
	CheckValueInRange(api, b.BeforeCexAssets[i].TotalEquity)
	CheckValueInRange(api, b.BeforeCexAssets[i].TotalDebt)
	cexAssets[i] = api.Add(api.Mul(b.BeforeCexAssets[i].TotalEquity, utils.Uint64MaxValueFrSquare),
		api.Mul(b.BeforeCexAssets[i].TotalDebt, utils.Uint64MaxValueFr), b.BeforeCexAssets[i].BasePrice)
	afterCexAssets[i] = b.BeforeCexAssets[i]
}
actualCexAssetsCommitment := poseidon.Poseidon(api, cexAssets...)

So it is basically a combination of three parameters to one huge number: (TotalEquity << 128) + (TotalDebt << 64) + BasePrice.

Here’s how we can abuse it, let’s say that the BasePrice of the first asset is equal to 1000. After the 1st batch we have a TotalEquity equal 0 and TotalDebt equal 1 for the first asset.
The following value will be used to hash it: (1 << 64) + (1000)

In the 2nd batch, instead of sending TotalDebt equal 1, we send it equal to 0, but we also change the value of BasePrice to (1 << 64) + 1000 which will give us the same Poseidon hash. Now we can use this huge BasePrice to fake user equity/assets, a single coin with such high base price will allow to fake debt of any other coin.

In the 3td batch, we just restore TotalDebt to 1 and BasePrice to 1000, it still gives the same, correct checksum. Noone is able to detect that BasePrice was changed in the 2nd block.

binance_proof_bug drawio (2)

Now let's say exchange is listing bitcoin and has 10000 equity, and 0 debt. By using this bug, they can fake 9000 bitcoins debt, so in the end they would need to show that they hold only 1000 bitcoins instead of 10000.

I demonstrated this issue in https://github.com/hknio/zkmerkle-proof-of-solvency-debt-bug repository

I also prepared pull request #5 which fixes this issue

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.