diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index e13033c..a96349b 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -43,7 +43,7 @@ jobs: profile: minimal toolchain: stable - name: Build - run: cargo build --verbose + run: cargo build --verbose --workspace --exclude evm-vrfier build-wasm32: runs-on: ubuntu-latest diff --git a/evm-vrfier/Cargo.toml b/evm-vrfier/Cargo.toml index ee5274c..5511e84 100644 --- a/evm-vrfier/Cargo.toml +++ b/evm-vrfier/Cargo.toml @@ -4,8 +4,16 @@ version = "0.1.0" edition = "2021" [dependencies] -alloy = { version = "0.13", default-features = false, features = ["contract", "provider-anvil-node"] } +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"] } + +[dev-dependencies] tokio = { version = "1.44", default-features = false } +ark-std = { workspace = true } +ark-ec = { workspace = true } +w3f-pcs = { workspace = true } [build-dependencies] -foundry-config = { git="https://github.com/foundry-rs/foundry" } \ No newline at end of file +foundry-config = { git = "https://github.com/foundry-rs/foundry" } +foundry-compilers = { version = "0.14.0" } \ No newline at end of file diff --git a/evm-vrfier/contracts/foundry.toml b/evm-vrfier/contracts/foundry.toml index 25b918f..a40938d 100644 --- a/evm-vrfier/contracts/foundry.toml +++ b/evm-vrfier/contracts/foundry.toml @@ -3,4 +3,7 @@ src = "src" out = "out" libs = ["lib"] +evm_version = "prague" +via_ir = true + # 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/BlsGenerators.sol b/evm-vrfier/contracts/src/BlsGenerators.sol new file mode 100644 index 0000000..7de1818 --- /dev/null +++ b/evm-vrfier/contracts/src/BlsGenerators.sol @@ -0,0 +1,66 @@ +pragma solidity ^0.8.24; + +import "./SoladyBls.sol"; + +library BlsGenerators { + uint256 constant q = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001; + + function add_fr(uint256 a, uint256 b) internal pure returns (uint256 c) { + c = addmod(a, b, q); + } + + function mul_fr(uint256 a, uint256 b) internal pure returns (uint256 c) { + c = mulmod(a, b, q); + } + + function G1() internal pure returns (BLS.G1Point memory) { + return BLS.G1Point( + bytes32(uint256(31827880280837800241567138048534752271)), + bytes32(uint256(88385725958748408079899006800036250932223001591707578097800747617502997169851)), + bytes32(uint256(11568204302792691131076548377920244452)), + bytes32(uint256(114417265404584670498511149331300188430316142484413708742216858159411894806497)) + ); + } + + function G2() internal pure returns (BLS.G2Point memory) { + return BLS.G2Point( + bytes32(uint256(3045985886519456750490515843806728273)), + bytes32(uint256(89961632905173714226157479458612185649920463576279427516307505038263245192632)), + bytes32(uint256(26419286191256893424348605754143887205)), + bytes32(uint256(40446337346877272185227670183527379362551741423616556919902061939448715946878)), + bytes32(uint256(17144095208600308495026432580569150746)), + bytes32(uint256(78698209480990513415779284580404715789311803115477290401294577488850054555649)), + bytes32(uint256(8010509799087472606393075511737879449)), + bytes32(uint256(91929332261301883145239454754739358796115486554644188311284324629555800144318)) + ); + } + + function G2_NEG() internal pure returns (BLS.G2Point memory) { + return BLS.G2Point( + bytes32(uint256(3045985886519456750490515843806728273)), + bytes32(uint256(89961632905173714226157479458612185649920463576279427516307505038263245192632)), + bytes32(uint256(26419286191256893424348605754143887205)), + bytes32(uint256(40446337346877272185227670183527379362551741423616556919902061939448715946878)), + bytes32(uint256(17421388336814597573762763446246275004)), + bytes32(uint256(82535940630695547964844822885348920226556672312706312698172214783216175252138)), + bytes32(uint256(26554973746327433462396120515077546301)), + bytes32(uint256(69304817850384178235384652711014277219752988873539414788182467642510429663469)) + ); + } + + function g1_mul(BLS.G1Point memory point, bytes32 scalar) internal view returns (BLS.G1Point memory) { + BLS.G1Point[] memory points = new BLS.G1Point[](1); + bytes32[] memory scalars = new bytes32[](1); + points[0] = point; + scalars[0] = scalar; + return BLS.msm(points, scalars); + } + + function g2_mul(BLS.G2Point memory point, bytes32 scalar) internal view returns (BLS.G2Point memory) { + BLS.G2Point[] memory points = new BLS.G2Point[](1); + bytes32[] memory scalars = new bytes32[](1); + points[0] = point; + scalars[0] = scalar; + return BLS.msm(points, scalars); + } +} diff --git a/evm-vrfier/contracts/src/PlonkKzg.sol b/evm-vrfier/contracts/src/PlonkKzg.sol new file mode 100644 index 0000000..f9d6761 --- /dev/null +++ b/evm-vrfier/contracts/src/PlonkKzg.sol @@ -0,0 +1,93 @@ +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 new file mode 100644 index 0000000..4b6aa4c --- /dev/null +++ b/evm-vrfier/contracts/src/SoladyBls.sol @@ -0,0 +1,310 @@ +// Thanks! https://github.com/Vectorized/solady/blob/dcdfab80f4e6cb9ac35c91610b2a2ec42689ec79/src/utils/ext/ithaca/BLS.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +/// @notice BLS wrapper. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/BLS.sol) +/// @author Ithaca (https://github.com/ithacaxyz/odyssey-examples/blob/main/chapter1/contracts/src/libraries/BLS.sol) +/// +/// @dev Precompile addresses come from the BLS addresses submodule in AlphaNet, see +/// See: (https://github.com/paradigmxyz/alphanet/blob/main/crates/precompile/src/addresses.rs) +/// +/// Note: +/// - This implementation uses `mcopy`, since any chain that is edgy enough to +/// implement the BLS precompiles will definitely have implemented cancun. +/// - For efficiency, we use the legacy `staticcall` to call the precompiles. +/// For the intended use case in an entry points that requires gas-introspection, +/// which requires legacy bytecode, this won't be a blocker. +library BLS { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* STRUCTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // We use flattened structs to make encoding more efficient. + // All structs use Big endian encoding. + // See: https://eips.ethereum.org/EIPS/eip-2537 + + /// @dev A representation of a base field element (Fp) in the BLS12-381 curve. + /// Due to the size of `p`, + /// `0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab` + /// the top 16 bytes are always zeroes. + struct Fp { + bytes32 a; // Upper 32 bytes. + bytes32 b; // Lower 32 bytes. + } + + /// @dev A representation of an extension field element (Fp2) in the BLS12-381 curve. + struct Fp2 { + bytes32 c0_a; + bytes32 c0_b; + bytes32 c1_a; + bytes32 c1_b; + } + + /// @dev A representation of a point on the G1 curve of BLS12-381. + struct G1Point { + bytes32 x_a; + bytes32 x_b; + bytes32 y_a; + bytes32 y_b; + } + + /// @dev A representation of a point on the G2 curve of BLS12-381. + 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; + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PRECOMPILE ADDRESSES */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev For addition of two points on the BLS12-381 G1 curve, + address internal constant BLS12_G1ADD = 0x000000000000000000000000000000000000000b; + + /// @dev For multi-scalar multiplication (MSM) on the BLS12-381 G1 curve. + address internal constant BLS12_G1MSM = 0x000000000000000000000000000000000000000C; + + /// @dev For addition of two points on the BLS12-381 G2 curve. + address internal constant BLS12_G2ADD = 0x000000000000000000000000000000000000000d; + + /// @dev For multi-scalar multiplication (MSM) on the BLS12-381 G2 curve. + address internal constant BLS12_G2MSM = 0x000000000000000000000000000000000000000E; + + /// @dev For performing a pairing check on the BLS12-381 curve. + address internal constant BLS12_PAIRING_CHECK = 0x000000000000000000000000000000000000000F; + + /// @dev For mapping a Fp to a point on the BLS12-381 G1 curve. + address internal constant BLS12_MAP_FP_TO_G1 = 0x0000000000000000000000000000000000000010; + + /// @dev For mapping a Fp2 to a point on the BLS12-381 G2 curve. + address internal constant BLS12_MAP_FP2_TO_G2 = 0x0000000000000000000000000000000000000011; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // A custom error for each precompile helps us in debugging which precompile has failed. + + /// @dev The G1Add operation failed. + error G1AddFailed(); + + /// @dev The G1MSM operation failed. + error G1MSMFailed(); + + /// @dev The G2Add operation failed. + error G2AddFailed(); + + /// @dev The G2MSM operation failed. + error G2MSMFailed(); + + /// @dev The pairing operation failed. + error PairingFailed(); + + /// @dev The MapFpToG1 operation failed. + error MapFpToG1Failed(); + + /// @dev The MapFpToG2 operation failed. + error MapFp2ToG2Failed(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Adds two G1 points. Returns a new G1 point. + 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))) { + mstore(0x00, 0xd6cc76eb) // `G1AddFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @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) { + assembly ("memory-safe") { + let k := mload(points) + let d := sub(scalars, points) + for { let i := 0 } iszero(eq(i, k)) { i := add(i, 1) } { + points := add(points, 0x20) + let o := add(result, mul(0xa0, i)) + mcopy(o, mload(points), 0x80) + mstore(add(o, 0x80), mload(add(points, d))) + } + if iszero( + and( + and(eq(k, mload(scalars)), eq(returndatasize(), 0x80)), + staticcall(gas(), BLS12_G1MSM, result, mul(0xa0, k), result, 0x80) + ) + ) { + mstore(0x00, 0x5f776986) // `G1MSMFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Adds two G2 points. Returns a new G2 point. + 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))) { + mstore(0x00, 0xc55e5e33) // `G2AddFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @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) { + assembly ("memory-safe") { + let k := mload(points) + let d := sub(scalars, points) + for { let i := 0 } iszero(eq(i, k)) { i := add(i, 1) } { + points := add(points, 0x20) + let o := add(result, mul(0x120, i)) + mcopy(o, mload(points), 0x100) + mstore(add(o, 0x100), mload(add(d, points))) + } + if iszero( + and( + and(eq(k, mload(scalars)), eq(returndatasize(), 0x100)), + staticcall(gas(), BLS12_G2MSM, result, mul(0x120, k), result, 0x100) + ) + ) { + mstore(0x00, 0xe3dc5425) // `G2MSMFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @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) { + assembly ("memory-safe") { + let k := mload(g1Points) + let m := mload(0x40) + let d := sub(g2Points, g1Points) + for { let i := 0 } iszero(eq(i, k)) { i := add(i, 1) } { + g1Points := add(g1Points, 0x20) + let o := add(m, mul(0x180, i)) + mcopy(o, mload(g1Points), 0x80) + mcopy(add(o, 0x80), mload(add(d, g1Points)), 0x100) + } + if iszero( + and( + and(eq(k, mload(g2Points)), eq(returndatasize(), 0x20)), + staticcall(gas(), BLS12_PAIRING_CHECK, m, mul(0x180, k), 0x00, 0x20) + ) + ) { + mstore(0x00, 0x4df45e2f) // `PairingFailed()`. + revert(0x1c, 0x04) + } + result := mload(0x00) + } + } + + /// @dev Maps a Fp element to a G1 point. + 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)) + ) { + mstore(0x00, 0x24a289fc) // `MapFpToG1Failed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Maps a Fp2 element to a G2 point. + 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)) + ) { + mstore(0x00, 0x89083b91) // `MapFp2ToG2Failed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Computes a point in G2 from a message. + function hashToG2(bytes memory message) internal view returns (G2Point memory result) { + assembly ("memory-safe") { + function dstPrime(o_, i_) -> _o { + mstore8(o_, i_) // 1. + mstore(add(o_, 0x01), "BLS_SIG_BLS12381G2_XMD:SHA-256_S") // 32. + mstore(add(o_, 0x21), "SWU_RO_NUL_\x2b") // 12. + _o := add(0x2d, o_) + } + + function sha2(data_, n_) -> _h { + 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) + } + } + + function mapToG2(s_, r_) { + if iszero(and(eq(returndatasize(), 0x100), staticcall(gas(), BLS12_MAP_FP2_TO_G2, s_, 0x80, r_, 0x100))) + { + mstore(0x00, 0x89083b91) // `MapFp2ToG2Failed()`. + revert(0x1c, 0x04) + } + } + + let b := mload(0x40) + let s := add(b, 0x100) + calldatacopy(s, calldatasize(), 0x40) + mcopy(add(0x40, s), add(0x20, message), mload(message)) + let o := add(add(0x40, s), mload(message)) + mstore(o, shl(240, 256)) + let b0 := sha2(s, sub(dstPrime(add(0x02, o), 0), s)) + mstore(0x20, b0) + mstore(s, b0) + mstore(b, sha2(s, sub(dstPrime(add(0x20, s), 1), s))) + let j := b + for { let i := 2 } 1 {} { + mstore(s, xor(b0, mload(j))) + j := add(j, 0x20) + mstore(j, sha2(s, sub(dstPrime(add(0x20, s), i), s))) + i := add(i, 1) + if eq(i, 9) { break } + } + + mstore(add(s, 0x00), 0x40) + mstore(add(s, 0x20), 0x20) + mstore(add(s, 0x40), 0x40) + mstore(add(s, 0xa0), 1) + mstore(add(s, 0xc0), 0x000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd7) + mstore(add(s, 0xe0), 0x64774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab) + modfield(s, add(b, 0x00)) + modfield(s, add(b, 0x40)) + modfield(s, add(b, 0x80)) + modfield(s, add(b, 0xc0)) + + mapToG2(b, result) + mapToG2(add(0x80, b), add(0x100, result)) + + if iszero(and(eq(returndatasize(), 0x100), staticcall(gas(), BLS12_G2ADD, result, 0x200, result, 0x100))) { + mstore(0x00, 0xc55e5e33) // `G2AddFailed()`. + revert(0x1c, 0x04) + } + } + } +} diff --git a/evm-vrfier/contracts/test/PlonkKzg.t.sol b/evm-vrfier/contracts/test/PlonkKzg.t.sol new file mode 100644 index 0000000..60576de --- /dev/null +++ b/evm-vrfier/contracts/test/PlonkKzg.t.sol @@ -0,0 +1,118 @@ +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"; + +contract PlonkKzgTest is Test { + PlonkKzg kzg; + BLS.G1Point[] srs_g1; + BLS.G2Point tau_g2; + + function setUp() public { + uint256 n = 3; + bytes32 tau = bytes32(uint256(123)); + srs_g1.push(BlsGenerators.G1()); + 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); + } + + function commit(bytes32[] memory coeffs) internal view returns (BLS.G1Point memory c) { + assert(coeffs.length == srs_g1.length); + c = BLS.msm(srs_g1, coeffs); + } + + function div_by_x_minus_z(bytes32[] memory coeffs, uint256 z) internal pure returns (bytes32[] memory res_coeffs) { + uint256 l = coeffs.length; + res_coeffs = new bytes32[](l); + res_coeffs[l - 1] = 0; + res_coeffs[l - 2] = bytes32(coeffs[l - 1]); + for (uint256 j = l - 2; j > 0; j--) { + res_coeffs[j - 1] = + bytes32(BlsGenerators.add_fr(BlsGenerators.mul_fr(uint256(res_coeffs[j]), z), uint256(coeffs[j]))); + } + } + + function prove(bytes32[] memory coeffs, uint256 z) internal view returns (BLS.G1Point memory proof) { + bytes32[] memory q = div_by_x_minus_z(coeffs, z); + proof = commit(q); + } + + function eval(bytes32[] memory coeffs, uint256 z) internal pure returns (uint256 v) { + uint256 l = coeffs.length; + v = uint256(coeffs[l - 1]); + for (uint256 j = l - 1; j > 0; j--) { + v = BlsGenerators.add_fr(BlsGenerators.mul_fr(v, z), uint256(coeffs[j - 1])); + } + } + + 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); + for (uint256 i = 0; i < poly.length; i++) { + poly[i] = bytes32(i + 1); + } + bytes32[] memory res = div_by_x_minus_z(poly, 1); + // 3x + 5 = (3x^2 + 2x + 1) // (x - 1) = [5, 3, 0] + assertEq(res.length, 3); + assertEq(res[0], bytes32(uint256(5))); + assertEq(res[1], bytes32(uint256(3))); + assertEq(res[2], bytes32(uint256(0))); + } + + function test_kzg() public view { + bytes32[] memory poly = new bytes32[](3); + for (uint256 i = 0; i < poly.length; i++) { + poly[i] = bytes32(i + 10); + } + BLS.G1Point memory c = commit(poly); + uint256 z = 777; + BLS.G1Point memory proof = prove(poly, z); + uint256 v = eval(poly, z); + assert(kzg.verify(c, z, v, proof)); + } + + function test_plonk_kzg() public view { + bytes32[] memory poly = new bytes32[](3); + for (uint256 i = 0; i < poly.length; i++) { + poly[i] = bytes32(i + 10); + } + BLS.G1Point memory c = commit(poly); + uint256 z1 = 666; + uint256 z2 = 777; + 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)); + + BLS.G1Point[] memory polys_z1 = new BLS.G1Point[](1); + uint256[] memory evals_at_z1 = new uint256[](1); + polys_z1[0] = c; + 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); + 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)); + } +} diff --git a/evm-vrfier/contracts/test/SoladyBls.t.sol b/evm-vrfier/contracts/test/SoladyBls.t.sol new file mode 100644 index 0000000..f830c94 --- /dev/null +++ b/evm-vrfier/contracts/test/SoladyBls.t.sol @@ -0,0 +1,17 @@ +pragma solidity ^0.8.24; + +import {Test, console} from "forge-std/Test.sol"; +import "../src/SoladyBls.sol"; +import "../src/BlsGenerators.sol"; + +contract SoladyBlsTest is Test { + function test_pairing() public view { + BLS.G1Point[] memory g1_points = new BLS.G1Point[](2); + BLS.G2Point[] memory g2_points = new BLS.G2Point[](2); + g1_points[0] = BlsGenerators.G1(); + g2_points[0] = BlsGenerators.G2_NEG(); + g1_points[1] = BlsGenerators.G1(); + g2_points[1] = BlsGenerators.G2(); + assertEq(BLS.pairing(g1_points, g2_points), true); + } +} diff --git a/evm-vrfier/src/lib.rs b/evm-vrfier/src/lib.rs index 4d0fd20..07d116b 100644 --- a/evm-vrfier/src/lib.rs +++ b/evm-vrfier/src/lib.rs @@ -1,3 +1,5 @@ +pub mod plonk_kzg; + #[cfg(test)] mod tests { use alloy::primitives::Uint; @@ -14,11 +16,11 @@ mod tests { .with_recommended_fillers() .on_anvil_with_wallet(); - let counter = crate::tests::Counter::deploy(&provider).await?; + let counter = Counter::deploy(&provider).await?; - let number = counter.number().call().await?._0; + let number = counter.number().call().await?; let _ = counter.increment().send().await?; - assert_eq!(counter.number().call().await?._0, number + Uint::from(1)); + assert_eq!(counter.number().call().await?, number + Uint::from(1)); Ok(()) } diff --git a/evm-vrfier/src/plonk_kzg.rs b/evm-vrfier/src/plonk_kzg.rs new file mode 100644 index 0000000..84ed236 --- /dev/null +++ b/evm-vrfier/src/plonk_kzg.rs @@ -0,0 +1,257 @@ +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(()) + } +}