(a.k.a. zkp-toolkit-ckb)
Zero-knowledge proofs toolkit for CKB, empowering the community with the cutting-edge techniques of zero-knowledge proofs to develop all kinds of decentralized applications.
The project is going to bridge the gap of cryptographic engineering between thriving academic research and aspiring dAPPs developers, by providing multiple zkp schemes and curve options, a more user-friendly interface, many useful gadget libraries, and many more tutorials and examples.
Besides, it provides smart contracts that run as zero-knowledge proof verifiers on the Nervos CKB chain. CKB developers and users can implement various complex zero-knowledge verification processes through the simplest contract invocation. Cooperate with the core zkp-toolkit to complete off-chain prove and on-chain verify.
This project is also known as zkp-toolkit-ckb and is supported by the Nervos Foundation. Check out the original proposal and grant announcement.
The following document is more focused on CKB smart contracts. Check this doc for more details on zkp-toolkit usage and features.
- ckb-zkp
- Table of contents
- How does this contract help to verify a zero-knowledge proof?
- Prerequisites
- Build contracts
- Tests
- Deployment
- Optimizations & Benchmarks
- Troubleshooting
capsule
complainederror: Can't found capsule.toml, current directory is not a project
- I can't see any output of my contract in the CKB's log on dev chain.
- The test can't find contract binary/proof file/vk file.
- How is the project mounted into the Docker container?
- What does "cycles" mean in Nervos ckb?
- Acknowledgement
- Security
- License
A contract for verification is deployed on the ckb chain. The prover and the verifier know where the contract is deployed.
- The prover completes the trusted-setup, and generates a proof (in the form of a file);
- The prover sends a transaction that creates some new cells(aka. utxo, but carrying some data), with one containing the proof and vk files and using the previous contract as its type script (which means, this cell should pass the verification of the contract logic);
- The miner collects the transaction and execute the assigned contract. All the cells in a transaction assigning one contract as type script are verified by the contract logic. Otherwise, the transaction is rejected by the miner.
- The prover goes public with the transaction, the proof, the vk file, and the verification contract address that is needed to do the verification.
- The verifier is able to verify the proof using the information provided by the prover.
-
Ensure version of rustc is not lower than 1.42 and use stable version of toolchain.
-
Install the CKB contract development framework capsule. Access the wiki page for more details about
capsule
.cargo install capsule --git https://github.com/nervosnetwork/capsule.git --rev=089a5505
capsule
is under development and not stable, so please specify the revision when installing. -
Deploy a ckb dev chain if you need to deploy the contract to the blockchain. See https://docs.nervos.org/dev-guide/devchain.html for guidance.
Like Cargo, you can choose to build the contract in dev mode or release mode. The product under release mode is suitable for deployment with a reasonable size and execution consumption, and, debug!
macro is disabled. Dev mode product allows you to use debug!
macro to print logs in ckb log, but on the cost of larger binary size and execution cycles. The product resides in ./build/[release|debug]/mimc-groth16-verifier.
ATTENTION:
- all the
capsule
commands should be executed at the project root. - Users in mainland China can add the tuna's mirror of crates.io in the file ./cargo/config for faster download of dependencies..
# At project root
# Dev mode, enable debug! macro but result in bloated size.
capsule build
# Release mode. Slim, no outputs in the logs.
capsule build --release
In ckb-std
version 0.2.2 and newer, debug!
macro is disabled in release mode. If you still want to enable debug!
macro in release mode, insert debug-assertions = true
under [profile.release]
in contracts/mimc-groth16-verifier/Cargo.toml
.
A simplified, one-time blockchain context is used in the tests environment using ckb-tool crate. Needless to setup an authentic blockchain and run a ckb node, one can simply send a transaction to invoke the contract and checkout if the contract works as expected.
-
Go to ./cli and generate a vk file and a proof file using ckb-zkp's command line utility.
Use groth16 scheme & bn_256 curve:
-
Complete trusted-setup:
# ./cli cargo run --bin trusted-setup mimc
-
Prove the secret string.
# ./cli cargo run --bin zkp-prove mimc --string=iamsecret
When successful, it will create a proof file at proofs_files.
-
(Optional) Do the verification.
# ./cli cargo run --bin zkp-verify mimc proofs_files/mimc.groth16-bn_256.proof
Use groth16 as scheme and bls12_381 as curve:
# ./cli # trusted-setup cargo run --bin trusted-setup mimc groth16 bls12_381 # Prove the secret string cargo run --bin zkp-prove mimc groth16 bls12_381 --string=iamsecret # Verification. cargo run --bin zkp-verify mimc groth16 bls12_381 proofs_files/mimc.groth16-bls12_381.proof
See cli document for further help.
-
Make sure vk file(s) and proof file(s) are prepared and can be found by the test suit.
Then type the following command.
ATTENTION:
- If you build the contract with
--release
flag, you should run tests withCAPSULE_TEST_ENV=release
. - The flag
--test-threads 1
after--
is used to ensuredebug!
outputs print in order. - In the file ./tests/src/tests.rs, you can uncomment the
#[ignore]
attribute (By remove the leading double slants//
) before a test function to omit during the testing. Or specify the test function name to filter others out. - Or you can specify a test function name, and perform only one test.
# At project root
# Dev mode contracts.
cargo test -p tests --tests -- --nocapture --test-threads 1
# Release mode contracts.
CAPSULE_TEST_ENV=release cargo test -p tests --tests -- --nocapture
# Specify a test name `test_proof_bn_256` that you want to execute
CAPSULE_TEST_ENV=release cargo test -p tests test_proof_bn_256 -- --nocapture
Capsule
brings out-of-box contract deploying and migrating. It works for development and test on dev chain. To deploy a contract you have just cooked, you need:
- A running ckb client on the local machine or the net.
- A ckb-cli executable.
capsule
uses ckb-cli to interact with ckb client. - An account with sufficient CKBs for deployment (1 Byte of contract binary will consume 1 CKB. The transaction body will also take some extra CKBs, but not much). This account should be imported into ckb-cli.
- A deployment manifest ./deployment.toml, which assigns the contract binary and cell lock-arg.
When everything needed is met, you should theoretically be able to deploy the contract. Use the command below to launch the transaction, and note that commonly the <ADDRESS>
is a 46-bit alphanumeric string (Starting with ckt1
if you use a test net or dev chain).
# At project root
capsule deploy --address <ADDRESS>
No ready-to-use gear for invoking a contract on a real chain. Use ckb-cli, or an SDK to build a transaction to invoke the contract on-chain.
You can use the master branch of capsule
and the following commands to track the panics.
# At project root
RUST_LOG=capsule=trace capsule deploy --address <ADDRESS>
In Nervos ckb, one should pay for data storaging, transaction fees and computer resources. Paying for data storaging means, one needs to pay an amount of ckb tokens in direct proportion to the size of the transaction he raises. Paying for computer resources means one should pay extra ckbs based on the amount of computer resources that are used to verify a transaction. The computer resources are measured as cycles.
On the other hand, On mainnet Lina, the value of MAX_BLOCK_BYTES
is 597_000
and MAX_BLOCK_CYCLES
is 3_500_000_000
.
For these reasons, we take contract binary size and execution cost both into consideration.
The deployer should pay for storaging his contract on-chain. The larger the binary is, the more ckb tokens will be spent for deployment. So several compiling options are analyzed to reduce the contract binary size.
- To build in release mode, this is enabled by default.
- LTO
- Strip
opt-level
codegen-units
To use LTO, opt-level
and codegen-units
, modify Cargo.toml:
# File: contracts/mimc-groth16-verifier/Cargo.toml
[profile.release]
overflow-checks = true
# lto: true, "thin", false(default)
lto = true
# opt-level: 0, 1, 2, 3(default), "s", "z"
opt-level = "z"
# codegen-units: greater than 0, default 16
codegen-units = 1
To strip the binary, use rustflags = "-C link-arg=-s"
in cargo config, which is a default option in Capsule with release compiling mode.
We will not try to explain what each option means (Explained in The Cargo Book), but list the size and running cost of the contract binaries under different combinations of these building options.
Test setup:
- Release mode;
- stripped;
- using
jjy0/ckb-capsule-recipe-rust:2020-6-2
to build and test and measure running costs; - using scheme groth16 and curve bn_256;
- ckb-std version 0.3.0;
- ckb-zkp revision d90fe30e;
- ckb-tool and ckb-testtool version 0.0.1;
- Default profile setting:
overflow-checks = true
.
LTO | opt-level |
codegen-units |
panic |
Binary size(Byte) | Execution cost (cycles) |
---|---|---|---|---|---|
not set | not set | not set | not set | 496,472 | 94,503,867 |
true |
not set | not set | not set | 418,576 | 99,383,945 |
not set | "z" |
not set | not set | 217,944 | 1,145,530,398 |
true |
"z" |
not set | not set | 172,816 | 212,532,245 |
not set | "z" |
1 |
not set | 136,024 | 1,181,106,112 |
true |
"z" |
1 |
not set | 115,472 | 222,347,063 |
true |
"z" |
1 |
"abort" |
115,472 | 222,347,059 |
true |
"s" |
1 |
"abort" |
213,776 | 158,341,065 |
Here comes a rough result:
- Generally, size decreasing results to execution cost increasing.
- Enabling LTO, use
opt-level = "z"
,codegen-units = 1
andpanic = "abort"
for minimum binary size, at the cost of a higher cycle consumption.
Currently, we use two different curves in proving and verifying, so we performed a simple benchmark on execution costs separately.
Test setup:
- Release mode;
- stripped;
- Profile:
LTO = true
,codegen-units = 1
,panic = "abort"
; - using
jjy0/ckb-capsule-recipe-rust:2020-6-2
to build and test and measure running costs; - using scheme groth16 and curve bn_256;
- ckb-std version 0.3.0;
- ckb-zkp revision d90fe30e;
- ckb-tool and ckb-testtool version 0.0.1.
Curve | opt-level |
Binary size(Byte) | Execution cost (cycles) |
---|---|---|---|
bn_256 | "z" | 115,472 | 222,347,059 |
bn_256 | "s" | 213,776 | 158,341,065 |
bls12_381 | "z" | 115,472 | 354,875,909 |
bls12_381 | "s" | 213,776 | 314,460,704 |
Different curves are enabled as features of crate ckb-zkp in the contract, which is specified in ./contracts/mimc-groth16-verifier/Cargo.toml, at array [dependencies.zkp.features]
.
The number of enabled features will impact the contract binary size and execution cost. If one curve is not enabled as a crate feature, this curve cannot be used for verification.
Test setup:
- Release mode;
- stripped;
- Profile:
LTO = true
,opt-level = "z"
codegen-units = 1
,panic = "abort"
; - using
jjy0/ckb-capsule-recipe-rust:2020-6-2
to build and test and measure running costs; - using scheme groth16;
- ckb-std 0.3.0;
- ckb-zkp revision d90fe30e;
- ckb-tool and ckb-testtool version 0.0.1.
Feature enabled | Binary size(Byte) | Curve using | Execution cost (cycles) | Execution cost Diff |
---|---|---|---|---|
None | 29,456 | N/A | N/A | N/A |
bn_256 | 74,512 | bn_256 | 222,299,004 | -48,055 |
bls12_381 | 74,512 | bls12_381 | 354,787,488 | -88,421 |
bls12_381, bn_256 | 115,472 | bn_256 | 222,347,059 | 0 |
bls12_381, bn_256 | 115,472 | bls12_381 | 354,875,909 | 0 |
We have accomplished the main goal we set for the Milestone-I of the zkp-toolkit-ckb, which was a simple on-chain verifier for CKB. The proof-of-concept smart contract code shows that we can make a usable zkp verifier for CKB with pure Rust without modifying the underlying chain. This also gives us a baseline on the performance of zkp verifiers for CKB-VM.
We'll implement more zkp verifiers in the following milestones, looking at reducing the binary size and execution cost, as well as the best practice to integrate with other contracts.
All the commands executed by capsule
should be executed under the project root.
Modify ckb's configuration as below:
# File: ckb.toml of your chain.
[logger]
filter = "info,ckb-script=debug"
Make sure you build and test the contract in the same mode (dev or release, specified by flag --release
).
# At project root
capsule build && cargo test -p tests --tests -- --nocapture --test-threads 1
# Or
capsule build --release && CAPSULE_TEST_ENV=release cargo test -p tests --tests -- --nocapture
As capsule executes building and testing in docker, the absolute path may not work as expected, so use relative path. And currently, the Capsule (nervosnetwork/capsule revision 2f9513f8) mount the whole project folder into docker, so any relative location inside the project folder is allowed.
In the nervosnetwork/capsule
revision 2f9513f8
, capsule
mounts the project folder into the container with path /code. But in the main source nervosnetwork/capsule
, capsule
may only mount the contract folder into the container. As docker is used, the absolute path is not recommended.
The concept and intruduction of cycles can be found here.
- Many, many thanks to jjy, a developer of nervosnetwork, for his selfless help and advice on this project.
This project is still under active development and is currently being used for research and experimental purposes only, please DO NOT USE IT IN PRODUCTION for now.
This project is licensed under either of
- Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.