diff --git a/src/blocks/IpaPcs.sol b/src/blocks/IpaPcs.sol index 61e4883..c75973b 100644 --- a/src/blocks/IpaPcs.sol +++ b/src/blocks/IpaPcs.sol @@ -156,6 +156,7 @@ library InnerProductArgument { function compute_P_hat_right(P_hat_right_input memory input) private + view returns (Grumpkin.GrumpkinAffinePoint memory) { uint256[] memory s = new uint256[](input.n); @@ -194,6 +195,7 @@ library InnerProductArgument { function compute_P_hat_left(IpaInputGrumpkin memory input, R memory r_vec, Grumpkin.GrumpkinAffinePoint memory ck_c) private + view returns (Grumpkin.GrumpkinAffinePoint memory) { Grumpkin.GrumpkinAffinePoint memory P = Grumpkin.add(input.commitment, Grumpkin.scalarMul(ck_c, input.eval)); @@ -232,7 +234,7 @@ library InnerProductArgument { uint256 a_hat, Grumpkin.GrumpkinAffinePoint memory ck_hat, Grumpkin.GrumpkinAffinePoint memory ck_c - ) private returns (Grumpkin.GrumpkinAffinePoint memory) { + ) private view returns (Grumpkin.GrumpkinAffinePoint memory) { Grumpkin.GrumpkinAffinePoint[] memory bases = new Grumpkin.GrumpkinAffinePoint[](2); bases[0] = ck_hat; bases[1] = ck_c; diff --git a/src/blocks/KeccakTranscript.sol b/src/blocks/KeccakTranscript.sol index ab01e55..20aa572 100644 --- a/src/blocks/KeccakTranscript.sol +++ b/src/blocks/KeccakTranscript.sol @@ -1033,7 +1033,7 @@ library KeccakTranscriptLib { uint256[] memory inputs_0, uint256[] memory inputs_1, uint256[] memory inputs_2 - ) public returns (KeccakTranscript memory) { + ) public pure returns (KeccakTranscript memory) { uint8[] memory input = new uint8[](32 * inputs_0.length + 32 * inputs_1.length + 32 * inputs_2.length); uint256 input_index = 0; @@ -1387,7 +1387,7 @@ library KeccakTranscriptLib { KeccakTranscript memory keccak, uint8[] memory label, InnerProductArgument.InstanceGrumpkin memory ipa_input - ) public returns (KeccakTranscript memory) { + ) public pure returns (KeccakTranscript memory) { uint256 output_length = 0; output_length += 32 * 2 + 1; // comm_a_vec // we don't write b_vec to transcript according to reference implementation diff --git a/src/blocks/Pairing.sol b/src/blocks/Pairing.sol new file mode 100644 index 0000000..32b0748 --- /dev/null +++ b/src/blocks/Pairing.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.16; + +import "src/blocks/grumpkin/Bn256.sol"; + +/** + * @title Pairing Library + * @notice Provides functionalities for elliptic curve pairing operations, specifically for BN256 curve. Based on: + * https://gist.github.com/chriseth/f9be9d9391efc5beb9704255a8e2989d + * @dev This library is essential for cryptographic operations that require pairing checks. + */ +library Pairing { + // Represents a point on G1 (first group of BN256 curve). + struct G1Point { + Bn256.Bn256AffinePoint inner; + } + // Represents a point on G2 (second group of BN256 curve). + + struct G2Point { + uint256[2] X; + uint256[2] Y; + } + + /** + * @notice Computes the pairing check of two sets of points. + * @param p1 Array of G1 points. + * @param p2 Array of G2 points. + * @return True if the pairing check passes, false otherwise. + * @dev For example pairing([P1(), P1().negate()], [P2(), P2()]) should + * return true. + */ + function pairing(G1Point[] memory p1, G2Point[] memory p2) internal returns (bool) { + require(p1.length == p2.length); + uint256 elements = p1.length; + uint256 inputSize = elements * 6; + uint256[] memory input = new uint256[](inputSize); + for (uint256 i = 0; i < elements; i++) { + input[i * 6 + 0] = p1[i].inner.x; + input[i * 6 + 1] = p1[i].inner.y; + // To be compatible with Rust, it is necessary to reverse the field element encoding + input[i * 6 + 2] = p2[i].X[1]; + input[i * 6 + 3] = p2[i].X[0]; + input[i * 6 + 4] = p2[i].Y[1]; + input[i * 6 + 5] = p2[i].Y[0]; + } + uint256[1] memory out; + assembly { + let success := call(sub(gas(), 2000), 8, 0, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) + switch success + case 0 { revert(0, 0) } + } + return out[0] != 0; + } + + /** + * @notice Computes the product of pairings for two pairs of points. + * @param a1 The first G1 point in the first pair. + * @param a2 The first G2 point in the first pair. + * @param b1 The second G1 point in the second pair. + * @param b2 The second G2 point in the second pair. + * @return True if the product of pairings check passes, false otherwise. + */ + function pairingProd2(G1Point memory a1, G2Point memory a2, G1Point memory b1, G2Point memory b2) + internal + returns (bool) + { + G1Point[] memory p1 = new G1Point[](2); + G2Point[] memory p2 = new G2Point[](2); + p1[0] = a1; + p1[1] = b1; + p2[0] = a2; + p2[1] = b2; + return pairing(p1, p2); + } +} diff --git a/src/blocks/ZeromorphEngine.sol b/src/blocks/ZeromorphEngine.sol index 0f19a21..51d2e42 100644 --- a/src/blocks/ZeromorphEngine.sol +++ b/src/blocks/ZeromorphEngine.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.16; import "@std/Test.sol"; import "src/blocks/grumpkin/Bn256.sol"; +import "src/blocks/Pairing.sol"; /** * @title Zeromorph Library @@ -157,128 +158,3 @@ library Zeromorph { return output; } } - -/** - * @title Pairing Library - * @notice Provides functionalities for elliptic curve pairing operations, specifically for BN256 curve. Based on: - * https://gist.github.com/chriseth/f9be9d9391efc5beb9704255a8e2989d - * @dev This library is essential for cryptographic operations that require pairing checks. - */ -library Pairing { - // Represents a point on G1 (first group of BN256 curve). - struct G1Point { - Bn256.Bn256AffinePoint inner; - } - // Represents a point on G2 (second group of BN256 curve). - - struct G2Point { - uint256[2] X; - uint256[2] Y; - } - - /** - * @notice Provides a predefined G1 point. - * @return A predefined point on G1. - */ - function P1() internal pure returns (G1Point memory) { - return G1Point(Bn256.Bn256AffinePoint(1, 2)); - } - - /** - * @notice Provides a predefined G2 point. - * @return A predefined point on G2. - */ - function P2() internal pure returns (G2Point memory) { - return G2Point( - [ - 10857046999023057135944570762232829481370756359578518086990519993285655852781, - 11559732032986387107991004021392285783925812861821192530917403151452391805634 - ], - [ - 8495653923123431417604973247489272438418190587263600148770280649306958101930, - 4082367875863433681332203403145435568316851327593401208105741076214120093531 - ] - ); - } - - /** - * @notice Negates a G1 point. - * @param p The G1 point to negate. - * @return The negated G1 point. - */ - function negate(G1Point memory p) internal pure returns (G1Point memory) { - return G1Point(Bn256.negate(p.inner)); - } - - /** - * @notice Multiplies a G1 point with a scalar. - * @param p The G1 point to multiply. - * @param s The scalar to multiply with. - * @return The result of the multiplication. - */ - function mul(G1Point memory p, uint256 s) internal returns (G1Point memory) { - return G1Point(Bn256.scalarMul(p.inner, s)); - } - - /** - * @notice Adds two G1 points. - * @param p1 The first G1 point. - * @param p2 The second G1 point. - * @return The sum of the two G1 points. - */ - function add(G1Point memory p1, G1Point memory p2) internal returns (G1Point memory) { - return G1Point(Bn256.add(p1.inner, p2.inner)); - } - - /** - * @notice Computes the pairing check of two sets of points. - * @param p1 Array of G1 points. - * @param p2 Array of G2 points. - * @return True if the pairing check passes, false otherwise. - * @dev For example pairing([P1(), P1().negate()], [P2(), P2()]) should - * return true. - */ - function pairing(G1Point[] memory p1, G2Point[] memory p2) internal returns (bool) { - require(p1.length == p2.length); - uint256 elements = p1.length; - uint256 inputSize = elements * 6; - uint256[] memory input = new uint256[](inputSize); - for (uint256 i = 0; i < elements; i++) { - input[i * 6 + 0] = p1[i].inner.x; - input[i * 6 + 1] = p1[i].inner.y; - // To be compatible with Rust, it is necessary to reverse the field element encoding - input[i * 6 + 2] = p2[i].X[1]; - input[i * 6 + 3] = p2[i].X[0]; - input[i * 6 + 4] = p2[i].Y[1]; - input[i * 6 + 5] = p2[i].Y[0]; - } - uint256[1] memory out; - assembly { - let success := call(sub(gas(), 2000), 8, 0, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) - switch success - case 0 { revert(0, 0) } - } - return out[0] != 0; - } - - /** - * @notice Computes the product of pairings for two pairs of points. - * @param a1 The first G1 point in the first pair. - * @param a2 The first G2 point in the first pair. - * @param b1 The second G1 point in the second pair. - * @param b2 The second G2 point in the second pair. - * @return True if the product of pairings check passes, false otherwise. - */ - function pairingProd2(G1Point memory a1, G2Point memory a2, G1Point memory b1, G2Point memory b2) - internal - returns (bool) - { - G1Point[] memory p1 = new G1Point[](2); - G2Point[] memory p2 = new G2Point[](2); - p1[0] = a1; - p1[1] = b1; - p2[0] = a2; - p2[1] = b2; - return pairing(p1, p2); - } -} diff --git a/src/blocks/grumpkin/Grumpkin.sol b/src/blocks/grumpkin/Grumpkin.sol index ce51075..1850b6b 100644 --- a/src/blocks/grumpkin/Grumpkin.sol +++ b/src/blocks/grumpkin/Grumpkin.sol @@ -260,6 +260,7 @@ library Grumpkin { function multiScalarMul(GrumpkinAffinePoint[] memory bases, uint256[] memory scalars) public + view returns (GrumpkinAffinePoint memory r) { require(scalars.length == bases.length, "MSM error: length does not match"); diff --git a/test/hyperkzg.t.sol b/test/hyperkzg.t.sol index c484792..705f600 100644 --- a/test/hyperkzg.t.sol +++ b/test/hyperkzg.t.sol @@ -5,10 +5,10 @@ import "@std/Test.sol"; import "src/blocks/KeccakTranscript.sol"; import "src/blocks/grumpkin/Bn256.sol"; import "src/Utilities.sol"; -import "src/blocks/ZeromorphEngine.sol"; +import "src/blocks/Pairing.sol"; contract HyperKzgTest is Test { - function pushElement(uint256[] memory input, uint256 element) private returns (uint256[] memory) { + function pushElement(uint256[] memory input, uint256 element) private pure returns (uint256[] memory) { uint256[] memory output = new uint256[](input.length + 1); for (uint256 i = 0; i < input.length; i++) { output[i] = input[i]; @@ -19,6 +19,7 @@ contract HyperKzgTest is Test { function insertPoint(Bn256.Bn256AffinePoint[] memory input, Bn256.Bn256AffinePoint memory point, uint256 index) private + pure returns (Bn256.Bn256AffinePoint[] memory) { require(index <= input.length, "unexpected index"); @@ -41,7 +42,7 @@ contract HyperKzgTest is Test { uint256[] memory point_in, uint256 r, uint256 p_of_x - ) private returns (bool) { + ) private pure returns (bool) { require(pi_evals_0.length == point_in.length, "unexpected length of pi_evals_0"); require(pi_evals_1.length == point_in.length, "unexpected length of pi_evals_1"); require(pi_evals_2.length == point_in.length, "unexpected length of pi_evals_2"); @@ -148,7 +149,7 @@ contract HyperKzgTest is Test { Pairing.G2Point h; } - function composeHyperKzgInput() private returns (HyperKzgInput memory) { + function composeHyperKzgInput() private pure returns (HyperKzgInput memory) { Bn256.Bn256AffinePoint[] memory pi_comms = new Bn256.Bn256AffinePoint[](2); pi_comms[0] = Bn256.Bn256AffinePoint( 0x1e252582f77d12b3fbf9376aa756426e7c9c6496be4f60f35f5b6a09cd65b580, diff --git a/test/zeromorph.t.sol b/test/zeromorph.t.sol index 9ad14f5..81a07de 100644 --- a/test/zeromorph.t.sol +++ b/test/zeromorph.t.sol @@ -8,11 +8,20 @@ import "src/blocks/KeccakTranscript.sol"; contract ZeromorphContract is Test { function testPairingUsage() public { - Pairing.G1Point memory g1_0 = Pairing.P1(); - Pairing.G2Point memory g2_0 = Pairing.P2(); + Pairing.G1Point memory g1_0 = Pairing.G1Point(Bn256.Bn256AffinePoint(1, 2)); + Pairing.G2Point memory g2_0 = Pairing.G2Point( + [ + 10857046999023057135944570762232829481370756359578518086990519993285655852781, + 11559732032986387107991004021392285783925812861821192530917403151452391805634 + ], + [ + 8495653923123431417604973247489272438418190587263600148770280649306958101930, + 4082367875863433681332203403145435568316851327593401208105741076214120093531 + ] + ); - Pairing.G1Point memory g1_1 = Pairing.negate(Pairing.P1()); - Pairing.G2Point memory g2_1 = Pairing.P2(); + Pairing.G1Point memory g1_1 = Pairing.G1Point(Bn256.negate(Bn256.Bn256AffinePoint(1, 2))); + Pairing.G2Point memory g2_1 = g2_0; bool pairingResult = Pairing.pairingProd2(g1_0, g2_0, g1_1, g2_1); assert(pairingResult);