zkMega v0.1 报告(Megaclite v0.1 报告)
- Original Proposal: https://polkadot.polkassembly.io/post/167
- Project Repositories
#
1. 回顾 Megaclite v0.1 的设计目标- Provide more on-chain underlying cryptography support than Ethereum. The current stage includes four curves: alt_bn128, bls12_377, bls12_381, bw6_761
- Integrate ADD, Scalar MUL, Paring units under Runtime layer, and provide them to Runtime applications through Runtime-Interface, and further provide them to Wasm contract applications through Contract-Seal
- Through Pallet and Ink! contract libraries, providing more higher-level verification and crypto tools than Ethereum, improving execution efficiency and reducing development costs
- Provide typical sample applications through Ink! sample contracts
#
2. Pairing Friendly Curves我们基于 arkworks-curve 封装了bls12_377
, bls12_381
, bw6_761
, bn254
这四条 Piaring Friendly Curves 的基础运算,通过在 Runtime 直接引入 与制作 Host Functions 的办法将它们集成到了 jupiter,并制作了 Benchmarks。
#
2.1 基于 arkworks-curve 的封装- 代码: https://github.com/patractlabs/megaclite/blob/master/crates/curve/arkworks/src/ops.rs
- 测试: https://github.com/patractlabs/megaclite/tree/master/crates/curve/arkworks/src/tests
我们通过 CurveBasicOperations trait 继承 PairingEngine trait 组合了 add,mul,pairings 三个方法:
fn add(input: &[u8]) -> Result<Vec<u8>, SerializationError> { \\omit }fn mul(input: &[u8]) -> Result<Vec<u8>, SerializationError> { \\omit }fn pairings(input: &[u8]) -> Result<bool, SerializationError> { \\omit }
其中三个方法以字节切片类型的接口暴露给 runtime 和 ink 层, add, mul 返回的是字节向量类型的椭圆曲线点, pairings 内部通过批量配对然后累加,得到的结果再与 Fqk::one() 进行对比,相等则返回 true,否则返回。
在 CurveBasicOperations trait 里还封装了一些编写 groth16 verify 代码所需要用到的不同的椭圆曲线参数:
// curve basic parametersconst SCALAR_FIELD: &'static str;const MODULUS: &'static [u8];// G1 bytes lengthconst G1_LEN: usize;// G2 bytes lengthconst G2_LEN: usize;// Scalar bytes lengthconst SCALAR_LEN: usize;// Curve ID is used for adaptation chain extension const CURVE_ID: u32;
#
2.2 通过 Host Call 使用 megaclite/// Pairing runtime interface#[runtime_interface]pub trait Pairing { /// | curve | add | mul | pairing | /// |-----------|------------|------------|------------| /// | bls12_377 | 0x01000000 | 0x01000001 | 0x01000002 | /// | bls12_381 | 0x01000010 | 0x01000011 | 0x01000012 | /// | bn254 | 0x01000020 | 0x01000021 | 0x01000022 | /// | bw6_761 | 0x01000030 | 0x01000031 | 0x01000032 | fn call(func_id: u32, input: &[u8]) -> Option<Vec<u8>> { curve::call(func_id, input).ok() }}
#
2.3 在 Runtime 中直接使用 megaclite- 案例:https://github.com/patractlabs/jupiter/blob/features/runtime-interfaces/pallets/template/src/lib.rs
# Cargo.toml[dependencies.curve]package = "megaclite-arkworks"git = "https://github.com/patractlabs/megaclite.git"features = ["tests"]default-features = false
megaclite-arkworks
支持 no_std
,我们可以在 runtime 中直接引入。
//! lib.rsdecl_module! { #[weight = 10_000 + T::DbWeight::get().writes(1)] pub fn wasm_bls12_377_add(origin) { curve::tests::add(0x2a); }}
#
2.4 Benchmarks#
2.4.1 Building# Clone the branch `curve-benchmark` of our forkgit clone https://github.com/patractlabs/jupiter.git \ --branch features/runtime-interfaces \ --depth=1
# Build the templatecd jupiter \ && git submodule update --init \ && cargo build -p jupiter-dev --all-features --release
# Check the command benchmark works fine./target/release/jupiter-dev benchmark -p pallet_template -e wasm_bls_12_381_add
#
2.4.2 Result# 1. Under the jupiter repo# 2. Has compiled the release version jupiter-dev./target/benchmark.sh
memory | processor |
---|---|
64GiB System memory | AMD Ryzen 9 5900X 12-Core Processor |
Here we test the curevs on ubuntu LTS 20.04, Time is measured in µs
Curve | Operation | Native Time(µs) | Wasm Time(µs) | Speed(Native/Wasm) |
---|---|---|---|---|
bls12_377(~9.5x) | add | 9.588 | 29.02 | ~3x |
mul | 183.1 | 1893 | ~10x | |
pairing_two | 1732 | 15310 | ~7x | |
bls12_381(~10x) | add | 13.9 | 28.31 | ~2x |
mul | 177.1 | 1879 | ~10x | |
pairing_two | 1438 | 14770 | ~10x | |
bn254(~5x) | add | 5.631 | 16.05 | ~3x |
mul | 107.7 | 534.3 | ~5x | |
pairing_two | 1150 | 5061 | ~5x | |
bw6_761(~13x) | add | 26.9 | 92.94 | ~4x |
mul | 956.8 | 14330 | ~15x | |
pairing_two | 5715 | 60960 | ~10x |
- Add: 取 arkworks 的 test 用例数据, 测试了两个 generator 相加。
- Mul: 取 arkworks 的 test 用例数据, 测试了一个私钥大小的随机数和 generator 相乘.
- Pairing: 使用 arkworks 产生测试数据, 测试了
bilinearity: e(s * a, b) = e(s * b, a)
根据测试结果来看,Wasm 版本性能与 Native 版有一定差距,但从结果来讲,性能足以满足需求,在 3.1 中,我们将通过测试在 ink! 中调用链上的 megaclite 来进一步比较 Host Call 版本与 Wasm 版本的性能。
#
3. Groth16 Verify System- 代码:https://github.com/patractlabs/megaclite/blob/master/crates/curve/arkworks/src/groth16/verify.rs
- 测试:https://github.com/patractlabs/megaclite/blob/master/tests/src/arkworks/bench.rs
gronth16 是目前零知识证明里验证效率最高(仅仅需要四次 pairing )且 proof 尺寸最小的算法, 所以我们优先选择了 groth16 proof system, 其 verifier 图示如下:
在论文里我们可以看到, verifier的验证核心是一个等式:
为了方便使用,工程实现中,底层pairing算法实现了批量 pairing 计算并进行累加,所以我们需要做个转化:
从公式中可以看出来,需要四次pairing,l次add和mul操作(与实际电路有关), 最终, 四次配对的结果会返回true或者false。
#
3.1 在 runtime 中 通过 chain-extension 暴露 megaclite 给 ink! 合约调用- 案例:https://github.com/patractlabs/jupiter/blob/features/runtime-interfaces/primitives/chain-extension/src/lib.rs
- 测试:https://github.com/patractlabs/jupiter/blob/features/runtime-interfaces/pallets/template/src/tests.rs
我们在 chain-extension 中对 megaclite function id 的分配如下:
curve | add | mul | pairing |
---|---|---|---|
bls12_377 | 0x01000000 | 0x01000001 | 0x01000002 |
bls12_381 | 0x01000010 | 0x01000011 | 0x01000012 |
bn254 | 0x01000020 | 0x01000021 | 0x01000022 |
bw6_761 | 0x01000030 | 0x01000031 | 0x01000032 |
Megaclite 的对应接口通过条件编译来支持(从 ink! 合约中调用)chain extension 或直接执行相关函数。
//! https://github.com/patractlabs/megaclite/blob/master/crates/curve/arkworks/src/lib.rs/// Call curve functions using chain extensions#[cfg(feature = "ink")]pub fn call(func_id: u32, input: &[u8]) -> Result<Vec<u8>> { Ok(ink_env::call_chain_extension(func_id, &Vec::from(input))?)}
/// Call curve functions directly#[cfg(not(feature = "ink"))]pub fn call(func_id: u32, input: &[u8]) -> Result<Vec<u8>> { Ok(match func_id { 0x01000000 => <ark_bls12_377::Bls12_377 as CurveBasicOperations>::add(input), 0x01000010 => <ark_bls12_381::Bls12_381 as CurveBasicOperations>::add(input), 0x01000020 => <ark_bn254::Bn254 as CurveBasicOperations>::add(input), 0x01000030 => <ark_bw6_761::BW6_761 as CurveBasicOperations>::add(input), 0x01000001 => <ark_bls12_377::Bls12_377 as CurveBasicOperations>::mul(input), 0x01000011 => <ark_bls12_381::Bls12_381 as CurveBasicOperations>::mul(input), 0x01000021 => <ark_bn254::Bn254 as CurveBasicOperations>::mul(input), 0x01000031 => <ark_bw6_761::BW6_761 as CurveBasicOperations>::mul(input), 0x01000002 => <ark_bls12_377::Bls12_377 as CurveBasicOperations>::pairings(input).map(b2b), 0x01000012 => <ark_bls12_381::Bls12_381 as CurveBasicOperations>::pairings(input).map(b2b), 0x01000022 => <ark_bn254::Bn254 as CurveBasicOperations>::pairings(input).map(b2b), 0x01000032 => <ark_bw6_761::BW6_761 as CurveBasicOperations>::pairings(input).map(b2b), _ => Err(Error::new(ErrorKind::Other, "Invalid function id").into()), }?)}
#
3.2 在 ink! 合约中调用链上的 megaclite 实现简单的 Groth16 Verifier[dependencies.curve]package = "megaclite-arkworks"git = "https://github.com/patractlabs/megaclite"features = [ "ink" ]
使用 ink
feautre 开启 megaclite 的 chain extension 接口。
// ink! contract#[ink(message)]pub fn bls12_377_verify( &self, vk_gamma_abc: Vec<u8>, vk: Vec<u8>, proof: Vec<u8>, public_inputs: Vec<Vec<u8>>,) -> bool { if let Ok(res) = groth16::verify::<Bls12_377>(&vk_gamma_abc, &vk, &proof, &public_inputs) { res } else { false }}
#
3.3 Benchmarks#
3.3.1 BuildingBuild jupiter 办法与 2.3.1 中相同,但在此我们需要传递 groth16
给 scripts/benchmark.sh
# 1. Under the jupiter repo# 2. Has compiled the release version jupiter-dev./target/benchmark.sh groth16
#
3.3.2 MiMC-Based Groth16 Verify Bench Result- mimc rounds : 322
Curve | Native Time(µs) | Wasm Time(µs) | Speed(Native/Wasm) |
---|---|---|---|
bls12_377 | 40860 | 60550 | ~1.5x |
bls12_381 | 39120 | 58400 | ~1.5x |
bn254 | 30760 | 36800 | ~1.2x |
bw6_761 | 63798 | 172900 | ~2.7x |
NOTE: 此处的 MiMC-Based Groth16 Verify 的实现为,在合约中引入 megaclite 中可以通过调用 chain extension 运行 add,mul,pairing 的 verify 函数,测试合约:https://github.com/patractlabs/metis/blob/master/groth16/lib.rs
根据 MiMC Groth16 Verifiy 的测试结果来看,两者的运行时间差别不大,而在实现上 Wasm 版本不需要修改 Host Call,因此 megaclite 后续将延续在 wasm 层的修改,暂停 native 层的开发方向。并且,Jupiter 将在 runtime 和 ink! 集成 megaclite,提供公共的线上测试环境。
#
4. More Libraries built for ink!#
4.1 mimc-based merkle tree 的实现- MiMC 实现:https://github.com/patractlabs/megaclite/blob/master/crates/merkle-tree/src/mimc.rs
- Merkle Tree 实现: https://github.com/patractlabs/megaclite/blob/master/crates/merkle-tree/src/merkle_tree.rs
mimc 是在 alt_bn128 这条椭圆曲线上实现了基于 Field 的一种 hash 算法,所以它在零知识证明的 prove system (基于 alt_bn128 曲线)里的电路实现十分友好.
mimc 实现如下图所示, 采用 Sponge mode, Sponge mode instantiated by MiMC permutation with a fixed key
代码实现:
let mut r = in_k.clone();for i in 0..in_x.len() { r = &r + in_x[i] + mimc_pe7(&mut in_x[i], &r, &in_seed, round_count) % &*SCALAR_FIELD;}
在 snark setting 中, MiMC-n/n block-cipher 一般采用 Even-Mansour mode
Our MiMC-p/p with exponent of 7, so:
代码实现:
let mut keccak = Keccak::v256();let mut received = [0u8; 32];keccak.update(&c.to_bytes_be()[..]);keccak.finalize(&mut received);c = U256::from_bytes_be(&received) % &*SCALAR_FIELD;
// x = (x + c_i + k)^7t = &in_x + &c % &*SCALAR_FIELD + in_k % &*SCALAR_FIELD; // t = x + c_i + ka = t.mulmod(&t, &*SCALAR_FIELD); // t^2a = a.mulmod(&a, &*SCALAR_FIELD).mulmod(&a, &*SCALAR_FIELD);in_x = a.mulmod(&t, &*SCALAR_FIELD); // t^7
#
4.2 eddsa verifier 的实现这里 eddsa 签名算法是在JubJub curve上实现的:
Jubjub is the twisted Edwards curve -u^2 + v^2 = 1 + d.u^2.v^2
of rational points over GF(q)
with a subgroup of prime order r
.
q = 21888242871839275222246405745257275088548364400416034343698204186575808495617r = 21888242871839275222246405745257275088696311157297823662689037894645226208583
The choice of GF(q)
is made to be the scalar field of the Bn254 elliptic curve construction.
且还实现了 ETEC(Extened Twisted Edwards coordinate), 在 Extended 坐标系下, 可提供更快的加法运算,在 Projective 坐标系下,可避免求逆运算, 提供更快的 double 运算。
eddsa 签名的核心验证算法如下所示:
其中(s,R)是签名, Pk 是公钥, h 是 message 的 hash 值,因为 R 通过私钥和 message 哈希产生的, 所以 eddsa 也是一种确定性签名算法.
核心验证代码实现:
if let Some(lhs) = scalar_mult(GENERATE[0].clone(), GENERATE[1].clone(), s) { let t = hash_to_u256(&input); if let Some((pk_x, pk_y)) = scalar_mult(public_key[0].clone(), public_key[1].clone(), t) { let [r_x, r_y] = r; let etec_point = etec_add( &point_to_etec(r_x, r_y, Q.clone()), &point_to_etec(pk_x, pk_y, Q.clone()), &*Q, &JUBJUB_A.into(), &JUBJUB_D.into(), ); if let Some(rhs) = etec_to_point(etec_point, Q.clone()) { return lhs == rhs; } }}false
#
5. 回顾验证信息Provide more on-chain underlying cryptography support than Ethereum. The current stage includes two curves : alt_bn128 and bls12_381Integrate ADD, MUL, Paring units under Runtime layer, and provide them to Runtime applications through Runtime-Interface, and further provide them to Wasm contract applications through Contract-SealThrough Pallet and Ink! contract libraries, providing more higher-level verification and crypto tools than Ethereum, improving execution efficiency and reducing development costsProvide off-chain cryptography toolbox through Rust SDKProvide typical sample applications through Ink! sample contracts