From e1df2a8852fac45a7b5612b7020f1170431bf419 Mon Sep 17 00:00:00 2001 From: Artem Storozhuk Date: Thu, 1 Feb 2024 17:40:17 +0200 Subject: [PATCH 1/3] Add Grumpkin MSM --- src/blocks/grumpkin/Grumpkin.sol | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/blocks/grumpkin/Grumpkin.sol b/src/blocks/grumpkin/Grumpkin.sol index 391d748..8be9d78 100644 --- a/src/blocks/grumpkin/Grumpkin.sol +++ b/src/blocks/grumpkin/Grumpkin.sol @@ -258,6 +258,18 @@ library Grumpkin { return acc; } + function multiScalarMul(GrumpkinAffinePoint[] memory bases, uint256[] memory scalars) + public + returns (GrumpkinAffinePoint memory r) + { + require(scalars.length == bases.length, "MSM error: length does not match"); + + r = scalarMul(bases[0], scalars[0]); + for (uint256 i = 1; i < scalars.length; i++) { + r = add(r, scalarMul(bases[i], scalars[i])); + } + } + /** * @dev This function converts the compressed Grumpkin point back into the full point representation. * @param compressed The compressed representation of the point. From 51e34085ed0b9dfb03e71db229ab46771abf1daa Mon Sep 17 00:00:00 2001 From: Artem Storozhuk Date: Fri, 2 Feb 2024 18:55:20 +0200 Subject: [PATCH 2/3] Unit-test with IPA successful computation --- src/blocks/grumpkin/Grumpkin.sol | 4 +- test/ipa.t.sol | 380 +++++++++++++++++++++++++++++++ 2 files changed, 382 insertions(+), 2 deletions(-) create mode 100644 test/ipa.t.sol diff --git a/src/blocks/grumpkin/Grumpkin.sol b/src/blocks/grumpkin/Grumpkin.sol index 8be9d78..ce51075 100644 --- a/src/blocks/grumpkin/Grumpkin.sol +++ b/src/blocks/grumpkin/Grumpkin.sol @@ -259,8 +259,8 @@ library Grumpkin { } function multiScalarMul(GrumpkinAffinePoint[] memory bases, uint256[] memory scalars) - public - returns (GrumpkinAffinePoint memory r) + public + returns (GrumpkinAffinePoint memory r) { require(scalars.length == bases.length, "MSM error: length does not match"); diff --git a/test/ipa.t.sol b/test/ipa.t.sol new file mode 100644 index 0000000..67cc30d --- /dev/null +++ b/test/ipa.t.sol @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.16; + +import "@std/Test.sol"; +import "src/blocks/grumpkin/Grumpkin.sol"; +import "src/blocks/EqPolynomial.sol"; +import "src/Utilities.sol"; + +// TODO: +// 1) Refactor and expose IPA as a library +// 2) Detect what is the max length of polynomial which can be supported without OutOfGas + +contract IpaTest is Test { + struct R { + uint256[] r_vec; + uint256[] r_vec_squared; + uint256[] r_vec_inversed; + uint256[] r_vec_inversed_squared; + } + + function split_at(Grumpkin.GrumpkinAffinePoint[] memory ck, uint256 n) + public + returns (Grumpkin.GrumpkinAffinePoint[] memory, Grumpkin.GrumpkinAffinePoint[] memory) + { + require(n <= ck.length, "[split_at] unexpected n"); + + Grumpkin.GrumpkinAffinePoint[] memory ck1 = new Grumpkin.GrumpkinAffinePoint[](n); + Grumpkin.GrumpkinAffinePoint[] memory ck2 = new Grumpkin.GrumpkinAffinePoint[](n); + uint256 ck_index = 0; + for (uint256 i = 0; i < n; i++) { + ck1[i] = ck[ck_index]; + ck_index++; + } + for (uint256 i = n; i < ck.length; i++) { + ck2[i] = ck[ck_index]; + ck_index++; + } + + return (ck1, ck2); + } + + function scale(Grumpkin.GrumpkinAffinePoint[] memory ck_c, uint256 r) + public + returns (Grumpkin.GrumpkinAffinePoint[] memory) + { + Grumpkin.GrumpkinAffinePoint[] memory scaled = new Grumpkin.GrumpkinAffinePoint[](ck_c.length); + for (uint256 index = 0; index < ck_c.length; index++) { + scaled[index] = Grumpkin.scalarMul(ck_c[index], r); + } + return scaled; + } + + function batchInvert(uint256[] memory r_vec) public returns (uint256[] memory) { + uint256[] memory products = new uint256[](r_vec.length); + uint256 acc = 1; + uint256 index; + for (index = 0; index < r_vec.length; index++) { + products[index] = acc; + acc = mulmod(acc, r_vec[index], Grumpkin.P_MOD); + } + + acc = Field.invert(acc, Grumpkin.P_MOD); + + uint256[] memory inversed = new uint256[](r_vec.length); + + uint256 tmp; + for (index = 0; index < r_vec.length; index++) { + tmp = mulmod(acc, r_vec[r_vec.length - index - 1], Grumpkin.P_MOD); + inversed[r_vec.length - index - 1] = mulmod(products[r_vec.length - index - 1], acc, Grumpkin.P_MOD); + acc = tmp; + } + + return inversed; + } + + function compute_P( + Grumpkin.GrumpkinAffinePoint memory commitment, + Grumpkin.GrumpkinAffinePoint[] memory ck_s, + uint256 eval, + uint256 r + ) public returns (Grumpkin.GrumpkinAffinePoint memory) { + Grumpkin.GrumpkinAffinePoint[] memory ck_s_scaled = scale(ck_s, r); + return Grumpkin.add(commitment, Grumpkin.scalarMul(ck_s_scaled[0], eval)); + } + + function compute_r_based_values( + Grumpkin.GrumpkinAffinePoint[] memory ck_s, + uint256 eval, + Grumpkin.GrumpkinAffinePoint[] memory L_vec, + Grumpkin.GrumpkinAffinePoint memory commitment, + uint256 r + ) public returns (R memory) { + /* + // compute a vector of public coins using self.L_vec and self.R_vec + let r = (0..self.L_vec.len()) + .map(|i| { + transcript.absorb(b"L", &self.L_vec[i]); + transcript.absorb(b"R", &self.R_vec[i]); + transcript.squeeze(b"r") + }) + .collect::, NovaError>>()?; + */ + + uint256[] memory r_vec = new uint256[](L_vec.length); + r_vec[0] = 0x1a6d268ae789320863b0760561bf92cc5539e81239b97cea90542b5163cdd1c7; + r_vec[1] = 0x0fa5b3f5911542c0976b82385f398e58e7fadf188c80638c541697005a97ab6d; + + uint256[] memory r_vec_squared = new uint256[](L_vec.length); + + uint256 index; + for (index = 0; index < r_vec.length; index++) { + r_vec_squared[index] = mulmod(r_vec[index], r_vec[index], Grumpkin.P_MOD); + } + + uint256[] memory r_vec_inversed = batchInvert(r_vec); + + uint256[] memory r_vec_inversed_squared = new uint256[](L_vec.length); + for (index = 0; index < r_vec.length; index++) { + r_vec_inversed_squared[index] = mulmod(r_vec_inversed[index], r_vec_inversed[index], Grumpkin.P_MOD); + } + return R (r_vec, r_vec_squared, r_vec_inversed, r_vec_inversed_squared); + } + + function get_pos_value(uint256 i) public pure returns (uint256) { + require(i >= 1, "[get_pos_value], i < 1"); + require(i <= 16, "[get_pos_value], i > 16"); + uint256[] memory result = new uint256[](16); + result[0] = 0; + result[1] = 1; + result[2] = 1; + result[3] = 2; + result[4] = 2; + result[5] = 2; + result[6] = 2; + result[7] = 3; + result[8] = 3; + result[9] = 3; + result[10] = 3; + result[11] = 3; + result[12] = 3; + result[13] = 3; + result[14] = 3; + result[15] = 4; + return result[i - 1]; + } + + function compute_s(uint256 n, uint256[] memory r_inverse, uint256[] memory r_square, uint256 L_vec_len) + public + returns (uint256[] memory) + { + uint256[] memory s = new uint256[](n); + + uint256 v = 1; + uint256 index; + for (index = 0; index < r_inverse.length; index++) { + v = mulmod(v, r_inverse[index], Grumpkin.P_MOD); + } + s[0] = v; + + uint256 pos_in_r; + for (index = 1; index < n; index++) { + pos_in_r = get_pos_value(index); + s[index] = mulmod(s[index - (1 << pos_in_r)], r_square[L_vec_len - 1 - pos_in_r], Grumpkin.P_MOD); + } + + return s; + } + + function inner_product_inner(uint256[] memory c) public returns (uint256[] memory) { + if (c.length == 1) { + return c; + } + uint256[] memory c_inner = new uint256[](c.length / 2); + for (uint256 index = 0; index < c_inner.length; index++) { + c_inner[index] = addmod(c[2 * index], c[2 * index + 1], Grumpkin.P_MOD); + } + return inner_product_inner(c_inner); + } + + function inner_product(uint256[] memory a, uint256[] memory b) public returns (uint256) { + require(a.length == b.length); + uint256[] memory c = new uint256[](a.length); + uint256 index; + for (index = 0; index < a.length; index++) { + c[index] = mulmod(a[index], b[index], Grumpkin.P_MOD); + } + + c = inner_product_inner(c); + return c[0]; + } + + function compute_P_hat_left( + Grumpkin.GrumpkinAffinePoint[] memory L_vec, + Grumpkin.GrumpkinAffinePoint[] memory R_vec, + Grumpkin.GrumpkinAffinePoint memory P, + R memory r_vec + ) public returns (Grumpkin.GrumpkinAffinePoint memory){ + uint256 msm_len = L_vec.length + R_vec.length + 1; + + uint256 msm_index = 0; + uint256[] memory scalars = new uint256[](msm_len); + for (uint256 index = 0; index < r_vec.r_vec_squared.length; index++) { + scalars[msm_index] = r_vec.r_vec_squared[index]; + msm_index++; + } + for (uint256 index = 0; index < r_vec.r_vec_inversed_squared.length; index++) { + scalars[msm_index] = r_vec.r_vec_inversed_squared[index]; + msm_index++; + } + scalars[msm_index] = 0x01; + + msm_index = 0; + Grumpkin.GrumpkinAffinePoint[] memory bases = new Grumpkin.GrumpkinAffinePoint[](msm_len); + for (uint256 index = 0; index < L_vec.length; index++) { + bases[msm_index] = L_vec[index]; + msm_index++; + } + for (uint256 index = 0; index < R_vec.length; index++) { + bases[msm_index] = R_vec[index]; + msm_index++; + } + bases[msm_index] = P; + + return Grumpkin.multiScalarMul(bases, scalars); + } + + function compute_P_hat_right( + uint256 b_hat, + uint256 a_hat, + Grumpkin.GrumpkinAffinePoint memory ck_hat, + Grumpkin.GrumpkinAffinePoint memory ck_c + ) public returns (Grumpkin.GrumpkinAffinePoint memory){ + Grumpkin.GrumpkinAffinePoint[] memory bases = new Grumpkin.GrumpkinAffinePoint[](2); + bases[0] = ck_hat; + bases[1] = ck_c; + + uint256[] memory scalars = new uint256[](2); + scalars[0] = a_hat; + scalars[1] = mulmod(a_hat, b_hat, Grumpkin.P_MOD); + + return Grumpkin.multiScalarMul(bases, scalars); + } + + function ipa_computations_inner( + uint256 n, + Grumpkin.GrumpkinAffinePoint memory commitment, + Grumpkin.GrumpkinAffinePoint[] memory ck_s, + uint256 eval, + Grumpkin.GrumpkinAffinePoint[] memory L_vec, + Grumpkin.GrumpkinAffinePoint[] memory ck1, + uint256[] memory b_vec, + uint256 r + ) public returns (R memory, uint256, Grumpkin.GrumpkinAffinePoint memory){ + + R memory r_vectors = compute_r_based_values(ck_s, eval, L_vec, commitment, r); + + uint256[] memory s = compute_s(n, r_vectors.r_vec_inversed, r_vectors.r_vec_squared, L_vec.length); + + Grumpkin.GrumpkinAffinePoint memory ck_hat = Grumpkin.multiScalarMul(ck1, s); + + uint256 b_hat = inner_product(b_vec, s); + + return (r_vectors, b_hat, ck_hat); + } + + struct IpaInput{ + Grumpkin.GrumpkinAffinePoint[] ck_v; + Grumpkin.GrumpkinAffinePoint[] ck_s; + uint256[] point; + Grumpkin.GrumpkinAffinePoint[] L_vec; + Grumpkin.GrumpkinAffinePoint[] R_vec; + Grumpkin.GrumpkinAffinePoint commitment; + uint256 eval; + uint256 a_hat; + } + + + function ipa_computations( + IpaInput memory input + ) public { + uint256 n = 2 ** input.point.length; + + uint256[] memory b_vec = EqPolynomialLib.evals(input.point, Grumpkin.P_MOD, Grumpkin.negateBase); + (Grumpkin.GrumpkinAffinePoint[] memory ck1,) = split_at(input.ck_v, b_vec.length); + + // transcript.dom_sep(Self::protocol_name()); + + if (b_vec.length != n) { + revert("NovaError::InvalidInputLength"); + } + if (n != 1 << input.L_vec.length) { + revert("NovaError::InvalidInputLength"); + } + if (input.L_vec.length != input.R_vec.length) { + revert("NovaError::InvalidInputLength"); + } + if (input.L_vec.length >= 32) { + revert("NovaError::InvalidInputLength"); + } + + //transcript.absorb(b"U", U); + //let r = transcript.squeeze(b"r")?; + + uint256 r = 0x26dbed0c0b99929de85b3c8d4c501f5216b0c3fa951b92afb75b6044c7b6885d; + + (R memory r_vectors, uint256 b_hat, Grumpkin.GrumpkinAffinePoint memory ck_hat) = ipa_computations_inner(n, input.commitment, input.ck_s, input.eval, input.L_vec, ck1, b_vec, r); + + Grumpkin.GrumpkinAffinePoint memory P = compute_P(input.commitment, input.ck_s, input.eval, r); + + Grumpkin.GrumpkinAffinePoint memory P_hat_left = compute_P_hat_left(input.L_vec, input.R_vec, P, r_vectors); + + Grumpkin.GrumpkinAffinePoint[] memory ck_c = scale(input.ck_s, r); + + Grumpkin.GrumpkinAffinePoint memory P_hat_right = compute_P_hat_right(b_hat, input.a_hat, ck_hat, ck_c[0]); + + assertEq(P_hat_left.x, P_hat_right.x); + assertEq(P_hat_left.y, P_hat_right.y); + } + + function testDebug() public { + Grumpkin.GrumpkinAffinePoint[] memory ck_v = new Grumpkin.GrumpkinAffinePoint[](4); + ck_v[0] = Grumpkin.GrumpkinAffinePoint( + 0x15afa1c1de43e186ee615ee76389d1ca9de572d426869ab062a03f1ba65808a2, + 0x28d6d43cb5ba89778111ceaa56cb8bf2c34a5fb6013988513d5798a60846d423 + ); + ck_v[1] = Grumpkin.GrumpkinAffinePoint( + 0x132126b357d7299c5c18e04cbe13c4206b763dbc56a8d19900270cd0c59f3981, + 0x169077205c0ed8e9f2738a9f04d064e17c457a531a93e9ec5131e35d587cd381 + ); + ck_v[2] = Grumpkin.GrumpkinAffinePoint( + 0x20c9d6e3d55f0142ce09b6d1cd8b86c8eaecf8f204bce4c9b88a75c720e34b74, + 0x227f66a87a7649e8a76a2314e14c0c44877e1eca54015d5ecd8b1da08ccbb779 + ); + ck_v[3] = Grumpkin.GrumpkinAffinePoint( + 0x1300fe5112d72be0b65d1d365f294a136df15671e4f56e2fbf65be2ffec64e4f, + 0x0c93e3b91eeead0adf19f228e2a361b3b6055d1b89e699196c6a5550be5824b9 + ); + + Grumpkin.GrumpkinAffinePoint[] memory ck_s = new Grumpkin.GrumpkinAffinePoint[](1); + ck_s[0] = Grumpkin.GrumpkinAffinePoint( + 0x2e8facd7beb3da0e505fa1e33ee77b0b19fa1dfc1c5e04537cda07bf56cc248b, + 0x11a32df7bf180b18e526371ee2e21bb42ee2d9a7ac875f0816be6effda4e3dfb + ); + + uint256[] memory point = new uint256[](2); + point[0] = 0x1fe29a0b699fa3cbc723126c4ad0e4a5f410c5f699f3599e92c4f0e99c1abd97; + point[1] = 0x0ed4861fc966ff194c23744c2e6f63139211dc3550a28a9c8e0979427ff9c677; + + Grumpkin.GrumpkinAffinePoint[] memory L_vec = new Grumpkin.GrumpkinAffinePoint[](2); + L_vec[0] = Grumpkin.GrumpkinAffinePoint( + 0x1aedd46eb53cfded07f7c3710015340b8cb21983fe71d24f0e7d9f5ab4854e2d, + 0x06d42154bbf58e193faa5443312aa938c3fc88648f1a0912d890ea1f7edc3ade + ); + L_vec[1] = Grumpkin.GrumpkinAffinePoint( + 0x1c95cbc06044e13eca63f164a8d2dbd3bfc7ed470dd244154e2ae5f83592b649, + 0x0abde1d3428cfe8b21442f486b010f14042f5d84b54a811d06307104c4755a2c + ); + + Grumpkin.GrumpkinAffinePoint[] memory R_vec = new Grumpkin.GrumpkinAffinePoint[](2); + R_vec[0] = Grumpkin.GrumpkinAffinePoint( + 0x2f1727ea1ac3c3862caa797261db6a9b0714f7d8e65adb97e5f4da457044ccfe, + 0x185e59b83d3e903a804f6dcfd68a3e34b5cb9d048aca562e7e89c77b5c7db13e + ); + R_vec[1] = Grumpkin.GrumpkinAffinePoint( + 0x08adac48b78bbb3435da3efc7162332b5693f5db927e184c0d1faaeaaf60fdbd, + 0x1770ed9ec1f5ed7815a86ec6a5acc1b66d6c89d9bbbb53a2663ce292f7fe48b0 + ); + + uint256 a_hat = 0x144237bc694bfa4f625dab1f8bfc854e3e7b9a612027e16bcd840383d088e190; + + // InnerProductInstance + Grumpkin.GrumpkinAffinePoint memory commitment = Grumpkin.GrumpkinAffinePoint( + 0x1e7268591a2b38be3ff689fe1eb31600f9161a2163a08ee9842d458ac0bddf05, + 0x1f3070c0592c3f0135e1aba5100d43785490023f9536025b119bf9c0f96d5281 + ); + uint256 eval = 0x2514662a7e8e9a7a4ab7ea7c8e6a3423e7a47fca5105e6f3264d20d88e6d33bf; + + ipa_computations(IpaInput(ck_v, ck_s, point, L_vec, R_vec, commitment, eval, a_hat)); + } +} From b2c12a08f9a53099b49aee355fe23e04a0d9d7dd Mon Sep 17 00:00:00 2001 From: Artem Storozhuk Date: Wed, 7 Feb 2024 15:53:13 +0200 Subject: [PATCH 3/3] IPA over Grumpkin implementation as a library --- src/blocks/IpaPcs.sol | 324 ++++++++++++++++++++++++++++ src/blocks/KeccakTranscript.sol | 84 ++++++++ test/ipa.t.sol | 335 ++--------------------------- test/keccak-transcript-tests.t.sol | 242 +++++++++++++++++++++ 4 files changed, 673 insertions(+), 312 deletions(-) create mode 100644 src/blocks/IpaPcs.sol diff --git a/src/blocks/IpaPcs.sol b/src/blocks/IpaPcs.sol new file mode 100644 index 0000000..61e4883 --- /dev/null +++ b/src/blocks/IpaPcs.sol @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.16; + +import "src/blocks/grumpkin/Grumpkin.sol"; +import "src/blocks/KeccakTranscript.sol"; + +library InnerProductArgument { + struct IpaInputGrumpkin { + Grumpkin.GrumpkinAffinePoint[] ck_v; + Grumpkin.GrumpkinAffinePoint[] ck_s; + uint256[] point; + Grumpkin.GrumpkinAffinePoint[] L_vec; + Grumpkin.GrumpkinAffinePoint[] R_vec; + Grumpkin.GrumpkinAffinePoint commitment; + uint256 eval; + uint256 a_hat; + } + + struct InstanceGrumpkin { + Grumpkin.GrumpkinAffinePoint comm_a_vec; + uint256[] b_vec; + uint256 c; + } + + struct R { + uint256[] r_vec; + uint256[] r_vec_squared; + uint256[] r_vec_inversed; + uint256[] r_vec_inversed_squared; + } + + struct P_hat_right_input { + uint256 n; + R r_vectors; + Grumpkin.GrumpkinAffinePoint[] ck1; + uint256[] b_vec; + uint256 a_hat; + Grumpkin.GrumpkinAffinePoint ck_c; + } + + function batchInvert(uint256[] memory r_vec, uint256 modulus) private view returns (uint256[] memory) { + uint256[] memory products = new uint256[](r_vec.length); + uint256 acc = 1; + uint256 index; + for (index = 0; index < r_vec.length; index++) { + products[index] = acc; + acc = mulmod(acc, r_vec[index], modulus); + } + + acc = Field.invert(acc, modulus); + + uint256[] memory inversed = new uint256[](r_vec.length); + + uint256 tmp; + for (index = 0; index < r_vec.length; index++) { + tmp = mulmod(acc, r_vec[r_vec.length - index - 1], modulus); + inversed[r_vec.length - index - 1] = mulmod(products[r_vec.length - index - 1], acc, modulus); + acc = tmp; + } + + return inversed; + } + + function compute_r_based_values(uint256[] memory r_vec, uint256 modulus) private view returns (R memory) { + uint256[] memory r_vec_squared = new uint256[](r_vec.length); + uint256 index; + for (index = 0; index < r_vec.length; index++) { + r_vec_squared[index] = mulmod(r_vec[index], r_vec[index], modulus); + } + + uint256[] memory r_vec_inversed = batchInvert(r_vec, modulus); + + uint256[] memory r_vec_inversed_squared = new uint256[](r_vec.length); + for (index = 0; index < r_vec.length; index++) { + r_vec_inversed_squared[index] = mulmod(r_vec_inversed[index], r_vec_inversed[index], modulus); + } + return R(r_vec, r_vec_squared, r_vec_inversed, r_vec_inversed_squared); + } + + function split_at(Grumpkin.GrumpkinAffinePoint[] memory ck, uint256 n) + private + pure + returns (Grumpkin.GrumpkinAffinePoint[] memory, Grumpkin.GrumpkinAffinePoint[] memory) + { + require(n <= ck.length, "[split_at] unexpected n"); + + Grumpkin.GrumpkinAffinePoint[] memory ck1 = new Grumpkin.GrumpkinAffinePoint[](n); + Grumpkin.GrumpkinAffinePoint[] memory ck2 = new Grumpkin.GrumpkinAffinePoint[](n); + uint256 ck_index = 0; + for (uint256 i = 0; i < n; i++) { + ck1[i] = ck[ck_index]; + ck_index++; + } + for (uint256 i = n; i < ck.length; i++) { + ck2[i] = ck[ck_index]; + ck_index++; + } + + return (ck1, ck2); + } + + function scale(Grumpkin.GrumpkinAffinePoint[] memory ck_c, uint256 r) + private + view + returns (Grumpkin.GrumpkinAffinePoint memory) + { + require(ck_c.length == 1, "[scale] unexpected ck_c"); + return Grumpkin.scalarMul(ck_c[0], r); + } + + function inner_product_inner(uint256[] memory c) private pure returns (uint256[] memory) { + if (c.length == 1) { + return c; + } + uint256[] memory c_inner = new uint256[](c.length / 2); + for (uint256 index = 0; index < c_inner.length; index++) { + c_inner[index] = addmod(c[2 * index], c[2 * index + 1], Grumpkin.P_MOD); + } + return inner_product_inner(c_inner); + } + + function inner_product(uint256[] memory a, uint256[] memory b) private pure returns (uint256) { + require(a.length == b.length); + uint256[] memory c = new uint256[](a.length); + uint256 index; + for (index = 0; index < a.length; index++) { + c[index] = mulmod(a[index], b[index], Grumpkin.P_MOD); + } + + c = inner_product_inner(c); + return c[0]; + } + + function get_pos_value(uint256 i) private pure returns (uint256) { + require(i >= 1, "[get_pos_value], i < 1"); + require(i <= 16, "[get_pos_value], i > 16"); + uint256[] memory result = new uint256[](16); + result[0] = 0; + result[1] = 1; + result[2] = 1; + result[3] = 2; + result[4] = 2; + result[5] = 2; + result[6] = 2; + result[7] = 3; + result[8] = 3; + result[9] = 3; + result[10] = 3; + result[11] = 3; + result[12] = 3; + result[13] = 3; + result[14] = 3; + result[15] = 4; + return result[i - 1]; + } + + function compute_P_hat_right(P_hat_right_input memory input) + private + returns (Grumpkin.GrumpkinAffinePoint memory) + { + uint256[] memory s = new uint256[](input.n); + + uint256 v = 1; + uint256 index; + for (index = 0; index < input.r_vectors.r_vec_inversed.length; index++) { + v = mulmod(v, input.r_vectors.r_vec_inversed[index], Grumpkin.P_MOD); + } + s[0] = v; + + uint256 pos_in_r; + uint256 r_square_length = input.r_vectors.r_vec_squared.length; + for (index = 1; index < input.n; index++) { + pos_in_r = get_pos_value(index); + s[index] = mulmod( + s[index - (1 << pos_in_r)], + input.r_vectors.r_vec_squared[r_square_length - 1 - pos_in_r], + Grumpkin.P_MOD + ); + } + + uint256 b_hat = inner_product(input.b_vec, s); + Grumpkin.GrumpkinAffinePoint memory ck_hat = Grumpkin.multiScalarMul(input.ck1, s); + + Grumpkin.GrumpkinAffinePoint[] memory bases = new Grumpkin.GrumpkinAffinePoint[](2); + bases[0] = ck_hat; + bases[1] = input.ck_c; + + uint256[] memory scalars = new uint256[](2); + scalars[0] = input.a_hat; + scalars[1] = mulmod(input.a_hat, b_hat, Grumpkin.P_MOD); + + return Grumpkin.multiScalarMul(bases, scalars); + } + + function compute_P_hat_left(IpaInputGrumpkin memory input, R memory r_vec, Grumpkin.GrumpkinAffinePoint memory ck_c) + private + returns (Grumpkin.GrumpkinAffinePoint memory) + { + Grumpkin.GrumpkinAffinePoint memory P = Grumpkin.add(input.commitment, Grumpkin.scalarMul(ck_c, input.eval)); + + uint256 msm_len = input.L_vec.length + input.R_vec.length + 1; + + uint256 msm_index = 0; + uint256[] memory scalars = new uint256[](msm_len); + for (uint256 index = 0; index < r_vec.r_vec_squared.length; index++) { + scalars[msm_index] = r_vec.r_vec_squared[index]; + msm_index++; + } + for (uint256 index = 0; index < r_vec.r_vec_inversed_squared.length; index++) { + scalars[msm_index] = r_vec.r_vec_inversed_squared[index]; + msm_index++; + } + scalars[msm_index] = 0x01; + + msm_index = 0; + Grumpkin.GrumpkinAffinePoint[] memory bases = new Grumpkin.GrumpkinAffinePoint[](msm_len); + for (uint256 index = 0; index < input.L_vec.length; index++) { + bases[msm_index] = input.L_vec[index]; + msm_index++; + } + for (uint256 index = 0; index < input.R_vec.length; index++) { + bases[msm_index] = input.R_vec[index]; + msm_index++; + } + bases[msm_index] = P; + + return Grumpkin.multiScalarMul(bases, scalars); + } + + function compute_P_hat_right( + uint256 b_hat, + uint256 a_hat, + Grumpkin.GrumpkinAffinePoint memory ck_hat, + Grumpkin.GrumpkinAffinePoint memory ck_c + ) private returns (Grumpkin.GrumpkinAffinePoint memory) { + Grumpkin.GrumpkinAffinePoint[] memory bases = new Grumpkin.GrumpkinAffinePoint[](2); + bases[0] = ck_hat; + bases[1] = ck_c; + + uint256[] memory scalars = new uint256[](2); + scalars[0] = a_hat; + scalars[1] = mulmod(a_hat, b_hat, Grumpkin.P_MOD); + + return Grumpkin.multiScalarMul(bases, scalars); + } + + function verifyGrumpkin(IpaInputGrumpkin memory input, KeccakTranscriptLib.KeccakTranscript memory transcript) + public + returns (bool) + { + uint256 n = 2 ** input.point.length; + + uint256[] memory b_vec = EqPolynomialLib.evals(input.point, Grumpkin.P_MOD, Grumpkin.negateBase); + (Grumpkin.GrumpkinAffinePoint[] memory ck1,) = split_at(input.ck_v, b_vec.length); + + // b"IPA" in Rust + uint8[] memory label = new uint8[](3); + label[0] = 0x49; + label[1] = 0x50; + label[2] = 0x41; + + transcript = KeccakTranscriptLib.dom_sep(transcript, label); + + if (b_vec.length != n) { + revert("NovaError::InvalidInputLength"); + } + if (n != 1 << input.L_vec.length) { + revert("NovaError::InvalidInputLength"); + } + if (input.L_vec.length != input.R_vec.length) { + revert("NovaError::InvalidInputLength"); + } + if (input.L_vec.length >= 32) { + revert("NovaError::InvalidInputLength"); + } + + // b"U" in Rust + label = new uint8[](1); + label[0] = 0x55; + + transcript = + KeccakTranscriptLib.absorb(transcript, label, InstanceGrumpkin(input.commitment, b_vec, input.eval)); + + // b"r" in Rust + label = new uint8[](1); + label[0] = 0x72; + uint256 r; + (transcript, r) = KeccakTranscriptLib.squeeze(transcript, ScalarFromUniformLib.curveGrumpkin(), label); + + uint256[] memory r_vec = new uint256[](input.L_vec.length); + for (uint256 index = 0; index < r_vec.length; index++) { + // b"L" in Rust + label[0] = 0x4c; + transcript = KeccakTranscriptLib.absorb(transcript, label, input.L_vec[index].x); + + // b"R" in Rust + label[0] = 0x52; + transcript = KeccakTranscriptLib.absorb(transcript, label, input.R_vec[index].x); + + // b"r" in Rust + label[0] = 0x72; + (transcript, r_vec[index]) = + KeccakTranscriptLib.squeeze(transcript, ScalarFromUniformLib.curveGrumpkin(), label); + } + + R memory r_vectors = compute_r_based_values(r_vec, Grumpkin.P_MOD); + + Grumpkin.GrumpkinAffinePoint memory ck_c = scale(input.ck_s, r); + + Grumpkin.GrumpkinAffinePoint memory P_hat_right = + compute_P_hat_right(P_hat_right_input(n, r_vectors, ck1, b_vec, input.a_hat, ck_c)); + + Grumpkin.GrumpkinAffinePoint memory P_hat_left = compute_P_hat_left(input, r_vectors, ck_c); + + if (P_hat_right.x != P_hat_left.x) { + return false; + } + if (P_hat_right.y != P_hat_left.y) { + return false; + } + + return true; + } +} diff --git a/src/blocks/KeccakTranscript.sol b/src/blocks/KeccakTranscript.sol index df397d8..8962bc3 100644 --- a/src/blocks/KeccakTranscript.sol +++ b/src/blocks/KeccakTranscript.sol @@ -5,6 +5,7 @@ import "src/blocks/pasta/Vesta.sol"; import "src/blocks/pasta/Pallas.sol"; import "src/blocks/grumpkin/Bn256.sol"; import "src/blocks/grumpkin/Grumpkin.sol"; +import "src/blocks/IpaPcs.sol"; import "src/Utilities.sol"; /** @@ -1259,6 +1260,41 @@ library KeccakTranscriptLib { return absorb(keccak, label, output); } + /** + * @notice Absorbs a Grumpkin affine point into the Keccak transcript. + * @dev Converts the affine point's coordinates to bytes and handles the infinity case. + * @param keccak The existing Keccak transcript. + * @param label A byte array label used in the absorption process. + * @param point The Grumpkin affine point to be absorbed. + * @return The updated Keccak transcript after absorption. + */ + function absorb(KeccakTranscript memory keccak, uint8[] memory label, Grumpkin.GrumpkinAffinePoint memory point) + public + returns (KeccakTranscript memory) + { + uint8[] memory output = new uint8[](32 * 2 + 1); + uint256 index = 0; + // write x coordinate + for (uint256 i = 0; i < 32; i++) { + output[index] = uint8(bytes1(bytes32(point.x)[31 - i])); + index++; + } + // write y coordinate + for (uint256 i = 0; i < 32; i++) { + output[index] = uint8(bytes1(bytes32(point.y)[31 - i])); + index++; + } + + // write byte indicating whether point is at infinity + if (Grumpkin.is_identity(point)) { + output[index] = 0x00; + } else { + output[index] = 0x01; + } + + return absorb(keccak, label, output); + } + /** * @notice Absorbs an array of Bn256 affine points into the Keccak transcript. * @dev Converts each point's coordinates to bytes and handles the infinity case for each point. @@ -1298,6 +1334,54 @@ library KeccakTranscriptLib { return absorb(keccak, label, output); } + /** + * @notice Absorbs an instance of InnerProductArgument into the Keccak transcript. + * @dev Writes 'comm_a_vec', 'c' fields of InnerProductArgument into the transcript + * @param keccak The existing Keccak transcript. + * @param label A byte array label used in the absorption process. + * @param ipa_input An instance of InnerProductArgument to be absorbed. + * @return The updated Keccak transcript after absorbing the points. + */ + function absorb( + KeccakTranscript memory keccak, + uint8[] memory label, + InnerProductArgument.InstanceGrumpkin memory ipa_input + ) public 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 + output_length += 32; // c + + uint8[] memory output = new uint8[](output_length); + uint256 index = 0; + + // write x coordinate + for (uint256 i = 0; i < 32; i++) { + output[index] = uint8(bytes1(bytes32(ipa_input.comm_a_vec.x)[31 - i])); + index++; + } + // write y coordinate + for (uint256 i = 0; i < 32; i++) { + output[index] = uint8(bytes1(bytes32(ipa_input.comm_a_vec.y)[31 - i])); + index++; + } + + // write byte indicating whether point is at infinity + if (Grumpkin.is_identity(ipa_input.comm_a_vec)) { + output[index] = 0x00; + } else { + output[index] = 0x01; + } + index++; + + for (uint256 i = 0; i < 32; i++) { + output[index] = uint8(bytes1(bytes32(ipa_input.c)[31 - i])); + index++; + } + + return absorb(keccak, label, output); + } + /** * @notice Generates a scalar from the Keccak transcript using a specified curve and updates the transcript. * @dev Updates the Keccak transcript state and computes a scalar value based on the selected elliptic curve. diff --git a/test/ipa.t.sol b/test/ipa.t.sol index 67cc30d..af5fe8e 100644 --- a/test/ipa.t.sol +++ b/test/ipa.t.sol @@ -5,319 +5,10 @@ import "@std/Test.sol"; import "src/blocks/grumpkin/Grumpkin.sol"; import "src/blocks/EqPolynomial.sol"; import "src/Utilities.sol"; - -// TODO: -// 1) Refactor and expose IPA as a library -// 2) Detect what is the max length of polynomial which can be supported without OutOfGas +import "src/blocks/IpaPcs.sol"; contract IpaTest is Test { - struct R { - uint256[] r_vec; - uint256[] r_vec_squared; - uint256[] r_vec_inversed; - uint256[] r_vec_inversed_squared; - } - - function split_at(Grumpkin.GrumpkinAffinePoint[] memory ck, uint256 n) - public - returns (Grumpkin.GrumpkinAffinePoint[] memory, Grumpkin.GrumpkinAffinePoint[] memory) - { - require(n <= ck.length, "[split_at] unexpected n"); - - Grumpkin.GrumpkinAffinePoint[] memory ck1 = new Grumpkin.GrumpkinAffinePoint[](n); - Grumpkin.GrumpkinAffinePoint[] memory ck2 = new Grumpkin.GrumpkinAffinePoint[](n); - uint256 ck_index = 0; - for (uint256 i = 0; i < n; i++) { - ck1[i] = ck[ck_index]; - ck_index++; - } - for (uint256 i = n; i < ck.length; i++) { - ck2[i] = ck[ck_index]; - ck_index++; - } - - return (ck1, ck2); - } - - function scale(Grumpkin.GrumpkinAffinePoint[] memory ck_c, uint256 r) - public - returns (Grumpkin.GrumpkinAffinePoint[] memory) - { - Grumpkin.GrumpkinAffinePoint[] memory scaled = new Grumpkin.GrumpkinAffinePoint[](ck_c.length); - for (uint256 index = 0; index < ck_c.length; index++) { - scaled[index] = Grumpkin.scalarMul(ck_c[index], r); - } - return scaled; - } - - function batchInvert(uint256[] memory r_vec) public returns (uint256[] memory) { - uint256[] memory products = new uint256[](r_vec.length); - uint256 acc = 1; - uint256 index; - for (index = 0; index < r_vec.length; index++) { - products[index] = acc; - acc = mulmod(acc, r_vec[index], Grumpkin.P_MOD); - } - - acc = Field.invert(acc, Grumpkin.P_MOD); - - uint256[] memory inversed = new uint256[](r_vec.length); - - uint256 tmp; - for (index = 0; index < r_vec.length; index++) { - tmp = mulmod(acc, r_vec[r_vec.length - index - 1], Grumpkin.P_MOD); - inversed[r_vec.length - index - 1] = mulmod(products[r_vec.length - index - 1], acc, Grumpkin.P_MOD); - acc = tmp; - } - - return inversed; - } - - function compute_P( - Grumpkin.GrumpkinAffinePoint memory commitment, - Grumpkin.GrumpkinAffinePoint[] memory ck_s, - uint256 eval, - uint256 r - ) public returns (Grumpkin.GrumpkinAffinePoint memory) { - Grumpkin.GrumpkinAffinePoint[] memory ck_s_scaled = scale(ck_s, r); - return Grumpkin.add(commitment, Grumpkin.scalarMul(ck_s_scaled[0], eval)); - } - - function compute_r_based_values( - Grumpkin.GrumpkinAffinePoint[] memory ck_s, - uint256 eval, - Grumpkin.GrumpkinAffinePoint[] memory L_vec, - Grumpkin.GrumpkinAffinePoint memory commitment, - uint256 r - ) public returns (R memory) { - /* - // compute a vector of public coins using self.L_vec and self.R_vec - let r = (0..self.L_vec.len()) - .map(|i| { - transcript.absorb(b"L", &self.L_vec[i]); - transcript.absorb(b"R", &self.R_vec[i]); - transcript.squeeze(b"r") - }) - .collect::, NovaError>>()?; - */ - - uint256[] memory r_vec = new uint256[](L_vec.length); - r_vec[0] = 0x1a6d268ae789320863b0760561bf92cc5539e81239b97cea90542b5163cdd1c7; - r_vec[1] = 0x0fa5b3f5911542c0976b82385f398e58e7fadf188c80638c541697005a97ab6d; - - uint256[] memory r_vec_squared = new uint256[](L_vec.length); - - uint256 index; - for (index = 0; index < r_vec.length; index++) { - r_vec_squared[index] = mulmod(r_vec[index], r_vec[index], Grumpkin.P_MOD); - } - - uint256[] memory r_vec_inversed = batchInvert(r_vec); - - uint256[] memory r_vec_inversed_squared = new uint256[](L_vec.length); - for (index = 0; index < r_vec.length; index++) { - r_vec_inversed_squared[index] = mulmod(r_vec_inversed[index], r_vec_inversed[index], Grumpkin.P_MOD); - } - return R (r_vec, r_vec_squared, r_vec_inversed, r_vec_inversed_squared); - } - - function get_pos_value(uint256 i) public pure returns (uint256) { - require(i >= 1, "[get_pos_value], i < 1"); - require(i <= 16, "[get_pos_value], i > 16"); - uint256[] memory result = new uint256[](16); - result[0] = 0; - result[1] = 1; - result[2] = 1; - result[3] = 2; - result[4] = 2; - result[5] = 2; - result[6] = 2; - result[7] = 3; - result[8] = 3; - result[9] = 3; - result[10] = 3; - result[11] = 3; - result[12] = 3; - result[13] = 3; - result[14] = 3; - result[15] = 4; - return result[i - 1]; - } - - function compute_s(uint256 n, uint256[] memory r_inverse, uint256[] memory r_square, uint256 L_vec_len) - public - returns (uint256[] memory) - { - uint256[] memory s = new uint256[](n); - - uint256 v = 1; - uint256 index; - for (index = 0; index < r_inverse.length; index++) { - v = mulmod(v, r_inverse[index], Grumpkin.P_MOD); - } - s[0] = v; - - uint256 pos_in_r; - for (index = 1; index < n; index++) { - pos_in_r = get_pos_value(index); - s[index] = mulmod(s[index - (1 << pos_in_r)], r_square[L_vec_len - 1 - pos_in_r], Grumpkin.P_MOD); - } - - return s; - } - - function inner_product_inner(uint256[] memory c) public returns (uint256[] memory) { - if (c.length == 1) { - return c; - } - uint256[] memory c_inner = new uint256[](c.length / 2); - for (uint256 index = 0; index < c_inner.length; index++) { - c_inner[index] = addmod(c[2 * index], c[2 * index + 1], Grumpkin.P_MOD); - } - return inner_product_inner(c_inner); - } - - function inner_product(uint256[] memory a, uint256[] memory b) public returns (uint256) { - require(a.length == b.length); - uint256[] memory c = new uint256[](a.length); - uint256 index; - for (index = 0; index < a.length; index++) { - c[index] = mulmod(a[index], b[index], Grumpkin.P_MOD); - } - - c = inner_product_inner(c); - return c[0]; - } - - function compute_P_hat_left( - Grumpkin.GrumpkinAffinePoint[] memory L_vec, - Grumpkin.GrumpkinAffinePoint[] memory R_vec, - Grumpkin.GrumpkinAffinePoint memory P, - R memory r_vec - ) public returns (Grumpkin.GrumpkinAffinePoint memory){ - uint256 msm_len = L_vec.length + R_vec.length + 1; - - uint256 msm_index = 0; - uint256[] memory scalars = new uint256[](msm_len); - for (uint256 index = 0; index < r_vec.r_vec_squared.length; index++) { - scalars[msm_index] = r_vec.r_vec_squared[index]; - msm_index++; - } - for (uint256 index = 0; index < r_vec.r_vec_inversed_squared.length; index++) { - scalars[msm_index] = r_vec.r_vec_inversed_squared[index]; - msm_index++; - } - scalars[msm_index] = 0x01; - - msm_index = 0; - Grumpkin.GrumpkinAffinePoint[] memory bases = new Grumpkin.GrumpkinAffinePoint[](msm_len); - for (uint256 index = 0; index < L_vec.length; index++) { - bases[msm_index] = L_vec[index]; - msm_index++; - } - for (uint256 index = 0; index < R_vec.length; index++) { - bases[msm_index] = R_vec[index]; - msm_index++; - } - bases[msm_index] = P; - - return Grumpkin.multiScalarMul(bases, scalars); - } - - function compute_P_hat_right( - uint256 b_hat, - uint256 a_hat, - Grumpkin.GrumpkinAffinePoint memory ck_hat, - Grumpkin.GrumpkinAffinePoint memory ck_c - ) public returns (Grumpkin.GrumpkinAffinePoint memory){ - Grumpkin.GrumpkinAffinePoint[] memory bases = new Grumpkin.GrumpkinAffinePoint[](2); - bases[0] = ck_hat; - bases[1] = ck_c; - - uint256[] memory scalars = new uint256[](2); - scalars[0] = a_hat; - scalars[1] = mulmod(a_hat, b_hat, Grumpkin.P_MOD); - - return Grumpkin.multiScalarMul(bases, scalars); - } - - function ipa_computations_inner( - uint256 n, - Grumpkin.GrumpkinAffinePoint memory commitment, - Grumpkin.GrumpkinAffinePoint[] memory ck_s, - uint256 eval, - Grumpkin.GrumpkinAffinePoint[] memory L_vec, - Grumpkin.GrumpkinAffinePoint[] memory ck1, - uint256[] memory b_vec, - uint256 r - ) public returns (R memory, uint256, Grumpkin.GrumpkinAffinePoint memory){ - - R memory r_vectors = compute_r_based_values(ck_s, eval, L_vec, commitment, r); - - uint256[] memory s = compute_s(n, r_vectors.r_vec_inversed, r_vectors.r_vec_squared, L_vec.length); - - Grumpkin.GrumpkinAffinePoint memory ck_hat = Grumpkin.multiScalarMul(ck1, s); - - uint256 b_hat = inner_product(b_vec, s); - - return (r_vectors, b_hat, ck_hat); - } - - struct IpaInput{ - Grumpkin.GrumpkinAffinePoint[] ck_v; - Grumpkin.GrumpkinAffinePoint[] ck_s; - uint256[] point; - Grumpkin.GrumpkinAffinePoint[] L_vec; - Grumpkin.GrumpkinAffinePoint[] R_vec; - Grumpkin.GrumpkinAffinePoint commitment; - uint256 eval; - uint256 a_hat; - } - - - function ipa_computations( - IpaInput memory input - ) public { - uint256 n = 2 ** input.point.length; - - uint256[] memory b_vec = EqPolynomialLib.evals(input.point, Grumpkin.P_MOD, Grumpkin.negateBase); - (Grumpkin.GrumpkinAffinePoint[] memory ck1,) = split_at(input.ck_v, b_vec.length); - - // transcript.dom_sep(Self::protocol_name()); - - if (b_vec.length != n) { - revert("NovaError::InvalidInputLength"); - } - if (n != 1 << input.L_vec.length) { - revert("NovaError::InvalidInputLength"); - } - if (input.L_vec.length != input.R_vec.length) { - revert("NovaError::InvalidInputLength"); - } - if (input.L_vec.length >= 32) { - revert("NovaError::InvalidInputLength"); - } - - //transcript.absorb(b"U", U); - //let r = transcript.squeeze(b"r")?; - - uint256 r = 0x26dbed0c0b99929de85b3c8d4c501f5216b0c3fa951b92afb75b6044c7b6885d; - - (R memory r_vectors, uint256 b_hat, Grumpkin.GrumpkinAffinePoint memory ck_hat) = ipa_computations_inner(n, input.commitment, input.ck_s, input.eval, input.L_vec, ck1, b_vec, r); - - Grumpkin.GrumpkinAffinePoint memory P = compute_P(input.commitment, input.ck_s, input.eval, r); - - Grumpkin.GrumpkinAffinePoint memory P_hat_left = compute_P_hat_left(input.L_vec, input.R_vec, P, r_vectors); - - Grumpkin.GrumpkinAffinePoint[] memory ck_c = scale(input.ck_s, r); - - Grumpkin.GrumpkinAffinePoint memory P_hat_right = compute_P_hat_right(b_hat, input.a_hat, ck_hat, ck_c[0]); - - assertEq(P_hat_left.x, P_hat_right.x); - assertEq(P_hat_left.y, P_hat_right.y); - } - - function testDebug() public { + function composeIpaInput() public pure returns (InnerProductArgument.IpaInputGrumpkin memory) { Grumpkin.GrumpkinAffinePoint[] memory ck_v = new Grumpkin.GrumpkinAffinePoint[](4); ck_v[0] = Grumpkin.GrumpkinAffinePoint( 0x15afa1c1de43e186ee615ee76389d1ca9de572d426869ab062a03f1ba65808a2, @@ -374,7 +65,27 @@ contract IpaTest is Test { 0x1f3070c0592c3f0135e1aba5100d43785490023f9536025b119bf9c0f96d5281 ); uint256 eval = 0x2514662a7e8e9a7a4ab7ea7c8e6a3423e7a47fca5105e6f3264d20d88e6d33bf; + return InnerProductArgument.IpaInputGrumpkin(ck_v, ck_s, point, L_vec, R_vec, commitment, eval, a_hat); + } + + function testIpaGrumpkinVerification_2_Variables() public { + InnerProductArgument.IpaInputGrumpkin memory input = composeIpaInput(); + assertTrue(InnerProductArgument.verifyGrumpkin(input, getTranscript())); + } + + function getTranscript() public pure returns (KeccakTranscriptLib.KeccakTranscript memory) { + // b"TestEval" in Rust + uint8[] memory label = new uint8[](8); + label[0] = 0x54; + label[1] = 0x65; + label[2] = 0x73; + label[3] = 0x74; + label[4] = 0x45; + label[5] = 0x76; + label[6] = 0x61; + label[7] = 0x6c; - ipa_computations(IpaInput(ck_v, ck_s, point, L_vec, R_vec, commitment, eval, a_hat)); + KeccakTranscriptLib.KeccakTranscript memory keccak_transcript = KeccakTranscriptLib.instantiate(label); + return keccak_transcript; } } diff --git a/test/keccak-transcript-tests.t.sol b/test/keccak-transcript-tests.t.sol index 084d088..8f41f2d 100644 --- a/test/keccak-transcript-tests.t.sol +++ b/test/keccak-transcript-tests.t.sol @@ -79,6 +79,14 @@ contract KeccakTranscriptContractTest is Test { uint256 scalarVesta = ScalarFromUniformLib.scalarFromUniform(uniform, ScalarFromUniformLib.curveVesta()); uint256 expectedVesta = 0x0c34755d6b4566f930b8371afd2e4818ae49878a10527fd4443dbec811582d43; assertEq(scalarVesta, expectedVesta); + + uint256 scalarBn256 = ScalarFromUniformLib.scalarFromUniform(uniform, ScalarFromUniformLib.curveBn256()); + uint256 expectedBn256 = 0x02c64acce6367d59e231e9333e02a497b617fe0fffe49e395b8601550d16f993; + assertEq(scalarBn256, expectedBn256); + + uint256 scalarGrumpkin = ScalarFromUniformLib.scalarFromUniform(uniform, ScalarFromUniformLib.curveGrumpkin()); + uint256 expectedGrumpkin = 0x04876bd2613243de5a5e8cce6f8e3d1e3adbb695c1b596ed476b96711bec2313; + assertEq(scalarGrumpkin, expectedGrumpkin); } // Following test has been ported: https://github.com/lurk-lab/Nova/blob/solidity-verifier-pp-spartan/src/provider/keccak.rs#L138 @@ -180,4 +188,238 @@ contract KeccakTranscriptContractTest is Test { expected = Field.reverse256(0xe3585da385704879ec03ef201dbf228e7b227a5af709f83f3ed5f92a5037d633); assertEq(output, expected); } + + function testKeccakTranscriptBn256() public { + uint8[] memory input = new uint8[](4); // b"test" in Rust + input[0] = 0x74; + input[1] = 0x65; + input[2] = 0x73; + input[3] = 0x74; + + KeccakTranscriptLib.KeccakTranscript memory transcript = KeccakTranscriptLib.instantiate(input); + + uint256 input1 = 2; + uint8[] memory label1 = new uint8[](2); // b"s1" in Rust + label1[0] = 0x73; + label1[1] = 0x31; + + uint256 input2 = 5; + uint8[] memory label2 = new uint8[](2); // b"s2" in Rust + label2[0] = 0x73; + label2[1] = 0x32; + + transcript = KeccakTranscriptLib.absorb(transcript, label1, input1); + transcript = KeccakTranscriptLib.absorb(transcript, label2, input2); + + uint8[] memory squeezeLabel = new uint8[](2); // b"c1" in Rust + squeezeLabel[0] = 0x63; + squeezeLabel[1] = 0x31; + + ScalarFromUniformLib.Curve curve = ScalarFromUniformLib.curveBn256(); + uint256 output; + (transcript, output) = KeccakTranscriptLib.squeeze(transcript, curve, squeezeLabel); + + uint256 expected = Field.reverse256(0x9fb71e3b74bfd0b60d97349849b895595779a240b92a6fae86bd2812692b6b0e); + assertEq(output, expected); + + uint256 s3 = 128; + uint8[] memory label3 = new uint8[](2); // b"s3" in Rust + label3[0] = 0x73; + label3[1] = 0x33; + + transcript = KeccakTranscriptLib.absorb(transcript, label3, s3); + + // b"c2" in Rust + squeezeLabel[0] = 0x63; + squeezeLabel[1] = 0x32; + (, output) = KeccakTranscriptLib.squeeze(transcript, curve, squeezeLabel); + + expected = Field.reverse256(0xbfd4c50b7d6317e9267d5d65c985eb455a3561129c0b3beef79bfc8461a84f18); + assertEq(output, expected); + } + + function testKeccakTranscriptGrumpkin() public { + uint8[] memory input = new uint8[](4); // b"test" in Rust + input[0] = 0x74; + input[1] = 0x65; + input[2] = 0x73; + input[3] = 0x74; + + KeccakTranscriptLib.KeccakTranscript memory transcript = KeccakTranscriptLib.instantiate(input); + + uint256 input1 = 2; + uint8[] memory label1 = new uint8[](2); // b"s1" in Rust + label1[0] = 0x73; + label1[1] = 0x31; + + uint256 input2 = 5; + uint8[] memory label2 = new uint8[](2); // b"s2" in Rust + label2[0] = 0x73; + label2[1] = 0x32; + + transcript = KeccakTranscriptLib.absorb(transcript, label1, input1); + transcript = KeccakTranscriptLib.absorb(transcript, label2, input2); + + uint8[] memory squeezeLabel = new uint8[](2); // b"c1" in Rust + squeezeLabel[0] = 0x63; + squeezeLabel[1] = 0x31; + + ScalarFromUniformLib.Curve curve = ScalarFromUniformLib.curveGrumpkin(); + uint256 output; + (transcript, output) = KeccakTranscriptLib.squeeze(transcript, curve, squeezeLabel); + + uint256 expected = Field.reverse256(0xd12b7cd39aa2fc3af9bfd4f1dfd8ffa6498f57e35021675f4227d448b5540922); + assertEq(output, expected); + + uint256 s3 = 128; + uint8[] memory label3 = new uint8[](2); // b"s3" in Rust + label3[0] = 0x73; + label3[1] = 0x33; + + transcript = KeccakTranscriptLib.absorb(transcript, label3, s3); + + // b"c2" in Rust + squeezeLabel[0] = 0x63; + squeezeLabel[1] = 0x32; + (, output) = KeccakTranscriptLib.squeeze(transcript, curve, squeezeLabel); + + expected = Field.reverse256(0xfb894998c48dd652b32a109d3d2e579a0878f7f5cacfb572dc21666b9cfe221a); + assertEq(output, expected); + } + + function testKeccakTranscriptIPAGrumpkinComputations() public { + // b"TestEval" in Rust + uint8[] memory label = new uint8[](8); + label[0] = 0x54; + label[1] = 0x65; + label[2] = 0x73; + label[3] = 0x74; + label[4] = 0x45; + label[5] = 0x76; + label[6] = 0x61; + label[7] = 0x6c; + KeccakTranscriptLib.KeccakTranscript memory transcript = KeccakTranscriptLib.instantiate(label); + + // b"IPA" in Rust + label = new uint8[](3); + label[0] = 0x49; + label[1] = 0x50; + label[2] = 0x41; + + transcript = KeccakTranscriptLib.dom_sep(transcript, label); + + // b"U" in Rust + label = new uint8[](1); + label[0] = 0x55; + + uint8[] memory U_bytes = new uint8[](97); + U_bytes[0] = 0x05; + U_bytes[1] = 0xdf; + U_bytes[2] = 0xbd; + U_bytes[3] = 0xc0; + U_bytes[4] = 0x8a; + U_bytes[5] = 0x45; + U_bytes[6] = 0x2d; + U_bytes[7] = 0x84; + U_bytes[8] = 0xe9; + U_bytes[9] = 0x8e; + U_bytes[10] = 0xa0; + U_bytes[11] = 0x63; + U_bytes[12] = 0x21; + U_bytes[13] = 0x1a; + U_bytes[14] = 0x16; + U_bytes[15] = 0xf9; + U_bytes[16] = 0x00; + U_bytes[17] = 0x16; + U_bytes[18] = 0xb3; + U_bytes[19] = 0x1e; + U_bytes[20] = 0xfe; + U_bytes[21] = 0x89; + U_bytes[22] = 0xf6; + U_bytes[23] = 0x3f; + U_bytes[24] = 0xbe; + U_bytes[25] = 0x38; + U_bytes[26] = 0x2b; + U_bytes[27] = 0x1a; + U_bytes[28] = 0x59; + U_bytes[29] = 0x68; + U_bytes[30] = 0x72; + U_bytes[31] = 0x1e; + U_bytes[32] = 0x81; + U_bytes[33] = 0x52; + U_bytes[34] = 0x6d; + U_bytes[35] = 0xf9; + U_bytes[36] = 0xc0; + U_bytes[37] = 0xf9; + U_bytes[38] = 0x9b; + U_bytes[39] = 0x11; + U_bytes[40] = 0x5b; + U_bytes[41] = 0x02; + U_bytes[42] = 0x36; + U_bytes[43] = 0x95; + U_bytes[44] = 0x3f; + U_bytes[45] = 0x02; + U_bytes[46] = 0x90; + U_bytes[47] = 0x54; + U_bytes[48] = 0x78; + U_bytes[49] = 0x43; + U_bytes[50] = 0x0d; + U_bytes[51] = 0x10; + U_bytes[52] = 0xa5; + U_bytes[53] = 0xab; + U_bytes[54] = 0xe1; + U_bytes[55] = 0x35; + U_bytes[56] = 0x01; + U_bytes[57] = 0x3f; + U_bytes[58] = 0x2c; + U_bytes[59] = 0x59; + U_bytes[60] = 0xc0; + U_bytes[61] = 0x70; + U_bytes[62] = 0x30; + U_bytes[63] = 0x1f; + U_bytes[64] = 0x01; + U_bytes[65] = 0xbf; + U_bytes[66] = 0x33; + U_bytes[67] = 0x6d; + U_bytes[68] = 0x8e; + U_bytes[69] = 0xd8; + U_bytes[70] = 0x20; + U_bytes[71] = 0x4d; + U_bytes[72] = 0x26; + U_bytes[73] = 0xf3; + U_bytes[74] = 0xe6; + U_bytes[75] = 0x05; + U_bytes[76] = 0x51; + U_bytes[77] = 0xca; + U_bytes[78] = 0x7f; + U_bytes[79] = 0xa4; + U_bytes[80] = 0xe7; + U_bytes[81] = 0x23; + U_bytes[82] = 0x34; + U_bytes[83] = 0x6a; + U_bytes[84] = 0x8e; + U_bytes[85] = 0x7c; + U_bytes[86] = 0xea; + U_bytes[87] = 0xb7; + U_bytes[88] = 0x4a; + U_bytes[89] = 0x7a; + U_bytes[90] = 0x9a; + U_bytes[91] = 0x8e; + U_bytes[92] = 0x7e; + U_bytes[93] = 0x2a; + U_bytes[94] = 0x66; + U_bytes[95] = 0x14; + U_bytes[96] = 0x25; + + transcript = KeccakTranscriptLib.absorb(transcript, label, U_bytes); + + // b"r" in Rust + label = new uint8[](1); + label[0] = 0x72; + uint256 r; + (transcript, r) = KeccakTranscriptLib.squeeze(transcript, ScalarFromUniformLib.curveGrumpkin(), label); + + uint256 r_expected = 0x26dbed0c0b99929de85b3c8d4c501f5216b0c3fa951b92afb75b6044c7b6885d; + assertEq(r_expected, r); + } }