diff --git a/contracts/algebra/bn254.sol b/contracts/algebra/bn254.sol index fd87c92..f35e0a3 100644 --- a/contracts/algebra/bn254.sol +++ b/contracts/algebra/bn254.sol @@ -93,8 +93,59 @@ library bn254_crypto { }); } + // ECADD + function ecadd(types.g1_point memory a, types.g1_point memory b) + internal view returns(types.g1_point memory res) + { + validateG1Point(a); + validateG1Point(b); + bool success; + + assembly { + let mPtr := mload(0x40) + mstore(mPtr, mload(a)) + mstore(add(mPtr, 0x20), mload(add(a, 0x20))) + mstore(add(mPtr, 0x40), mload(b)) + mstore(add(mPtr, 0x60), mload(add(b, 0x20))) + success := staticcall( + gas(), + 6, + mPtr, + 0x80, + mload(res), + 0x40 + ) + } + require(success, "ECADD check failed!"); + } + + // ECMUL + function ecmul(types.g1_point memory a, uint256 s) + internal view returns(types.g1_point memory res) + { + validateG1Point(a); + bool success; + + assembly { + let mPtr := mload(0x40) + mstore(mPtr, mload(a)) + mstore(add(mPtr, 0x20), mload(add(a, 0x20))) + mstore(add(mPtr, 0x40), s) + success := staticcall( + gas(), + 6, + mPtr, + 0x60, + mload(res), + 0x40 + ) + } + require(success, "ECMUL check failed!"); + } + + /// Evaluate the following pairing product: - /// e(a1, a2).e(-b1, b2) == 1 + /// e(a1, a2).e(b1, b2) == 1 function pairingProd2( types.g1_point memory a1, types.g2_point memory a2, @@ -159,4 +210,4 @@ library bn254_crypto { } require(is_well_formed, "Bn254: G1 point not on curve, or is malformed"); } -} \ No newline at end of file +} diff --git a/contracts/commitments/batched_kzg_verifier.sol b/contracts/commitments/batched_kzg_verifier.sol new file mode 100644 index 0000000..6c0222d --- /dev/null +++ b/contracts/commitments/batched_kzg_verifier.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: Apache-2.0. +//---------------------------------------------------------------------------// +// Copyright (c) 2024 Vasiliy Olekhov +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//---------------------------------------------------------------------------// +pragma solidity >=0.8.4; + +import "../types.sol"; +import "./batched_fri_verifier.sol"; +import "../algebra/polynomial.sol"; +import "../basic_marshalling.sol"; +import "../algebra/bn254.sol"; +import "../../contracts/interfaces/modular_verifier.sol"; + +import "hardhat/console.sol"; + +library batched_kzg_verifier { + + function verify_proof( + types.g1_point[] memory commitments, + uint256[] memory Z, + uint256[] memory U, + types.transcript_data memory tr_state, + types.kzg_params_type memory kzg_params, + types.kzg_proof_type memory kzg_proof) + internal view returns (bool result) { + unchecked { + + uint256 i; + + /* 1. send to transcript all commitments */ + for (i = 0; i < kzg_params.commitments_num; ++i ) { + transcript.update_transcript_b32(tr_state, bytes32(commitments[i].x)); + transcript.update_transcript_b32(tr_state, bytes32(commitments[i].y)); + } + for (i = 0; i < kzg_params.points_num; ++i ) { + transcript.update_transcript_b32(tr_state, bytes32(Z[i])); + } + for (i = 0; i < kzg_params.points_num; ++i ) { + transcript.update_transcript_b32(tr_state, bytes32(U[i])); + } + /* TWICE ? */ + for (i = 0; i < kzg_params.commitments_num; ++i ) { + transcript.update_transcript_b32(tr_state, bytes32(commitments[i].x)); + transcript.update_transcript_b32(tr_state, bytes32(commitments[i].y)); + } + for (i = 0; i < kzg_params.points_num; ++i ) { + transcript.update_transcript_b32(tr_state, bytes32(Z[i])); + } + for (i = 0; i < kzg_params.points_num; ++i ) { + transcript.update_transcript_b32(tr_state, bytes32(U[i])); + } + + /* 2. challenge theta from transcript */ + uint256 theta = transcript.get_field_challenge(tr_state, bn254_crypto.r_mod); + + /* 3. send pi_1 to transcript */ + transcript.update_transcript_b32(tr_state, bytes32(kzg_proof.pi_1.x)); + transcript.update_transcript_b32(tr_state, bytes32(kzg_proof.pi_1.y)); + + /* 4. challenge theta_2 from transcript */ + uint256 theta_2 = transcript.get_field_challenge(tr_state, bn254_crypto.r_mod); + + /* check theta and theta_2 values */ + if(theta != kzg_params.theta) { + console.log("wrong theta: ", theta); + console.log("expecting : ", kzg_params.theta); + } + + if(theta_2 != kzg_params.theta_2) { + console.log("wrong theta_2 :", theta_2); + console.log("expecting : ", kzg_params.theta_2); + } + + /* 5. for a set of commitments construct F */ + + uint256 theta_i = 1; + types.g1_point memory F = bn254_crypto.new_g1(0,0); + uint256 rsum = 0; + uint256 tmp; + uint256 r_i; + + for (i = 0; i < kzg_params.commitments_num; ++i) { + tmp = 13537094572093675138513973797244805699587981214733746302150278342976640424397; + r_i = mulmod(theta_i, tmp, bn254_crypto.r_mod); + types.g1_point memory f = bn254_crypto.ecmul(commitments[i], r_i); + F = bn254_crypto.ecadd(F, f); + tmp = 9554926369995100646077410167290930878381045633071770559944038420370185418405; + r_i = mulmod(r_i, tmp, bn254_crypto.r_mod); + rsum = addmod(rsum, r_i, bn254_crypto.r_mod); + theta_i = mulmod(theta_i, theta, bn254_crypto.r_mod); + } + + types.g1_point memory F_last = bn254_crypto.ecmul(bn254_crypto.P1(), rsum); + F_last.x = bn254_crypto.p_mod - F_last.x; + F = bn254_crypto.ecadd(F, F_last); + + tmp= 11559732032986387107991004021392285783925812861821192530917403151452391805634; + F_last = bn254_crypto.ecmul(kzg_proof.pi_1, tmp); + F_last.x = bn254_crypto.p_mod - F_last.x; + F = bn254_crypto.ecadd(F, F_last); + + F_last = bn254_crypto.ecmul(kzg_proof.pi_1, theta_2); + F = bn254_crypto.ecadd(F, F_last); + + types.g2_point memory g2 = bn254_crypto.P2(); + + return bn254_crypto.pairingProd2(F , g2, kzg_proof.pi_2, kzg_params.verification_key); + } + } +} diff --git a/contracts/test/bn254/bn254_test.sol b/contracts/test/bn254/bn254_test.sol new file mode 100644 index 0000000..90018eb --- /dev/null +++ b/contracts/test/bn254/bn254_test.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +//---------------------------------------------------------------------------// +// Copyright (c) 2024 Vasiliy Olekhov +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//---------------------------------------------------------------------------// +pragma solidity ^0.8.0; + +import '../../../contracts/commitments/batched_kzg_verifier.sol'; +import "../../../contracts/interfaces/modular_verifier.sol"; +import 'hardhat/console.sol'; + +contract kzg_estimation is IModularVerifier { + + /** + * @dev Initializes verifier + */ + function initialize( + address lookup_argument_contract_address, + address gate_argument_contract_address, + address commitment_contract_address) public + { + + } + + /** + * @dev Verifies proof + */ + function verify( + bytes calldata blob, + uint256[] calldata public_input + ) public returns (bool result) + { + + types.g1_point[] memory commitments; + commitments = new types.g1_point[](4); + + commitments[0].x = 0x0c44878bb8ff269d74d4c82a460b47503e72178c24ea96b071db39f540083856; + commitments[0].y = 0x15e1d289c6429156a6812b89f5f2b9c5adbcae41274c88a317d7b394e15a339; + commitments[1].x = 0x1a61eddcc2d2c249e4fea2895bf5d5c258dccd230ce16b355e22da9ccef3d796; + commitments[1].y = 0x88dfa0426989fe5b61b796b53fb18bd72b74bc513524e62ba8c0651366d1e96; + commitments[2].x = 0x00a81266b641f4305269eaf67036ee6a5d532143cbc706c1e97c7a84ba769d14; + commitments[2].y = 0x347d40c1e4471ca1423f38a73aec0a009819eff6571813db1620156d4719b67; + commitments[3].x = 0x5804dfe02a750643061c878b6d938cb74c621375c63c77cde1eb3804b8206d80; + commitments[3].y = 0x2501fa3f32e1cd9b04d66803dc467373c9f4f8fe285e2d53422fe9e508fe75e3; + + uint256[] memory Z; + Z = new uint256[](12); + Z[ 0] = 0x0000000000000000000000000000000000000000000000000000000000000701; + Z[ 1] = 0x000000000000000000000000000000000000000000000000000000000000601c; + Z[ 2] = 0x000000000000000000000000000000000000000000000000000312e5a822d164; + Z[ 3] = 0x00000000000000000000000000000000000000000000000000000000000010f7; + Z[ 4] = 0x000000000000000000000000000000000000000000000000000000000000e03c; + Z[ 5] = 0x00000000000000000000000000000000000000000000000000076a1399d05b27; + Z[ 6] = 0x00000000000000000000000000000000000000000000000000000000000000c4; + Z[ 7] = 0x0000000000000000000000000000000000000000000000000000000000001aed; + Z[ 8] = 0x000000000000000000000000000000000000000000000000000000000001605c; + Z[ 9] = 0x00000000000000000000000000000000000000000000000000000000000024e3; + Z[10] = 0x000000000000000000000000000000000000000000000000000000000001e07c; + Z[11] = 0x0000000000000000000000000000000000000000000000000011eef27398435f; + + uint256[] memory U; + U = new uint256[](12); + U[ 0] = 0x0000000000000000000000000000000000000000000000000000007c9471f41d; + U[ 1] = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f52c1ef629ad; + U[ 2] = 0x00000000000000000000000000000000000000000000000000000014c3686fe3; + U[ 3] = 0x000000000000000000000000000000000000000000000000000001267d4b52fb; + U[ 4] = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f49e87961425; + U[ 5] = 0x0000000000000000000000000000000000000000000000000000003114e2256d; + U[ 6] = 0x00000000000000000000000000000000000000000000000000000000000111e1; + U[ 7] = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593effe5941; + U[ 8] = 0x00000000000000000000000000000000000000000000000000000000000095a3; + U[ 9] = 0x000000000000000000000000000000000000000000000000000002ac7f620cdf; + U[10] = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f35985d7ec49; + U[11] = 0x00000000000000000000000000000000000000000000000000000072153b8fdd; + + types.kzg_params_type memory kzg_params; + + kzg_params.commitments_num = 4; + kzg_params.points_num = 12; + kzg_params.theta = 0x146669b8f0f804db1f65879b69ebd1eb5a62d5a304f397fe2214f1a09512289; + kzg_params.theta_2 = 0x60ccd0177322ae60e62f674ae1f0594ef507a2296f0fe7ea45537a2493e5c6a; + kzg_params.verification_key = bn254_crypto.new_g2( + 18551411094430470096460536606940536822990217226529861227533666875800903099477, + 15512671280233143720612069991584289591749188907863576513414377951116606878472, + 1711576522631428957817575436337311654689480489843856945284031697403898093784, + 13376798835316611669264291046140500151806347092962367781523498857425536295743); + + + types.kzg_proof_type memory kzg_proof; + kzg_proof.pi_1 = bn254_crypto.new_g1( + 0x2a5962762ebd3540336267427860b210e8cd10ceb111d10c279ca20d3419c36f, + 0x79cc53475493803aac3f585ccaf63d893d009dc8c143160098327542c4c6d87); + kzg_proof.pi_2 = bn254_crypto.new_g1( + 0x28fbae026e8b174c26e0ab3ad4d3e523fa0ed914cfc4b81d206b985108471305, + 0x21532211d9b11dc61e52f52f66f2408d577278b6fc33c6fe83f3a8d089ad1a1c); + /* + kzg_proof.pi_1 = bn254_crypt.new_g1( + 0x2a5962762ebd3540336267427860b210e8cd10ceb111d10c279ca20d3419c36f, + 0x79cc53475493803aac3f585ccaf63d893d009dc8c143160098327542c4c6d87); + kzg_proof.pi_2 = bn254_crypt.new_g1( + 0x28fbae026e8b174c26e0ab3ad4d3e523fa0ed914cfc4b81d206b985108471305, + 0x21532211d9b11dc61e52f52f66f2408d577278b6fc33c6fe83f3a8d089ad1a1c); + */ + + types.transcript_data memory tr_state; + transcript.init_transcript(tr_state, hex""); + + result = batched_kzg_verifier.verify_proof(commitments, Z, U, tr_state, kzg_params, kzg_proof); + emit VerificationResult(result); + } + +} diff --git a/contracts/types.sol b/contracts/types.sol index 8f3a226..e81046d 100644 --- a/contracts/types.sol +++ b/contracts/types.sol @@ -192,6 +192,20 @@ library types { uint256[] fri_roots; // It should be bytes32 } + + struct kzg_params_type { + uint256 commitments_num; + uint256 theta; + uint256 theta_2; + uint256 points_num; + types.g2_point verification_key; + } + + struct kzg_proof_type { + types.g1_point pi_1; + types.g1_point pi_2; + } + struct fri_state_type { bytes b; //0x0 diff --git a/deploy/test-kzg-verifier.js b/deploy/test-kzg-verifier.js new file mode 100644 index 0000000..7fd7e5b --- /dev/null +++ b/deploy/test-kzg-verifier.js @@ -0,0 +1,15 @@ +const hre = require('hardhat') +const {getNamedAccounts} = hre + +module.exports = async function () { + const {deployments, getNamedAccounts} = hre; + const {deploy} = deployments; + const {deployer, tokenOwner} = await getNamedAccounts(); + + await deploy('kzg_estimation', { + from: deployer, + log: true, + }); +} + +module.exports.tags = ['testKZGVerifierFixture'] diff --git a/tasks/modular-test.ts b/tasks/modular-test.ts index ee8baaa..8429ab1 100644 --- a/tasks/modular-test.ts +++ b/tasks/modular-test.ts @@ -147,3 +147,15 @@ task("verify-circuit-proof") let circuit = test.test; process.exit((await verify_circuit_proof(modular_path, circuit)) ? 0 : 1); }); + +task("verify-kzg") + .setAction(async (hre) => { + console.log("Verify KZG"); + await deployments.fixture(['testKZGVerifierFixture']); + const v = await ethers.getContract('kzg_estimation'); + const proof = []; + const public_input = []; + const receipt = await (await v.verify(proof, public_input, {gasLimit: 30_500_000})).wait(); + console.log("⛽Gas used: ", receipt.gasUsed.toNumber()); +// console.log(receipt); + }); diff --git a/test/kzg-test.js b/test/kzg-test.js new file mode 100644 index 0000000..5b2cc77 --- /dev/null +++ b/test/kzg-test.js @@ -0,0 +1,16 @@ +const hre = require('hardhat') + +/* global BigInt */ + +describe('KZG test', function () { + const {deployments} = hre; + + it("Pairing precompile test", async function () { + await deployments.fixture(['testKZGVerifierFixture']); + const v = await ethers.getContract('kzg_estimation'); + let x = await v.verify({gasLimit: 30_500_000}); + console.log(x.gasUsed); + const receipt = await (await v.test_kzg({gasLimit: 30_500_000})).wait(); + console.log("⛽Gas used: ", receipt.gasUsed.toNumber()); + }); +})