diff --git a/evm-vrfier/Cargo.toml b/evm-vrfier/Cargo.toml index bbf642b..d670a9c 100644 --- a/evm-vrfier/Cargo.toml +++ b/evm-vrfier/Cargo.toml @@ -7,11 +7,13 @@ edition = "2021" alloy = { version = "0.14", default-features = false, features = ["contract", "provider-anvil-node"] } ark-ff = { workspace = true } ark-bls12-381 = { version = "0.5", default-features = false, features = ["curve"] } +ark-ed-on-bls12-381-bandersnatch = { version = "0.5", default-features = false } [dev-dependencies] tokio = { version = "1.44", default-features = false } ark-std = { workspace = true } ark-ec = { workspace = true } +w3f-plonk-common = { path = "../w3f-plonk-common", default-features = false , features = ["std"]} w3f-pcs = { workspace = true } [build-dependencies] diff --git a/evm-vrfier/contracts/foundry.toml b/evm-vrfier/contracts/foundry.toml index a40938d..afeb7e9 100644 --- a/evm-vrfier/contracts/foundry.toml +++ b/evm-vrfier/contracts/foundry.toml @@ -6,4 +6,7 @@ libs = ["lib"] evm_version = "prague" via_ir = true +[fmt] +ignore = ["src/SoladyBls.sol"] + # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/evm-vrfier/contracts/src/Constraints.sol b/evm-vrfier/contracts/src/Constraints.sol new file mode 100644 index 0000000..3c66c53 --- /dev/null +++ b/evm-vrfier/contracts/src/Constraints.sol @@ -0,0 +1,97 @@ +pragma solidity ^0.8.24; + +library Constraints { + uint256 constant domain_size = 256; + + uint256 constant w = 36007022166693598376559747923784822035233416720563672082740011604939309541707; + uint256 constant w_inv = 11184958699465346337974417366548385058372410568086779736245770566382283753344; + uint256 constant w_inv_2 = 43775291915288810309377910988321681322896939416379112495208008906206324170002; + uint256 constant w_inv_3 = 24824062393296269928157607240610716359041681219294130923310247842219009400878; + uint256 constant w_inv_4 = 45254319123522011116259460062854627366454101350769349111320208945036885998124; + + uint256 constant r = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001; + uint256 constant te_coeff_a = r - 5; + + function add(uint256 a, uint256 b) internal pure returns (uint256 c) { + c = addmod(a, b, r); + } + + function mul(uint256 a, uint256 b) internal pure returns (uint256 c) { + c = mulmod(a, b, r); + } + + function cond_te_addition( + uint256 b, + uint256 x1, + uint256 y1, + uint256 x2, + uint256 y2, + uint256 x3, + uint256 y3, + uint256 not_last + ) internal pure returns (uint256 cx, uint256 cy) { + /// `cx = {[(a.x1.x2 + y1.y2).x3 - x1.y1 - x2.y2].b + (x3 - x1).(1 - b)}.not_last` + /// `cy = {[(x1.y2 - x2.y1).y3 - x1.y1 + x2.y2].b + (y3 - y1).(1 - b)}.not_last` + uint256 x1y1 = mul(x1, y1); + uint256 x2y2 = mul(x2, y2); + uint256 one_minus_b = add(1, r - b); + // forgefmt: disable-next-item + cx = mul( // [(a.x1.x2 + y1.y2).x3 - x1.y1 - x2.y2].b + add( + mul( + add(mul(te_coeff_a, mul(x1, x2)), mul(y1, y2)), //a.x1.x2 + y1.y2 + x3 + ), + r - add(x1y1, x2y2) + ), + b + ); + cx = mul(add(cx, mul(add(x3, r - x1), one_minus_b)), not_last); + // forgefmt: disable-next-item + cy = mul( // [(x1.y2 - x2.y1).y3 - x1.y1 + x2.y2].b + add( + add( + mul( + + add(mul(x1, y2), r - mul(x2,y1)), //x1.y2 - x2.y1 + y3 + ), + r - x1y1), + x2y2 + ), + b + ); + cy = mul(add(cy, mul(add(y3, r - y1), one_minus_b)), not_last); + } + + function mod_exp(uint256 base, uint256 exp) internal view returns (uint256) { + bytes memory precompileData = abi.encode(32, 32, 32, base, exp, r); + (bool ok, bytes memory data) = address(5).staticcall(precompileData); + require(ok, "expMod failed"); + return abi.decode(data, (uint256)); + } + + function inv(uint256 x) internal view returns (uint256) { + return mod_exp(x, r - 2); + } + + function v_at(uint256 z) internal view returns (uint256) { + return mod_exp(z, domain_size) - 1; + } + + function v_inv_at(uint256 z) internal view returns (uint256) { + return inv(v_at(z)); + } + + function v_inv_hiding_at(uint256 z) internal view returns (uint256) { + return mul(mul(mul(v_inv_at(z), add(z, r - w_inv)), add(z, r - w_inv_2)), add(z, r - w_inv_3)); + } + + function quotient_at(uint256 c, uint256 z) internal view returns (uint256) { + return mul(c, v_inv_hiding_at(z)); + } + + function not_last_row(uint256 z) internal pure returns (uint256) { + return add(z, r - w_inv_4); + } +} diff --git a/evm-vrfier/contracts/src/Kzg.sol b/evm-vrfier/contracts/src/Kzg.sol new file mode 100644 index 0000000..058ea52 --- /dev/null +++ b/evm-vrfier/contracts/src/Kzg.sol @@ -0,0 +1,106 @@ +pragma solidity ^0.8.24; + +import "./BlsGenerators.sol"; + +library Kzg { + // Verifies a batch of `2` kzg proofs: + // 1. `proofs[0]` certifying that `polys[i](zs[0]) = evals_at_z1[i], i = 0,...,k, k = evals_at_z1.length`, + // 2. `proofs[1]` certifying that `polys[j](zs[1]) = evals_at_z2[j], j = 0,...,l, l = evals_at_z2.length`. + function verify_plonk_kzg( + BLS.G1Point[] memory polys, + uint256[] memory zs, + uint256[] memory evals_at_z1, + uint256[] memory evals_at_z2, + BLS.G1Point[] memory proofs, + uint256[] memory nus, + // uint256 r, + BLS.G2Point memory tau_g2 + ) internal view returns (bool) { + uint256 r = 123; //TODO + + uint256 k = polys.length; + assert(evals_at_z1.length == k); + assert(nus.length == k); + uint256 l = evals_at_z2.length; + assert(l <= k); + + // all the g1 points the verifier knows should go to a single msm + uint256 n_bases = k + 3; // `n` commitments to the polynomials, proofs in `zs[0]` and `zs[1]`, and `g1` to commit to the evaluations + + BLS.G1Point[] memory msm_bases = new BLS.G1Point[](n_bases); + bytes32[] memory msm_scalars = new bytes32[](n_bases); + + uint256 i; + for (i = 0; i < k; i++) { + msm_bases[i] = polys[i]; + } + + uint256 r_plus_1 = BlsGenerators.add_fr(r, 1); + for (i = 0; i < l; i++) { + msm_scalars[i] = bytes32(BlsGenerators.mul_fr(r_plus_1, nus[i])); + } + for (i = l; i < k; i++) { + msm_scalars[i] = bytes32(nus[i]); + } + + uint256 agg_at_z = 0; + for (i = 0; i < l; i++) { + agg_at_z = BlsGenerators.add_fr(agg_at_z, BlsGenerators.mul_fr(uint256(nus[i]), evals_at_z2[i])); + } + agg_at_z = BlsGenerators.mul_fr(agg_at_z, r); + for (i = 0; i < polys.length; i++) { + agg_at_z = BlsGenerators.add_fr(agg_at_z, BlsGenerators.mul_fr(uint256(nus[i]), evals_at_z1[i])); + } + msm_bases[i] = BlsGenerators.G1(); + msm_scalars[i] = bytes32(BlsGenerators.q - agg_at_z); + + msm_bases[++i] = proofs[0]; + msm_scalars[i] = bytes32(zs[0]); + + msm_bases[++i] = proofs[1]; + msm_scalars[i] = bytes32(BlsGenerators.mul_fr(r, zs[1])); + + BLS.G1Point memory agg_acc = BLS.msm(msm_bases, msm_scalars); + BLS.G1Point memory acc_proof = BLS.add(proofs[0], BlsGenerators.g1_mul(proofs[1], bytes32(r))); + return verify_acc(agg_acc, acc_proof, tau_g2); + } + + function verify(BLS.G1Point memory c, uint256 z, uint256 v, BLS.G1Point memory proof, BLS.G2Point memory tau_g2) + internal + view + returns (bool) + { + bytes32[] memory msm_scalars = new bytes32[](2); + BLS.G1Point[] memory msm_bases = new BLS.G1Point[](2); + msm_scalars[0] = bytes32(z); + msm_bases[0] = proof; + msm_scalars[1] = bytes32(BlsGenerators.q - v); + msm_bases[1] = BlsGenerators.G1(); + BLS.G1Point memory acc = BLS.msm(msm_bases, msm_scalars); + acc = BLS.add(acc, c); + return verify_acc(acc, proof, tau_g2); + } + + function verify_acc(BLS.G1Point memory acc, BLS.G1Point memory acc_proof, BLS.G2Point memory tau_g2) + internal + view + returns (bool) + { + return pairing2(acc, BlsGenerators.G2_NEG(), acc_proof, tau_g2); + } + + function pairing2( + BLS.G1Point memory g1_1, + BLS.G2Point memory g2_1, + BLS.G1Point memory g1_2, + BLS.G2Point memory g2_2 + ) internal view returns (bool result) { + BLS.G1Point[] memory g1_points = new BLS.G1Point[](2); + BLS.G2Point[] memory g2_points = new BLS.G2Point[](2); + g1_points[0] = g1_1; + g2_points[0] = g2_1; + g1_points[1] = g1_2; + g2_points[1] = g2_2; + return BLS.pairing(g1_points, g2_points); + } +} diff --git a/evm-vrfier/contracts/src/Plonk.sol b/evm-vrfier/contracts/src/Plonk.sol new file mode 100644 index 0000000..e999d71 --- /dev/null +++ b/evm-vrfier/contracts/src/Plonk.sol @@ -0,0 +1,67 @@ +pragma solidity ^0.8.24; + +import {BLS, Kzg} from "src/Kzg.sol"; +import {Constraints} from "src/Constraints.sol"; + +contract Plonk { + // The trapdoor `tau` in G2, part of the standard KZG verification key. + BLS.G2Point tau_g2; + + constructor(BLS.G2Point memory tau_g2_) { + tau_g2 = tau_g2_; + } + + function verify_proof( + BLS.G1Point[] memory columns, + BLS.G1Point memory quotient, + uint256 z, + uint256[] memory columns_at_z, + uint256[] memory columns_at_zw, + BLS.G1Point memory kzg_proof_at_z, + BLS.G1Point memory kzg_proof_at_zw, + uint256[] memory nus + ) public view returns (bool) { + uint256 k = columns.length; + require(columns_at_z.length == k); + require(columns_at_zw.length <= k); + + BLS.G1Point[] memory polys = new BLS.G1Point[](k + 1); + for (uint256 i = 0; i < k; i++) { + polys[i] = columns[i]; + } + polys[k] = quotient; + + uint256[] memory evals_at_z = new uint256[](k + 1); + for (uint256 i = 0; i < k; i++) { + evals_at_z[i] = columns_at_z[i]; + } + evals_at_z[k] = compute_quotient(columns_at_z, columns_at_zw, z); + + BLS.G1Point[] memory kzg_proofs = new BLS.G1Point[](2); + kzg_proofs[0] = kzg_proof_at_z; + kzg_proofs[1] = kzg_proof_at_zw; + uint256[] memory zs = new uint256[](2); + zs[0] = z; + zs[1] = Constraints.mul(z, Constraints.w); + return Kzg.verify_plonk_kzg(polys, zs, evals_at_z, columns_at_zw, kzg_proofs, nus, tau_g2); + } + + function compute_quotient(uint256[] memory columns_at_z1, uint256[] memory columns_at_z2, uint256 z) + internal + view + returns (uint256) + { + uint256 not_last = Constraints.not_last_row(z); + (uint256 cx, uint256 cy) = Constraints.cond_te_addition( + columns_at_z1[4], + columns_at_z1[0], + columns_at_z1[1], + columns_at_z1[2], + columns_at_z1[3], + columns_at_z2[0], + columns_at_z2[1], + not_last + ); + return Constraints.quotient_at(Constraints.add(cx, cy), z); //TODO: alphas + } +} diff --git a/evm-vrfier/contracts/src/PlonkKzg.sol b/evm-vrfier/contracts/src/PlonkKzg.sol deleted file mode 100644 index f9d6761..0000000 --- a/evm-vrfier/contracts/src/PlonkKzg.sol +++ /dev/null @@ -1,93 +0,0 @@ -pragma solidity ^0.8.24; - -import "./BlsGenerators.sol"; - -contract PlonkKzg { - // The trapdoor `tau` in G2, part of the standard KZG verification key. - BLS.G2Point tau_g2; - - constructor(BLS.G2Point memory tau_g2_) { - tau_g2 = tau_g2_; - } - - function verify_plonk_kzg( - BLS.G1Point[] memory polys_z1, - BLS.G1Point memory poly_z2, - uint256 z1, - uint256 z2, - uint256[] memory evals_at_z1, - uint256 eval_at_z2, - BLS.G1Point memory proof_z1, - BLS.G1Point memory proof_z2, - bytes32[] memory nus, - uint256 r - ) public view returns (bool) { - assert(evals_at_z1.length == polys_z1.length); - assert(nus.length == polys_z1.length); - - uint256 n_bases = polys_z1.length + 4; - - BLS.G1Point[] memory msm_bases = new BLS.G1Point[](n_bases); - bytes32[] memory msm_scalars = new bytes32[](n_bases); - - uint256 i; - for (i = 0; i < polys_z1.length; i++) { - msm_bases[i] = polys_z1[i]; - } - - for (i = 0; i < polys_z1.length; i++) { - msm_scalars[i] = nus[i]; - } - - uint256 agg_at_z = 0; - for (i = 0; i < polys_z1.length; i++) { - agg_at_z = BlsGenerators.add_fr(agg_at_z, BlsGenerators.mul_fr(uint256(nus[i]), evals_at_z1[i])); - } - msm_bases[i] = BlsGenerators.G1(); - msm_scalars[i] = bytes32(BlsGenerators.q - BlsGenerators.add_fr(agg_at_z, BlsGenerators.mul_fr(r, eval_at_z2))); - - msm_bases[++i] = proof_z1; - msm_scalars[i] = bytes32(z1); - - msm_bases[++i] = poly_z2; - msm_scalars[i] = bytes32(r); - - msm_bases[++i] = proof_z2; - msm_scalars[i] = bytes32(BlsGenerators.mul_fr(r, z2)); - - BLS.G1Point memory agg_acc = BLS.msm(msm_bases, msm_scalars); - BLS.G1Point memory acc_proof = BLS.add(proof_z1, BlsGenerators.g1_mul(proof_z2, bytes32(r))); - return verify_acc(agg_acc, acc_proof); - } - - function verify(BLS.G1Point memory c, uint256 z, uint256 v, BLS.G1Point memory proof) public view returns (bool) { - bytes32[] memory msm_scalars = new bytes32[](2); - BLS.G1Point[] memory msm_bases = new BLS.G1Point[](2); - msm_scalars[0] = bytes32(z); - msm_bases[0] = proof; - msm_scalars[1] = bytes32(BlsGenerators.q - v); - msm_bases[1] = BlsGenerators.G1(); - BLS.G1Point memory acc = BLS.msm(msm_bases, msm_scalars); - acc = BLS.add(acc, c); - return verify_acc(acc, proof); - } - - function verify_acc(BLS.G1Point memory acc, BLS.G1Point memory acc_proof) public view returns (bool) { - return pairing2(acc, BlsGenerators.G2_NEG(), acc_proof, tau_g2); - } - - function pairing2( - BLS.G1Point memory g1_1, - BLS.G2Point memory g2_1, - BLS.G1Point memory g1_2, - BLS.G2Point memory g2_2 - ) public view returns (bool result) { - BLS.G1Point[] memory g1_points = new BLS.G1Point[](2); - BLS.G2Point[] memory g2_points = new BLS.G2Point[](2); - g1_points[0] = g1_1; - g2_points[0] = g2_1; - g1_points[1] = g1_2; - g2_points[1] = g2_2; - return BLS.pairing(g1_points, g2_points); - } -} diff --git a/evm-vrfier/contracts/src/SoladyBls.sol b/evm-vrfier/contracts/src/SoladyBls.sol index 4b6aa4c..ada0bea 100644 --- a/evm-vrfier/contracts/src/SoladyBls.sol +++ b/evm-vrfier/contracts/src/SoladyBls.sol @@ -118,11 +118,20 @@ library BLS { /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Adds two G1 points. Returns a new G1 point. - function add(G1Point memory point0, G1Point memory point1) internal view returns (G1Point memory result) { + function add(G1Point memory point0, G1Point memory point1) + internal + view + returns (G1Point memory result) + { assembly ("memory-safe") { mcopy(result, point0, 0x80) mcopy(add(result, 0x80), point1, 0x80) - if iszero(and(eq(returndatasize(), 0x80), staticcall(gas(), BLS12_G1ADD, result, 0x100, result, 0x80))) { + if iszero( + and( + eq(returndatasize(), 0x80), + staticcall(gas(), BLS12_G1ADD, result, 0x100, result, 0x80) + ) + ) { mstore(0x00, 0xd6cc76eb) // `G1AddFailed()`. revert(0x1c, 0x04) } @@ -130,7 +139,11 @@ library BLS { } /// @dev Multi-scalar multiplication of G1 points with scalars. Returns a new G1 point. - function msm(G1Point[] memory points, bytes32[] memory scalars) internal view returns (G1Point memory result) { + function msm(G1Point[] memory points, bytes32[] memory scalars) + internal + view + returns (G1Point memory result) + { assembly ("memory-safe") { let k := mload(points) let d := sub(scalars, points) @@ -153,11 +166,20 @@ library BLS { } /// @dev Adds two G2 points. Returns a new G2 point. - function add(G2Point memory point0, G2Point memory point1) internal view returns (G2Point memory result) { + function add(G2Point memory point0, G2Point memory point1) + internal + view + returns (G2Point memory result) + { assembly ("memory-safe") { mcopy(result, point0, 0x100) mcopy(add(result, 0x100), point1, 0x100) - if iszero(and(eq(returndatasize(), 0x100), staticcall(gas(), BLS12_G2ADD, result, 0x200, result, 0x100))) { + if iszero( + and( + eq(returndatasize(), 0x100), + staticcall(gas(), BLS12_G2ADD, result, 0x200, result, 0x100) + ) + ) { mstore(0x00, 0xc55e5e33) // `G2AddFailed()`. revert(0x1c, 0x04) } @@ -165,7 +187,11 @@ library BLS { } /// @dev Multi-scalar multiplication of G2 points with scalars. Returns a new G2 point. - function msm(G2Point[] memory points, bytes32[] memory scalars) internal view returns (G2Point memory result) { + function msm(G2Point[] memory points, bytes32[] memory scalars) + internal + view + returns (G2Point memory result) + { assembly ("memory-safe") { let k := mload(points) let d := sub(scalars, points) @@ -188,7 +214,11 @@ library BLS { } /// @dev Checks the pairing of G1 points with G2 points. Returns whether the pairing is valid. - function pairing(G1Point[] memory g1Points, G2Point[] memory g2Points) internal view returns (bool result) { + function pairing(G1Point[] memory g1Points, G2Point[] memory g2Points) + internal + view + returns (bool result) + { assembly ("memory-safe") { let k := mload(g1Points) let m := mload(0x40) @@ -216,7 +246,10 @@ library BLS { function toG1(Fp memory element) internal view returns (G1Point memory result) { assembly ("memory-safe") { if iszero( - and(eq(returndatasize(), 0x80), staticcall(gas(), BLS12_MAP_FP_TO_G1, element, 0x40, result, 0x80)) + and( + eq(returndatasize(), 0x80), + staticcall(gas(), BLS12_MAP_FP_TO_G1, element, 0x40, result, 0x80) + ) ) { mstore(0x00, 0x24a289fc) // `MapFpToG1Failed()`. revert(0x1c, 0x04) @@ -228,7 +261,10 @@ library BLS { function toG2(Fp2 memory element) internal view returns (G2Point memory result) { assembly ("memory-safe") { if iszero( - and(eq(returndatasize(), 0x100), staticcall(gas(), BLS12_MAP_FP2_TO_G2, element, 0x80, result, 0x100)) + and( + eq(returndatasize(), 0x100), + staticcall(gas(), BLS12_MAP_FP2_TO_G2, element, 0x80, result, 0x100) + ) ) { mstore(0x00, 0x89083b91) // `MapFp2ToG2Failed()`. revert(0x1c, 0x04) @@ -247,22 +283,26 @@ library BLS { } function sha2(data_, n_) -> _h { - if iszero(and(eq(returndatasize(), 0x20), staticcall(gas(), 2, data_, n_, 0x00, 0x20))) { - revert(calldatasize(), 0x00) - } + if iszero( + and(eq(returndatasize(), 0x20), staticcall(gas(), 2, data_, n_, 0x00, 0x20)) + ) { revert(calldatasize(), 0x00) } _h := mload(0x00) } function modfield(s_, b_) { mcopy(add(s_, 0x60), b_, 0x40) - if iszero(and(eq(returndatasize(), 0x40), staticcall(gas(), 5, s_, 0x100, b_, 0x40))) { - revert(calldatasize(), 0x00) - } + if iszero( + and(eq(returndatasize(), 0x40), staticcall(gas(), 5, s_, 0x100, b_, 0x40)) + ) { revert(calldatasize(), 0x00) } } function mapToG2(s_, r_) { - if iszero(and(eq(returndatasize(), 0x100), staticcall(gas(), BLS12_MAP_FP2_TO_G2, s_, 0x80, r_, 0x100))) - { + if iszero( + and( + eq(returndatasize(), 0x100), + staticcall(gas(), BLS12_MAP_FP2_TO_G2, s_, 0x80, r_, 0x100) + ) + ) { mstore(0x00, 0x89083b91) // `MapFp2ToG2Failed()`. revert(0x1c, 0x04) } @@ -301,10 +341,15 @@ library BLS { mapToG2(b, result) mapToG2(add(0x80, b), add(0x100, result)) - if iszero(and(eq(returndatasize(), 0x100), staticcall(gas(), BLS12_G2ADD, result, 0x200, result, 0x100))) { + if iszero( + and( + eq(returndatasize(), 0x100), + staticcall(gas(), BLS12_G2ADD, result, 0x200, result, 0x100) + ) + ) { mstore(0x00, 0xc55e5e33) // `G2AddFailed()`. revert(0x1c, 0x04) } } } -} +} \ No newline at end of file diff --git a/evm-vrfier/contracts/test/Constraints.t.sol b/evm-vrfier/contracts/test/Constraints.t.sol new file mode 100644 index 0000000..4b157d1 --- /dev/null +++ b/evm-vrfier/contracts/test/Constraints.t.sol @@ -0,0 +1,47 @@ +pragma solidity ^0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {Constraints} from "src/Constraints.sol"; + +library ConstraintsExt { + function cond_te_addition( + uint256 b, + uint256 x1, + uint256 y1, + uint256 x2, + uint256 y2, + uint256 x3, + uint256 y3, + uint256 not_last + ) public pure returns (uint256, uint256) { + return Constraints.cond_te_addition(b, x1, y1, x2, y2, x3, y3, not_last); + } + + function mod_exp(uint256 base, uint256 exp) public view returns (uint256) { + return Constraints.mod_exp(base, exp); + } + + function inv(uint256 x) public view returns (uint256) { + return Constraints.inv(x); + } + + function v_at(uint256 z) public view returns (uint256) { + return Constraints.v_at(z); + } + + function v_inv_at(uint256 z) public view returns (uint256) { + return Constraints.v_inv_at(z); + } + + function v_inv_hiding_at(uint256 z) public view returns (uint256) { + return Constraints.v_inv_hiding_at(z); + } + + function quotient_at(uint256 c, uint256 z) public view returns (uint256) { + return Constraints.quotient_at(c, z); + } + + function not_last_row(uint256 z) public pure returns (uint256) { + return Constraints.not_last_row(z); + } +} diff --git a/evm-vrfier/contracts/test/PlonkKzg.t.sol b/evm-vrfier/contracts/test/Kzg.t.sol similarity index 57% rename from evm-vrfier/contracts/test/PlonkKzg.t.sol rename to evm-vrfier/contracts/test/Kzg.t.sol index 60576de..cf207c4 100644 --- a/evm-vrfier/contracts/test/PlonkKzg.t.sol +++ b/evm-vrfier/contracts/test/Kzg.t.sol @@ -1,14 +1,44 @@ pragma solidity ^0.8.24; -import {Test, console} from "forge-std/Test.sol"; -import "../src/SoladyBls.sol"; -import "../src/BlsGenerators.sol"; -import "../src/PlonkKzg.sol"; +import {Test} from "forge-std/Test.sol"; +import {BLS, BlsGenerators, Kzg} from "src/Kzg.sol"; + +contract KzgExt { + // The trapdoor `tau` in G2, part of the standard KZG verification key. + BLS.G2Point tau_g2; + + constructor(BLS.G2Point memory tau_g2_) { + tau_g2 = tau_g2_; + } + + function verify(BLS.G1Point memory c, uint256 z, uint256 v, BLS.G1Point memory proof) public view returns (bool) { + return Kzg.verify(c, z, v, proof, tau_g2); + } + + function verify_plonk_kzg( + BLS.G1Point[] memory polys, + uint256[] memory zs, + uint256[] memory evals_at_z1, + uint256[] memory evals_at_z2, + BLS.G1Point[] memory proofs, + uint256[] memory nus + ) public view returns (bool) { + return Kzg.verify_plonk_kzg(polys, zs, evals_at_z1, evals_at_z2, proofs, nus, tau_g2); + } + + function pairing2( + BLS.G1Point memory g1_1, + BLS.G2Point memory g2_1, + BLS.G1Point memory g1_2, + BLS.G2Point memory g2_2 + ) public view returns (bool) { + return Kzg.pairing2(g1_1, g2_1, g1_2, g2_2); + } +} contract PlonkKzgTest is Test { - PlonkKzg kzg; + KzgExt kzg; BLS.G1Point[] srs_g1; - BLS.G2Point tau_g2; function setUp() public { uint256 n = 3; @@ -17,8 +47,8 @@ contract PlonkKzgTest is Test { for (uint256 i = 1; i < n; i++) { srs_g1.push(BlsGenerators.g1_mul(srs_g1[i - 1], tau)); } - tau_g2 = BlsGenerators.g2_mul(BlsGenerators.G2(), tau); - kzg = new PlonkKzg(tau_g2); + BLS.G2Point memory tau_g2 = BlsGenerators.g2_mul(BlsGenerators.G2(), tau); + kzg = new KzgExt(tau_g2); } function commit(bytes32[] memory coeffs) internal view returns (BLS.G1Point memory c) { @@ -50,14 +80,6 @@ contract PlonkKzgTest is Test { } } - function verify_single(BLS.G1Point memory c, uint256 z, uint256 v, BLS.G1Point memory proof) - internal - view - returns (bool) - { - return kzg.verify(c, z, v, proof); - } - function test_div() public pure { // 3x^2 + 2x + 1 = [1, 2, 3] bytes32[] memory poly = new bytes32[](3); @@ -81,7 +103,7 @@ contract PlonkKzgTest is Test { uint256 z = 777; BLS.G1Point memory proof = prove(poly, z); uint256 v = eval(poly, z); - assert(kzg.verify(c, z, v, proof)); + assertTrue(kzg.verify(c, z, v, proof)); } function test_plonk_kzg() public view { @@ -89,30 +111,35 @@ contract PlonkKzgTest is Test { for (uint256 i = 0; i < poly.length; i++) { poly[i] = bytes32(i + 10); } - BLS.G1Point memory c = commit(poly); + BLS.G1Point memory commitment = commit(poly); uint256 z1 = 666; - uint256 z2 = 777; + uint256 z2 = 668; BLS.G1Point memory proof_z1 = prove(poly, z1); BLS.G1Point memory proof_z2 = prove(poly, z2); uint256 eval_at_z1 = eval(poly, z1); uint256 eval_at_z2 = eval(poly, z2); - assert(kzg.verify(c, z1, eval_at_z1, proof_z1)); - assert(kzg.verify(c, z2, eval_at_z2, proof_z2)); + assertTrue(kzg.verify(commitment, z1, eval_at_z1, proof_z1)); + assertTrue(kzg.verify(commitment, z2, eval_at_z2, proof_z2)); - BLS.G1Point[] memory polys_z1 = new BLS.G1Point[](1); + BLS.G1Point[] memory commitments = new BLS.G1Point[](1); uint256[] memory evals_at_z1 = new uint256[](1); - polys_z1[0] = c; + uint256[] memory evals_at_z2 = new uint256[](1); + commitments[0] = commitment; evals_at_z1[0] = eval_at_z1; - - BLS.G1Point memory poly_z2 = c; - - bytes32 nu = bytes32(uint256(123)); - uint256 r = 345; - bytes32[] memory nus = new bytes32[](1); + evals_at_z2[0] = eval_at_z2; + uint256 nu = 2; + uint256[] memory nus = new uint256[](1); nus[0] = nu; - proof_z1 = BlsGenerators.g1_mul(proof_z1, nu); - - assert(kzg.verify_plonk_kzg(polys_z1, poly_z2, z1, z2, evals_at_z1, eval_at_z2, proof_z1, proof_z2, nus, r)); + BLS.G1Point[] memory proofs = new BLS.G1Point[](2); + proofs[0] = BlsGenerators.g1_mul(proof_z1, bytes32(nu)); + proofs[1] = BlsGenerators.g1_mul(proof_z2, bytes32(nu)); + uint256[] memory zs = new uint256[](2); + zs[0] = z1; + zs[1] = z2; + assertTrue( + kzg.verify_plonk_kzg(commitments, zs, evals_at_z1, evals_at_z2, proofs, nus), + "Batch KZG verification failed" + ); } } diff --git a/evm-vrfier/src/lib.rs b/evm-vrfier/src/lib.rs index 07d116b..8f8d178 100644 --- a/evm-vrfier/src/lib.rs +++ b/evm-vrfier/src/lib.rs @@ -1,4 +1,78 @@ -pub mod plonk_kzg; +use alloy::primitives::{FixedBytes, U256}; +use ark_ff::{BigInteger, PrimeField}; + +mod test_constraints; +mod test_kzg; +mod test_plonk; + +alloy::sol! { + struct G1Point { + bytes32 x_a; + bytes32 x_b; + bytes32 y_a; + bytes32 y_b; + } + + struct G2Point { + bytes32 x_c0_a; + bytes32 x_c0_b; + bytes32 x_c1_a; + bytes32 x_c1_b; + bytes32 y_c0_a; + bytes32 y_c0_b; + bytes32 y_c1_a; + bytes32 y_c1_b; + } +} + +/// Encodes a BLS12-381 base field element (381 bits) into 2 bytes32 as specified in +/// [eip-2537](https://eips.ethereum.org/EIPS/eip-2537#fine-points-and-encoding-of-base-elements): +/// > A base field element (Fp) is encoded as 64 bytes +/// > by performing the BigEndian encoding of the corresponding (unsigned) integer. +/// > Due to the size of p, the top 16 bytes are always zeroes. +pub fn fq_to_bytes(fq: ark_bls12_381::Fq) -> [FixedBytes<32>; 2] { + let be_bytes = fq.into_bigint().to_bytes_be(); // 48 bytes + let high_bytes = FixedBytes::left_padding_from(&be_bytes[..16]); // 16 bytes + let low_bytes = FixedBytes::from_slice(&be_bytes[16..]); // 32 bytes + [high_bytes, low_bytes] +} + +pub fn fr_to_bytes(fr: ark_bls12_381::Fr) -> FixedBytes<32> { + let be_bytes = fr.into_bigint().to_bytes_be(); + FixedBytes::left_padding_from(&be_bytes) +} + +pub fn fr_to_uint(fr: ark_bls12_381::Fr) -> U256 { + let be_bytes = fr.into_bigint().to_bytes_be(); + U256::from_be_slice(&be_bytes) +} + +pub fn unit_to_fr(f: U256) -> ark_bls12_381::Fr { + ark_bls12_381::Fr::from_le_bytes_mod_order(&f.as_le_bytes()) +} + +pub fn encode_g1(p: ark_bls12_381::G1Affine) -> G1Point { + let [x_a, x_b] = fq_to_bytes(p.x); + let [y_a, y_b] = fq_to_bytes(p.y); + G1Point { x_a, x_b, y_a, y_b } +} + +pub fn encode_g2(p: ark_bls12_381::G2Affine) -> G2Point { + let [x_c0_a, x_c0_b] = fq_to_bytes(p.x.c0); + let [x_c1_a, x_c1_b] = fq_to_bytes(p.x.c1); + let [y_c0_a, y_c0_b] = fq_to_bytes(p.y.c0); + let [y_c1_a, y_c1_b] = fq_to_bytes(p.y.c1); + G2Point { + x_c0_a, + x_c0_b, + x_c1_a, + x_c1_b, + y_c0_a, + y_c0_b, + y_c1_a, + y_c1_b, + } +} #[cfg(test)] mod tests { diff --git a/evm-vrfier/src/plonk_kzg.rs b/evm-vrfier/src/plonk_kzg.rs deleted file mode 100644 index 84ed236..0000000 --- a/evm-vrfier/src/plonk_kzg.rs +++ /dev/null @@ -1,257 +0,0 @@ -use alloy::primitives::{FixedBytes, U256}; -use ark_ff::fields::PrimeField; -use ark_ff::BigInteger; - -alloy::sol!( - #[sol(rpc)] - PlonkKzg, - "contracts/out/PlonkKzg.sol/PlonkKzg.json" -); - -/// Encodes a BLS12-381 base field element (381 bits) as specified in -/// [eip-2537](https://eips.ethereum.org/EIPS/eip-2537#fine-points-and-encoding-of-base-elements): -/// > A base field element (Fp) is encoded as 64 bytes -/// > by performing the BigEndian encoding of the corresponding (unsigned) integer. -/// > Due to the size of p, the top 16 bytes are always zeroes. -pub fn bls_base_field_to_bytes(fq: ark_bls12_381::Fq) -> [FixedBytes<32>; 2] { - let be_bytes = fq.into_bigint().to_bytes_be(); // 48 bytes - let high_bytes = FixedBytes::left_padding_from(&be_bytes[..16]); // 16 bytes - let low_bytes = FixedBytes::from_slice(&be_bytes[16..]); // 32 bytes - [high_bytes, low_bytes] -} - -pub fn bls_scalar_field_to_uint256(fr: ark_bls12_381::Fr) -> U256 { - let be_bytes = fr.into_bigint().to_bytes_be(); - U256::from_be_slice(&be_bytes) -} - -pub fn bls_scalar_field_to_bytes32(fr: ark_bls12_381::Fr) -> FixedBytes<32> { - let be_bytes = fr.into_bigint().to_bytes_be(); - FixedBytes::left_padding_from(&be_bytes) -} - -pub fn encode_bls_g1(p: ark_bls12_381::G1Affine) -> BLS::G1Point { - let [x_a, x_b] = bls_base_field_to_bytes(p.x); - let [y_a, y_b] = bls_base_field_to_bytes(p.y); - BLS::G1Point { x_a, x_b, y_a, y_b } -} - -pub fn encode_bls_g2(p: ark_bls12_381::G2Affine) -> BLS::G2Point { - let [x_c0_a, x_c0_b] = bls_base_field_to_bytes(p.x.c0); - let [x_c1_a, x_c1_b] = bls_base_field_to_bytes(p.x.c1); - let [y_c0_a, y_c0_b] = bls_base_field_to_bytes(p.y.c0); - let [y_c1_a, y_c1_b] = bls_base_field_to_bytes(p.y.c1); - BLS::G2Point { - x_c0_a, - x_c0_b, - x_c1_a, - x_c1_b, - y_c0_a, - y_c0_b, - y_c1_a, - y_c1_b, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::plonk_kzg::PlonkKzg; - use ark_bls12_381::{Bls12_381, Fr, G2Affine}; - use ark_ec::pairing::Pairing; - use ark_ec::{AffineRepr, PrimeGroup}; - use ark_std::rand::Rng; - use ark_std::{test_rng, UniformRand}; - use w3f_pcs::aggregation::single::aggregate_polys; - use w3f_pcs::pcs::kzg::params::RawKzgVerifierKey; - use w3f_pcs::pcs::kzg::urs::URS; - use w3f_pcs::pcs::kzg::KZG; - use w3f_pcs::pcs::{PcsParams, PCS}; - use w3f_pcs::DenseUVPolynomial; - use w3f_pcs::Poly; - use w3f_pcs::Polynomial; - - struct ArksBatchKzgOpenning { - polys_z1: Vec, - poly_z2: E::G1Affine, - z1: E::ScalarField, - z2: E::ScalarField, - evals_at_z1: Vec, - eval_at_z2: E::ScalarField, - kzg_proof_at_z1: E::G1Affine, - kzg_proof_at_z2: E::G1Affine, - } - - struct EthBatchKzgOpenning { - polys_z1: Vec, - poly_z2: BLS::G1Point, - z1: U256, - z2: U256, - evals_at_z1: Vec, - eval_at_z2: U256, - kzg_proof_at_z1: BLS::G1Point, - kzg_proof_at_z2: BLS::G1Point, - } - - impl ArksBatchKzgOpenning { - fn encode(self) -> EthBatchKzgOpenning { - EthBatchKzgOpenning { - polys_z1: self.polys_z1.into_iter().map(encode_bls_g1).collect(), - poly_z2: encode_bls_g1(self.poly_z2), - z1: bls_scalar_field_to_uint256(self.z1), - z2: bls_scalar_field_to_uint256(self.z2), - evals_at_z1: self - .evals_at_z1 - .into_iter() - .map(bls_scalar_field_to_uint256) - .collect(), - eval_at_z2: bls_scalar_field_to_uint256(self.eval_at_z2), - kzg_proof_at_z1: encode_bls_g1(self.kzg_proof_at_z1), - kzg_proof_at_z2: encode_bls_g1(self.kzg_proof_at_z2), - } - } - } - - fn random_opening( - d: usize, - k: usize, - rng: &mut R, - ) -> ( - ArksBatchKzgOpenning, - Vec, - RawKzgVerifierKey, - ) { - // KZG setup - let urs = URS::from_trapdoor( - E::ScalarField::rand(rng), - d + 1, - 2, - E::G1::generator(), - E::G2::generator(), - ); - let (ck, rvk) = (urs.ck(), urs.raw_vk()); - - // Polynomials - let polys_z1: Vec> = (0..k) - .map(|_| Poly::::rand(d, rng)) - .collect(); - let poly_z2 = Poly::::rand(d, rng); - - // Aggregate polynomial - let nus: Vec = (0..k).map(|_| E::ScalarField::rand(rng)).collect(); - let agg_poly_z1 = aggregate_polys(&polys_z1, &nus); - - // Evaluation points - let z1 = E::ScalarField::rand(rng); - let z2 = E::ScalarField::rand(rng); - - // Proofs - let kzg_proof_at_z1 = KZG::::open(&ck, &agg_poly_z1, z1).unwrap(); - let kzg_proof_at_z2 = KZG::::open(&ck, &poly_z2, z2).unwrap(); - - // Evaluations - let evals_at_z1: Vec = polys_z1.iter().map(|p| p.evaluate(&z1)).collect(); - let eval_at_z2 = poly_z2.evaluate(&z2); - - // Commitments - let polys_z1: Vec = polys_z1 - .iter() - .map(|p| KZG::::commit(&ck, p).unwrap().0) - .collect(); - let poly_z2 = KZG::::commit(&ck, &poly_z2).unwrap().0; - - ( - ArksBatchKzgOpenning { - polys_z1, - poly_z2, - z1, - z2, - evals_at_z1, - eval_at_z2, - kzg_proof_at_z1, - kzg_proof_at_z2, - }, - nus, - rvk, - ) - } - - #[tokio::test] - async fn test_batch_openning() -> Result<(), Box> { - let provider = alloy::providers::builder() - .with_recommended_fillers() - .on_anvil_with_wallet_and_config(|anvil| anvil.prague())?; - - let (test_openning, nus, kzg_vk) = random_opening::(123, 1, &mut test_rng()); - let test_openning = test_openning.encode(); - - let plonk_kzg = PlonkKzg::deploy(&provider, encode_bls_g2(kzg_vk.tau_in_g2)).await?; - - let res = plonk_kzg - .verify_plonk_kzg( - test_openning.polys_z1, - test_openning.poly_z2, - test_openning.z1, - test_openning.z2, - test_openning.evals_at_z1, - test_openning.eval_at_z2, - test_openning.kzg_proof_at_z1, - test_openning.kzg_proof_at_z2, - nus.into_iter().map(bls_scalar_field_to_bytes32).collect(), - bls_scalar_field_to_uint256(Fr::from(1)), - ) - .call() - .await?; - assert!(res); - - Ok(()) - } - - #[tokio::test] - async fn test_single_openning() -> Result<(), Box> { - let provider = alloy::providers::builder() - .with_recommended_fillers() - .on_anvil_with_wallet_and_config(|anvil| anvil.prague())?; - - let (test_openning, _, kzg_vk) = random_opening::(123, 0, &mut test_rng()); - let test_openning = test_openning.encode(); - - let plonk_kzg = PlonkKzg::deploy(&provider, encode_bls_g2(kzg_vk.tau_in_g2)).await?; - - let res = plonk_kzg - .verify( - test_openning.poly_z2, - test_openning.z2, - test_openning.eval_at_z2, - test_openning.kzg_proof_at_z2, - ) - .call() - .await?; - assert!(res); - - Ok(()) - } - - #[tokio::test] - async fn test_pairing() -> Result<(), Box> { - let provider = alloy::providers::builder() - .with_recommended_fillers() - .on_anvil_with_wallet_and_config(|anvil| anvil.prague())?; - - let _tau_g2 = G2Affine::generator(); - let plonk_kzg = PlonkKzg::deploy(&provider, encode_bls_g2(_tau_g2)).await?; - - let res = plonk_kzg - .pairing2( - encode_bls_g1(-ark_bls12_381::G1Affine::generator()), - encode_bls_g2(ark_bls12_381::G2Affine::generator()), - encode_bls_g1(ark_bls12_381::G1Affine::generator()), - encode_bls_g2(ark_bls12_381::G2Affine::generator()), - ) - .call() - .await?; - assert!(res); - - Ok(()) - } -} diff --git a/evm-vrfier/src/test_constraints.rs b/evm-vrfier/src/test_constraints.rs new file mode 100644 index 0000000..9a39e3a --- /dev/null +++ b/evm-vrfier/src/test_constraints.rs @@ -0,0 +1,114 @@ +#[cfg(test)] +mod tests { + use crate::{fr_to_uint, unit_to_fr}; + use alloy::primitives::U256; + use ark_bls12_381::Fr; + use ark_ec::twisted_edwards::TECurveConfig; + use ark_ed_on_bls12_381_bandersnatch::BandersnatchConfig; + use ark_ff::{Field, One}; + use ark_std::{test_rng, UniformRand}; + use w3f_plonk_common::domain::Domain; + use w3f_plonk_common::gadgets::ec::te_cond_add::cond_te_addition; + + alloy::sol!( + #[sol(rpc)] + Constraints, + "contracts/out/Constraints.t.sol/ConstraintsExt.json" + ); + + #[tokio::test] + async fn constraints() -> Result<(), Box> { + let provider = alloy::providers::builder() + .with_recommended_fillers() + .on_anvil_with_wallet(); + let constraints = Constraints::deploy(&provider).await?; + + let rng = &mut test_rng(); + + let te_coeff_a = BandersnatchConfig::COEFF_A; + let b = Fr::rand(rng); + let x1 = Fr::rand(rng); + let y1 = Fr::rand(rng); + let x2 = Fr::rand(rng); + let y2 = Fr::rand(rng); + let x3 = Fr::rand(rng); + let y3 = Fr::rand(rng); + let not_last = Fr::rand(rng); + let cs_rust = cond_te_addition( + te_coeff_a, + &b, + &x1, + &y1, + &x2, + &y2, + x3, + y3, + ¬_last, + Fr::one(), + ); + let cs_sol = constraints + .cond_te_addition( + fr_to_uint(b), + fr_to_uint(x1), + fr_to_uint(y1), + fr_to_uint(x2), + fr_to_uint(y2), + fr_to_uint(x3), + fr_to_uint(y3), + fr_to_uint(not_last), + ) + .call() + .await?; + assert_eq!(unit_to_fr(cs_sol._0), cs_rust[0]); + assert_eq!(unit_to_fr(cs_sol._1), cs_rust[1]); + + Ok(()) + } + + #[tokio::test] + async fn domain_at_zeta() -> Result<(), Box> { + let provider = alloy::providers::builder() + .with_recommended_fillers() + .on_anvil_with_wallet(); + + let constraints = Constraints::deploy(&provider).await?; + + let rng = &mut test_rng(); + + let domain = Domain::new(256, true); + + let w = domain.omega(); + let w_inv = domain.omega_inv(); + let w_inv_2 = w_inv * w_inv; + let w_inv_3 = w_inv_2 * w_inv; + let w_inv_4 = w_inv_3 * w_inv; + println!("\tuint256 constant w = {};", fr_to_uint(w)); + println!("\tuint256 constant w_inv = {};", fr_to_uint(w_inv)); + println!("\tuint256 constant w_inv_2 = {};", fr_to_uint(w_inv_2)); + println!("\tuint256 constant w_inv_3 = {};", fr_to_uint(w_inv_3)); + println!("\tuint256 constant w_inv_4 = {};", fr_to_uint(w_inv_4)); + + let z = Fr::rand(rng); + let domain_at_z = domain.evaluate(z); + + let z_n = constraints + .mod_exp(fr_to_uint(z), U256::from(123)) + .call() + .await?; + assert_eq!(unit_to_fr(z_n), z.pow([123])); + + let z_inv = constraints.inv(fr_to_uint(z)).call().await?; + assert_eq!(unit_to_fr(z_inv), z.inverse().unwrap()); + + let v_inv_hiding_at = constraints.v_inv_hiding_at(fr_to_uint(z)).call().await?; + assert_eq!( + unit_to_fr(v_inv_hiding_at), + domain_at_z.vanishing_polynomial_inv + ); + + let not_last_row = constraints.not_last_row(fr_to_uint(z)).call().await?; + assert_eq!(unit_to_fr(not_last_row), domain_at_z.not_last_row); + + Ok(()) + } +} diff --git a/evm-vrfier/src/test_kzg.rs b/evm-vrfier/src/test_kzg.rs new file mode 100644 index 0000000..d64a049 --- /dev/null +++ b/evm-vrfier/src/test_kzg.rs @@ -0,0 +1,233 @@ +#[cfg(test)] +mod tests { + use crate::{encode_g1, encode_g2, fr_to_uint, G1Point, G2Point}; + use alloy::primitives::U256; + use ark_bls12_381::{Bls12_381, G1Affine, G2Affine}; + use ark_ec::pairing::Pairing; + use ark_ec::{AffineRepr, PrimeGroup}; + use ark_ff::One; + use ark_std::rand::Rng; + use ark_std::{test_rng, UniformRand}; + use w3f_pcs::aggregation::single::aggregate_polys; + use w3f_pcs::pcs::kzg::params::RawKzgVerifierKey; + use w3f_pcs::pcs::kzg::urs::URS; + use w3f_pcs::pcs::kzg::KZG; + use w3f_pcs::pcs::{PcsParams, PCS}; + use w3f_pcs::DenseUVPolynomial; + use w3f_pcs::Poly; + use w3f_pcs::Polynomial; + + alloy::sol!( + #[sol(rpc)] + Kzg, + "contracts/out/Kzg.t.sol/KzgExt.json" + ); + + impl From for BLS::G1Point { + fn from(p: G1Point) -> Self { + Self { + x_a: p.x_a, + x_b: p.x_b, + y_a: p.y_a, + y_b: p.y_b, + } + } + } + + impl From for BLS::G2Point { + fn from(p: G2Point) -> Self { + Self { + x_c0_a: p.x_c0_a, + x_c0_b: p.x_c0_b, + x_c1_a: p.x_c1_a, + x_c1_b: p.x_c1_b, + y_c0_a: p.y_c0_a, + y_c0_b: p.y_c0_b, + y_c1_a: p.y_c1_a, + y_c1_b: p.y_c1_b, + } + } + } + + struct ArksBatchKzgOpenning { + polys: Vec, + z1: E::ScalarField, + z2: E::ScalarField, + evals_at_z1: Vec, + evals_at_z2: Vec, + kzg_proof_at_z1: E::G1Affine, + kzg_proof_at_z2: E::G1Affine, + } + + struct EthBatchKzgOpenning { + polys: Vec, + z1: U256, + z2: U256, + evals_at_z1: Vec, + evals_at_z2: Vec, + kzg_proof_at_z1: BLS::G1Point, + kzg_proof_at_z2: BLS::G1Point, + } + + impl ArksBatchKzgOpenning { + fn encode(self) -> EthBatchKzgOpenning { + EthBatchKzgOpenning { + polys: self + .polys + .into_iter() + .map(|p| encode_g1(p).into()) + .collect(), + z1: fr_to_uint(self.z1), + z2: fr_to_uint(self.z2), + evals_at_z1: self.evals_at_z1.into_iter().map(fr_to_uint).collect(), + evals_at_z2: self.evals_at_z2.into_iter().map(fr_to_uint).collect(), + kzg_proof_at_z1: encode_g1(self.kzg_proof_at_z1).into(), + kzg_proof_at_z2: encode_g1(self.kzg_proof_at_z2).into(), + } + } + } + + // generates an opening proof for `k` random degree up to `d` polynomials at a random point + // and a subset of the first `l <= k` polynomials at another random point. + fn random_opening( + d: usize, + k: usize, + l: usize, + rng: &mut R, + ) -> ( + ArksBatchKzgOpenning, + Vec, + RawKzgVerifierKey, + ) { + assert!(l <= k); + // KZG setup + let urs = URS::from_trapdoor( + E::ScalarField::rand(rng), + d + 1, + 2, + E::G1::generator(), + E::G2::generator(), + ); + let (ck, rvk) = (urs.ck(), urs.raw_vk()); + + // Polynomials + let polys: Vec> = (0..k) + .map(|_| Poly::::rand(d, rng)) + .collect(); + + // Aggregate polynomial + let nus: Vec = ark_std::iter::once(E::ScalarField::one()) + .chain((0..k - 1).map(|_| E::ScalarField::rand(rng))) + .collect(); + let agg_poly_z1 = aggregate_polys(&polys, &nus); + let agg_poly_z2 = aggregate_polys(&polys[..l], &nus[..l]); + + // Evaluation points + let z1 = E::ScalarField::rand(rng); + let z2 = E::ScalarField::rand(rng); + + // Proofs + let kzg_proof_at_z1 = KZG::::open(&ck, &agg_poly_z1, z1).unwrap(); + let kzg_proof_at_z2 = KZG::::open(&ck, &agg_poly_z2, z2).unwrap(); + + // Evaluations + let evals_at_z1: Vec = polys.iter().map(|p| p.evaluate(&z1)).collect(); + let evals_at_z2: Vec = polys[..l].iter().map(|p| p.evaluate(&z2)).collect(); + + // Commitments + let polys: Vec = polys + .iter() + .map(|p| KZG::::commit(&ck, p).unwrap().0) + .collect(); + + ( + ArksBatchKzgOpenning { + polys, + z1, + z2, + evals_at_z1, + evals_at_z2, + kzg_proof_at_z1, + kzg_proof_at_z2, + }, + nus, + rvk, + ) + } + + #[tokio::test] + async fn test_batch_openning() -> Result<(), Box> { + let provider = alloy::providers::builder() + .with_recommended_fillers() + .on_anvil_with_wallet_and_config(|anvil| anvil.prague())?; + + let (test_openning, nus, kzg_vk) = + random_opening::(123, 2, 1, &mut test_rng()); + let test_openning = test_openning.encode(); + + let plonk_kzg = Kzg::deploy(&provider, encode_g2(kzg_vk.tau_in_g2).into()).await?; + + let res = plonk_kzg + .verify_plonk_kzg( + test_openning.polys, + vec![test_openning.z1, test_openning.z2], + test_openning.evals_at_z1, + test_openning.evals_at_z2, + vec![test_openning.kzg_proof_at_z1, test_openning.kzg_proof_at_z2], + nus.into_iter().map(fr_to_uint).collect(), + ) + .call() + .await?; + assert!(res); + + Ok(()) + } + + #[tokio::test] + async fn test_single_openning() -> Result<(), Box> { + let provider = alloy::providers::builder() + .with_recommended_fillers() + .on_anvil_with_wallet_and_config(|anvil| anvil.prague())?; + + let (test_openning, _, kzg_vk) = random_opening::(123, 1, 0, &mut test_rng()); + let test_openning = test_openning.encode(); + + let plonk_kzg = Kzg::deploy(&provider, encode_g2(kzg_vk.tau_in_g2).into()).await?; + + let res = plonk_kzg + .verify( + test_openning.polys[0].clone(), + test_openning.z1, + test_openning.evals_at_z1[0], + test_openning.kzg_proof_at_z1, + ) + .call() + .await?; + assert!(res); + + Ok(()) + } + + #[tokio::test] + async fn test_pairing() -> Result<(), Box> { + let provider = alloy::providers::builder() + .with_recommended_fillers() + .on_anvil_with_wallet_and_config(|anvil| anvil.prague())?; + + let _tau_g2 = G2Affine::generator(); + let plonk_kzg = Kzg::deploy(&provider, encode_g2(_tau_g2).into()).await?; + + let res = plonk_kzg + .pairing2( + encode_g1(-G1Affine::generator()).into(), + encode_g2(G2Affine::generator()).into(), + encode_g1(G1Affine::generator()).into(), + encode_g2(G2Affine::generator()).into(), + ) + .call() + .await?; + assert!(res); + + Ok(()) + } +} diff --git a/evm-vrfier/src/test_plonk.rs b/evm-vrfier/src/test_plonk.rs new file mode 100644 index 0000000..57691bb --- /dev/null +++ b/evm-vrfier/src/test_plonk.rs @@ -0,0 +1,207 @@ +alloy::sol!( + #[sol(rpc)] + Plonk, + "contracts/out/Plonk.sol/Plonk.json" +); + +#[cfg(test)] +mod tests { + use super::*; + use crate::{encode_g1, encode_g2, fr_to_uint, G1Point, G2Point}; + use alloy::primitives::U256; + use ark_bls12_381::Bls12_381; + use ark_ec::pairing::Pairing; + use ark_ec::twisted_edwards::{Affine, TECurveConfig}; + use ark_ec::PrimeGroup; + use ark_ed_on_bls12_381_bandersnatch::EdwardsConfig; + use ark_ff::{BigInteger, One, PrimeField}; + use ark_std::rand::Rng; + use ark_std::{test_rng, UniformRand}; + use w3f_pcs::aggregation::single::aggregate_polys; + use w3f_pcs::pcs::kzg::params::RawKzgVerifierKey; + use w3f_pcs::pcs::kzg::urs::URS; + use w3f_pcs::pcs::kzg::KZG; + use w3f_pcs::pcs::{PcsParams, PCS}; + + use w3f_pcs::Polynomial; + use w3f_plonk_common::domain::Domain; + use w3f_plonk_common::gadgets::booleanity::BitColumn; + use w3f_plonk_common::gadgets::ec::te_doubling::Doubling; + use w3f_plonk_common::gadgets::ec::{AffineColumn, CondAdd}; + use w3f_plonk_common::gadgets::ProverGadget; + use w3f_plonk_common::prover::aggregate_evaluations; + use w3f_plonk_common::Column; + + impl From for BLS::G1Point { + fn from(p: G1Point) -> Self { + Self { + x_a: p.x_a, + x_b: p.x_b, + y_a: p.y_a, + y_b: p.y_b, + } + } + } + + impl From for BLS::G2Point { + fn from(p: G2Point) -> Self { + Self { + x_c0_a: p.x_c0_a, + x_c0_b: p.x_c0_b, + x_c1_a: p.x_c1_a, + x_c1_b: p.x_c1_b, + y_c0_a: p.y_c0_a, + y_c0_b: p.y_c0_b, + y_c1_a: p.y_c1_a, + y_c1_b: p.y_c1_b, + } + } + } + + struct ArkProof { + columns: Vec, + quotient: E::G1Affine, + z: E::ScalarField, + columns_at_z: Vec, + columns_at_zw: Vec, + kzg_proof_at_z: E::G1Affine, + kzg_proof_at_zw: E::G1Affine, + } + + struct EthProof { + columns: Vec, + quotient: BLS::G1Point, + z: U256, + columns_at_z: Vec, + columns_at_zw: Vec, + kzg_proof_at_z: BLS::G1Point, + kzg_proof_at_zw: BLS::G1Point, + } + + impl ArkProof { + fn encode(self) -> EthProof { + EthProof { + columns: self + .columns + .into_iter() + .map(|p| encode_g1(p).into()) + .collect(), + quotient: encode_g1(self.quotient).into(), + z: fr_to_uint(self.z), + columns_at_z: self.columns_at_z.into_iter().map(fr_to_uint).collect(), + columns_at_zw: self.columns_at_zw.into_iter().map(fr_to_uint).collect(), + kzg_proof_at_z: encode_g1(self.kzg_proof_at_z).into(), + kzg_proof_at_zw: encode_g1(self.kzg_proof_at_zw).into(), + } + } + } + + fn produce_proof, R: Rng>( + rng: &mut R, + ) -> (ArkProof, RawKzgVerifierKey, Vec) { + let n = 256; + let domain = Domain::new(n, true); + + // KZG setup + let urs = URS::from_trapdoor( + E::ScalarField::rand(rng), + 3 * n + 1, + 2, + E::G1::generator(), + E::G2::generator(), + ); + let (ck, rvk) = (urs.ck(), urs.raw_vk()); + + // scalar.POINT + let scalar = Jubjub::ScalarField::rand(rng); + let scalar_bits = &scalar.into_bigint().to_bits_le()[..252]; + let scalar_bits = BitColumn::init(scalar_bits.to_vec(), &domain); + let point = Affine::::rand(rng); + let doublings = Doubling::doublings_of(point, &domain); + let doublings = AffineColumn::public_column(doublings, &domain); + let seed = Affine::::rand(rng); + let cond_add = CondAdd::init(scalar_bits.clone(), doublings.clone(), seed, &domain); + + let column_polys = vec![ + cond_add.acc.xs.as_poly().clone(), + cond_add.acc.ys.as_poly().clone(), + doublings.xs.as_poly().clone(), + doublings.ys.as_poly().clone(), + scalar_bits.as_poly().clone(), + ]; + + let column_commitments: Vec = column_polys + .iter() + .map(|p| KZG::::commit(&ck, p).unwrap().0) + .collect(); + + // TODO: sample alphas (coeffs to agg constraints) + let constraints = cond_add.constraints(); + let agg_constraint = aggregate_evaluations( + &constraints, + &[E::ScalarField::one(), E::ScalarField::one()], + ); + let quotient_poly = domain.divide_by_vanishing_poly(&agg_constraint.interpolate()); + let quotient_commitment = KZG::::commit(&ck, "ient_poly).unwrap().0; + + // sample zeta + let z = E::ScalarField::rand(rng); + let zw = z * domain.omega(); + let columns_at_z = column_polys.iter().map(|p| p.evaluate(&z)).collect(); + let columns_at_zw = column_polys[..2].iter().map(|p| p.evaluate(&zw)).collect(); + + // sample nus + let mut polys_at_z = column_polys.clone(); + polys_at_z.push(quotient_poly); + let nus: Vec = (0..polys_at_z.len()) + .map(|_| E::ScalarField::rand(rng)) + .collect(); + let agg_poly_z = aggregate_polys(&polys_at_z, &nus); + let agg_columns_zw = aggregate_polys(&column_polys[..2], &nus[..2]); + + let kzg_proof_at_z = KZG::::open(&ck, &agg_poly_z, z).unwrap(); + let kzg_proof_at_zw = KZG::::open(&ck, &agg_columns_zw, zw).unwrap(); + + let proof = ArkProof { + columns: column_commitments, + quotient: quotient_commitment, + z, + columns_at_z, + columns_at_zw, + kzg_proof_at_z, + kzg_proof_at_zw, + }; + + (proof, rvk, nus) + } + + #[tokio::test] + async fn verify_proof() -> Result<(), Box> { + let provider = alloy::providers::builder() + .with_recommended_fillers() + .on_anvil_with_wallet_and_config(|anvil| anvil.prague())?; + + let (test_proof, kzg_vk, nus) = + produce_proof::(&mut test_rng()); + let test_proof = test_proof.encode(); + + let plonk = Plonk::deploy(&provider, encode_g2(kzg_vk.tau_in_g2).into()).await?; + + let res = plonk + .verify_proof( + test_proof.columns, + test_proof.quotient, + test_proof.z, + test_proof.columns_at_z, + test_proof.columns_at_zw, + test_proof.kzg_proof_at_z, + test_proof.kzg_proof_at_zw, + nus.into_iter().map(fr_to_uint).collect(), + ) + .call() + .await?; + assert!(res); + + Ok(()) + } +} diff --git a/w3f-plonk-common/src/domain.rs b/w3f-plonk-common/src/domain.rs index 42241dc..5698126 100644 --- a/w3f-plonk-common/src/domain.rs +++ b/w3f-plonk-common/src/domain.rs @@ -95,7 +95,7 @@ impl Domain { } } - pub(crate) fn divide_by_vanishing_poly(&self, poly: &DensePolynomial) -> DensePolynomial { + pub fn divide_by_vanishing_poly(&self, poly: &DensePolynomial) -> DensePolynomial { let (quotient, remainder) = if self.hiding { let exclude_zk_rows = poly * self.zk_rows_vanishing_poly.as_ref().unwrap(); exclude_zk_rows.divide_by_vanishing_poly(self.domains.x1) @@ -133,6 +133,10 @@ impl Domain { self.domains.x1.group_gen() } + pub fn omega_inv(&self) -> F { + self.domains.x1.group_gen_inv() + } + pub fn domain(&self) -> GeneralEvaluationDomain { self.domains.x1 } diff --git a/w3f-plonk-common/src/gadgets/ec/mod.rs b/w3f-plonk-common/src/gadgets/ec/mod.rs index 048548c..ed8e26d 100644 --- a/w3f-plonk-common/src/gadgets/ec/mod.rs +++ b/w3f-plonk-common/src/gadgets/ec/mod.rs @@ -117,3 +117,12 @@ pub struct CondAddValues> { pub acc: (F, F), pub _phantom: PhantomData

, } + +pub struct CondAddEvals> { + pub bitmask_at_z: F, + pub points_at_z: (F, F), + pub not_last_at_z: F, + pub acc_at_z: (F, F), + pub acc_at_zw: (F, F), + pub _phantom: PhantomData

, +} diff --git a/w3f-plonk-common/src/gadgets/ec/te_cond_add.rs b/w3f-plonk-common/src/gadgets/ec/te_cond_add.rs index 9eb327f..6141b44 100644 --- a/w3f-plonk-common/src/gadgets/ec/te_cond_add.rs +++ b/w3f-plonk-common/src/gadgets/ec/te_cond_add.rs @@ -1,13 +1,90 @@ use ark_ec::twisted_edwards::{Affine, TECurveConfig}; use ark_ff::{FftField, Field}; use ark_poly::univariate::DensePolynomial; -use ark_poly::{Evaluations, GeneralEvaluationDomain}; +use ark_poly::{EvaluationDomain, Evaluations, GeneralEvaluationDomain}; +use ark_std::ops::{AddAssign, MulAssign, SubAssign}; use ark_std::{vec, vec::Vec}; -use crate::gadgets::ec::{CondAdd, CondAddValues}; +use crate::gadgets::ec::{CondAdd, CondAddEvals, CondAddValues}; use crate::gadgets::{ProverGadget, VerifierGadget}; use crate::{const_evals, Column}; +/// These constraints are deduced from +/// "Affine addition formulae (independent of d) for twisted Edwards curves", +/// see e.g. formula (3) in https://eprint.iacr.org/2008/522.pdf. +/// Works for distinct prime-order points. +/// `cx = {[(a.x1.x2 + y1.y2).x3 - x1.y1 - x2.y2].b + (x3 - x1).(1 - b)}.not_last` +/// `cy = {[(x1.y2 - x2.y1).y3 - x1.y1 + x2.y2].b + (y3 - y1).(1 - b)}.not_last` +// Where: +/// `(x3, y3) = (x1, y1) + (x2, y2)`, if `b = 1`, and +/// `(x3, y3) = (x1, y1)` otherwise. +pub fn cond_te_addition( + te_coeff_a: F, + b: &F, + x1: &F, + y1: &F, + x2: &F, + y2: &F, + x3: F, + y3: F, + not_last: &F, + one: F, +) -> Vec +where + F: Clone, + F: for<'a> AddAssign<&'a F>, + F: for<'a> SubAssign<&'a F>, + F: for<'a> MulAssign<&'a F>, +{ + let mut x1y1 = x1.clone(); + x1y1 *= y1; + let mut x2y2 = x2.clone(); + x2y2 *= y2; + let mut y1y2 = y1.clone(); + y1y2 *= y2; + let mut x2y1 = x2.clone(); + x2y1 *= y1; + + // lx = [(a.x1.x2 + y1.y2).x3 - x1.y1 - x2.y2].b + let mut lx = te_coeff_a; + lx *= x1; + lx *= x2; + lx += &y1y2; + lx *= &x3; + lx -= &x1y1; + lx -= &x2y2; + lx *= b; + + // ly = [(x1.y2 - x2.y1).y3 - x1.y1 + x2.y2].b + let mut ly = x1.clone(); + ly *= y2; + ly -= &x2y1; + ly *= &y3; + ly -= &x1y1; + ly += &x2y2; + ly *= b; + + // rx = (x3 - x1).(1 - b) + // ry = (y3 - y1).(1 - b) + let mut one_minus_b = one; + one_minus_b -= b; + let mut rx = x3; + rx -= x1; + rx *= &one_minus_b; + let mut ry = y3; + ry -= y1; + ry *= &one_minus_b; + + // cx = {lx + rx}.not_last + // cy = {ly + ry}.not_last + lx += ℞ + lx *= ¬_last; + ly += &ry; + ly *= ¬_last; + + vec![lx, ly] +} + impl ProverGadget for CondAdd> where F: FftField, @@ -20,59 +97,13 @@ where fn constraints(&self) -> Vec> { let domain = self.bitmask.domain_4x(); let b = &self.bitmask.col.evals_4x; - let one = &const_evals(F::one(), domain); - let te_a_coeff = &const_evals(Curve::COEFF_A, domain); + let one = const_evals(F::one(), domain); + let te_coeff_a = const_evals(Curve::COEFF_A, domain); + let not_last = &self.not_last.evals_4x; let (x1, y1) = (&self.acc.xs.evals_4x, &self.acc.ys.evals_4x); let (x2, y2) = (&self.points.xs.evals_4x, &self.points.ys.evals_4x); - let (x3, y3) = (&self.acc.xs.shifted_4x(), &self.acc.ys.shifted_4x()); - - //b (x_3 (y_1 y_2 + ax_1 x_2) - x_1 y_1 - y_2 x_2) + (1 - b) (x_3 - x_1) = 0 - #[rustfmt::skip] - let mut c1 = - &( - b * - &( - &( - x3 * - &( - &(y1 * y2) + - - &(te_a_coeff * - &(x1 * x2) - ) - ) - ) - - - &( - &(x1 * y1) + &(y2* x2) - ) - ) - ) + - &( - &(one - b) * &(x3 - x1) - ); - - //b (y_3 (x_1 y_2 - x_2 y_1) - x_1 y_1 + x_2 y_2) + (1 - b) (y_3 - y_1) = 0 - #[rustfmt::skip] - let mut c2 = - &( - b * - &( &(y3 * - &( - &(x1 * y2) - &(x2 * y1))) - - &(&(x1 * y1) - &(x2 * y2)) - ) - ) - + - &( - &(one - b) * &(y3 - y1) - ); - - let not_last = &self.not_last.evals_4x; - c1 *= not_last; - c2 *= not_last; - - vec![c1, c2] + let (x3, y3) = (self.acc.xs.shifted_4x(), self.acc.ys.shifted_4x()); + cond_te_addition(te_coeff_a, b, x1, y1, x2, y2, x3, y3, not_last, one) } /// Mary-Oana Linearization technique. See: https://hackmd.io/0kdBl3GVSmmcB7QJe1NTuw?view#Linearization @@ -95,6 +126,41 @@ where } } +impl CondAdd> +where + F: FftField, + Curve: TECurveConfig, +{ + pub fn evaluate_at_z_and_zw(&self, zeta: &F) -> CondAddEvals> { + let zeta_omega = self.bitmask.domain().group_gen() * zeta; + CondAddEvals { + bitmask_at_z: self.bitmask.evaluate(zeta), + points_at_z: self.points.evaluate(zeta), + not_last_at_z: self.not_last.evaluate(zeta), + acc_at_z: self.acc.evaluate(zeta), + acc_at_zw: self.acc.evaluate(&zeta_omega), + _phantom: Default::default(), + } + } +} + +impl> CondAddEvals> { + pub fn constraints_at_z(&self) -> Vec { + cond_te_addition( + C::COEFF_A, + &self.bitmask_at_z, + &self.acc_at_z.0, + &self.acc_at_z.1, + &self.points_at_z.0, + &self.points_at_z.1, + self.acc_at_zw.0, + self.acc_at_zw.1, + &self.not_last_at_z, + F::one(), + ) + } +} + impl> CondAddValues> { pub fn acc_coeffs_1(&self) -> (F, F) { let b = self.bitmask; @@ -155,10 +221,10 @@ mod tests { use crate::gadgets::ec::BitColumn; use crate::gadgets::ec::Domain; use ark_ec::AffineRepr; - use ark_ed_on_bls12_381_bandersnatch::EdwardsAffine; + use ark_ed_on_bls12_381_bandersnatch::{EdwardsAffine, Fq}; use ark_poly::Polynomial; - use ark_std::test_rng; + use ark_std::{test_rng, UniformRand}; use crate::test_helpers::cond_sum; use crate::test_helpers::*; @@ -184,16 +250,26 @@ mod tests { assert_eq!(res, &expected_res); let cs = gadget.constraints(); - let (c1, c2) = (&cs[0], &cs[1]); - let c1 = c1.interpolate_by_ref(); - let c2 = c2.interpolate_by_ref(); + let c1 = cs[0].interpolate_by_ref(); + let c2 = cs[1].interpolate_by_ref(); assert_eq!(c1.degree(), 4 * n - 3); assert_eq!(c2.degree(), 4 * n - 3); - domain.divide_by_vanishing_poly(&c1); - domain.divide_by_vanishing_poly(&c2); - - // test_gadget(gadget); + let q1 = domain.divide_by_vanishing_poly(&c1); + let q2 = domain.divide_by_vanishing_poly(&c2); + + let zeta = Fq::rand(rng); + let domain_at_z = domain.evaluate(zeta); + let evals = gadget.evaluate_at_z_and_zw(&zeta); + let cs_at_z = evals.constraints_at_z(); + assert_eq!( + domain_at_z.divide_by_vanishing_poly_in_zeta(cs_at_z[0]), + q1.evaluate(&zeta) + ); + assert_eq!( + domain_at_z.divide_by_vanishing_poly_in_zeta(cs_at_z[1]), + q2.evaluate(&zeta) + ); } #[test] diff --git a/w3f-plonk-common/src/prover.rs b/w3f-plonk-common/src/prover.rs index 6dc96be..c50d463 100644 --- a/w3f-plonk-common/src/prover.rs +++ b/w3f-plonk-common/src/prover.rs @@ -1,4 +1,4 @@ -use ark_ff::PrimeField; +use ark_ff::{FftField, PrimeField}; use ark_poly::{Evaluations, Polynomial}; use ark_serialize::CanonicalSerialize; use ark_std::vec; @@ -47,7 +47,7 @@ impl, T: PlonkTranscript> PlonkProver let constraint_polys = piop.constraints(); let alphas = transcript.get_constraints_aggregation_coeffs(constraint_polys.len()); // Aggregate constraint polynomials in evaluation form... - let agg_constraint_poly = Self::aggregate_evaluations(&constraint_polys, &alphas); + let agg_constraint_poly = aggregate_evaluations(&constraint_polys, &alphas); // ...and then interpolate (to save some FFTs). let agg_constraint_poly = agg_constraint_poly.interpolate(); let quotient_poly = piop.domain().divide_by_vanishing_poly(&agg_constraint_poly); @@ -82,14 +82,17 @@ impl, T: PlonkTranscript> PlonkProver lin_at_zeta_omega_proof, } } +} - fn aggregate_evaluations(polys: &[Evaluations], coeffs: &[F]) -> Evaluations { - assert_eq!(coeffs.len(), polys.len()); - polys - .iter() - .zip(coeffs.iter()) - .map(|(p, &c)| p * c) - .reduce(|acc, p| &acc + &p) - .unwrap() - } +pub fn aggregate_evaluations( + polys: &[Evaluations], + coeffs: &[F], +) -> Evaluations { + assert_eq!(coeffs.len(), polys.len()); + polys + .iter() + .zip(coeffs.iter()) + .map(|(p, &c)| p * c) + .reduce(|acc, p| &acc + &p) + .unwrap() }