Provide ZK tools that allow 0G users to use ZK tools to display and prove arbitrary granularity of fields in verifiable certificates.
Before installing the project, ensure your system meets the following requirements on Ubuntu.
-
Install Rust: Use the official installation script:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Install the specific version used
rustup install nightly-2024-02-04
Configure your current shell to start using Rust:
source $HOME/.cargo/env
-
Install Node.js (>= v18): Install using NodeSource binary distributions:
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - sudo apt-get install -y nodejs
-
Install Yarn: Install via npm after Node.js:
npm install --global yarn
-
Install CUDA (optional, for GPU acceleration): Download and install the CUDA 12.4 toolkit from the NVIDIA CUDA Toolkit webpage.
-
Install libsnark-rust dependencies (optional, for performance comparison): Update the package list
sudo apt update
Install dependencies:
# libsnark dependencies sudo apt install build-essential cmake git libgmp3-dev python3-markdown libboost-program-options-dev libssl-dev python3 pkg-config # gmp-mpfr-sys dependencies sudo apt install diffutils gcc m4 make # libsnark must work with the specific gcc version sudo apt install gcc-10 g++-10
Build the project using Cargo.
cargo build --release
This project offers the following compilation:
-
CUDA Support: To enable GPU acceleration using CUDA, make sure the CUDA dependencies are correctly installed as described in the Prerequisites section. Compile the project with the
--features cuda
flag:cargo build --release --features cuda
Note: If you enable the CUDA feature, we highly recommend using a fixed thread to call the proof function and communicate with other threads via channels, rather than calling the proof function in every thread.
-
Trace Logging: For more verbose output and trace logging for groth16 process, enable the trace feature by using the
--features trace
flag:cargo build --release --features trace
-
Libsnark FFI: For comparing performance with libsnark FFI, enable the libsnark feature by using the
--features libsnark
flag:cargo build --release --features libsnark
Note: If you get stucked on updating submodules, try the following command:
git config --global url."https://".insteadOf git:https://
Each feature can be enabled individually or combined depending on the development and debugging needs. For combined features, use:
cargo build --release --features "cuda,trace"
Before running the application, it is necessary to prepare the environment and generate required parameters. Follow these steps to set up:
-
Setup: Install all JavaScript dependencies, build the necessary components, and generate parameters:
yarn yarn build yarn setup
Note: This process may take 15 minutes or even longer, depending on your CPU performance. This process may consume more than 64 GB of memory. Please ensure that you have sufficient memory/virtual memory, or use a high-performance machine to generate the parameters and then copy them to others.
-
Running Example Code: To run the example code, compile the project using the following command:
cargo build --release --bin example --features trace
Add
cuda
feature if you have configured CUDA support and wish to use GPU acceleration. -
Running libsnark Comparison Code: If you have installed all necessary dependencies for libsnark as per the Prerequisites section, compile and run the libsnark comparison code:
cargo build --release --bin libsnark --features libsnark
This proof of concept (PoC) demonstrates a simplified example of verifiable certificates using the circom circuit framework, input and output construction, Groth16 proof generation, CUDA acceleration, and a performance comparison with libsnark.
Verifiable certificates should contain at least a serial_no field to determine the ownership of the certificate. Users who know the serial_no own the certificate. Other fields in the certificate vary depending on what the certificate is and can be customized. The serial_no also serves to prevent exhaustive guessing of private fields. Here, an academic certificate is used as an example to illustrate the certificate format.
An academic certificate contains the fields of name, age, date of birth, and education level code:
pub struct VC {
// <= 16 bytes, UTF-8 supported
name: String,
age: u8,
birth_date: NaiveDate,
edu_level: u8,
// <= 32 bytes
serial_no: Vec<u8>,
}
Here is an example code constructing VC
from a json string.
let vc_json = r#"{"name": "Alice", "age": 25, "birth_date": "19991231", "edu_level": 4, "serial_no": "3921b15ceb8f4be8891d1de1e64af044"}"#;
let vc = VC::from_json(vc_json).unwrap();
Rust developers can also construct VC
directly with VC::new(...)
. For the birth_date
field, please refer to NativeDate
's document to initialize it.
The VC is encoded into a content of 79 bytes in a unique format, followed by the computation of its keccak256 hash value (32 bytes). The hash is then uploaded to the decentralized storage flow (not covered in this repo). Since the minimum unit of decentralized storage is 256 bytes, it is padded with zeros to meet this minimum length.
Despite zero-knowledge proofs requiring large integers from finite fields as inputs, developers do not need to concern themselves with the details of these conversions. Simply provide inputs using the predefined types ProveInput
and VerifyInput
(source code).
pub struct ProveInput {
data: VC,
birthdate_threshold: NaiveDate,
merkle_proof: Vec<H256>,
// The position index of the corresponding VC in the storage flow
path_index: usize,
}
pub struct VerifyInput {
birthdate_threshold: NaiveDate,
// The merkle root of the storage flow when generating proof,
// should be consistent with `ProveInput::merkle_root`
root: H256,
}
The zero-knowledge proof circuit primarily verifies the following conditions (all must be satisfied):
- The VC's birthdate is later than the specified
birthdate_threshold
provided in the public inputs. - The VC is legitimate data that exists on the storage flow.
Note: Although the storage flow supports up to 2^64
leaves, this code only supports VCs located within the first 2^32
leaves, which is equivalent to approximately 1PB of storage.
The fields of the certificate should be properly encoded to facilitate subsequent hashing. The format of the encoded VC is as follows:
pub struct EncodedVC {
name: Vec<u8>, // 4+16bytes
age: Vec<u8>, // 3+1bytes
birth_date: Vec<u8>, // 5+8bytes
edu_level: Vec<u8>, // 3+1bytes
serial_no: Vec<u8>, // 5+32bytes
}
- name: The prefix is "name", and the main part is encoded using UTF-8 with a length of 16 bytes.
- age: The prefix is "age", and the main part uses the u8 type.
- birth_date: The prefix is "birth", and the main part is represented using a Unix timestamp with a length of 8 bytes (u64).
- edu_level: The prefix is "edu", and the main part uses the u8 type.
- serial_no: The prefix is "serial", and the main part uses the hexString type with a length of 32 bytes.
The effective data part of the encoded VC is 79 bytes, padded to 256 bytes.