From a279fa92f637aaea2646141e9119631c095cfefa Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xKitsune@protonmail.com> Date: Thu, 7 Sep 2023 16:26:28 -0400 Subject: [PATCH 01/20] added optimized verifier contract --- src/Verifier.sol | 602 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 602 insertions(+) create mode 100644 src/Verifier.sol diff --git a/src/Verifier.sol b/src/Verifier.sol new file mode 100644 index 0000000..02b5c8b --- /dev/null +++ b/src/Verifier.sol @@ -0,0 +1,602 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/// @title Groth16 verifier template. +/// @author Remco Bloemen +/// @notice Supports verifying Groth16 proofs. Proofs can be in uncompressed +/// (256 bytes) and compressed (128 bytes) format. A view function is provided +/// to compress proofs. +/// @notice See for further explanation. +contract Verifier { + /// Some of the provided public input values are larger than the field modulus. + /// @dev Public input elements are not automatically reduced, as this is can be + /// a dangerous source of bugs. + error PublicInputNotInField(); + + /// The proof is invalid. + /// @dev This can mean that provided Groth16 proof points are not on their + /// curves, that pairing equation fails, or that the proof is not for the + /// provided public input. + error ProofInvalid(); + + // Addresses of precompiles + uint256 constant PRECOMPILE_MODEXP = 0x05; + uint256 constant PRECOMPILE_ADD = 0x06; + uint256 constant PRECOMPILE_MUL = 0x07; + uint256 constant PRECOMPILE_VERIFY = 0x08; + + // Base field Fp order P and scalar field Fr order R. + // For BN254 these are computed as follows: + // t = 4965661367192848881 + // P = 36⋅t⁴ + 36⋅t³ + 24⋅t² + 6⋅t + 1 + // R = 36⋅t⁴ + 36⋅t³ + 18⋅t² + 6⋅t + 1 + uint256 constant P = + 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; + uint256 constant R = + 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001; + + // Extension field Fp2 = Fp[i] / (i² + 1) + // Note: This is the complex extension field of Fp with i² = -1. + // Values in Fp2 are represented as a pair of Fp elements (a₀, a₁) as a₀ + a₁⋅i. + // Note: The order of Fp2 elements is *opposite* that of the pairing contract, which + // expects Fp2 elements in order (a₁, a₀). This is also the order in which + // Fp2 elements are encoded in the public interface as this became convention. + + // Constants in Fp + uint256 constant FRACTION_1_2_FP = + 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4; + uint256 constant FRACTION_27_82_FP = + 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; + uint256 constant FRACTION_3_82_FP = + 0x2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e775; + + // Exponents for inversions and square roots mod P + uint256 constant EXP_INVERSE_FP = + 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45; // P - 2 + uint256 constant EXP_SQRT_FP = + 0xC19139CB84C680A6E14116DA060561765E05AA45A1C72A34F082305B61F3F52; // (P + 1) / 4; + + // Groth16 alpha point in G1 + uint256 constant ALPHA_X = + 2242961077891286189232088401565058430638551409769971691174039306808479001726; + uint256 constant ALPHA_Y = + 6969648571020826426232351672320482919363560217229366459860664517559631080757; + + // Groth16 beta point in G2 in powers of i + uint256 constant BETA_NEG_X_0 = + 21309852969759716563455082909216581124199994689869145584530914218836863194958; + uint256 constant BETA_NEG_X_1 = + 14566398461550838525890798225888346145373093461777150529022601766939342668635; + uint256 constant BETA_NEG_Y_0 = + 2372502941978917581474476225619749058290327718775207078277303966752416490458; + uint256 constant BETA_NEG_Y_1 = + 8425692252281462785145578767253759871416681879517813899489686972355797368074; + + // Groth16 gamma point in G2 in powers of i + uint256 constant GAMMA_NEG_X_0 = + 10803961533257770974466386570502321879218187091208673974555579429032853444409; + uint256 constant GAMMA_NEG_X_1 = + 17557098246337556678125064291007974970450008904985902953974171868414964624226; + uint256 constant GAMMA_NEG_Y_0 = + 7088073269572672835522844700354069415725634576643608880908774321951204338694; + uint256 constant GAMMA_NEG_Y_1 = + 5421577620664095482104497050919009738035270517856603940968056299658528535290; + + // Groth16 delta point in G2 in powers of i + uint256 constant DELTA_NEG_X_0 = + 1104593003299016666694814175902791705668276618272164873009065897094067229391; + uint256 constant DELTA_NEG_X_1 = + 11505061035449492186126827485358858174335696125224061259025534564586715531787; + uint256 constant DELTA_NEG_Y_0 = + 5704920376734931819781911696336058168851666893999357199849218856509635477797; + uint256 constant DELTA_NEG_Y_1 = + 4816248763938325897377673516468489268542745650871950877886949714369417969310; + + // Constant and public input points + uint256 constant CONSTANT_X = + 8396384844704842949570828357304316952869258417897073485093386629008800324494; + uint256 constant CONSTANT_Y = + 6274789729337228905233920769912826201233188510026527916824269072258358328157; + uint256 constant PUB_0_X = + 13626639644280500580133612995416734561962379306341104283661603205682850893287; + uint256 constant PUB_0_Y = + 15571633603831817252940955776191458326874891271960410789162613573752494647572; + + /// Negation in Fp. + /// @notice Returns a number x such that a + x = 0 in Fp. + /// @notice The input does not need to be reduced. + /// @param a the base + /// @return x the result + function negate(uint256 a) internal pure returns (uint256 x) { + unchecked { + x = (P - (a % P)) % P; // Modulo is cheaper than branching + } + } + + /// Exponentiation in Fp. + /// @notice Returns a number x such that a ^ e = x in Fp. + /// @notice The input does not need to be reduced. + /// @param a the base + /// @param e the exponent + /// @return x the result + function exp(uint256 a, uint256 e) internal view returns (uint256 x) { + bool success; + assembly ("memory-safe") { + let f := mload(0x40) + mstore(f, 0x20) + mstore(add(f, 0x20), 0x20) + mstore(add(f, 0x40), 0x20) + mstore(add(f, 0x60), a) + mstore(add(f, 0x80), e) + mstore(add(f, 0xa0), P) + success := staticcall(gas(), PRECOMPILE_MODEXP, f, 0xc0, f, 0x20) + x := mload(f) + } + if (!success) { + // Exponentiation failed. + // Should not happen. + revert ProofInvalid(); + } + } + + /// Invertsion in Fp. + /// @notice Returns a number x such that a * x = 1 in Fp. + /// @notice The input does not need to be reduced. + /// @notice Reverts with ProofInvalid() if the inverse does not exist + /// @param a the input + /// @return x the solution + function invert_Fp(uint256 a) internal view returns (uint256 x) { + x = exp(a, EXP_INVERSE_FP); + if (mulmod(a, x, P) != 1) { + // Inverse does not exist. + // Can only happen during G2 point decompression. + revert ProofInvalid(); + } + } + + /// Square root in Fp. + /// @notice Returns a number x such that x * x = a in Fp. + /// @notice Will revert with InvalidProof() if the input is not a square + /// or not reduced. + /// @param a the square + /// @return x the solution + function sqrt_Fp(uint256 a) internal view returns (uint256 x) { + x = exp(a, EXP_SQRT_FP); + if (mulmod(x, x, P) != a) { + // Square root does not exist or a is not reduced. + // Happens when G1 point is not on curve. + revert ProofInvalid(); + } + } + + /// Square test in Fp. + /// @notice Returns wheter a number x exists such that x * x = a in Fp. + /// @notice Will revert with InvalidProof() if the input is not a square + /// or not reduced. + /// @param a the square + /// @return x the solution + function isSquare_Fp(uint256 a) internal view returns (bool) { + uint256 x = exp(a, EXP_SQRT_FP); + return mulmod(x, x, P) == a; + } + + /// Square root in Fp2. + /// @notice Fp2 is the complex extension Fp[i]/(i^2 + 1). The input is + /// a0 + a1 ⋅ i and the result is x0 + x1 ⋅ i. + /// @notice Will revert with InvalidProof() if + /// * the input is not a square, + /// * the hint is incorrect, or + /// * the input coefficents are not reduced. + /// @param a0 The real part of the input. + /// @param a1 The imaginary part of the input. + /// @param hint A hint which of two possible signs to pick in the equation. + /// @return x0 The real part of the square root. + /// @return x1 The imaginary part of the square root. + function sqrt_Fp2( + uint256 a0, + uint256 a1, + bool hint + ) internal view returns (uint256 x0, uint256 x1) { + // If this square root reverts there is no solution in Fp2. + uint256 d = sqrt_Fp(addmod(mulmod(a0, a0, P), mulmod(a1, a1, P), P)); + if (hint) { + d = negate(d); + } + // If this square root reverts there is no solution in Fp2. + x0 = sqrt_Fp(mulmod(addmod(a0, d, P), FRACTION_1_2_FP, P)); + x1 = mulmod(a1, invert_Fp(mulmod(x0, 2, P)), P); + + // Check result to make sure we found a root. + // Note: this also fails if a0 or a1 is not reduced. + if ( + a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P) || + a1 != mulmod(2, mulmod(x0, x1, P), P) + ) { + revert ProofInvalid(); + } + } + + /// Compress a G1 point. + /// @notice Reverts with InvalidProof if the coordinates are not reduced + /// or if the point is not on the curve. + /// @notice The point at infinity is encoded as (0,0) and compressed to 0. + /// @param x The X coordinate in Fp. + /// @param y The Y coordinate in Fp. + /// @return c The compresed point (x with one signal bit). + function compress_g1( + uint256 x, + uint256 y + ) internal view returns (uint256 c) { + if (x >= P || y >= P) { + // G1 point not in field. + revert ProofInvalid(); + } + if (x == 0 && y == 0) { + // Point at infinity + return 0; + } + + // Note: sqrt_Fp reverts if there is no solution, i.e. the x coordinate is invalid. + uint256 y_pos = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P)); + if (y == y_pos) { + return (x << 1) | 0; + } else if (y == negate(y_pos)) { + return (x << 1) | 1; + } else { + // G1 point not on curve. + revert ProofInvalid(); + } + } + + /// Decompress a G1 point. + /// @notice Reverts with InvalidProof if the input does not represent a valid point. + /// @notice The point at infinity is encoded as (0,0) and compressed to 0. + /// @param c The compresed point (x with one signal bit). + /// @return x The X coordinate in Fp. + /// @return y The Y coordinate in Fp. + function decompress_g1( + uint256 c + ) internal view returns (uint256 x, uint256 y) { + // Note that X = 0 is not on the curve since 0³ + 3 = 3 is not a square. + // so we can use it to represent the point at infinity. + if (c == 0) { + // Point at infinity as encoded in EIP196 and EIP197. + return (0, 0); + } + bool negate_point = c & 1 == 1; + x = c >> 1; + if (x >= P) { + // G1 x coordinate not in field. + revert ProofInvalid(); + } + + // Note: (x³ + 3) is irreducible in Fp, so it can not be zero and therefore + // y can not be zero. + // Note: sqrt_Fp reverts if there is no solution, i.e. the point is not on the curve. + y = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P)); + if (negate_point) { + y = negate(y); + } + } + + /// Compress a G2 point. + /// @notice Reverts with InvalidProof if the coefficients are not reduced + /// or if the point is not on the curve. + /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). + /// @param x0 The real part of the X coordinate. + /// @param x1 The imaginary poart of the X coordinate. + /// @param y0 The real part of the Y coordinate. + /// @param y1 The imaginary part of the Y coordinate. + /// @return c0 The first half of the compresed point (x0 with two signal bits). + /// @return c1 The second half of the compressed point (x1 unmodified). + function compress_g2( + uint256 x0, + uint256 x1, + uint256 y0, + uint256 y1 + ) internal view returns (uint256 c0, uint256 c1) { + if (x0 >= P || x1 >= P || y0 >= P || y1 >= P) { + // G2 point not in field. + revert ProofInvalid(); + } + if ((x0 | x1 | y0 | y1) == 0) { + // Point at infinity + return (0, 0); + } + + // Compute y^2 + // Note: shadowing variables and scoping to avoid stack-to-deep. + uint256 y0_pos; + uint256 y1_pos; + { + uint256 n3ab = mulmod(mulmod(x0, x1, P), P - 3, P); + uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); + uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); + y0_pos = addmod( + FRACTION_27_82_FP, + addmod(a_3, mulmod(n3ab, x1, P), P), + P + ); + y1_pos = negate( + addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P) + ); + } + + // Determine hint bit + // If this sqrt fails the x coordinate is not on the curve. + bool hint; + { + uint256 d = sqrt_Fp( + addmod(mulmod(y0_pos, y0_pos, P), mulmod(y1_pos, y1_pos, P), P) + ); + hint = !isSquare_Fp( + mulmod(addmod(y0_pos, d, P), FRACTION_1_2_FP, P) + ); + } + + // Recover y + (y0_pos, y1_pos) = sqrt_Fp2(y0_pos, y1_pos, hint); + if (y0 == y0_pos && y1 == y1_pos) { + c0 = (x0 << 2) | (hint ? 2 : 0) | 0; + c1 = x1; + } else if (y0 == negate(y0_pos) && y1 == negate(y1_pos)) { + c0 = (x0 << 2) | (hint ? 2 : 0) | 1; + c1 = x1; + } else { + // G1 point not on curve. + revert ProofInvalid(); + } + } + + /// Decompress a G2 point. + /// @notice Reverts with InvalidProof if the input does not represent a valid point. + /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). + /// @param c0 The first half of the compresed point (x0 with two signal bits). + /// @param c1 The second half of the compressed point (x1 unmodified). + /// @return x0 The real part of the X coordinate. + /// @return x1 The imaginary poart of the X coordinate. + /// @return y0 The real part of the Y coordinate. + /// @return y1 The imaginary part of the Y coordinate. + function decompress_g2( + uint256 c0, + uint256 c1 + ) internal view returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1) { + // Note that X = (0, 0) is not on the curve since 0³ + 3/(9 + i) is not a square. + // so we can use it to represent the point at infinity. + if (c0 == 0 && c1 == 0) { + // Point at infinity as encoded in EIP197. + return (0, 0, 0, 0); + } + bool negate_point = c0 & 1 == 1; + bool hint = c0 & 2 == 2; + x0 = c0 >> 2; + x1 = c1; + if (x0 >= P || x1 >= P) { + // G2 x0 or x1 coefficient not in field. + revert ProofInvalid(); + } + + uint256 n3ab = mulmod(mulmod(x0, x1, P), P - 3, P); + uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); + uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); + + y0 = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); + y1 = negate( + addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P) + ); + + // Note: sqrt_Fp2 reverts if there is no solution, i.e. the point is not on the curve. + // Note: (X³ + 3/(9 + i)) is irreducible in Fp2, so y can not be zero. + // But y0 or y1 may still independently be zero. + (y0, y1) = sqrt_Fp2(y0, y1, hint); + if (negate_point) { + y0 = negate(y0); + y1 = negate(y1); + } + } + + /// Compute the public input linear combination. + /// @notice Reverts with PublicInputNotInField if the input is not in the field. + /// @notice Computes the multi-scalar-multiplication of the public input + /// elements and the verification key including the constant term. + /// @param input The public inputs. These are elements of the scalar field Fr. + /// @return x The X coordinate of the resulting G1 point. + /// @return y The Y coordinate of the resulting G1 point. + function publicInputMSM( + uint256[1] calldata input + ) internal view returns (uint256 x, uint256 y) { + // Note: The ECMUL precompile does not reject unreduced values, so we check this. + // Note: Unrolling this loop does not cost much extra in code-size, the bulk of the + // code-size is in the PUB_ constants. + // ECMUL has input (x, y, scalar) and output (x', y'). + // ECADD has input (x1, y1, x2, y2) and output (x', y'). + // We call them such that ecmul output is already in the second point + // argument to ECADD so we can have a tight loop. + bool success = true; + assembly ("memory-safe") { + let f := mload(0x40) + let g := add(f, 0x40) + let s + mstore(f, CONSTANT_X) + mstore(add(f, 0x20), CONSTANT_Y) + mstore(g, PUB_0_X) + mstore(add(g, 0x20), PUB_0_Y) + s := calldataload(input) + mstore(add(g, 0x40), s) + success := and(success, lt(s, R)) + success := and( + success, + staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40) + ) + success := and( + success, + staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40) + ) + x := mload(f) + y := mload(add(f, 0x20)) + } + if (!success) { + // Either Public input not in field, or verification key invalid. + // We assume the contract is correctly generated, so the verification key is valid. + revert PublicInputNotInField(); + } + } + + /// Compress a proof. + /// @notice Will revert with InvalidProof if the curve points are invalid, + /// but does not verify the proof itself. + /// @param proof The uncompressed Groth16 proof. Elements are in the same order as for + /// verifyProof. I.e. Groth16 points (A, B, C) encoded as in EIP-197. + /// @return compressed The compressed proof. Elements are in the same order as for + /// verifyCompressedProof. I.e. points (A, B, C) in compressed format. + function compressProof( + uint256[8] calldata proof + ) public view returns (uint256[4] memory compressed) { + compressed[0] = compress_g1(proof[0], proof[1]); + (compressed[2], compressed[1]) = compress_g2( + proof[3], + proof[2], + proof[5], + proof[4] + ); + compressed[3] = compress_g1(proof[6], proof[7]); + } + + /// Verify a Groth16 proof with compressed points. + /// @notice Reverts with InvalidProof if the proof is invalid or + /// with PublicInputNotInField the public input is not reduced. + /// @notice There is no return value. If the function does not revert, the + /// proof was succesfully verified. + /// @param compressedProof the points (A, B, C) in compressed format + /// matching the output of compressProof. + /// @param input the public input field elements in the scalar field Fr. + /// Elements must be reduced. + function verifyCompressedProof( + uint256[4] calldata compressedProof, + uint256[1] calldata input + ) public view { + (uint256 Ax, uint256 Ay) = decompress_g1(compressedProof[0]); + (uint256 Bx0, uint256 Bx1, uint256 By0, uint256 By1) = decompress_g2( + compressedProof[2], + compressedProof[1] + ); + (uint256 Cx, uint256 Cy) = decompress_g1(compressedProof[3]); + (uint256 Lx, uint256 Ly) = publicInputMSM(input); + + // Verify the pairing + // Note: The precompile expects the F2 coefficients in big-endian order. + // Note: The pairing precompile rejects unreduced values, so we won't check that here. + uint256[24] memory pairings; + // e(A, B) + pairings[0] = Ax; + pairings[1] = Ay; + pairings[2] = Bx1; + pairings[3] = Bx0; + pairings[4] = By1; + pairings[5] = By0; + // e(C, -δ) + pairings[6] = Cx; + pairings[7] = Cy; + pairings[8] = DELTA_NEG_X_1; + pairings[9] = DELTA_NEG_X_0; + pairings[10] = DELTA_NEG_Y_1; + pairings[11] = DELTA_NEG_Y_0; + // e(α, -β) + pairings[12] = ALPHA_X; + pairings[13] = ALPHA_Y; + pairings[14] = BETA_NEG_X_1; + pairings[15] = BETA_NEG_X_0; + pairings[16] = BETA_NEG_Y_1; + pairings[17] = BETA_NEG_Y_0; + // e(L_pub, -γ) + pairings[18] = Lx; + pairings[19] = Ly; + pairings[20] = GAMMA_NEG_X_1; + pairings[21] = GAMMA_NEG_X_0; + pairings[22] = GAMMA_NEG_Y_1; + pairings[23] = GAMMA_NEG_Y_0; + + // Check pairing equation. + bool success; + uint256[1] memory output; + assembly ("memory-safe") { + success := staticcall( + gas(), + PRECOMPILE_VERIFY, + pairings, + 0x300, + output, + 0x20 + ) + } + if (!success || output[0] != 1) { + // Either proof or verification key invalid. + // We assume the contract is correctly generated, so the verification key is valid. + revert ProofInvalid(); + } + } + + /// Verify an uncompressed Groth16 proof. + /// @notice Reverts with InvalidProof if the proof is invalid or + /// with PublicInputNotInField the public input is not reduced. + /// @notice There is no return value. If the function does not revert, the + /// proof was succesfully verified. + /// @param proof the points (A, B, C) in EIP-197 format matching the output + /// of compressProof. + /// @param input the public input field elements in the scalar field Fr. + /// Elements must be reduced. + function verifyProof( + uint256[8] calldata proof, + uint256[1] calldata input + ) public view { + (uint256 x, uint256 y) = publicInputMSM(input); + + // Note: The precompile expects the F2 coefficients in big-endian order. + // Note: The pairing precompile rejects unreduced values, so we won't check that here. + + bool success; + assembly ("memory-safe") { + let f := mload(0x40) // Free memory pointer. + + // Copy points (A, B, C) to memory. They are already in correct encoding. + // This is pairing e(A, B) and G1 of e(C, -δ). + calldatacopy(f, proof, 0x100) + + // Complete e(C, -δ) and write e(α, -β), e(L_pub, -γ) to memory. + // OPT: This could be better done using a single codecopy, but + // Solidity (unlike standalone Yul) doesn't provide a way to + // to do this. + mstore(add(f, 0x100), DELTA_NEG_X_1) + mstore(add(f, 0x120), DELTA_NEG_X_0) + mstore(add(f, 0x140), DELTA_NEG_Y_1) + mstore(add(f, 0x160), DELTA_NEG_Y_0) + mstore(add(f, 0x180), ALPHA_X) + mstore(add(f, 0x1a0), ALPHA_Y) + mstore(add(f, 0x1c0), BETA_NEG_X_1) + mstore(add(f, 0x1e0), BETA_NEG_X_0) + mstore(add(f, 0x200), BETA_NEG_Y_1) + mstore(add(f, 0x220), BETA_NEG_Y_0) + mstore(add(f, 0x240), x) + mstore(add(f, 0x260), y) + mstore(add(f, 0x280), GAMMA_NEG_X_1) + mstore(add(f, 0x2a0), GAMMA_NEG_X_0) + mstore(add(f, 0x2c0), GAMMA_NEG_Y_1) + mstore(add(f, 0x2e0), GAMMA_NEG_Y_0) + + // Check pairing equation. + success := staticcall(gas(), PRECOMPILE_VERIFY, f, 0x300, f, 0x20) + // Also check returned value (both are either 1 or 0). + success := and(success, mload(f)) + } + if (!success) { + // Either proof or verification key invalid. + // We assume the contract is correctly generated, so the verification key is valid. + revert ProofInvalid(); + } + } +} From 628a8064725f74eeb3749eab5e2a2a22d063d686 Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xKitsune@protonmail.com> Date: Mon, 11 Sep 2023 09:08:34 -0400 Subject: [PATCH 02/20] updated optimized verifier test --- ...IdentityManagerSemaphoreVerification.t.sol | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol b/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol index 83977ea..a3e24f8 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol @@ -6,7 +6,7 @@ import {WorldIDIdentityManagerTest} from "./WorldIDIdentityManagerTest.sol"; import {ISemaphoreVerifier} from "semaphore/interfaces/ISemaphoreVerifier.sol"; import {SemaphoreTreeDepthValidator} from "../../utils/SemaphoreTreeDepthValidator.sol"; import {SimpleSemaphoreVerifier} from "../mock/SimpleSemaphoreVerifier.sol"; - +import {Verifier} from "src/Verifier.sol"; import {WorldIDIdentityManager as IdentityManager} from "../../WorldIDIdentityManager.sol"; import {WorldIDIdentityManagerImplV2 as ManagerImpl} from "../../WorldIDIdentityManagerImplV2.sol"; import {WorldIDIdentityManagerImplV1 as ManagerImplV1} from "../../WorldIDIdentityManagerImplV1.sol"; @@ -75,4 +75,33 @@ contract WorldIDIdentityManagerSemaphoreValidation is WorldIDIdentityManagerTest // Test assertCallFailsOn(identityManagerAddress, verifyProofCallData); } + + /// @notice Checks that the proof validates properly with the correct inputs. + function testOptimizedProofVerificationWithCorrectInputs( + uint8 actualTreeDepth, + uint256 nullifierHash, + uint256 signalHash, + uint256 externalNullifierHash, + uint256[8] memory prf + ) public { + // Setup + ISemaphoreVerifier actualSemaphoreVerifier = ISemaphoreVerifier(address(new Verifier())); + vm.assume(SemaphoreTreeDepthValidator.validate(actualTreeDepth)); + vm.assume(prf[0] != 0); + makeNewIdentityManager( + actualTreeDepth, + insertionPreRoot, + defaultInsertVerifiers, + defaultDeletionVerifiers, + defaultUpdateVerifiers, + actualSemaphoreVerifier + ); + bytes memory verifyProofCallData = abi.encodeCall( + ManagerImplV1.verifyProof, + (insertionPreRoot, nullifierHash, signalHash, externalNullifierHash, insertionProof) + ); + + // Test + assertCallSucceedsOn(identityManagerAddress, verifyProofCallData); + } } From 4053d20d2a5cdcdec97d7689350ffe3323483991 Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xKitsune@protonmail.com> Date: Tue, 12 Sep 2023 12:30:44 -0400 Subject: [PATCH 03/20] updated verifier contract to use depth 30 and batch size 100 --- src/Verifier.sol | 214 ++++++++++++++++------------------------------- 1 file changed, 70 insertions(+), 144 deletions(-) diff --git a/src/Verifier.sol b/src/Verifier.sol index 02b5c8b..0434277 100644 --- a/src/Verifier.sol +++ b/src/Verifier.sol @@ -1,3 +1,4 @@ + // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; @@ -9,6 +10,7 @@ pragma solidity ^0.8.0; /// to compress proofs. /// @notice See for further explanation. contract Verifier { + /// Some of the provided public input values are larger than the field modulus. /// @dev Public input elements are not automatically reduced, as this is can be /// a dangerous source of bugs. @@ -31,10 +33,8 @@ contract Verifier { // t = 4965661367192848881 // P = 36⋅t⁴ + 36⋅t³ + 24⋅t² + 6⋅t + 1 // R = 36⋅t⁴ + 36⋅t³ + 18⋅t² + 6⋅t + 1 - uint256 constant P = - 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; - uint256 constant R = - 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001; + uint256 constant P = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; + uint256 constant R = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001; // Extension field Fp2 = Fp[i] / (i² + 1) // Note: This is the complex extension field of Fp with i² = -1. @@ -44,64 +44,41 @@ contract Verifier { // Fp2 elements are encoded in the public interface as this became convention. // Constants in Fp - uint256 constant FRACTION_1_2_FP = - 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4; - uint256 constant FRACTION_27_82_FP = - 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; - uint256 constant FRACTION_3_82_FP = - 0x2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e775; + uint256 constant FRACTION_1_2_FP = 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4; + uint256 constant FRACTION_27_82_FP = 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; + uint256 constant FRACTION_3_82_FP = 0x2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e775; // Exponents for inversions and square roots mod P - uint256 constant EXP_INVERSE_FP = - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45; // P - 2 - uint256 constant EXP_SQRT_FP = - 0xC19139CB84C680A6E14116DA060561765E05AA45A1C72A34F082305B61F3F52; // (P + 1) / 4; + uint256 constant EXP_INVERSE_FP = 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45; // P - 2 + uint256 constant EXP_SQRT_FP = 0xC19139CB84C680A6E14116DA060561765E05AA45A1C72A34F082305B61F3F52; // (P + 1) / 4; // Groth16 alpha point in G1 - uint256 constant ALPHA_X = - 2242961077891286189232088401565058430638551409769971691174039306808479001726; - uint256 constant ALPHA_Y = - 6969648571020826426232351672320482919363560217229366459860664517559631080757; + uint256 constant ALPHA_X = 21238094701099767992181020574097576567248669231933303288906499800492665245646; + uint256 constant ALPHA_Y = 17835722675051865338243836993818778396240839209371887976268210291316141770442; // Groth16 beta point in G2 in powers of i - uint256 constant BETA_NEG_X_0 = - 21309852969759716563455082909216581124199994689869145584530914218836863194958; - uint256 constant BETA_NEG_X_1 = - 14566398461550838525890798225888346145373093461777150529022601766939342668635; - uint256 constant BETA_NEG_Y_0 = - 2372502941978917581474476225619749058290327718775207078277303966752416490458; - uint256 constant BETA_NEG_Y_1 = - 8425692252281462785145578767253759871416681879517813899489686972355797368074; + uint256 constant BETA_NEG_X_0 = 17328241642904145666695767828612798629577735363797370984204279730597246374999; + uint256 constant BETA_NEG_X_1 = 3860372090784917774458215217940662090649652140977504592804245872521163657018; + uint256 constant BETA_NEG_Y_0 = 10271963604709049923747050159865377390989698297635199231422940911768575903284; + uint256 constant BETA_NEG_Y_1 = 15652994649667530601973737381462460227196275508968367336119989296260301572852; // Groth16 gamma point in G2 in powers of i - uint256 constant GAMMA_NEG_X_0 = - 10803961533257770974466386570502321879218187091208673974555579429032853444409; - uint256 constant GAMMA_NEG_X_1 = - 17557098246337556678125064291007974970450008904985902953974171868414964624226; - uint256 constant GAMMA_NEG_Y_0 = - 7088073269572672835522844700354069415725634576643608880908774321951204338694; - uint256 constant GAMMA_NEG_Y_1 = - 5421577620664095482104497050919009738035270517856603940968056299658528535290; + uint256 constant GAMMA_NEG_X_0 = 13072135626714886665743299238380777558485915182186148044840030375227830297167; + uint256 constant GAMMA_NEG_X_1 = 15728445629349648373029025074665124823451612105079365074482822873179926876367; + uint256 constant GAMMA_NEG_Y_0 = 574425249090245226726507580262558613212067342295152730122360551738055447720; + uint256 constant GAMMA_NEG_Y_1 = 14984396635537120619265753845843371019305161489040275839272534175355415469117; // Groth16 delta point in G2 in powers of i - uint256 constant DELTA_NEG_X_0 = - 1104593003299016666694814175902791705668276618272164873009065897094067229391; - uint256 constant DELTA_NEG_X_1 = - 11505061035449492186126827485358858174335696125224061259025534564586715531787; - uint256 constant DELTA_NEG_Y_0 = - 5704920376734931819781911696336058168851666893999357199849218856509635477797; - uint256 constant DELTA_NEG_Y_1 = - 4816248763938325897377673516468489268542745650871950877886949714369417969310; + uint256 constant DELTA_NEG_X_0 = 11641495633934915211954655655028364970241467353476707112972519067148042156236; + uint256 constant DELTA_NEG_X_1 = 2862574649712655062568935342174214330174596565118547226721602157017065773404; + uint256 constant DELTA_NEG_Y_0 = 19077360648152442030655834928434260675626758144638095983278277208712230645850; + uint256 constant DELTA_NEG_Y_1 = 14251475007345879229751465975664358189685218843746247716834082671723536271198; // Constant and public input points - uint256 constant CONSTANT_X = - 8396384844704842949570828357304316952869258417897073485093386629008800324494; - uint256 constant CONSTANT_Y = - 6274789729337228905233920769912826201233188510026527916824269072258358328157; - uint256 constant PUB_0_X = - 13626639644280500580133612995416734561962379306341104283661603205682850893287; - uint256 constant PUB_0_Y = - 15571633603831817252940955776191458326874891271960410789162613573752494647572; + uint256 constant CONSTANT_X = 16288867954761114276402155371115785036905027315050399275668719978219442996927; + uint256 constant CONSTANT_Y = 15611120916366083495836852937429193652107718465176612711134377435436192859809; + uint256 constant PUB_0_X = 4147432231571547026123777586694593106967411953796535970917709407254986235876; + uint256 constant PUB_0_Y = 10326630917030366329673551361728562727322815783171171513266831996416185441581; /// Negation in Fp. /// @notice Returns a number x such that a + x = 0 in Fp. @@ -137,7 +114,7 @@ contract Verifier { // Exponentiation failed. // Should not happen. revert ProofInvalid(); - } + } } /// Invertsion in Fp. @@ -193,11 +170,7 @@ contract Verifier { /// @param hint A hint which of two possible signs to pick in the equation. /// @return x0 The real part of the square root. /// @return x1 The imaginary part of the square root. - function sqrt_Fp2( - uint256 a0, - uint256 a1, - bool hint - ) internal view returns (uint256 x0, uint256 x1) { + function sqrt_Fp2(uint256 a0, uint256 a1, bool hint) internal view returns (uint256 x0, uint256 x1) { // If this square root reverts there is no solution in Fp2. uint256 d = sqrt_Fp(addmod(mulmod(a0, a0, P), mulmod(a1, a1, P), P)); if (hint) { @@ -209,10 +182,8 @@ contract Verifier { // Check result to make sure we found a root. // Note: this also fails if a0 or a1 is not reduced. - if ( - a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P) || - a1 != mulmod(2, mulmod(x0, x1, P), P) - ) { + if (a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P) + || a1 != mulmod(2, mulmod(x0, x1, P), P)) { revert ProofInvalid(); } } @@ -224,10 +195,7 @@ contract Verifier { /// @param x The X coordinate in Fp. /// @param y The Y coordinate in Fp. /// @return c The compresed point (x with one signal bit). - function compress_g1( - uint256 x, - uint256 y - ) internal view returns (uint256 c) { + function compress_g1(uint256 x, uint256 y) internal view returns (uint256 c) { if (x >= P || y >= P) { // G1 point not in field. revert ProofInvalid(); @@ -236,7 +204,7 @@ contract Verifier { // Point at infinity return 0; } - + // Note: sqrt_Fp reverts if there is no solution, i.e. the x coordinate is invalid. uint256 y_pos = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P)); if (y == y_pos) { @@ -255,9 +223,7 @@ contract Verifier { /// @param c The compresed point (x with one signal bit). /// @return x The X coordinate in Fp. /// @return y The Y coordinate in Fp. - function decompress_g1( - uint256 c - ) internal view returns (uint256 x, uint256 y) { + function decompress_g1(uint256 c) internal view returns (uint256 x, uint256 y) { // Note that X = 0 is not on the curve since 0³ + 3 = 3 is not a square. // so we can use it to represent the point at infinity. if (c == 0) { @@ -284,7 +250,7 @@ contract Verifier { /// @notice Reverts with InvalidProof if the coefficients are not reduced /// or if the point is not on the curve. /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) - /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). /// @param x0 The real part of the X coordinate. /// @param x1 The imaginary poart of the X coordinate. @@ -292,12 +258,8 @@ contract Verifier { /// @param y1 The imaginary part of the Y coordinate. /// @return c0 The first half of the compresed point (x0 with two signal bits). /// @return c1 The second half of the compressed point (x1 unmodified). - function compress_g2( - uint256 x0, - uint256 x1, - uint256 y0, - uint256 y1 - ) internal view returns (uint256 c0, uint256 c1) { + function compress_g2(uint256 x0, uint256 x1, uint256 y0, uint256 y1) + internal view returns (uint256 c0, uint256 c1) { if (x0 >= P || x1 >= P || y0 >= P || y1 >= P) { // G2 point not in field. revert ProofInvalid(); @@ -312,38 +274,28 @@ contract Verifier { uint256 y0_pos; uint256 y1_pos; { - uint256 n3ab = mulmod(mulmod(x0, x1, P), P - 3, P); + uint256 n3ab = mulmod(mulmod(x0, x1, P), P-3, P); uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); - y0_pos = addmod( - FRACTION_27_82_FP, - addmod(a_3, mulmod(n3ab, x1, P), P), - P - ); - y1_pos = negate( - addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P) - ); + y0_pos = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); + y1_pos = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); } // Determine hint bit // If this sqrt fails the x coordinate is not on the curve. bool hint; { - uint256 d = sqrt_Fp( - addmod(mulmod(y0_pos, y0_pos, P), mulmod(y1_pos, y1_pos, P), P) - ); - hint = !isSquare_Fp( - mulmod(addmod(y0_pos, d, P), FRACTION_1_2_FP, P) - ); + uint256 d = sqrt_Fp(addmod(mulmod(y0_pos, y0_pos, P), mulmod(y1_pos, y1_pos, P), P)); + hint = !isSquare_Fp(mulmod(addmod(y0_pos, d, P), FRACTION_1_2_FP, P)); } // Recover y (y0_pos, y1_pos) = sqrt_Fp2(y0_pos, y1_pos, hint); if (y0 == y0_pos && y1 == y1_pos) { - c0 = (x0 << 2) | (hint ? 2 : 0) | 0; + c0 = (x0 << 2) | (hint ? 2 : 0) | 0; c1 = x1; } else if (y0 == negate(y0_pos) && y1 == negate(y1_pos)) { - c0 = (x0 << 2) | (hint ? 2 : 0) | 1; + c0 = (x0 << 2) | (hint ? 2 : 0) | 1; c1 = x1; } else { // G1 point not on curve. @@ -354,7 +306,7 @@ contract Verifier { /// Decompress a G2 point. /// @notice Reverts with InvalidProof if the input does not represent a valid point. /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) - /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). /// @param c0 The first half of the compresed point (x0 with two signal bits). /// @param c1 The second half of the compressed point (x1 unmodified). @@ -362,10 +314,8 @@ contract Verifier { /// @return x1 The imaginary poart of the X coordinate. /// @return y0 The real part of the Y coordinate. /// @return y1 The imaginary part of the Y coordinate. - function decompress_g2( - uint256 c0, - uint256 c1 - ) internal view returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1) { + function decompress_g2(uint256 c0, uint256 c1) + internal view returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1) { // Note that X = (0, 0) is not on the curve since 0³ + 3/(9 + i) is not a square. // so we can use it to represent the point at infinity. if (c0 == 0 && c1 == 0) { @@ -381,14 +331,12 @@ contract Verifier { revert ProofInvalid(); } - uint256 n3ab = mulmod(mulmod(x0, x1, P), P - 3, P); + uint256 n3ab = mulmod(mulmod(x0, x1, P), P-3, P); uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); y0 = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); - y1 = negate( - addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P) - ); + y1 = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); // Note: sqrt_Fp2 reverts if there is no solution, i.e. the point is not on the curve. // Note: (X³ + 3/(9 + i)) is irreducible in Fp2, so y can not be zero. @@ -407,9 +355,8 @@ contract Verifier { /// @param input The public inputs. These are elements of the scalar field Fr. /// @return x The X coordinate of the resulting G1 point. /// @return y The Y coordinate of the resulting G1 point. - function publicInputMSM( - uint256[1] calldata input - ) internal view returns (uint256 x, uint256 y) { + function publicInputMSM(uint256[1] calldata input) + internal view returns (uint256 x, uint256 y) { // Note: The ECMUL precompile does not reject unreduced values, so we check this. // Note: Unrolling this loop does not cost much extra in code-size, the bulk of the // code-size is in the PUB_ constants. @@ -426,17 +373,11 @@ contract Verifier { mstore(add(f, 0x20), CONSTANT_Y) mstore(g, PUB_0_X) mstore(add(g, 0x20), PUB_0_Y) - s := calldataload(input) + s := calldataload(input) mstore(add(g, 0x40), s) success := and(success, lt(s, R)) - success := and( - success, - staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40) - ) - success := and( - success, - staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40) - ) + success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) + success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) x := mload(f) y := mload(add(f, 0x20)) } @@ -454,16 +395,10 @@ contract Verifier { /// verifyProof. I.e. Groth16 points (A, B, C) encoded as in EIP-197. /// @return compressed The compressed proof. Elements are in the same order as for /// verifyCompressedProof. I.e. points (A, B, C) in compressed format. - function compressProof( - uint256[8] calldata proof - ) public view returns (uint256[4] memory compressed) { + function compressProof(uint256[8] calldata proof) + public view returns (uint256[4] memory compressed) { compressed[0] = compress_g1(proof[0], proof[1]); - (compressed[2], compressed[1]) = compress_g2( - proof[3], - proof[2], - proof[5], - proof[4] - ); + (compressed[2], compressed[1]) = compress_g2(proof[3], proof[2], proof[5], proof[4]); compressed[3] = compress_g1(proof[6], proof[7]); } @@ -482,9 +417,7 @@ contract Verifier { ) public view { (uint256 Ax, uint256 Ay) = decompress_g1(compressedProof[0]); (uint256 Bx0, uint256 Bx1, uint256 By0, uint256 By1) = decompress_g2( - compressedProof[2], - compressedProof[1] - ); + compressedProof[2], compressedProof[1]); (uint256 Cx, uint256 Cy) = decompress_g1(compressedProof[3]); (uint256 Lx, uint256 Ly) = publicInputMSM(input); @@ -493,17 +426,17 @@ contract Verifier { // Note: The pairing precompile rejects unreduced values, so we won't check that here. uint256[24] memory pairings; // e(A, B) - pairings[0] = Ax; - pairings[1] = Ay; - pairings[2] = Bx1; - pairings[3] = Bx0; - pairings[4] = By1; - pairings[5] = By0; + pairings[ 0] = Ax; + pairings[ 1] = Ay; + pairings[ 2] = Bx1; + pairings[ 3] = Bx0; + pairings[ 4] = By1; + pairings[ 5] = By0; // e(C, -δ) - pairings[6] = Cx; - pairings[7] = Cy; - pairings[8] = DELTA_NEG_X_1; - pairings[9] = DELTA_NEG_X_0; + pairings[ 6] = Cx; + pairings[ 7] = Cy; + pairings[ 8] = DELTA_NEG_X_1; + pairings[ 9] = DELTA_NEG_X_0; pairings[10] = DELTA_NEG_Y_1; pairings[11] = DELTA_NEG_Y_0; // e(α, -β) @@ -525,14 +458,7 @@ contract Verifier { bool success; uint256[1] memory output; assembly ("memory-safe") { - success := staticcall( - gas(), - PRECOMPILE_VERIFY, - pairings, - 0x300, - output, - 0x20 - ) + success := staticcall(gas(), PRECOMPILE_VERIFY, pairings, 0x300, output, 0x20) } if (!success || output[0] != 1) { // Either proof or verification key invalid. @@ -558,7 +484,7 @@ contract Verifier { // Note: The precompile expects the F2 coefficients in big-endian order. // Note: The pairing precompile rejects unreduced values, so we won't check that here. - + bool success; assembly ("memory-safe") { let f := mload(0x40) // Free memory pointer. From 730452fd9fa16d3ed3bdff5c651295c4571db890 Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xKitsune@protonmail.com> Date: Tue, 12 Sep 2023 16:44:16 -0400 Subject: [PATCH 04/20] updated ITreeVerifier interface --- src/Verifier.sol | 4 ++- src/interfaces/ITreeVerifier.sol | 31 ++++++++----------- ...DIdentityManagerIdentityRegistration.t.sol | 2 +- src/test/mock/SimpleVerifier.sol | 11 ++----- 4 files changed, 20 insertions(+), 28 deletions(-) diff --git a/src/Verifier.sol b/src/Verifier.sol index 0434277..64f6983 100644 --- a/src/Verifier.sol +++ b/src/Verifier.sol @@ -3,13 +3,15 @@ pragma solidity ^0.8.0; +import {ITreeVerifier} from "src/interfaces/ITreeVerifier.sol"; + /// @title Groth16 verifier template. /// @author Remco Bloemen /// @notice Supports verifying Groth16 proofs. Proofs can be in uncompressed /// (256 bytes) and compressed (128 bytes) format. A view function is provided /// to compress proofs. /// @notice See for further explanation. -contract Verifier { +contract Verifier is ITreeVerifier{ /// Some of the provided public input values are larger than the field modulus. /// @dev Public input elements are not automatically reduced, as this is can be diff --git a/src/interfaces/ITreeVerifier.sol b/src/interfaces/ITreeVerifier.sol index 7fa2001..b25280e 100644 --- a/src/interfaces/ITreeVerifier.sol +++ b/src/interfaces/ITreeVerifier.sol @@ -5,22 +5,17 @@ pragma solidity ^0.8.21; /// @author Worldcoin /// @notice An interface representing a merkle tree verifier. interface ITreeVerifier { - /// @notice Verifies the provided proof data for the provided public inputs. - /// @dev It is highly recommended that the implementation is restricted to `view` if possible. - /// - /// @param a The first G1Point of the proof (ar). - /// @param b The G2Point for the proof (bs). - /// @param c The second G1Point of the proof (kr). - /// @param input The public inputs to the function, reduced such that it is a member of the - /// field `Fr` where `r` is `SNARK_SCALAR_FIELD`. - /// - /// @return result True if the proof verifies successfully, false otherwise. - /// @custom:reverts string If the proof elements are not < `PRIME_Q` or if the `input` is not - /// less than `SNARK_SCALAR_FIELD`. - function verifyProof( - uint256[2] memory a, - uint256[2][2] memory b, - uint256[2] memory c, - uint256[1] memory input - ) external returns (bool result); + /// @notice Verify an uncompressed Groth16 proof. + /// @notice Reverts with InvalidProof if the proof is invalid or + /// with PublicInputNotInField the public input is not reduced. + /// @notice There is no return value. If the function does not revert, the + /// proof was succesfully verified. + /// @param proof the points (A, B, C) in EIP-197 format matching the output + /// of compressProof. + /// @param input the public input field elements in the scalar field Fr. + /// Elements must be reduced. + function verifyProof( + uint256[8] calldata proof, + uint256[1] calldata input + ) external; } diff --git a/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol b/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol index 679d769..8618852 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol @@ -8,7 +8,7 @@ import {WorldIDIdentityManagerTest} from "./WorldIDIdentityManagerTest.sol"; import {ITreeVerifier} from "../../interfaces/ITreeVerifier.sol"; import {SimpleVerifier, SimpleVerify} from "../mock/SimpleVerifier.sol"; import {TypeConverter as TC} from "../utils/TypeConverter.sol"; -import {Verifier as TreeVerifier} from "../mock/InsertionTreeVerifier.sol"; +import {Verifier as TreeVerifier} from "src/Verifier.sol"; import {VerifierLookupTable} from "../../data/VerifierLookupTable.sol"; import {WorldIDIdentityManager as IdentityManager} from "../../WorldIDIdentityManager.sol"; diff --git a/src/test/mock/SimpleVerifier.sol b/src/test/mock/SimpleVerifier.sol index e639688..7cbadab 100644 --- a/src/test/mock/SimpleVerifier.sol +++ b/src/test/mock/SimpleVerifier.sol @@ -16,15 +16,10 @@ contract SimpleVerifier is ITreeVerifier { } function verifyProof( - uint256[2] memory a, - uint256[2][2] memory b, - uint256[2] memory c, + uint256[8] memory proof, uint256[1] memory input - ) external override returns (bool result) { - delete b; - delete c; - delete input; - result = a[0] % 2 == 0; + ) external returns (bool result) { + result = proof[0] % 2 == 0; if (result) { emit VerifiedProof(batchSize); From 07a4b34203a206fdef7e37abcfacff31923d570f Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xKitsune@protonmail.com> Date: Tue, 12 Sep 2023 21:54:25 -0400 Subject: [PATCH 05/20] fixed compliation errors resulting from verifier interface change --- src/WorldIDIdentityManagerImplV1.sol | 24 ++++----------------- src/WorldIDIdentityManagerImplV2.sol | 11 ++-------- src/interfaces/ITreeVerifier.sol | 2 +- src/test/mock/DeletionTreeVerifier.sol | 20 +++++++++--------- src/test/mock/InsertionTreeVerifier.sol | 18 ++++++++-------- src/test/mock/SequencerVerifier.sol | 10 +++------ src/test/mock/SimpleVerifier.sol | 4 ++-- src/utils/UnimplementedTreeVerifier.sol | 28 +++++++++++-------------- 8 files changed, 43 insertions(+), 74 deletions(-) diff --git a/src/WorldIDIdentityManagerImplV1.sol b/src/WorldIDIdentityManagerImplV1.sol index 280a058..7331596 100644 --- a/src/WorldIDIdentityManagerImplV1.sol +++ b/src/WorldIDIdentityManagerImplV1.sol @@ -391,16 +391,9 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { // With that, we can properly try and verify. try insertionVerifier.verifyProof( - [insertionProof[0], insertionProof[1]], - [[insertionProof[2], insertionProof[3]], [insertionProof[4], insertionProof[5]]], - [insertionProof[6], insertionProof[7]], + insertionProof, [reducedElement] - ) returns (bool verifierResult) { - // If the proof did not verify, we revert with a failure. - if (!verifierResult) { - revert ProofValidationFailure(); - } - + ) { // If it did verify, we need to update the contract's state. We set the currently valid // root to the root after the insertions. _latestRoot = postRoot; @@ -534,20 +527,11 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { uint256 preRoot, uint256 postRoot ) internal virtual onlyProxy onlyInitialized onlyIdentityOperator { - // Pull out the proof terms and verifier input. - uint256[2] memory ar = [updateProof[0], updateProof[1]]; - uint256[2][2] memory bs = - [[updateProof[2], updateProof[3]], [updateProof[4], updateProof[5]]]; - uint256[2] memory krs = [updateProof[6], updateProof[7]]; + // Pull out the verifier input. uint256[1] memory proofInput = [inputHash]; // Now it's possible to verify the proof. - try updateVerifier.verifyProof(ar, bs, krs, proofInput) returns (bool verifierResult) { - // If the proof did not verify, we revert with a failure. - if (!verifierResult) { - revert ProofValidationFailure(); - } - + try updateVerifier.verifyProof(updateProof, proofInput) { // If it did verify, we need to update the contract's state. We set the currently valid // root to the root after the insertions. _latestRoot = postRoot; diff --git a/src/WorldIDIdentityManagerImplV2.sol b/src/WorldIDIdentityManagerImplV2.sol index e39ac10..2ee0fd6 100644 --- a/src/WorldIDIdentityManagerImplV2.sol +++ b/src/WorldIDIdentityManagerImplV2.sol @@ -131,16 +131,9 @@ contract WorldIDIdentityManagerImplV2 is WorldIDIdentityManagerImplV1 { // With that, we can properly try and verify. try deletionVerifier.verifyProof( - [deletionProof[0], deletionProof[1]], - [[deletionProof[2], deletionProof[3]], [deletionProof[4], deletionProof[5]]], - [deletionProof[6], deletionProof[7]], + deletionProof, [reducedElement] - ) returns (bool verifierResult) { - // If the proof did not verify, we revert with a failure. - if (!verifierResult) { - revert ProofValidationFailure(); - } - + ) { // If it did verify, we need to update the contract's state. We set the currently valid // root to the root after the insertions. _latestRoot = postRoot; diff --git a/src/interfaces/ITreeVerifier.sol b/src/interfaces/ITreeVerifier.sol index b25280e..cf8080d 100644 --- a/src/interfaces/ITreeVerifier.sol +++ b/src/interfaces/ITreeVerifier.sol @@ -14,7 +14,7 @@ interface ITreeVerifier { /// of compressProof. /// @param input the public input field elements in the scalar field Fr. /// Elements must be reduced. - function verifyProof( + function verifyProof( uint256[8] calldata proof, uint256[1] calldata input ) external; diff --git a/src/test/mock/DeletionTreeVerifier.sol b/src/test/mock/DeletionTreeVerifier.sol index fa8c091..1f906e1 100644 --- a/src/test/mock/DeletionTreeVerifier.sol +++ b/src/test/mock/DeletionTreeVerifier.sol @@ -266,15 +266,15 @@ contract Verifier is ITreeVerifier { * above and the public inputs */ function verifyProof( - uint256[2] memory a, - uint256[2][2] memory b, - uint256[2] memory c, - uint256[1] calldata input - ) public view returns (bool r) { + uint256[8] memory _proof, + uint256[1] memory input + ) public view { Proof memory proof; - proof.A = Pairing.G1Point(a[0], a[1]); - proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]); - proof.C = Pairing.G1Point(c[0], c[1]); + + //TODO: going to need to double check the order of nested b + proof.A = Pairing.G1Point(_proof[0], _proof[1]); + proof.B = Pairing.G2Point([_proof[2], _proof[3]], [_proof[4], _proof[5]]); + proof.C = Pairing.G1Point(_proof[6], _proof[7]); // Make sure that proof.A, B, and C are each less than the prime q require(proof.A.X < PRIME_Q, "verifier-aX-gte-prime-q"); @@ -321,7 +321,7 @@ contract Verifier is ITreeVerifier { mul_input[2] = input[0]; accumulate(mul_input, q, add_input, vk_x); // vk_x += vk.K[1] * input[0] - return Pairing.pairing( + require(Pairing.pairing( Pairing.negate(proof.A), proof.B, vk.alfa1, @@ -330,6 +330,6 @@ contract Verifier is ITreeVerifier { vk.gamma2, proof.C, vk.delta2 - ); + ), "Invalid proof"); } } diff --git a/src/test/mock/InsertionTreeVerifier.sol b/src/test/mock/InsertionTreeVerifier.sol index 9d38b14..337207f 100644 --- a/src/test/mock/InsertionTreeVerifier.sol +++ b/src/test/mock/InsertionTreeVerifier.sol @@ -217,15 +217,15 @@ contract Verifier is ITreeVerifier { * above and the public inputs */ function verifyProof( - uint256[2] memory a, - uint256[2][2] memory b, - uint256[2] memory c, + uint256[8] memory _proof, uint256[1] memory input - ) public view returns (bool r) { + ) public view { Proof memory proof; - proof.A = Pairing.G1Point(a[0], a[1]); - proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]); - proof.C = Pairing.G1Point(c[0], c[1]); + + //TODO: going to need to double check the order of nested b + proof.A = Pairing.G1Point(_proof[0], _proof[1]); + proof.B = Pairing.G2Point([_proof[2], _proof[3]], [_proof[4], _proof[5]]); + proof.C = Pairing.G1Point(_proof[6], _proof[7]); VerifyingKey memory vk = verifyingKey(); @@ -253,7 +253,7 @@ contract Verifier is ITreeVerifier { vk_x = Pairing.plus(vk_x, vk.IC[0]); - return Pairing.pairing( + require(Pairing.pairing( Pairing.negate(proof.A), proof.B, vk.alfa1, @@ -262,6 +262,6 @@ contract Verifier is ITreeVerifier { vk.gamma2, proof.C, vk.delta2 - ); + ), "Invalid proof"); } } diff --git a/src/test/mock/SequencerVerifier.sol b/src/test/mock/SequencerVerifier.sol index aa8b30e..40fd3ac 100644 --- a/src/test/mock/SequencerVerifier.sol +++ b/src/test/mock/SequencerVerifier.sol @@ -12,13 +12,9 @@ contract SequencerVerifier is ITreeVerifier { 21888242871839275222246405745257275088548364400416034343698204186575808495617; function verifyProof( - uint256[2] memory a, - uint256[2][2] memory b, - uint256[2] memory c, + uint256[8] memory proof, uint256[1] memory input - ) external pure override returns (bool) { - delete b; - delete c; - return a[0] % 2 == 0 && a[1] % SNARK_SCALAR_FIELD == input[0]; + ) external { + require(proof[0] % 2 == 0 && proof[1] % SNARK_SCALAR_FIELD == input[0], "Invalid Proof"); } } diff --git a/src/test/mock/SimpleVerifier.sol b/src/test/mock/SimpleVerifier.sol index 7cbadab..2ace791 100644 --- a/src/test/mock/SimpleVerifier.sol +++ b/src/test/mock/SimpleVerifier.sol @@ -18,8 +18,8 @@ contract SimpleVerifier is ITreeVerifier { function verifyProof( uint256[8] memory proof, uint256[1] memory input - ) external returns (bool result) { - result = proof[0] % 2 == 0; + ) external { + bool result = proof[0] % 2 == 0; if (result) { emit VerifiedProof(batchSize); diff --git a/src/utils/UnimplementedTreeVerifier.sol b/src/utils/UnimplementedTreeVerifier.sol index 706f935..32cfeda 100644 --- a/src/utils/UnimplementedTreeVerifier.sol +++ b/src/utils/UnimplementedTreeVerifier.sol @@ -14,25 +14,21 @@ contract UnimplementedTreeVerifier is ITreeVerifier { /// @notice Thrown when an operation is not supported. error UnsupportedOperation(); - /// @notice Verifies the provided proof data for the provided public inputs. - /// @dev Exists to satisfy the interface. Will always revert. - /// - /// @param a The first G1Point of the proof (ar). - /// @param b The G2Point for the proof (bs). - /// @param c The second G1Point of the proof (kr). - /// @param input The public inputs to the function, reduced such that it is a member of the - /// field `Fr` where `r` is `SNARK_SCALAR_FIELD`. - /// + /// @notice Verify an uncompressed Groth16 proof. + /// @notice Reverts with InvalidProof if the proof is invalid or + /// with PublicInputNotInField the public input is not reduced. + /// @notice There is no return value. If the function does not revert, the + /// proof was succesfully verified. + /// @param proof the points (A, B, C) in EIP-197 format matching the output + /// of compressProof. + /// @param input the public input field elements in the scalar field Fr. + /// Elements must be reduced. /// @custom:reverts UnsupportedOperation When called. function verifyProof( - uint256[2] memory a, - uint256[2][2] memory b, - uint256[2] memory c, + uint256[8] memory proof, uint256[1] memory input - ) external pure returns (bool) { - delete a; - delete b; - delete c; + ) external pure { + delete proof; delete input; revert UnsupportedOperation(); } From e06a17756654bb8f73206993a44d75602442f988 Mon Sep 17 00:00:00 2001 From: "dcbuilder.eth" Date: Wed, 13 Sep 2023 14:02:25 +0100 Subject: [PATCH 06/20] update test params and add docs tests still break --- README.md | 8 +- src/Verifier.sol | 163 +++++++++++------- src/WorldIDIdentityManagerImplV1.sol | 5 +- src/WorldIDIdentityManagerImplV2.sol | 5 +- src/interfaces/ITreeVerifier.sol | 5 +- src/test/data/DeletionProof.json | 20 +++ src/test/data/InsertionProof.json | 20 +++ src/test/data/TestInsertionParams.json | 51 +++++- ...rldIDIdentityManagerIdentityDeletion.t.sol | 2 +- ...IdentityManagerSemaphoreVerification.t.sol | 2 +- .../WorldIDIdentityManagerTest.sol | 49 +++--- src/test/mock/DeletionTreeVerifier.sol | 28 +-- src/test/mock/InsertionTreeVerifier.sol | 28 +-- src/test/mock/SequencerVerifier.sol | 5 +- src/test/mock/SimpleVerifier.sol | 5 +- src/utils/UnimplementedTreeVerifier.sol | 7 +- 16 files changed, 254 insertions(+), 149 deletions(-) create mode 100644 src/test/data/DeletionProof.json create mode 100644 src/test/data/InsertionProof.json diff --git a/README.md b/README.md index df78c6c..ff52659 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,10 @@ > [Hardhat](https://github.com/worldcoin/world-id-starter-hardhat) starter kits. This repository contains the underlying contracts that make World ID work, powered by the -[Semaphore library](https://semaphore.pse.dev/). These contracts are responsible for -performing identity operations on chain, and attestation of identities for the purposes of semaphore -proofs. Check out [user-flows.md](./docs/user-flows.md) for more information on how these contracts -relate to the rest of the World ID system. +[Semaphore library](https://semaphore.pse.dev/). These contracts are responsible for performing +identity operations on chain, and attestation of identities for the purposes of semaphore proofs. +Check out [user-flows.md](./docs/user-flows.md) for more information on how these contracts relate +to the rest of the World ID system. ## World ID Logo About World ID diff --git a/src/Verifier.sol b/src/Verifier.sol index 64f6983..7e76a6d 100644 --- a/src/Verifier.sol +++ b/src/Verifier.sol @@ -1,4 +1,3 @@ - // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; @@ -11,8 +10,7 @@ import {ITreeVerifier} from "src/interfaces/ITreeVerifier.sol"; /// (256 bytes) and compressed (128 bytes) format. A view function is provided /// to compress proofs. /// @notice See for further explanation. -contract Verifier is ITreeVerifier{ - +contract Verifier is ITreeVerifier { /// Some of the provided public input values are larger than the field modulus. /// @dev Public input elements are not automatically reduced, as this is can be /// a dangerous source of bugs. @@ -46,41 +44,63 @@ contract Verifier is ITreeVerifier{ // Fp2 elements are encoded in the public interface as this became convention. // Constants in Fp - uint256 constant FRACTION_1_2_FP = 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4; - uint256 constant FRACTION_27_82_FP = 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; - uint256 constant FRACTION_3_82_FP = 0x2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e775; + uint256 constant FRACTION_1_2_FP = + 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4; + uint256 constant FRACTION_27_82_FP = + 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; + uint256 constant FRACTION_3_82_FP = + 0x2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e775; // Exponents for inversions and square roots mod P - uint256 constant EXP_INVERSE_FP = 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45; // P - 2 + uint256 constant EXP_INVERSE_FP = + 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45; // P - 2 uint256 constant EXP_SQRT_FP = 0xC19139CB84C680A6E14116DA060561765E05AA45A1C72A34F082305B61F3F52; // (P + 1) / 4; // Groth16 alpha point in G1 - uint256 constant ALPHA_X = 21238094701099767992181020574097576567248669231933303288906499800492665245646; - uint256 constant ALPHA_Y = 17835722675051865338243836993818778396240839209371887976268210291316141770442; + uint256 constant ALPHA_X = + 21238094701099767992181020574097576567248669231933303288906499800492665245646; + uint256 constant ALPHA_Y = + 17835722675051865338243836993818778396240839209371887976268210291316141770442; // Groth16 beta point in G2 in powers of i - uint256 constant BETA_NEG_X_0 = 17328241642904145666695767828612798629577735363797370984204279730597246374999; - uint256 constant BETA_NEG_X_1 = 3860372090784917774458215217940662090649652140977504592804245872521163657018; - uint256 constant BETA_NEG_Y_0 = 10271963604709049923747050159865377390989698297635199231422940911768575903284; - uint256 constant BETA_NEG_Y_1 = 15652994649667530601973737381462460227196275508968367336119989296260301572852; + uint256 constant BETA_NEG_X_0 = + 17328241642904145666695767828612798629577735363797370984204279730597246374999; + uint256 constant BETA_NEG_X_1 = + 3860372090784917774458215217940662090649652140977504592804245872521163657018; + uint256 constant BETA_NEG_Y_0 = + 10271963604709049923747050159865377390989698297635199231422940911768575903284; + uint256 constant BETA_NEG_Y_1 = + 15652994649667530601973737381462460227196275508968367336119989296260301572852; // Groth16 gamma point in G2 in powers of i - uint256 constant GAMMA_NEG_X_0 = 13072135626714886665743299238380777558485915182186148044840030375227830297167; - uint256 constant GAMMA_NEG_X_1 = 15728445629349648373029025074665124823451612105079365074482822873179926876367; - uint256 constant GAMMA_NEG_Y_0 = 574425249090245226726507580262558613212067342295152730122360551738055447720; - uint256 constant GAMMA_NEG_Y_1 = 14984396635537120619265753845843371019305161489040275839272534175355415469117; + uint256 constant GAMMA_NEG_X_0 = + 13072135626714886665743299238380777558485915182186148044840030375227830297167; + uint256 constant GAMMA_NEG_X_1 = + 15728445629349648373029025074665124823451612105079365074482822873179926876367; + uint256 constant GAMMA_NEG_Y_0 = + 574425249090245226726507580262558613212067342295152730122360551738055447720; + uint256 constant GAMMA_NEG_Y_1 = + 14984396635537120619265753845843371019305161489040275839272534175355415469117; // Groth16 delta point in G2 in powers of i - uint256 constant DELTA_NEG_X_0 = 11641495633934915211954655655028364970241467353476707112972519067148042156236; - uint256 constant DELTA_NEG_X_1 = 2862574649712655062568935342174214330174596565118547226721602157017065773404; - uint256 constant DELTA_NEG_Y_0 = 19077360648152442030655834928434260675626758144638095983278277208712230645850; - uint256 constant DELTA_NEG_Y_1 = 14251475007345879229751465975664358189685218843746247716834082671723536271198; + uint256 constant DELTA_NEG_X_0 = + 11641495633934915211954655655028364970241467353476707112972519067148042156236; + uint256 constant DELTA_NEG_X_1 = + 2862574649712655062568935342174214330174596565118547226721602157017065773404; + uint256 constant DELTA_NEG_Y_0 = + 19077360648152442030655834928434260675626758144638095983278277208712230645850; + uint256 constant DELTA_NEG_Y_1 = + 14251475007345879229751465975664358189685218843746247716834082671723536271198; // Constant and public input points - uint256 constant CONSTANT_X = 16288867954761114276402155371115785036905027315050399275668719978219442996927; - uint256 constant CONSTANT_Y = 15611120916366083495836852937429193652107718465176612711134377435436192859809; - uint256 constant PUB_0_X = 4147432231571547026123777586694593106967411953796535970917709407254986235876; - uint256 constant PUB_0_Y = 10326630917030366329673551361728562727322815783171171513266831996416185441581; + uint256 constant CONSTANT_X = + 16288867954761114276402155371115785036905027315050399275668719978219442996927; + uint256 constant CONSTANT_Y = + 15611120916366083495836852937429193652107718465176612711134377435436192859809; + uint256 constant PUB_0_X = + 4147432231571547026123777586694593106967411953796535970917709407254986235876; + uint256 constant PUB_0_Y = + 10326630917030366329673551361728562727322815783171171513266831996416185441581; /// Negation in Fp. /// @notice Returns a number x such that a + x = 0 in Fp. @@ -116,7 +136,7 @@ contract Verifier is ITreeVerifier{ // Exponentiation failed. // Should not happen. revert ProofInvalid(); - } + } } /// Invertsion in Fp. @@ -172,7 +192,11 @@ contract Verifier is ITreeVerifier{ /// @param hint A hint which of two possible signs to pick in the equation. /// @return x0 The real part of the square root. /// @return x1 The imaginary part of the square root. - function sqrt_Fp2(uint256 a0, uint256 a1, bool hint) internal view returns (uint256 x0, uint256 x1) { + function sqrt_Fp2(uint256 a0, uint256 a1, bool hint) + internal + view + returns (uint256 x0, uint256 x1) + { // If this square root reverts there is no solution in Fp2. uint256 d = sqrt_Fp(addmod(mulmod(a0, a0, P), mulmod(a1, a1, P), P)); if (hint) { @@ -184,8 +208,10 @@ contract Verifier is ITreeVerifier{ // Check result to make sure we found a root. // Note: this also fails if a0 or a1 is not reduced. - if (a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P) - || a1 != mulmod(2, mulmod(x0, x1, P), P)) { + if ( + a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P) + || a1 != mulmod(2, mulmod(x0, x1, P), P) + ) { revert ProofInvalid(); } } @@ -206,7 +232,7 @@ contract Verifier is ITreeVerifier{ // Point at infinity return 0; } - + // Note: sqrt_Fp reverts if there is no solution, i.e. the x coordinate is invalid. uint256 y_pos = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P)); if (y == y_pos) { @@ -252,7 +278,7 @@ contract Verifier is ITreeVerifier{ /// @notice Reverts with InvalidProof if the coefficients are not reduced /// or if the point is not on the curve. /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) - /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). /// @param x0 The real part of the X coordinate. /// @param x1 The imaginary poart of the X coordinate. @@ -261,7 +287,10 @@ contract Verifier is ITreeVerifier{ /// @return c0 The first half of the compresed point (x0 with two signal bits). /// @return c1 The second half of the compressed point (x1 unmodified). function compress_g2(uint256 x0, uint256 x1, uint256 y0, uint256 y1) - internal view returns (uint256 c0, uint256 c1) { + internal + view + returns (uint256 c0, uint256 c1) + { if (x0 >= P || x1 >= P || y0 >= P || y1 >= P) { // G2 point not in field. revert ProofInvalid(); @@ -276,11 +305,11 @@ contract Verifier is ITreeVerifier{ uint256 y0_pos; uint256 y1_pos; { - uint256 n3ab = mulmod(mulmod(x0, x1, P), P-3, P); + uint256 n3ab = mulmod(mulmod(x0, x1, P), P - 3, P); uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); y0_pos = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); - y1_pos = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); + y1_pos = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); } // Determine hint bit @@ -294,10 +323,10 @@ contract Verifier is ITreeVerifier{ // Recover y (y0_pos, y1_pos) = sqrt_Fp2(y0_pos, y1_pos, hint); if (y0 == y0_pos && y1 == y1_pos) { - c0 = (x0 << 2) | (hint ? 2 : 0) | 0; + c0 = (x0 << 2) | (hint ? 2 : 0) | 0; c1 = x1; } else if (y0 == negate(y0_pos) && y1 == negate(y1_pos)) { - c0 = (x0 << 2) | (hint ? 2 : 0) | 1; + c0 = (x0 << 2) | (hint ? 2 : 0) | 1; c1 = x1; } else { // G1 point not on curve. @@ -308,7 +337,7 @@ contract Verifier is ITreeVerifier{ /// Decompress a G2 point. /// @notice Reverts with InvalidProof if the input does not represent a valid point. /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) - /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). /// @param c0 The first half of the compresed point (x0 with two signal bits). /// @param c1 The second half of the compressed point (x1 unmodified). @@ -317,7 +346,10 @@ contract Verifier is ITreeVerifier{ /// @return y0 The real part of the Y coordinate. /// @return y1 The imaginary part of the Y coordinate. function decompress_g2(uint256 c0, uint256 c1) - internal view returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1) { + internal + view + returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1) + { // Note that X = (0, 0) is not on the curve since 0³ + 3/(9 + i) is not a square. // so we can use it to represent the point at infinity. if (c0 == 0 && c1 == 0) { @@ -333,12 +365,12 @@ contract Verifier is ITreeVerifier{ revert ProofInvalid(); } - uint256 n3ab = mulmod(mulmod(x0, x1, P), P-3, P); + uint256 n3ab = mulmod(mulmod(x0, x1, P), P - 3, P); uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); y0 = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); - y1 = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); + y1 = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); // Note: sqrt_Fp2 reverts if there is no solution, i.e. the point is not on the curve. // Note: (X³ + 3/(9 + i)) is irreducible in Fp2, so y can not be zero. @@ -358,7 +390,10 @@ contract Verifier is ITreeVerifier{ /// @return x The X coordinate of the resulting G1 point. /// @return y The Y coordinate of the resulting G1 point. function publicInputMSM(uint256[1] calldata input) - internal view returns (uint256 x, uint256 y) { + internal + view + returns (uint256 x, uint256 y) + { // Note: The ECMUL precompile does not reject unreduced values, so we check this. // Note: Unrolling this loop does not cost much extra in code-size, the bulk of the // code-size is in the PUB_ constants. @@ -375,7 +410,7 @@ contract Verifier is ITreeVerifier{ mstore(add(f, 0x20), CONSTANT_Y) mstore(g, PUB_0_X) mstore(add(g, 0x20), PUB_0_Y) - s := calldataload(input) + s := calldataload(input) mstore(add(g, 0x40), s) success := and(success, lt(s, R)) success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) @@ -398,7 +433,10 @@ contract Verifier is ITreeVerifier{ /// @return compressed The compressed proof. Elements are in the same order as for /// verifyCompressedProof. I.e. points (A, B, C) in compressed format. function compressProof(uint256[8] calldata proof) - public view returns (uint256[4] memory compressed) { + public + view + returns (uint256[4] memory compressed) + { compressed[0] = compress_g1(proof[0], proof[1]); (compressed[2], compressed[1]) = compress_g2(proof[3], proof[2], proof[5], proof[4]); compressed[3] = compress_g1(proof[6], proof[7]); @@ -413,13 +451,13 @@ contract Verifier is ITreeVerifier{ /// matching the output of compressProof. /// @param input the public input field elements in the scalar field Fr. /// Elements must be reduced. - function verifyCompressedProof( - uint256[4] calldata compressedProof, - uint256[1] calldata input - ) public view { + function verifyCompressedProof(uint256[4] calldata compressedProof, uint256[1] calldata input) + public + view + { (uint256 Ax, uint256 Ay) = decompress_g1(compressedProof[0]); - (uint256 Bx0, uint256 Bx1, uint256 By0, uint256 By1) = decompress_g2( - compressedProof[2], compressedProof[1]); + (uint256 Bx0, uint256 Bx1, uint256 By0, uint256 By1) = + decompress_g2(compressedProof[2], compressedProof[1]); (uint256 Cx, uint256 Cy) = decompress_g1(compressedProof[3]); (uint256 Lx, uint256 Ly) = publicInputMSM(input); @@ -428,17 +466,17 @@ contract Verifier is ITreeVerifier{ // Note: The pairing precompile rejects unreduced values, so we won't check that here. uint256[24] memory pairings; // e(A, B) - pairings[ 0] = Ax; - pairings[ 1] = Ay; - pairings[ 2] = Bx1; - pairings[ 3] = Bx0; - pairings[ 4] = By1; - pairings[ 5] = By0; + pairings[0] = Ax; + pairings[1] = Ay; + pairings[2] = Bx1; + pairings[3] = Bx0; + pairings[4] = By1; + pairings[5] = By0; // e(C, -δ) - pairings[ 6] = Cx; - pairings[ 7] = Cy; - pairings[ 8] = DELTA_NEG_X_1; - pairings[ 9] = DELTA_NEG_X_0; + pairings[6] = Cx; + pairings[7] = Cy; + pairings[8] = DELTA_NEG_X_1; + pairings[9] = DELTA_NEG_X_0; pairings[10] = DELTA_NEG_Y_1; pairings[11] = DELTA_NEG_Y_0; // e(α, -β) @@ -478,15 +516,12 @@ contract Verifier is ITreeVerifier{ /// of compressProof. /// @param input the public input field elements in the scalar field Fr. /// Elements must be reduced. - function verifyProof( - uint256[8] calldata proof, - uint256[1] calldata input - ) public view { + function verifyProof(uint256[8] calldata proof, uint256[1] calldata input) public view { (uint256 x, uint256 y) = publicInputMSM(input); // Note: The precompile expects the F2 coefficients in big-endian order. // Note: The pairing precompile rejects unreduced values, so we won't check that here. - + bool success; assembly ("memory-safe") { let f := mload(0x40) // Free memory pointer. diff --git a/src/WorldIDIdentityManagerImplV1.sol b/src/WorldIDIdentityManagerImplV1.sol index 7331596..7d9b95a 100644 --- a/src/WorldIDIdentityManagerImplV1.sol +++ b/src/WorldIDIdentityManagerImplV1.sol @@ -390,10 +390,7 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { batchInsertionVerifiers.getVerifierFor(identityCommitments.length); // With that, we can properly try and verify. - try insertionVerifier.verifyProof( - insertionProof, - [reducedElement] - ) { + try insertionVerifier.verifyProof(insertionProof, [reducedElement]) { // If it did verify, we need to update the contract's state. We set the currently valid // root to the root after the insertions. _latestRoot = postRoot; diff --git a/src/WorldIDIdentityManagerImplV2.sol b/src/WorldIDIdentityManagerImplV2.sol index 2ee0fd6..05ef0d0 100644 --- a/src/WorldIDIdentityManagerImplV2.sol +++ b/src/WorldIDIdentityManagerImplV2.sol @@ -130,10 +130,7 @@ contract WorldIDIdentityManagerImplV2 is WorldIDIdentityManagerImplV1 { ITreeVerifier deletionVerifier = batchDeletionVerifiers.getVerifierFor(batchSize); // With that, we can properly try and verify. - try deletionVerifier.verifyProof( - deletionProof, - [reducedElement] - ) { + try deletionVerifier.verifyProof(deletionProof, [reducedElement]) { // If it did verify, we need to update the contract's state. We set the currently valid // root to the root after the insertions. _latestRoot = postRoot; diff --git a/src/interfaces/ITreeVerifier.sol b/src/interfaces/ITreeVerifier.sol index cf8080d..543fc87 100644 --- a/src/interfaces/ITreeVerifier.sol +++ b/src/interfaces/ITreeVerifier.sol @@ -14,8 +14,5 @@ interface ITreeVerifier { /// of compressProof. /// @param input the public input field elements in the scalar field Fr. /// Elements must be reduced. - function verifyProof( - uint256[8] calldata proof, - uint256[1] calldata input - ) external; + function verifyProof(uint256[8] calldata proof, uint256[1] calldata input) external; } diff --git a/src/test/data/DeletionProof.json b/src/test/data/DeletionProof.json new file mode 100644 index 0000000..a54243c --- /dev/null +++ b/src/test/data/DeletionProof.json @@ -0,0 +1,20 @@ +{ + "ar": [ + "0x218c1210810f33ec6afd6edbd3311ebdc91ad86c5bd9f6c40e632c001fcb6589", + "0x283fed97c26434f2fb1209d5b8182475805985fa9465593b4de6f73983e11a70" + ], + "bs": [ + [ + "0x2876bbb4c3c686df15f3c91de08a26da35fce97ff4c0172426bb0619b11435e1", + "0x20fe2c10422b1350f688501e67bbb2154ac4ff63a6da3f8d049fae7a0e59f7d7" + ], + [ + "0x156b3808aff9884cb83bea0658b65b53e56f68033ace4b0da14271a84d98e9b7", + "0x10f2dba2ada60a0cf804fc9292da6f05dec6692e07406d6f77716b8c9b9dbf8c" + ] + ], + "krs": [ + "0x1f751a85dc3bae8c055d57e072a1d5ce5a849f22b2a5d121cebb24fe636c5d3", + "0x98eb20c27302e047f4ad218e1c15e6bd823936bbecceceac6c470606734716b" + ] +} diff --git a/src/test/data/InsertionProof.json b/src/test/data/InsertionProof.json new file mode 100644 index 0000000..d1f32c6 --- /dev/null +++ b/src/test/data/InsertionProof.json @@ -0,0 +1,20 @@ +{ + "ar": [ + "0x6753336854c309cda0b340dae718f8b625dc5fcf13c6358d3095dfe6d88c3f4", + "0x2bd788a6291ec15ec23d33cb457a750ee373c82b508d9e936048500764627427" + ], + "bs": [ + [ + "0x116b49f6c3092a835be023b86fd70aa9e7af5800402061e86b58dd51af4185a4", + "0x2e7528a5fe5b6e8d80052f215d1d12a3201154887f5cf4a99b5d9aef4a02ea8e" + ], + [ + "0x1f4b9f9e7456d6b50ee5407caa71364689db7ce3d29750e86ff697f4fb3ab12d", + "0xa624c5d6e8bcfae6c0e9953e251f3b7f7ae04fdacee382beecc309417c182be" + ] + ], + "krs": [ + "0xcb161f852a6c7449377fc637e691017d6a18e74ef9617f92210d173bac0add8", + "0x24fd0a88c531515e53001fc2df8df624a913f791a8bf8eaf62dba753391b8960" + ] +} diff --git a/src/test/data/TestInsertionParams.json b/src/test/data/TestInsertionParams.json index 7bcb91a..b03fb10 100644 --- a/src/test/data/TestInsertionParams.json +++ b/src/test/data/TestInsertionParams.json @@ -1,24 +1,63 @@ { - "inputHash": "0x7d7f77c56064e1f8577de14bba99eff85599ab0e76d0caeadd1ad61674b8a9c3", + "inputHash": "0x66f12f84870ce040647fb5f207b08f69676c8a7f6063dbe6b20de111183f2688", "startIndex": 0, - "preRoot": "0x18f43331537ee2af2e3d758d50f72106467c6eea50371dd528d57eb2b856d238", - "postRoot": "0x5c1e52b41a571293b30efacd2afdb7173b20cfaf1f646c4ac9f96eb75848270", + "preRoot": "0x2a7c7c9b6ce5880b9f6f228d72bf6a575a526f29c66ecceef8b753d38bba7323", + "postRoot": "0x193289951bec3e4a099d9f1b0fb22cf20fe9dc4ea75c253352f22848b08c888b", "identityCommitments": ["0x1", "0x2", "0x3"], "merkleProofs": [ [ "0x0", "0x2098f5fb9e239eab3ceac3f27b81e481dc3124d55ffed523a839ee8446b64864", - "0x1069673dcdb12263df301a6ff584a7ec261a44cb9dc68df067a4774460b1f1e1" + "0x1069673dcdb12263df301a6ff584a7ec261a44cb9dc68df067a4774460b1f1e1", + "0x18f43331537ee2af2e3d758d50f72106467c6eea50371dd528d57eb2b856d238", + "0x7f9d837cb17b0d36320ffe93ba52345f1b728571a568265caac97559dbc952a", + "0x2b94cf5e8746b3f5c9631f4c5df32907a699c58c94b2ad4d7b5cec1639183f55", + "0x2dee93c5a666459646ea7d22cca9e1bcfed71e6951b953611d11dda32ea09d78", + "0x78295e5a22b84e982cf601eb639597b8b0515a88cb5ac7fa8a4aabe3c87349d", + "0x2fa5e5f18f6027a6501bec864564472a616b2e274a41211a444cbe3a99f3cc61", + "0xe884376d0d8fd21ecb780389e941f66e45e7acce3e228ab3e2156a614fcd747", + "0x1b7201da72494f1e28717ad1a52eb469f95892f957713533de6175e5da190af2", + "0x1f8d8822725e36385200c0b201249819a6e6e1e4650808b5bebc6bface7d7636", + "0x2c5d82f66c914bafb9701589ba8cfcfb6162b0a12acf88a8d0879a0471b5f85a", + "0x14c54148a0940bb820957f5adf3fa1134ef5c4aaa113f4646458f270e0bfbfd0", + "0x190d33b12f986f961e10c0ee44d8b9af11be25588cad89d416118e4bf4ebe80c", + "0x22f98aa9ce704152ac17354914ad73ed1167ae6596af510aa5b3649325e06c92" ], [ "0x1", "0x2098f5fb9e239eab3ceac3f27b81e481dc3124d55ffed523a839ee8446b64864", - "0x1069673dcdb12263df301a6ff584a7ec261a44cb9dc68df067a4774460b1f1e1" + "0x1069673dcdb12263df301a6ff584a7ec261a44cb9dc68df067a4774460b1f1e1", + "0x18f43331537ee2af2e3d758d50f72106467c6eea50371dd528d57eb2b856d238", + "0x7f9d837cb17b0d36320ffe93ba52345f1b728571a568265caac97559dbc952a", + "0x2b94cf5e8746b3f5c9631f4c5df32907a699c58c94b2ad4d7b5cec1639183f55", + "0x2dee93c5a666459646ea7d22cca9e1bcfed71e6951b953611d11dda32ea09d78", + "0x78295e5a22b84e982cf601eb639597b8b0515a88cb5ac7fa8a4aabe3c87349d", + "0x2fa5e5f18f6027a6501bec864564472a616b2e274a41211a444cbe3a99f3cc61", + "0xe884376d0d8fd21ecb780389e941f66e45e7acce3e228ab3e2156a614fcd747", + "0x1b7201da72494f1e28717ad1a52eb469f95892f957713533de6175e5da190af2", + "0x1f8d8822725e36385200c0b201249819a6e6e1e4650808b5bebc6bface7d7636", + "0x2c5d82f66c914bafb9701589ba8cfcfb6162b0a12acf88a8d0879a0471b5f85a", + "0x14c54148a0940bb820957f5adf3fa1134ef5c4aaa113f4646458f270e0bfbfd0", + "0x190d33b12f986f961e10c0ee44d8b9af11be25588cad89d416118e4bf4ebe80c", + "0x22f98aa9ce704152ac17354914ad73ed1167ae6596af510aa5b3649325e06c92" ], [ "0x0", "0x115cc0f5e7d690413df64c6b9662e9cf2a3617f2743245519e19607a4417189a", - "0x1069673dcdb12263df301a6ff584a7ec261a44cb9dc68df067a4774460b1f1e1" + "0x1069673dcdb12263df301a6ff584a7ec261a44cb9dc68df067a4774460b1f1e1", + "0x18f43331537ee2af2e3d758d50f72106467c6eea50371dd528d57eb2b856d238", + "0x7f9d837cb17b0d36320ffe93ba52345f1b728571a568265caac97559dbc952a", + "0x2b94cf5e8746b3f5c9631f4c5df32907a699c58c94b2ad4d7b5cec1639183f55", + "0x2dee93c5a666459646ea7d22cca9e1bcfed71e6951b953611d11dda32ea09d78", + "0x78295e5a22b84e982cf601eb639597b8b0515a88cb5ac7fa8a4aabe3c87349d", + "0x2fa5e5f18f6027a6501bec864564472a616b2e274a41211a444cbe3a99f3cc61", + "0xe884376d0d8fd21ecb780389e941f66e45e7acce3e228ab3e2156a614fcd747", + "0x1b7201da72494f1e28717ad1a52eb469f95892f957713533de6175e5da190af2", + "0x1f8d8822725e36385200c0b201249819a6e6e1e4650808b5bebc6bface7d7636", + "0x2c5d82f66c914bafb9701589ba8cfcfb6162b0a12acf88a8d0879a0471b5f85a", + "0x14c54148a0940bb820957f5adf3fa1134ef5c4aaa113f4646458f270e0bfbfd0", + "0x190d33b12f986f961e10c0ee44d8b9af11be25588cad89d416118e4bf4ebe80c", + "0x22f98aa9ce704152ac17354914ad73ed1167ae6596af510aa5b3649325e06c92" ] ] } diff --git a/src/test/identity-manager/WorldIDIdentityManagerIdentityDeletion.t.sol b/src/test/identity-manager/WorldIDIdentityManagerIdentityDeletion.t.sol index 984695d..24ab2c7 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerIdentityDeletion.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerIdentityDeletion.t.sol @@ -222,7 +222,7 @@ contract WorldIDIdentityManagerIdentityDeletion is WorldIDIdentityManagerTest { // Setup vm.assume(!SimpleVerify.isValidInput(uint256(prf[0]))); vm.assume(newPreRoot != newPostRoot); - vm.assume(packedDeletionIndices.length <= 1000); + vm.assume(packedDeletionIndices.length >0 && packedDeletionIndices.length <= 1000); uint32 indicesLength = uint32(packedDeletionIndices.length * 8); ( diff --git a/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol b/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol index a3e24f8..892c03c 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol @@ -76,7 +76,7 @@ contract WorldIDIdentityManagerSemaphoreValidation is WorldIDIdentityManagerTest assertCallFailsOn(identityManagerAddress, verifyProofCallData); } - /// @notice Checks that the proof validates properly with the correct inputs. + /// @notice Checks that the proof validates properly with the correct inputs. function testOptimizedProofVerificationWithCorrectInputs( uint8 actualTreeDepth, uint256 nullifierHash, diff --git a/src/test/identity-manager/WorldIDIdentityManagerTest.sol b/src/test/identity-manager/WorldIDIdentityManagerTest.sol index f1ca0a8..05eb63c 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerTest.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerTest.sol @@ -52,13 +52,14 @@ contract WorldIDIdentityManagerTest is WorldIDTest { /////////////////////////////////////////////////////////////////// // All hardcoded test data taken from `src/test/data/TestInsertionParams.json`. This will be dynamically // generated at some point in the future. + /// @dev generated using `./semaphore-mtb/gnark-mbu gen-test-params --mode insertion --tree-depth 16 --batch-size 3` bytes32 internal constant insertionInputHash = - 0x7d7f77c56064e1f8577de14bba99eff85599ab0e76d0caeadd1ad61674b8a9c3; + 0x66f12f84870ce040647fb5f207b08f69676c8a7f6063dbe6b20de111183f2688; uint32 internal constant startIndex = 0; uint256 internal constant insertionPreRoot = - 0x18f43331537ee2af2e3d758d50f72106467c6eea50371dd528d57eb2b856d238; + 0x2a7c7c9b6ce5880b9f6f228d72bf6a575a526f29c66ecceef8b753d38bba7323; uint256 internal constant insertionPostRoot = - 0x5c1e52b41a571293b30efacd2afdb7173b20cfaf1f646c4ac9f96eb75848270; + 0x193289951bec3e4a099d9f1b0fb22cf20fe9dc4ea75c253352f22848b08c888b; uint256[] identityCommitments; uint256 identityCommitmentsSize = 3; @@ -69,6 +70,7 @@ contract WorldIDIdentityManagerTest is WorldIDTest { /////////////////////////////////////////////////////////////////// // All hardcoded test data taken from `src/test/data/TestDeletionParams.json`. This will be dynamically // generated at some point in the future. + /// @dev generated using semaphore-mtb: ./gnark-mbu gen-test-params --mode deletion --tree-depth 16 --batch-size 8 bytes32 internal constant deletionInputHash = 0x227590f99431e20f2f95fdfb1b7dfb648c04242c950c31263ba165647c96501a; uint256 internal constant deletionPreRoot = @@ -100,34 +102,41 @@ contract WorldIDIdentityManagerTest is WorldIDTest { /////////////////////////////////////////////////////////////////////////////// constructor() { - // Make the identity commitments. + // Make the identity commitments to be inserted. + // needs to match the params in src/test/data/TestInsertionParams.json identityCommitments = new uint256[](identityCommitmentsSize); identityCommitments[0] = 0x1; identityCommitments[1] = 0x2; identityCommitments[2] = 0x3; // Create the insertion proof term. + // output from semaphore-mtb prove in src/test/data/InsertionProof.json + /// @dev test_insertion.ps is generated using semaphore-mtb: `./gnark-mbu setup --mode insertion --batch-size 3 --tree-depth 16 --output test_insertion.ps` + /// @dev generated using semaphore-mtb: `./gnark-mbu gen-test-params --mode insertion --tree-depth 16 --batch-size 3 | ./gnark-mbu prove --mode insertion --keys-file test_insertion.ps` insertionProof = [ - 0x2a45bf326884bbf13c821a5e4f30690a391156cccf80a2922fb24250111dd7eb, - 0x23a7376a159513e6d0e22d43fcdca9d0c8a5c54a73b59fce6962a41e71355894, - 0x21b9fc7c2d1f76c2e1a972b00f18728a57a34d7e4ae040811bf1626132ff3658, - 0x2a7c3c660190a33ab92cd84e4b2540e49ea80bdc766eb3aeec49806a78071c75, - 0x2fc9a52a7f4bcc29faab28a8d8ec126b4fe604a7b41e7d2b3efe92422951d706, - 0x110740f0b21fb329de682dffc95a5ede11c11c6328606fe254b6ba469b15f68, - 0x23115ff1573808639f19724479b195b7894a45c9868242ad2a416767359c6c78, - 0x23f3fa30273c7f38e360496e7f9790450096d4a9592e1fe6e0a996cb05b8fb28 + 0x6753336854c309cda0b340dae718f8b625dc5fcf13c6358d3095dfe6d88c3f4, + 0x2bd788a6291ec15ec23d33cb457a750ee373c82b508d9e936048500764627427, + 0x116b49f6c3092a835be023b86fd70aa9e7af5800402061e86b58dd51af4185a4, + 0x2e7528a5fe5b6e8d80052f215d1d12a3201154887f5cf4a99b5d9aef4a02ea8e, + 0x1f4b9f9e7456d6b50ee5407caa71364689db7ce3d29750e86ff697f4fb3ab12d, + 0xa624c5d6e8bcfae6c0e9953e251f3b7f7ae04fdacee382beecc309417c182be, + 0xcb161f852a6c7449377fc637e691017d6a18e74ef9617f92210d173bac0add8, + 0x24fd0a88c531515e53001fc2df8df624a913f791a8bf8eaf62dba753391b8960 ]; // Create the deletion proof term. + // output from semaphore-mtb prove in src/test/data/DeletionProof.json + /// @dev test_deletion.ps is generated using semaphore-mtb: `./gnark-mbu setup --mode deletion --batch-size 3 --tree-depth 16 --output test_deletion.ps` + /// @dev generated using semaphore-mtb: `./gnark-mbu gen-test-params --mode deletion --tree-depth 16 --batch-size 3 | ./gnark-mbu prove --mode deletion --keys-file test_deletion.ps` deletionProof = [ - 0x226cb5c88ce8ccf2774dc13847c3b579c4e4bc8d47bfc2a9ac1454e1a9a42ee3, - 0x1e69ced73a40a88c9f68df4a1bf34c3ff67efca6e2682bd8d1bb96a7a3e4bf50, - 0x1029d8179a82355f902562af0f0e719e31ac12f63effb0c9006ee3332c280e01, - 0x24ae80c2f18161206a0eacc736ddef2c518e0dec37e13c6f0cf17034d04508cc, - 0x29213a2cb6582178edd743f8e8b5541175855b55d0c90f1894a1e415b625af70, - 0x2c9119a368d137dd2409dee4796eb96059296f87009e3d47b0a858cfd05d6954, - 0x2cdd77b17d2270a8fe1385ec60fdd6c644f83549331ae116a538c555f56a9540, - 0x1f7214627223f7839a538052d96ad480a9565a6ec8a9e1fcecf54a7d73a55495 + 0x218c1210810f33ec6afd6edbd3311ebdc91ad86c5bd9f6c40e632c001fcb6589, + 0x283fed97c26434f2fb1209d5b8182475805985fa9465593b4de6f73983e11a70, + 0x2876bbb4c3c686df15f3c91de08a26da35fce97ff4c0172426bb0619b11435e1, + 0x20fe2c10422b1350f688501e67bbb2154ac4ff63a6da3f8d049fae7a0e59f7d7, + 0x156b3808aff9884cb83bea0658b65b53e56f68033ace4b0da14271a84d98e9b7, + 0x10f2dba2ada60a0cf804fc9292da6f05dec6692e07406d6f77716b8c9b9dbf8c, + 0x1f751a85dc3bae8c055d57e072a1d5ce5a849f22b2a5d121cebb24fe636c5d3, + 0x98eb20c27302e047f4ad218e1c15e6bd823936bbecceceac6c470606734716b ]; } diff --git a/src/test/mock/DeletionTreeVerifier.sol b/src/test/mock/DeletionTreeVerifier.sol index 1f906e1..4bab5bc 100644 --- a/src/test/mock/DeletionTreeVerifier.sol +++ b/src/test/mock/DeletionTreeVerifier.sol @@ -265,10 +265,7 @@ contract Verifier is ITreeVerifier { * @returns Whether the proof is valid given the hardcoded verifying key * above and the public inputs */ - function verifyProof( - uint256[8] memory _proof, - uint256[1] memory input - ) public view { + function verifyProof(uint256[8] memory _proof, uint256[1] memory input) public view { Proof memory proof; //TODO: going to need to double check the order of nested b @@ -321,15 +318,18 @@ contract Verifier is ITreeVerifier { mul_input[2] = input[0]; accumulate(mul_input, q, add_input, vk_x); // vk_x += vk.K[1] * input[0] - require(Pairing.pairing( - Pairing.negate(proof.A), - proof.B, - vk.alfa1, - vk.beta2, - vk_x, - vk.gamma2, - proof.C, - vk.delta2 - ), "Invalid proof"); + require( + Pairing.pairing( + Pairing.negate(proof.A), + proof.B, + vk.alfa1, + vk.beta2, + vk_x, + vk.gamma2, + proof.C, + vk.delta2 + ), + "Invalid proof" + ); } } diff --git a/src/test/mock/InsertionTreeVerifier.sol b/src/test/mock/InsertionTreeVerifier.sol index 337207f..453d248 100644 --- a/src/test/mock/InsertionTreeVerifier.sol +++ b/src/test/mock/InsertionTreeVerifier.sol @@ -216,10 +216,7 @@ contract Verifier is ITreeVerifier { * @returns Whether the proof is valid given the hardcoded verifying key * above and the public inputs */ - function verifyProof( - uint256[8] memory _proof, - uint256[1] memory input - ) public view { + function verifyProof(uint256[8] memory _proof, uint256[1] memory input) public view { Proof memory proof; //TODO: going to need to double check the order of nested b @@ -253,15 +250,18 @@ contract Verifier is ITreeVerifier { vk_x = Pairing.plus(vk_x, vk.IC[0]); - require(Pairing.pairing( - Pairing.negate(proof.A), - proof.B, - vk.alfa1, - vk.beta2, - vk_x, - vk.gamma2, - proof.C, - vk.delta2 - ), "Invalid proof"); + require( + Pairing.pairing( + Pairing.negate(proof.A), + proof.B, + vk.alfa1, + vk.beta2, + vk_x, + vk.gamma2, + proof.C, + vk.delta2 + ), + "Invalid proof" + ); } } diff --git a/src/test/mock/SequencerVerifier.sol b/src/test/mock/SequencerVerifier.sol index 40fd3ac..5b1996d 100644 --- a/src/test/mock/SequencerVerifier.sol +++ b/src/test/mock/SequencerVerifier.sol @@ -11,10 +11,7 @@ contract SequencerVerifier is ITreeVerifier { uint256 internal constant SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617; - function verifyProof( - uint256[8] memory proof, - uint256[1] memory input - ) external { + function verifyProof(uint256[8] memory proof, uint256[1] memory input) external { require(proof[0] % 2 == 0 && proof[1] % SNARK_SCALAR_FIELD == input[0], "Invalid Proof"); } } diff --git a/src/test/mock/SimpleVerifier.sol b/src/test/mock/SimpleVerifier.sol index 2ace791..90f9cd4 100644 --- a/src/test/mock/SimpleVerifier.sol +++ b/src/test/mock/SimpleVerifier.sol @@ -15,10 +15,7 @@ contract SimpleVerifier is ITreeVerifier { batchSize = _batchSize; } - function verifyProof( - uint256[8] memory proof, - uint256[1] memory input - ) external { + function verifyProof(uint256[8] memory proof, uint256[1] memory input) external { bool result = proof[0] % 2 == 0; if (result) { diff --git a/src/utils/UnimplementedTreeVerifier.sol b/src/utils/UnimplementedTreeVerifier.sol index 32cfeda..7d4302a 100644 --- a/src/utils/UnimplementedTreeVerifier.sol +++ b/src/utils/UnimplementedTreeVerifier.sol @@ -14,7 +14,7 @@ contract UnimplementedTreeVerifier is ITreeVerifier { /// @notice Thrown when an operation is not supported. error UnsupportedOperation(); - /// @notice Verify an uncompressed Groth16 proof. + /// @notice Verify an uncompressed Groth16 proof. /// @notice Reverts with InvalidProof if the proof is invalid or /// with PublicInputNotInField the public input is not reduced. /// @notice There is no return value. If the function does not revert, the @@ -24,10 +24,7 @@ contract UnimplementedTreeVerifier is ITreeVerifier { /// @param input the public input field elements in the scalar field Fr. /// Elements must be reduced. /// @custom:reverts UnsupportedOperation When called. - function verifyProof( - uint256[8] memory proof, - uint256[1] memory input - ) external pure { + function verifyProof(uint256[8] memory proof, uint256[1] memory input) external pure { delete proof; delete input; revert UnsupportedOperation(); From a6f59aacf36858254964a7519767af114bf1543a Mon Sep 17 00:00:00 2001 From: "dcbuilder.eth" Date: Mon, 25 Sep 2023 19:30:40 +0200 Subject: [PATCH 07/20] update verifiers test fail --- src/test/data/DeletionProof.json | 18 +- src/test/data/InsertionProof.json | 18 +- .../WorldIDIdentityManagerTest.sol | 38 +- src/test/mock/DeletionTreeVerifier.sol | 774 +++++++++++------- src/test/mock/InsertionTreeVerifier.sol | 722 ++++++++++------ 5 files changed, 1013 insertions(+), 557 deletions(-) diff --git a/src/test/data/DeletionProof.json b/src/test/data/DeletionProof.json index a54243c..f495ddd 100644 --- a/src/test/data/DeletionProof.json +++ b/src/test/data/DeletionProof.json @@ -1,20 +1,20 @@ { "ar": [ - "0x218c1210810f33ec6afd6edbd3311ebdc91ad86c5bd9f6c40e632c001fcb6589", - "0x283fed97c26434f2fb1209d5b8182475805985fa9465593b4de6f73983e11a70" + "0x19233cf0c60aa740585125dd5936462b1795bc2c8da7b9d0b7e92392cf91e1fd", + "0x244096da06de365f3bd8e7f428c2de4214096c4cd0feeba579642435ab15e90a" ], "bs": [ [ - "0x2876bbb4c3c686df15f3c91de08a26da35fce97ff4c0172426bb0619b11435e1", - "0x20fe2c10422b1350f688501e67bbb2154ac4ff63a6da3f8d049fae7a0e59f7d7" + "0x107395cd3aa9bfe3bcaada7f171d43a1ffffd576325598e2b9c8fbe1cfd6d032", + "0xac23f21fb0376055adeee2a78491ca13afc288c63c6450d0ce6ded6fda14344" ], [ - "0x156b3808aff9884cb83bea0658b65b53e56f68033ace4b0da14271a84d98e9b7", - "0x10f2dba2ada60a0cf804fc9292da6f05dec6692e07406d6f77716b8c9b9dbf8c" + "0x29022f4cf64701ff88807430b9e333d87c670a4bdfe7d495d76271044a2d3711", + "0x134e41bef89e02289885852b368395b1b679dd243e5cf9e2f36b04ba990ab6a2" ] ], "krs": [ - "0x1f751a85dc3bae8c055d57e072a1d5ce5a849f22b2a5d121cebb24fe636c5d3", - "0x98eb20c27302e047f4ad218e1c15e6bd823936bbecceceac6c470606734716b" + "0x280894db66e6a9f9bf8aa48ffa1de98b755adadcf5962fb308cd1802a1101a0c", + "0x1484814b74243a07930c6af61079f94eefd843efe95e2388d9d49956cfacf3ab" ] -} +} \ No newline at end of file diff --git a/src/test/data/InsertionProof.json b/src/test/data/InsertionProof.json index d1f32c6..3f61e76 100644 --- a/src/test/data/InsertionProof.json +++ b/src/test/data/InsertionProof.json @@ -1,20 +1,20 @@ { "ar": [ - "0x6753336854c309cda0b340dae718f8b625dc5fcf13c6358d3095dfe6d88c3f4", - "0x2bd788a6291ec15ec23d33cb457a750ee373c82b508d9e936048500764627427" + "0x18491e665bc7128f0113b3cf187502311cf5a82b0304e02464099782483b14ba", + "0x1dace8033bc22eda25b483b2a260195b67ee5bef07990bf0e2c5f7923423fe" ], "bs": [ [ - "0x116b49f6c3092a835be023b86fd70aa9e7af5800402061e86b58dd51af4185a4", - "0x2e7528a5fe5b6e8d80052f215d1d12a3201154887f5cf4a99b5d9aef4a02ea8e" + "0x1d4489b99a91a972e878bef7a147251c8d4941b415bb7b36a9740e714f995b7e", + "0x772049285800265c330a0850d30d32c1ece88a0aa6adbd6a6197d0a1c2e2de2" ], [ - "0x1f4b9f9e7456d6b50ee5407caa71364689db7ce3d29750e86ff697f4fb3ab12d", - "0xa624c5d6e8bcfae6c0e9953e251f3b7f7ae04fdacee382beecc309417c182be" + "0x27afc608a28bd2f8743bc2b423dbc34829b374cf702789f9549d4b730fcc7ec8", + "0x1011cfd2347e8db6cd489a8090331a73db380b6774ec3bc14c77a2dabe0e83dd" ] ], "krs": [ - "0xcb161f852a6c7449377fc637e691017d6a18e74ef9617f92210d173bac0add8", - "0x24fd0a88c531515e53001fc2df8df624a913f791a8bf8eaf62dba753391b8960" + "0x2f5f37e84d6acff8cfd7988d33aaea072dbe5071093b2df022d23047f134ac45", + "0x24830332559eada283d4473b17091b239443e75e9e09f0ebce8e72c235ee665d" ] -} +} \ No newline at end of file diff --git a/src/test/identity-manager/WorldIDIdentityManagerTest.sol b/src/test/identity-manager/WorldIDIdentityManagerTest.sol index 05eb63c..76c4b47 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerTest.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerTest.sol @@ -113,30 +113,30 @@ contract WorldIDIdentityManagerTest is WorldIDTest { // output from semaphore-mtb prove in src/test/data/InsertionProof.json /// @dev test_insertion.ps is generated using semaphore-mtb: `./gnark-mbu setup --mode insertion --batch-size 3 --tree-depth 16 --output test_insertion.ps` /// @dev generated using semaphore-mtb: `./gnark-mbu gen-test-params --mode insertion --tree-depth 16 --batch-size 3 | ./gnark-mbu prove --mode insertion --keys-file test_insertion.ps` - insertionProof = [ - 0x6753336854c309cda0b340dae718f8b625dc5fcf13c6358d3095dfe6d88c3f4, - 0x2bd788a6291ec15ec23d33cb457a750ee373c82b508d9e936048500764627427, - 0x116b49f6c3092a835be023b86fd70aa9e7af5800402061e86b58dd51af4185a4, - 0x2e7528a5fe5b6e8d80052f215d1d12a3201154887f5cf4a99b5d9aef4a02ea8e, - 0x1f4b9f9e7456d6b50ee5407caa71364689db7ce3d29750e86ff697f4fb3ab12d, - 0xa624c5d6e8bcfae6c0e9953e251f3b7f7ae04fdacee382beecc309417c182be, - 0xcb161f852a6c7449377fc637e691017d6a18e74ef9617f92210d173bac0add8, - 0x24fd0a88c531515e53001fc2df8df624a913f791a8bf8eaf62dba753391b8960 + insertionProof = [ + 0x18491e665bc7128f0113b3cf187502311cf5a82b0304e02464099782483b14ba, + 0x1dace8033bc22eda25b483b2a260195b67ee5bef07990bf0e2c5f7923423fe, + 0x1d4489b99a91a972e878bef7a147251c8d4941b415bb7b36a9740e714f995b7e, + 0x772049285800265c330a0850d30d32c1ece88a0aa6adbd6a6197d0a1c2e2de2, + 0x27afc608a28bd2f8743bc2b423dbc34829b374cf702789f9549d4b730fcc7ec8, + 0x1011cfd2347e8db6cd489a8090331a73db380b6774ec3bc14c77a2dabe0e83dd, + 0x2f5f37e84d6acff8cfd7988d33aaea072dbe5071093b2df022d23047f134ac45, + 0x24830332559eada283d4473b17091b239443e75e9e09f0ebce8e72c235ee665d ]; // Create the deletion proof term. // output from semaphore-mtb prove in src/test/data/DeletionProof.json - /// @dev test_deletion.ps is generated using semaphore-mtb: `./gnark-mbu setup --mode deletion --batch-size 3 --tree-depth 16 --output test_deletion.ps` - /// @dev generated using semaphore-mtb: `./gnark-mbu gen-test-params --mode deletion --tree-depth 16 --batch-size 3 | ./gnark-mbu prove --mode deletion --keys-file test_deletion.ps` + /// @dev test_deletion.ps is generated using semaphore-mtb: `./gnark-mbu setup --mode deletion --batch-size 8 --tree-depth 16 --output test_deletion.ps` + /// @dev generated using semaphore-mtb: `./gnark-mbu gen-test-params --mode deletion --tree-depth 16 --batch-size 8 | ./gnark-mbu prove --mode deletion --keys-file test_deletion.ps` deletionProof = [ - 0x218c1210810f33ec6afd6edbd3311ebdc91ad86c5bd9f6c40e632c001fcb6589, - 0x283fed97c26434f2fb1209d5b8182475805985fa9465593b4de6f73983e11a70, - 0x2876bbb4c3c686df15f3c91de08a26da35fce97ff4c0172426bb0619b11435e1, - 0x20fe2c10422b1350f688501e67bbb2154ac4ff63a6da3f8d049fae7a0e59f7d7, - 0x156b3808aff9884cb83bea0658b65b53e56f68033ace4b0da14271a84d98e9b7, - 0x10f2dba2ada60a0cf804fc9292da6f05dec6692e07406d6f77716b8c9b9dbf8c, - 0x1f751a85dc3bae8c055d57e072a1d5ce5a849f22b2a5d121cebb24fe636c5d3, - 0x98eb20c27302e047f4ad218e1c15e6bd823936bbecceceac6c470606734716b + 0x19233cf0c60aa740585125dd5936462b1795bc2c8da7b9d0b7e92392cf91e1fd, + 0x244096da06de365f3bd8e7f428c2de4214096c4cd0feeba579642435ab15e90a, + 0x107395cd3aa9bfe3bcaada7f171d43a1ffffd576325598e2b9c8fbe1cfd6d032, + 0xac23f21fb0376055adeee2a78491ca13afc288c63c6450d0ce6ded6fda14344, + 0x29022f4cf64701ff88807430b9e333d87c670a4bdfe7d495d76271044a2d3711, + 0x134e41bef89e02289885852b368395b1b679dd243e5cf9e2f36b04ba990ab6a2, + 0x280894db66e6a9f9bf8aa48ffa1de98b755adadcf5962fb308cd1802a1101a0c, + 0x1484814b74243a07930c6af61079f94eefd843efe95e2388d9d49956cfacf3ab ]; } diff --git a/src/test/mock/DeletionTreeVerifier.sol b/src/test/mock/DeletionTreeVerifier.sol index 4bab5bc..804651d 100644 --- a/src/test/mock/DeletionTreeVerifier.sol +++ b/src/test/mock/DeletionTreeVerifier.sol @@ -1,335 +1,529 @@ -// SPDX-License-Identifier: AML -// -// Copyright 2017 Christian Reitwiessner -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -// 2019 OKIMS - -pragma solidity ^0.8.21; - -// Worldcoin Modification Begin import {ITreeVerifier} from "../../interfaces/ITreeVerifier.sol"; -// Worldcoin Modification End -library Pairing { - uint256 constant PRIME_Q = - 21888242871839275222246405745257275088696311157297823662689037894645226208583; +// SPDX-License-Identifier: MIT - struct G1Point { - uint256 X; - uint256 Y; - } - - // Encoding of field elements is: X[0] * z + X[1] - struct G2Point { - uint256[2] X; - uint256[2] Y; - } +pragma solidity ^0.8.0; - /* - * @return The negation of p, i.e. p.plus(p.negate()) should be zero. - */ - function negate(G1Point memory p) internal pure returns (G1Point memory) { - // The prime q in the base field F_q for G1 - if (p.X == 0 && p.Y == 0) { - return G1Point(0, 0); - } else { - return G1Point(p.X, PRIME_Q - (p.Y % PRIME_Q)); +/// @title Groth16 verifier template. +/// @author Remco Bloemen +/// @notice Supports verifying Groth16 proofs. Proofs can be in uncompressed +/// (256 bytes) and compressed (128 bytes) format. A view function is provided +/// to compress proofs. +/// @notice See for further explanation. +contract Verifier is ITreeVerifier { + + /// Some of the provided public input values are larger than the field modulus. + /// @dev Public input elements are not automatically reduced, as this is can be + /// a dangerous source of bugs. + error PublicInputNotInField(); + + /// The proof is invalid. + /// @dev This can mean that provided Groth16 proof points are not on their + /// curves, that pairing equation fails, or that the proof is not for the + /// provided public input. + error ProofInvalid(); + + // Addresses of precompiles + uint256 constant PRECOMPILE_MODEXP = 0x05; + uint256 constant PRECOMPILE_ADD = 0x06; + uint256 constant PRECOMPILE_MUL = 0x07; + uint256 constant PRECOMPILE_VERIFY = 0x08; + + // Base field Fp order P and scalar field Fr order R. + // For BN254 these are computed as follows: + // t = 4965661367192848881 + // P = 36⋅t⁴ + 36⋅t³ + 24⋅t² + 6⋅t + 1 + // R = 36⋅t⁴ + 36⋅t³ + 18⋅t² + 6⋅t + 1 + uint256 constant P = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; + uint256 constant R = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001; + + // Extension field Fp2 = Fp[i] / (i² + 1) + // Note: This is the complex extension field of Fp with i² = -1. + // Values in Fp2 are represented as a pair of Fp elements (a₀, a₁) as a₀ + a₁⋅i. + // Note: The order of Fp2 elements is *opposite* that of the pairing contract, which + // expects Fp2 elements in order (a₁, a₀). This is also the order in which + // Fp2 elements are encoded in the public interface as this became convention. + + // Constants in Fp + uint256 constant FRACTION_1_2_FP = 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4; + uint256 constant FRACTION_27_82_FP = 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; + uint256 constant FRACTION_3_82_FP = 0x2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e775; + + // Exponents for inversions and square roots mod P + uint256 constant EXP_INVERSE_FP = 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45; // P - 2 + uint256 constant EXP_SQRT_FP = 0xC19139CB84C680A6E14116DA060561765E05AA45A1C72A34F082305B61F3F52; // (P + 1) / 4; + + // Groth16 alpha point in G1 + uint256 constant ALPHA_X = 10540806559887167104588299892241621164255487150461496341626858664281664558273; + uint256 constant ALPHA_Y = 18696534755308846201677397222792953217528992421163095056941057317509629524722; + + // Groth16 beta point in G2 in powers of i + uint256 constant BETA_NEG_X_0 = 9289144700177602508224796928305680819301212642031268209898370879595438760917; + uint256 constant BETA_NEG_X_1 = 10547839143610067056656195135986672966679578259812908443118403685682429861701; + uint256 constant BETA_NEG_Y_0 = 12915174887173266773990929270621008261886710141330627051375005843032324032816; + uint256 constant BETA_NEG_Y_1 = 20204556387398089691578608866734867938413428713768226934160981536705045926684; + + // Groth16 gamma point in G2 in powers of i + uint256 constant GAMMA_NEG_X_0 = 12125917802287751299598970306127232317234244065863085706149700341692909445135; + uint256 constant GAMMA_NEG_X_1 = 12148123627061259167145600433069418819234101862686655936951485568840483717123; + uint256 constant GAMMA_NEG_Y_0 = 5988769189388486153600786474675906426283316229539620228678444567681682777498; + uint256 constant GAMMA_NEG_Y_1 = 5738750612292361316155676119434475570600542711558566821477860588397762422487; + + // Groth16 delta point in G2 in powers of i + uint256 constant DELTA_NEG_X_0 = 1634881525453069381539064669198838164371392344721377194711591834696682352477; + uint256 constant DELTA_NEG_X_1 = 17279327023445589453594845467776134123782766121800267509419424589125110740187; + uint256 constant DELTA_NEG_Y_0 = 11432830246305367590277540894794860588641953149336980701683372253417473334750; + uint256 constant DELTA_NEG_Y_1 = 4004758155600074163048436555321639796581387612468627300293241737049808612532; + + // Constant and public input points + uint256 constant CONSTANT_X = 7065344469851661926793643372215217621315658305837861579101548147761825653088; + uint256 constant CONSTANT_Y = 3816842911808951106840635623036133479452547290738636365883439290420360457712; + uint256 constant PUB_0_X = 3600420742132324519200509028486624821285996242068377631973021819080430036637; + uint256 constant PUB_0_Y = 372418442062690161950901896514875127229125198024957013636072554100336848194; + + /// Negation in Fp. + /// @notice Returns a number x such that a + x = 0 in Fp. + /// @notice The input does not need to be reduced. + /// @param a the base + /// @return x the result + function negate(uint256 a) internal pure returns (uint256 x) { + unchecked { + x = (P - (a % P)) % P; // Modulo is cheaper than branching } } - /* - * @return The sum of two points of G1 - */ - function plus(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) { - uint256[4] memory input; - input[0] = p1.X; - input[1] = p1.Y; - input[2] = p2.X; - input[3] = p2.Y; + /// Exponentiation in Fp. + /// @notice Returns a number x such that a ^ e = x in Fp. + /// @notice The input does not need to be reduced. + /// @param a the base + /// @param e the exponent + /// @return x the result + function exp(uint256 a, uint256 e) internal view returns (uint256 x) { bool success; + assembly ("memory-safe") { + let f := mload(0x40) + mstore(f, 0x20) + mstore(add(f, 0x20), 0x20) + mstore(add(f, 0x40), 0x20) + mstore(add(f, 0x60), a) + mstore(add(f, 0x80), e) + mstore(add(f, 0xa0), P) + success := staticcall(gas(), PRECOMPILE_MODEXP, f, 0xc0, f, 0x20) + x := mload(f) + } + if (!success) { + // Exponentiation failed. + // Should not happen. + revert ProofInvalid(); + } + } - // solium-disable-next-line security/no-inline-assembly - assembly { - success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60) - // Use "invalid" to make gas estimation work - switch success - case 0 { invalid() } + /// Invertsion in Fp. + /// @notice Returns a number x such that a * x = 1 in Fp. + /// @notice The input does not need to be reduced. + /// @notice Reverts with ProofInvalid() if the inverse does not exist + /// @param a the input + /// @return x the solution + function invert_Fp(uint256 a) internal view returns (uint256 x) { + x = exp(a, EXP_INVERSE_FP); + if (mulmod(a, x, P) != 1) { + // Inverse does not exist. + // Can only happen during G2 point decompression. + revert ProofInvalid(); } + } - require(success, "pairing-add-failed"); + /// Square root in Fp. + /// @notice Returns a number x such that x * x = a in Fp. + /// @notice Will revert with InvalidProof() if the input is not a square + /// or not reduced. + /// @param a the square + /// @return x the solution + function sqrt_Fp(uint256 a) internal view returns (uint256 x) { + x = exp(a, EXP_SQRT_FP); + if (mulmod(x, x, P) != a) { + // Square root does not exist or a is not reduced. + // Happens when G1 point is not on curve. + revert ProofInvalid(); + } } - /* - * Same as plus but accepts raw input instead of struct - * @return The sum of two points of G1, one is represented as array - */ - function plus_raw(uint256[4] memory input, G1Point memory r) internal view { - bool success; + /// Square test in Fp. + /// @notice Returns wheter a number x exists such that x * x = a in Fp. + /// @notice Will revert with InvalidProof() if the input is not a square + /// or not reduced. + /// @param a the square + /// @return x the solution + function isSquare_Fp(uint256 a) internal view returns (bool) { + uint256 x = exp(a, EXP_SQRT_FP); + return mulmod(x, x, P) == a; + } - // solium-disable-next-line security/no-inline-assembly - assembly { - success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60) - // Use "invalid" to make gas estimation work - switch success - case 0 { invalid() } + /// Square root in Fp2. + /// @notice Fp2 is the complex extension Fp[i]/(i^2 + 1). The input is + /// a0 + a1 ⋅ i and the result is x0 + x1 ⋅ i. + /// @notice Will revert with InvalidProof() if + /// * the input is not a square, + /// * the hint is incorrect, or + /// * the input coefficents are not reduced. + /// @param a0 The real part of the input. + /// @param a1 The imaginary part of the input. + /// @param hint A hint which of two possible signs to pick in the equation. + /// @return x0 The real part of the square root. + /// @return x1 The imaginary part of the square root. + function sqrt_Fp2(uint256 a0, uint256 a1, bool hint) internal view returns (uint256 x0, uint256 x1) { + // If this square root reverts there is no solution in Fp2. + uint256 d = sqrt_Fp(addmod(mulmod(a0, a0, P), mulmod(a1, a1, P), P)); + if (hint) { + d = negate(d); + } + // If this square root reverts there is no solution in Fp2. + x0 = sqrt_Fp(mulmod(addmod(a0, d, P), FRACTION_1_2_FP, P)); + x1 = mulmod(a1, invert_Fp(mulmod(x0, 2, P)), P); + + // Check result to make sure we found a root. + // Note: this also fails if a0 or a1 is not reduced. + if (a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P) + || a1 != mulmod(2, mulmod(x0, x1, P), P)) { + revert ProofInvalid(); } - - require(success, "pairing-add-failed"); } - /* - * @return The product of a point on G1 and a scalar, i.e. - * p == p.scalar_mul(1) and p.plus(p) == p.scalar_mul(2) for all - * points p. - */ - function scalar_mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) { - uint256[3] memory input; - input[0] = p.X; - input[1] = p.Y; - input[2] = s; - bool success; - // solium-disable-next-line security/no-inline-assembly - assembly { - success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60) - // Use "invalid" to make gas estimation work - switch success - case 0 { invalid() } + /// Compress a G1 point. + /// @notice Reverts with InvalidProof if the coordinates are not reduced + /// or if the point is not on the curve. + /// @notice The point at infinity is encoded as (0,0) and compressed to 0. + /// @param x The X coordinate in Fp. + /// @param y The Y coordinate in Fp. + /// @return c The compresed point (x with one signal bit). + function compress_g1(uint256 x, uint256 y) internal view returns (uint256 c) { + if (x >= P || y >= P) { + // G1 point not in field. + revert ProofInvalid(); + } + if (x == 0 && y == 0) { + // Point at infinity + return 0; + } + + // Note: sqrt_Fp reverts if there is no solution, i.e. the x coordinate is invalid. + uint256 y_pos = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P)); + if (y == y_pos) { + return (x << 1) | 0; + } else if (y == negate(y_pos)) { + return (x << 1) | 1; + } else { + // G1 point not on curve. + revert ProofInvalid(); } - require(success, "pairing-mul-failed"); } - /* - * Same as scalar_mul but accepts raw input instead of struct, - * Which avoid extra allocation. provided input can be allocated outside and re-used multiple times - */ - function scalar_mul_raw(uint256[3] memory input, G1Point memory r) internal view { - bool success; + /// Decompress a G1 point. + /// @notice Reverts with InvalidProof if the input does not represent a valid point. + /// @notice The point at infinity is encoded as (0,0) and compressed to 0. + /// @param c The compresed point (x with one signal bit). + /// @return x The X coordinate in Fp. + /// @return y The Y coordinate in Fp. + function decompress_g1(uint256 c) internal view returns (uint256 x, uint256 y) { + // Note that X = 0 is not on the curve since 0³ + 3 = 3 is not a square. + // so we can use it to represent the point at infinity. + if (c == 0) { + // Point at infinity as encoded in EIP196 and EIP197. + return (0, 0); + } + bool negate_point = c & 1 == 1; + x = c >> 1; + if (x >= P) { + // G1 x coordinate not in field. + revert ProofInvalid(); + } - // solium-disable-next-line security/no-inline-assembly - assembly { - success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60) - // Use "invalid" to make gas estimation work - switch success - case 0 { invalid() } + // Note: (x³ + 3) is irreducible in Fp, so it can not be zero and therefore + // y can not be zero. + // Note: sqrt_Fp reverts if there is no solution, i.e. the point is not on the curve. + y = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P)); + if (negate_point) { + y = negate(y); } - require(success, "pairing-mul-failed"); } - /* @return The result of computing the pairing check - * e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 - * For example, - * pairing([P1(), P1().negate()], [P2(), P2()]) should return true. - */ - function pairing( - G1Point memory a1, - G2Point memory a2, - G1Point memory b1, - G2Point memory b2, - G1Point memory c1, - G2Point memory c2, - G1Point memory d1, - G2Point memory d2 - ) internal view returns (bool) { - G1Point[4] memory p1 = [a1, b1, c1, d1]; - G2Point[4] memory p2 = [a2, b2, c2, d2]; - uint256 inputSize = 24; - uint256[] memory input = new uint256[](inputSize); - - for (uint256 i = 0; i < 4; i++) { - uint256 j = i * 6; - input[j + 0] = p1[i].X; - input[j + 1] = p1[i].Y; - input[j + 2] = p2[i].X[0]; - input[j + 3] = p2[i].X[1]; - input[j + 4] = p2[i].Y[0]; - input[j + 5] = p2[i].Y[1]; + /// Compress a G2 point. + /// @notice Reverts with InvalidProof if the coefficients are not reduced + /// or if the point is not on the curve. + /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). + /// @param x0 The real part of the X coordinate. + /// @param x1 The imaginary poart of the X coordinate. + /// @param y0 The real part of the Y coordinate. + /// @param y1 The imaginary part of the Y coordinate. + /// @return c0 The first half of the compresed point (x0 with two signal bits). + /// @return c1 The second half of the compressed point (x1 unmodified). + function compress_g2(uint256 x0, uint256 x1, uint256 y0, uint256 y1) + internal view returns (uint256 c0, uint256 c1) { + if (x0 >= P || x1 >= P || y0 >= P || y1 >= P) { + // G2 point not in field. + revert ProofInvalid(); + } + if ((x0 | x1 | y0 | y1) == 0) { + // Point at infinity + return (0, 0); } - uint256[1] memory out; - bool success; - - // solium-disable-next-line security/no-inline-assembly - assembly { - success := - staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) - // Use "invalid" to make gas estimation work - switch success - case 0 { invalid() } + // Compute y^2 + // Note: shadowing variables and scoping to avoid stack-to-deep. + uint256 y0_pos; + uint256 y1_pos; + { + uint256 n3ab = mulmod(mulmod(x0, x1, P), P-3, P); + uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); + uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); + y0_pos = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); + y1_pos = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); } - require(success, "pairing-opcode-failed"); + // Determine hint bit + // If this sqrt fails the x coordinate is not on the curve. + bool hint; + { + uint256 d = sqrt_Fp(addmod(mulmod(y0_pos, y0_pos, P), mulmod(y1_pos, y1_pos, P), P)); + hint = !isSquare_Fp(mulmod(addmod(y0_pos, d, P), FRACTION_1_2_FP, P)); + } - return out[0] != 0; + // Recover y + (y0_pos, y1_pos) = sqrt_Fp2(y0_pos, y1_pos, hint); + if (y0 == y0_pos && y1 == y1_pos) { + c0 = (x0 << 2) | (hint ? 2 : 0) | 0; + c1 = x1; + } else if (y0 == negate(y0_pos) && y1 == negate(y1_pos)) { + c0 = (x0 << 2) | (hint ? 2 : 0) | 1; + c1 = x1; + } else { + // G1 point not on curve. + revert ProofInvalid(); + } } -} -// Worldcoin Modification Begin -contract Verifier is ITreeVerifier { - // Worldcoin Modification End + /// Decompress a G2 point. + /// @notice Reverts with InvalidProof if the input does not represent a valid point. + /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). + /// @param c0 The first half of the compresed point (x0 with two signal bits). + /// @param c1 The second half of the compressed point (x1 unmodified). + /// @return x0 The real part of the X coordinate. + /// @return x1 The imaginary poart of the X coordinate. + /// @return y0 The real part of the Y coordinate. + /// @return y1 The imaginary part of the Y coordinate. + function decompress_g2(uint256 c0, uint256 c1) + internal view returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1) { + // Note that X = (0, 0) is not on the curve since 0³ + 3/(9 + i) is not a square. + // so we can use it to represent the point at infinity. + if (c0 == 0 && c1 == 0) { + // Point at infinity as encoded in EIP197. + return (0, 0, 0, 0); + } + bool negate_point = c0 & 1 == 1; + bool hint = c0 & 2 == 2; + x0 = c0 >> 2; + x1 = c1; + if (x0 >= P || x1 >= P) { + // G2 x0 or x1 coefficient not in field. + revert ProofInvalid(); + } - using Pairing for *; + uint256 n3ab = mulmod(mulmod(x0, x1, P), P-3, P); + uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); + uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); - uint256 constant SNARK_SCALAR_FIELD = - 21888242871839275222246405745257275088548364400416034343698204186575808495617; - uint256 constant PRIME_Q = - 21888242871839275222246405745257275088696311157297823662689037894645226208583; + y0 = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); + y1 = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); - struct VerifyingKey { - Pairing.G1Point alfa1; - Pairing.G2Point beta2; - Pairing.G2Point gamma2; - Pairing.G2Point delta2; + // Note: sqrt_Fp2 reverts if there is no solution, i.e. the point is not on the curve. + // Note: (X³ + 3/(9 + i)) is irreducible in Fp2, so y can not be zero. + // But y0 or y1 may still independently be zero. + (y0, y1) = sqrt_Fp2(y0, y1, hint); + if (negate_point) { + y0 = negate(y0); + y1 = negate(y1); + } } - // []G1Point IC (K in gnark) appears directly in verifyProof - struct Proof { - Pairing.G1Point A; - Pairing.G2Point B; - Pairing.G1Point C; + /// Compute the public input linear combination. + /// @notice Reverts with PublicInputNotInField if the input is not in the field. + /// @notice Computes the multi-scalar-multiplication of the public input + /// elements and the verification key including the constant term. + /// @param input The public inputs. These are elements of the scalar field Fr. + /// @return x The X coordinate of the resulting G1 point. + /// @return y The Y coordinate of the resulting G1 point. + function publicInputMSM(uint256[1] calldata input) + internal view returns (uint256 x, uint256 y) { + // Note: The ECMUL precompile does not reject unreduced values, so we check this. + // Note: Unrolling this loop does not cost much extra in code-size, the bulk of the + // code-size is in the PUB_ constants. + // ECMUL has input (x, y, scalar) and output (x', y'). + // ECADD has input (x1, y1, x2, y2) and output (x', y'). + // We call them such that ecmul output is already in the second point + // argument to ECADD so we can have a tight loop. + bool success = true; + assembly ("memory-safe") { + let f := mload(0x40) + let g := add(f, 0x40) + let s + mstore(f, CONSTANT_X) + mstore(add(f, 0x20), CONSTANT_Y) + mstore(g, PUB_0_X) + mstore(add(g, 0x20), PUB_0_Y) + s := calldataload(input) + mstore(add(g, 0x40), s) + success := and(success, lt(s, R)) + success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) + success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) + x := mload(f) + y := mload(add(f, 0x20)) + } + if (!success) { + // Either Public input not in field, or verification key invalid. + // We assume the contract is correctly generated, so the verification key is valid. + revert PublicInputNotInField(); + } } - function verifyingKey() internal pure returns (VerifyingKey memory vk) { - vk.alfa1 = Pairing.G1Point( - uint256(2184882805247985271871938329809546770618201109555895111244149305081310910327), - uint256(17955645170177143982732722322466892227921193317441707980265350454293781592165) - ); - vk.beta2 = Pairing.G2Point( - [ - uint256(19855677946098676054074545153598313691709048620187466153071818854499304267600), - uint256(18657555492226973921129443722983243120369700949936204046844056195692112150201) - ], - [ - uint256(19899331546556644910451623385834342033151091485086175875975296474884352013698), - uint256(10995968579664432264929184488637152820315692844617253571090316769579558136929) - ] - ); - vk.gamma2 = Pairing.G2Point( - [ - uint256(12712468093034557682736638829278859597903355905402452222082044636405719748293), - uint256(1917182124610107899409672606570417748821880411015039808590532928588300807548) - ], - [ - uint256(1438003574655150752592998959824941589353720796470233245442162954585047876922), - uint256(2676092741872789850724416751461362857645326402402773002025647629553435066621) - ] - ); - vk.delta2 = Pairing.G2Point( - [ - uint256(2056132247358553984450248077313152314306136758650729761826455114846826227966), - uint256(15515789660346665091954320736611020446193505887038665208802484819014444710907) - ], - [ - uint256(9291393946046405373282932088596154501511046549222190749151984429927112121942), - uint256(584737725353760575079302581443060760656177906229108303797581436421293000715) - ] - ); + /// Compress a proof. + /// @notice Will revert with InvalidProof if the curve points are invalid, + /// but does not verify the proof itself. + /// @param proof The uncompressed Groth16 proof. Elements are in the same order as for + /// verifyProof. I.e. Groth16 points (A, B, C) encoded as in EIP-197. + /// @return compressed The compressed proof. Elements are in the same order as for + /// verifyCompressedProof. I.e. points (A, B, C) in compressed format. + function compressProof(uint256[8] calldata proof) + public view returns (uint256[4] memory compressed) { + compressed[0] = compress_g1(proof[0], proof[1]); + (compressed[2], compressed[1]) = compress_g2(proof[3], proof[2], proof[5], proof[4]); + compressed[3] = compress_g1(proof[6], proof[7]); } - // accumulate scalarMul(mul_input) into q - // that is computes sets q = (mul_input[0:2] * mul_input[3]) + q - function accumulate( - uint256[3] memory mul_input, - Pairing.G1Point memory p, - uint256[4] memory buffer, - Pairing.G1Point memory q - ) internal view { - // computes p = mul_input[0:2] * mul_input[3] - Pairing.scalar_mul_raw(mul_input, p); - - // point addition inputs - buffer[0] = q.X; - buffer[1] = q.Y; - buffer[2] = p.X; - buffer[3] = p.Y; - - // q = p + q - Pairing.plus_raw(buffer, q); + /// Verify a Groth16 proof with compressed points. + /// @notice Reverts with InvalidProof if the proof is invalid or + /// with PublicInputNotInField the public input is not reduced. + /// @notice There is no return value. If the function does not revert, the + /// proof was succesfully verified. + /// @param compressedProof the points (A, B, C) in compressed format + /// matching the output of compressProof. + /// @param input the public input field elements in the scalar field Fr. + /// Elements must be reduced. + function verifyCompressedProof( + uint256[4] calldata compressedProof, + uint256[1] calldata input + ) public view { + (uint256 Ax, uint256 Ay) = decompress_g1(compressedProof[0]); + (uint256 Bx0, uint256 Bx1, uint256 By0, uint256 By1) = decompress_g2( + compressedProof[2], compressedProof[1]); + (uint256 Cx, uint256 Cy) = decompress_g1(compressedProof[3]); + (uint256 Lx, uint256 Ly) = publicInputMSM(input); + + // Verify the pairing + // Note: The precompile expects the F2 coefficients in big-endian order. + // Note: The pairing precompile rejects unreduced values, so we won't check that here. + uint256[24] memory pairings; + // e(A, B) + pairings[ 0] = Ax; + pairings[ 1] = Ay; + pairings[ 2] = Bx1; + pairings[ 3] = Bx0; + pairings[ 4] = By1; + pairings[ 5] = By0; + // e(C, -δ) + pairings[ 6] = Cx; + pairings[ 7] = Cy; + pairings[ 8] = DELTA_NEG_X_1; + pairings[ 9] = DELTA_NEG_X_0; + pairings[10] = DELTA_NEG_Y_1; + pairings[11] = DELTA_NEG_Y_0; + // e(α, -β) + pairings[12] = ALPHA_X; + pairings[13] = ALPHA_Y; + pairings[14] = BETA_NEG_X_1; + pairings[15] = BETA_NEG_X_0; + pairings[16] = BETA_NEG_Y_1; + pairings[17] = BETA_NEG_Y_0; + // e(L_pub, -γ) + pairings[18] = Lx; + pairings[19] = Ly; + pairings[20] = GAMMA_NEG_X_1; + pairings[21] = GAMMA_NEG_X_0; + pairings[22] = GAMMA_NEG_Y_1; + pairings[23] = GAMMA_NEG_Y_0; + + // Check pairing equation. + bool success; + uint256[1] memory output; + assembly ("memory-safe") { + success := staticcall(gas(), PRECOMPILE_VERIFY, pairings, 0x300, output, 0x20) + } + if (!success || output[0] != 1) { + // Either proof or verification key invalid. + // We assume the contract is correctly generated, so the verification key is valid. + revert ProofInvalid(); + } } - /* - * @returns Whether the proof is valid given the hardcoded verifying key - * above and the public inputs - */ - function verifyProof(uint256[8] memory _proof, uint256[1] memory input) public view { - Proof memory proof; - - //TODO: going to need to double check the order of nested b - proof.A = Pairing.G1Point(_proof[0], _proof[1]); - proof.B = Pairing.G2Point([_proof[2], _proof[3]], [_proof[4], _proof[5]]); - proof.C = Pairing.G1Point(_proof[6], _proof[7]); - - // Make sure that proof.A, B, and C are each less than the prime q - require(proof.A.X < PRIME_Q, "verifier-aX-gte-prime-q"); - require(proof.A.Y < PRIME_Q, "verifier-aY-gte-prime-q"); - - require(proof.B.X[0] < PRIME_Q, "verifier-bX0-gte-prime-q"); - require(proof.B.Y[0] < PRIME_Q, "verifier-bY0-gte-prime-q"); - - require(proof.B.X[1] < PRIME_Q, "verifier-bX1-gte-prime-q"); - require(proof.B.Y[1] < PRIME_Q, "verifier-bY1-gte-prime-q"); - - require(proof.C.X < PRIME_Q, "verifier-cX-gte-prime-q"); - require(proof.C.Y < PRIME_Q, "verifier-cY-gte-prime-q"); - - // Make sure that every input is less than the snark scalar field - for (uint256 i = 0; i < input.length; i++) { - require(input[i] < SNARK_SCALAR_FIELD, "verifier-gte-snark-scalar-field"); + /// Verify an uncompressed Groth16 proof. + /// @notice Reverts with InvalidProof if the proof is invalid or + /// with PublicInputNotInField the public input is not reduced. + /// @notice There is no return value. If the function does not revert, the + /// proof was succesfully verified. + /// @param proof the points (A, B, C) in EIP-197 format matching the output + /// of compressProof. + /// @param input the public input field elements in the scalar field Fr. + /// Elements must be reduced. + function verifyProof( + uint256[8] calldata proof, + uint256[1] calldata input + ) public view { + (uint256 x, uint256 y) = publicInputMSM(input); + + // Note: The precompile expects the F2 coefficients in big-endian order. + // Note: The pairing precompile rejects unreduced values, so we won't check that here. + + bool success; + assembly ("memory-safe") { + let f := mload(0x40) // Free memory pointer. + + // Copy points (A, B, C) to memory. They are already in correct encoding. + // This is pairing e(A, B) and G1 of e(C, -δ). + calldatacopy(f, proof, 0x100) + + // Complete e(C, -δ) and write e(α, -β), e(L_pub, -γ) to memory. + // OPT: This could be better done using a single codecopy, but + // Solidity (unlike standalone Yul) doesn't provide a way to + // to do this. + mstore(add(f, 0x100), DELTA_NEG_X_1) + mstore(add(f, 0x120), DELTA_NEG_X_0) + mstore(add(f, 0x140), DELTA_NEG_Y_1) + mstore(add(f, 0x160), DELTA_NEG_Y_0) + mstore(add(f, 0x180), ALPHA_X) + mstore(add(f, 0x1a0), ALPHA_Y) + mstore(add(f, 0x1c0), BETA_NEG_X_1) + mstore(add(f, 0x1e0), BETA_NEG_X_0) + mstore(add(f, 0x200), BETA_NEG_Y_1) + mstore(add(f, 0x220), BETA_NEG_Y_0) + mstore(add(f, 0x240), x) + mstore(add(f, 0x260), y) + mstore(add(f, 0x280), GAMMA_NEG_X_1) + mstore(add(f, 0x2a0), GAMMA_NEG_X_0) + mstore(add(f, 0x2c0), GAMMA_NEG_Y_1) + mstore(add(f, 0x2e0), GAMMA_NEG_Y_0) + + // Check pairing equation. + success := staticcall(gas(), PRECOMPILE_VERIFY, f, 0x300, f, 0x20) + // Also check returned value (both are either 1 or 0). + success := and(success, mload(f)) + } + if (!success) { + // Either proof or verification key invalid. + // We assume the contract is correctly generated, so the verification key is valid. + revert ProofInvalid(); } - - VerifyingKey memory vk = verifyingKey(); - - // Compute the linear combination vk_x - Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0); - - // Buffer reused for addition p1 + p2 to avoid memory allocations - // [0:2] -> p1.X, p1.Y ; [2:4] -> p2.X, p2.Y - uint256[4] memory add_input; - - // Buffer reused for multiplication p1 * s - // [0:2] -> p1.X, p1.Y ; [3] -> s - uint256[3] memory mul_input; - - // temporary point to avoid extra allocations in accumulate - Pairing.G1Point memory q = Pairing.G1Point(0, 0); - - vk_x.X = - uint256(3315258076715751967157201087019286537737159716424336725966632081613888671155); // vk.K[0].X - vk_x.Y = - uint256(2535036613140843436293083215368108378007620479265526787320589574026047767984); // vk.K[0].Y - mul_input[0] = - uint256(9542466376132327359478346341646067084942113202844591123106912654453266858527); // vk.K[1].X - mul_input[1] = - uint256(7493970464425500800073970400008818080866272618230969924226053545012177030554); // vk.K[1].Y - mul_input[2] = input[0]; - accumulate(mul_input, q, add_input, vk_x); // vk_x += vk.K[1] * input[0] - - require( - Pairing.pairing( - Pairing.negate(proof.A), - proof.B, - vk.alfa1, - vk.beta2, - vk_x, - vk.gamma2, - proof.C, - vk.delta2 - ), - "Invalid proof" - ); } } diff --git a/src/test/mock/InsertionTreeVerifier.sol b/src/test/mock/InsertionTreeVerifier.sol index 453d248..53125f8 100644 --- a/src/test/mock/InsertionTreeVerifier.sol +++ b/src/test/mock/InsertionTreeVerifier.sol @@ -1,267 +1,529 @@ -// SPDX-License-Identifier: AML -// -// Copyright 2017 Christian Reitwiessner -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -// 2019 OKIMS - -pragma solidity ^0.8.21; - -// Worldcoin Modification Begin import {ITreeVerifier} from "../../interfaces/ITreeVerifier.sol"; -// Worldcoin Modification End -library Pairing { - uint256 constant PRIME_Q = - 21888242871839275222246405745257275088696311157297823662689037894645226208583; +// SPDX-License-Identifier: MIT - struct G1Point { - uint256 X; - uint256 Y; - } - - // Encoding of field elements is: X[0] * z + X[1] - struct G2Point { - uint256[2] X; - uint256[2] Y; - } +pragma solidity ^0.8.0; - /* - * @return The negation of p, i.e. p.plus(p.negate()) should be zero. - */ - function negate(G1Point memory p) internal pure returns (G1Point memory) { - // The prime q in the base field F_q for G1 - if (p.X == 0 && p.Y == 0) { - return G1Point(0, 0); - } else { - return G1Point(p.X, PRIME_Q - (p.Y % PRIME_Q)); +/// @title Groth16 verifier template. +/// @author Remco Bloemen +/// @notice Supports verifying Groth16 proofs. Proofs can be in uncompressed +/// (256 bytes) and compressed (128 bytes) format. A view function is provided +/// to compress proofs. +/// @notice See for further explanation. +contract Verifier is ITreeVerifier { + + /// Some of the provided public input values are larger than the field modulus. + /// @dev Public input elements are not automatically reduced, as this is can be + /// a dangerous source of bugs. + error PublicInputNotInField(); + + /// The proof is invalid. + /// @dev This can mean that provided Groth16 proof points are not on their + /// curves, that pairing equation fails, or that the proof is not for the + /// provided public input. + error ProofInvalid(); + + // Addresses of precompiles + uint256 constant PRECOMPILE_MODEXP = 0x05; + uint256 constant PRECOMPILE_ADD = 0x06; + uint256 constant PRECOMPILE_MUL = 0x07; + uint256 constant PRECOMPILE_VERIFY = 0x08; + + // Base field Fp order P and scalar field Fr order R. + // For BN254 these are computed as follows: + // t = 4965661367192848881 + // P = 36⋅t⁴ + 36⋅t³ + 24⋅t² + 6⋅t + 1 + // R = 36⋅t⁴ + 36⋅t³ + 18⋅t² + 6⋅t + 1 + uint256 constant P = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; + uint256 constant R = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001; + + // Extension field Fp2 = Fp[i] / (i² + 1) + // Note: This is the complex extension field of Fp with i² = -1. + // Values in Fp2 are represented as a pair of Fp elements (a₀, a₁) as a₀ + a₁⋅i. + // Note: The order of Fp2 elements is *opposite* that of the pairing contract, which + // expects Fp2 elements in order (a₁, a₀). This is also the order in which + // Fp2 elements are encoded in the public interface as this became convention. + + // Constants in Fp + uint256 constant FRACTION_1_2_FP = 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4; + uint256 constant FRACTION_27_82_FP = 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; + uint256 constant FRACTION_3_82_FP = 0x2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e775; + + // Exponents for inversions and square roots mod P + uint256 constant EXP_INVERSE_FP = 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45; // P - 2 + uint256 constant EXP_SQRT_FP = 0xC19139CB84C680A6E14116DA060561765E05AA45A1C72A34F082305B61F3F52; // (P + 1) / 4; + + // Groth16 alpha point in G1 + uint256 constant ALPHA_X = 15976828965750791468988686334447811489136097632219377308651062054699114822216; + uint256 constant ALPHA_Y = 17571185980075114535712506428928541014285966203133597604199833733334669062947; + + // Groth16 beta point in G2 in powers of i + uint256 constant BETA_NEG_X_0 = 3774880652569784103101780290655946736778806829747412095447973768235447935627; + uint256 constant BETA_NEG_X_1 = 16140359218528844177661226532636814578832873241049546877451825018737878894415; + uint256 constant BETA_NEG_Y_0 = 4640315682033452018918186416519285362905365110971338138159134713745786498132; + uint256 constant BETA_NEG_Y_1 = 3591449710587988205963826035685830100614411910678456443129126525230663369361; + + // Groth16 gamma point in G2 in powers of i + uint256 constant GAMMA_NEG_X_0 = 17637628216749009270364930684841675593008642019322849310939298209033912045480; + uint256 constant GAMMA_NEG_X_1 = 16778406356398287643368954352598191898790242877372845103320576982242500933852; + uint256 constant GAMMA_NEG_Y_0 = 8224931393918135298340454914349355511588649579654293187876560463421379935080; + uint256 constant GAMMA_NEG_Y_1 = 8652273155883398190128238325969441551390725833029677073788838240017751601738; + + // Groth16 delta point in G2 in powers of i + uint256 constant DELTA_NEG_X_0 = 2280566876154873121729228932237318721748751798277472636568974908993913843953; + uint256 constant DELTA_NEG_X_1 = 20174276210268561659837178440442037241955176983492602351856686097389113846193; + uint256 constant DELTA_NEG_Y_0 = 2810000783863410994886532472239034393068688066928911515614533202542952344359; + uint256 constant DELTA_NEG_Y_1 = 3434233964589686342320626243495267687470464612485221367834666549409219897059; + + // Constant and public input points + uint256 constant CONSTANT_X = 19016103526843875126395145525124455195546522337268670813255409672571795425684; + uint256 constant CONSTANT_Y = 463262142987534641028551863519809496575105875074564654761628432482686134324; + uint256 constant PUB_0_X = 1379748789921737417066313098910363913169509602099243602246914882900464880825; + uint256 constant PUB_0_Y = 943304887426072701494501907600571048821602999217792043874057384381177139312; + + /// Negation in Fp. + /// @notice Returns a number x such that a + x = 0 in Fp. + /// @notice The input does not need to be reduced. + /// @param a the base + /// @return x the result + function negate(uint256 a) internal pure returns (uint256 x) { + unchecked { + x = (P - (a % P)) % P; // Modulo is cheaper than branching } } - /* - * @return The sum of two points of G1 - */ - function plus(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) { - uint256[4] memory input; - input[0] = p1.X; - input[1] = p1.Y; - input[2] = p2.X; - input[3] = p2.Y; + /// Exponentiation in Fp. + /// @notice Returns a number x such that a ^ e = x in Fp. + /// @notice The input does not need to be reduced. + /// @param a the base + /// @param e the exponent + /// @return x the result + function exp(uint256 a, uint256 e) internal view returns (uint256 x) { bool success; - - // solium-disable-next-line security/no-inline-assembly - assembly { - success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60) - // Use "invalid" to make gas estimation work - switch success - case 0 { invalid() } + assembly ("memory-safe") { + let f := mload(0x40) + mstore(f, 0x20) + mstore(add(f, 0x20), 0x20) + mstore(add(f, 0x40), 0x20) + mstore(add(f, 0x60), a) + mstore(add(f, 0x80), e) + mstore(add(f, 0xa0), P) + success := staticcall(gas(), PRECOMPILE_MODEXP, f, 0xc0, f, 0x20) + x := mload(f) } - - require(success, "pairing-add-failed"); + if (!success) { + // Exponentiation failed. + // Should not happen. + revert ProofInvalid(); + } } - /* - * @return The product of a point on G1 and a scalar, i.e. - * p == p.scalar_mul(1) and p.plus(p) == p.scalar_mul(2) for all - * points p. - */ - function scalar_mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) { - uint256[3] memory input; - input[0] = p.X; - input[1] = p.Y; - input[2] = s; - bool success; - // solium-disable-next-line security/no-inline-assembly - assembly { - success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60) - // Use "invalid" to make gas estimation work - switch success - case 0 { invalid() } + /// Invertsion in Fp. + /// @notice Returns a number x such that a * x = 1 in Fp. + /// @notice The input does not need to be reduced. + /// @notice Reverts with ProofInvalid() if the inverse does not exist + /// @param a the input + /// @return x the solution + function invert_Fp(uint256 a) internal view returns (uint256 x) { + x = exp(a, EXP_INVERSE_FP); + if (mulmod(a, x, P) != 1) { + // Inverse does not exist. + // Can only happen during G2 point decompression. + revert ProofInvalid(); } - require(success, "pairing-mul-failed"); } - /* @return The result of computing the pairing check - * e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 - * For example, - * pairing([P1(), P1().negate()], [P2(), P2()]) should return true. - */ - function pairing( - G1Point memory a1, - G2Point memory a2, - G1Point memory b1, - G2Point memory b2, - G1Point memory c1, - G2Point memory c2, - G1Point memory d1, - G2Point memory d2 - ) internal view returns (bool) { - G1Point[4] memory p1 = [a1, b1, c1, d1]; - G2Point[4] memory p2 = [a2, b2, c2, d2]; - uint256 inputSize = 24; - uint256[] memory input = new uint256[](inputSize); - - for (uint256 i = 0; i < 4; i++) { - uint256 j = i * 6; - input[j + 0] = p1[i].X; - input[j + 1] = p1[i].Y; - input[j + 2] = p2[i].X[0]; - input[j + 3] = p2[i].X[1]; - input[j + 4] = p2[i].Y[0]; - input[j + 5] = p2[i].Y[1]; + /// Square root in Fp. + /// @notice Returns a number x such that x * x = a in Fp. + /// @notice Will revert with InvalidProof() if the input is not a square + /// or not reduced. + /// @param a the square + /// @return x the solution + function sqrt_Fp(uint256 a) internal view returns (uint256 x) { + x = exp(a, EXP_SQRT_FP); + if (mulmod(x, x, P) != a) { + // Square root does not exist or a is not reduced. + // Happens when G1 point is not on curve. + revert ProofInvalid(); } + } - uint256[1] memory out; - bool success; + /// Square test in Fp. + /// @notice Returns wheter a number x exists such that x * x = a in Fp. + /// @notice Will revert with InvalidProof() if the input is not a square + /// or not reduced. + /// @param a the square + /// @return x the solution + function isSquare_Fp(uint256 a) internal view returns (bool) { + uint256 x = exp(a, EXP_SQRT_FP); + return mulmod(x, x, P) == a; + } - // solium-disable-next-line security/no-inline-assembly - assembly { - success := - staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) - // Use "invalid" to make gas estimation work - switch success - case 0 { invalid() } + /// Square root in Fp2. + /// @notice Fp2 is the complex extension Fp[i]/(i^2 + 1). The input is + /// a0 + a1 ⋅ i and the result is x0 + x1 ⋅ i. + /// @notice Will revert with InvalidProof() if + /// * the input is not a square, + /// * the hint is incorrect, or + /// * the input coefficents are not reduced. + /// @param a0 The real part of the input. + /// @param a1 The imaginary part of the input. + /// @param hint A hint which of two possible signs to pick in the equation. + /// @return x0 The real part of the square root. + /// @return x1 The imaginary part of the square root. + function sqrt_Fp2(uint256 a0, uint256 a1, bool hint) internal view returns (uint256 x0, uint256 x1) { + // If this square root reverts there is no solution in Fp2. + uint256 d = sqrt_Fp(addmod(mulmod(a0, a0, P), mulmod(a1, a1, P), P)); + if (hint) { + d = negate(d); + } + // If this square root reverts there is no solution in Fp2. + x0 = sqrt_Fp(mulmod(addmod(a0, d, P), FRACTION_1_2_FP, P)); + x1 = mulmod(a1, invert_Fp(mulmod(x0, 2, P)), P); + + // Check result to make sure we found a root. + // Note: this also fails if a0 or a1 is not reduced. + if (a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P) + || a1 != mulmod(2, mulmod(x0, x1, P), P)) { + revert ProofInvalid(); } - - require(success, "pairing-opcode-failed"); - - return out[0] != 0; } -} -// Worldcoin Modification Begin -contract Verifier is ITreeVerifier { - // Worldcoin Modification End - using Pairing for *; - - uint256 constant SNARK_SCALAR_FIELD = - 21888242871839275222246405745257275088548364400416034343698204186575808495617; - uint256 constant PRIME_Q = - 21888242871839275222246405745257275088696311157297823662689037894645226208583; - - struct VerifyingKey { - Pairing.G1Point alfa1; - Pairing.G2Point beta2; - Pairing.G2Point gamma2; - Pairing.G2Point delta2; - Pairing.G1Point[2] IC; + /// Compress a G1 point. + /// @notice Reverts with InvalidProof if the coordinates are not reduced + /// or if the point is not on the curve. + /// @notice The point at infinity is encoded as (0,0) and compressed to 0. + /// @param x The X coordinate in Fp. + /// @param y The Y coordinate in Fp. + /// @return c The compresed point (x with one signal bit). + function compress_g1(uint256 x, uint256 y) internal view returns (uint256 c) { + if (x >= P || y >= P) { + // G1 point not in field. + revert ProofInvalid(); + } + if (x == 0 && y == 0) { + // Point at infinity + return 0; + } + + // Note: sqrt_Fp reverts if there is no solution, i.e. the x coordinate is invalid. + uint256 y_pos = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P)); + if (y == y_pos) { + return (x << 1) | 0; + } else if (y == negate(y_pos)) { + return (x << 1) | 1; + } else { + // G1 point not on curve. + revert ProofInvalid(); + } } - struct Proof { - Pairing.G1Point A; - Pairing.G2Point B; - Pairing.G1Point C; - } + /// Decompress a G1 point. + /// @notice Reverts with InvalidProof if the input does not represent a valid point. + /// @notice The point at infinity is encoded as (0,0) and compressed to 0. + /// @param c The compresed point (x with one signal bit). + /// @return x The X coordinate in Fp. + /// @return y The Y coordinate in Fp. + function decompress_g1(uint256 c) internal view returns (uint256 x, uint256 y) { + // Note that X = 0 is not on the curve since 0³ + 3 = 3 is not a square. + // so we can use it to represent the point at infinity. + if (c == 0) { + // Point at infinity as encoded in EIP196 and EIP197. + return (0, 0); + } + bool negate_point = c & 1 == 1; + x = c >> 1; + if (x >= P) { + // G1 x coordinate not in field. + revert ProofInvalid(); + } - function verifyingKey() internal pure returns (VerifyingKey memory vk) { - vk.alfa1 = Pairing.G1Point( - uint256(20511579680485128435968492529477986054143624895130267136299969518428310397583), - uint256(12019183066292893271875993042849002846200951499939001731947162535134268084362) - ); - vk.beta2 = Pairing.G2Point( - [ - uint256(2089676812529243142469162728618839638435359905327620749498509643225358128912), - uint256(7904895411458501455966830052510986257464308709496849793787095953158487122304) - ], - [ - uint256(16797507473745040958844202637614442452840135422747682014230743717837892841028), - uint256(9473339184494274655577348453617870794049644188434434075522198845582264704106) - ] - ); - vk.gamma2 = Pairing.G2Point( - [ - uint256(18857088877831991395879893788315426632011108011495870716514470189262586374610), - uint256(4389555201093468693636136029755231137723360019751348191879841133076369674917) - ], - [ - uint256(13362381349504071289893854895447856933068773897799959938583933929038383825378), - uint256(16986196626872063819265139926040651012192136018211263012697320066417863414017) - ] - ); - vk.delta2 = Pairing.G2Point( - [ - uint256(12313730183775831354894919682887228076436409607715148516704569441139088930688), - uint256(2552231658739687496972790410151668919124141665121206694028316488936580642349) - ], - [ - uint256(2364937275864597293303184061344064915914201762511884649994290705518114506818), - uint256(14782994374673117770861880065787271579362240800510712289723727525292605454935) - ] - ); - vk.IC[0] = Pairing.G1Point( - uint256(18444671359917035747943528843359189897401309276553441532102135228117744117540), - uint256(14807363264404528745778014983613754484255874843241911076089872533921692657040) - ); - vk.IC[1] = Pairing.G1Point( - uint256(11483784651604377113147887759057304308126467247011330592789526483132957880096), - uint256(15913982624926250684711060136269733377890569322844166112237620732524677990300) - ); + // Note: (x³ + 3) is irreducible in Fp, so it can not be zero and therefore + // y can not be zero. + // Note: sqrt_Fp reverts if there is no solution, i.e. the point is not on the curve. + y = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P)); + if (negate_point) { + y = negate(y); + } } - /* - * @returns Whether the proof is valid given the hardcoded verifying key - * above and the public inputs - */ - function verifyProof(uint256[8] memory _proof, uint256[1] memory input) public view { - Proof memory proof; + /// Compress a G2 point. + /// @notice Reverts with InvalidProof if the coefficients are not reduced + /// or if the point is not on the curve. + /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). + /// @param x0 The real part of the X coordinate. + /// @param x1 The imaginary poart of the X coordinate. + /// @param y0 The real part of the Y coordinate. + /// @param y1 The imaginary part of the Y coordinate. + /// @return c0 The first half of the compresed point (x0 with two signal bits). + /// @return c1 The second half of the compressed point (x1 unmodified). + function compress_g2(uint256 x0, uint256 x1, uint256 y0, uint256 y1) + internal view returns (uint256 c0, uint256 c1) { + if (x0 >= P || x1 >= P || y0 >= P || y1 >= P) { + // G2 point not in field. + revert ProofInvalid(); + } + if ((x0 | x1 | y0 | y1) == 0) { + // Point at infinity + return (0, 0); + } - //TODO: going to need to double check the order of nested b - proof.A = Pairing.G1Point(_proof[0], _proof[1]); - proof.B = Pairing.G2Point([_proof[2], _proof[3]], [_proof[4], _proof[5]]); - proof.C = Pairing.G1Point(_proof[6], _proof[7]); + // Compute y^2 + // Note: shadowing variables and scoping to avoid stack-to-deep. + uint256 y0_pos; + uint256 y1_pos; + { + uint256 n3ab = mulmod(mulmod(x0, x1, P), P-3, P); + uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); + uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); + y0_pos = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); + y1_pos = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); + } - VerifyingKey memory vk = verifyingKey(); + // Determine hint bit + // If this sqrt fails the x coordinate is not on the curve. + bool hint; + { + uint256 d = sqrt_Fp(addmod(mulmod(y0_pos, y0_pos, P), mulmod(y1_pos, y1_pos, P), P)); + hint = !isSquare_Fp(mulmod(addmod(y0_pos, d, P), FRACTION_1_2_FP, P)); + } - // Compute the linear combination vk_x - Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0); + // Recover y + (y0_pos, y1_pos) = sqrt_Fp2(y0_pos, y1_pos, hint); + if (y0 == y0_pos && y1 == y1_pos) { + c0 = (x0 << 2) | (hint ? 2 : 0) | 0; + c1 = x1; + } else if (y0 == negate(y0_pos) && y1 == negate(y1_pos)) { + c0 = (x0 << 2) | (hint ? 2 : 0) | 1; + c1 = x1; + } else { + // G1 point not on curve. + revert ProofInvalid(); + } + } - // Make sure that proof.A, B, and C are each less than the prime q - require(proof.A.X < PRIME_Q, "verifier-aX-gte-prime-q"); - require(proof.A.Y < PRIME_Q, "verifier-aY-gte-prime-q"); + /// Decompress a G2 point. + /// @notice Reverts with InvalidProof if the input does not represent a valid point. + /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). + /// @param c0 The first half of the compresed point (x0 with two signal bits). + /// @param c1 The second half of the compressed point (x1 unmodified). + /// @return x0 The real part of the X coordinate. + /// @return x1 The imaginary poart of the X coordinate. + /// @return y0 The real part of the Y coordinate. + /// @return y1 The imaginary part of the Y coordinate. + function decompress_g2(uint256 c0, uint256 c1) + internal view returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1) { + // Note that X = (0, 0) is not on the curve since 0³ + 3/(9 + i) is not a square. + // so we can use it to represent the point at infinity. + if (c0 == 0 && c1 == 0) { + // Point at infinity as encoded in EIP197. + return (0, 0, 0, 0); + } + bool negate_point = c0 & 1 == 1; + bool hint = c0 & 2 == 2; + x0 = c0 >> 2; + x1 = c1; + if (x0 >= P || x1 >= P) { + // G2 x0 or x1 coefficient not in field. + revert ProofInvalid(); + } - require(proof.B.X[0] < PRIME_Q, "verifier-bX0-gte-prime-q"); - require(proof.B.Y[0] < PRIME_Q, "verifier-bY0-gte-prime-q"); + uint256 n3ab = mulmod(mulmod(x0, x1, P), P-3, P); + uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); + uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); - require(proof.B.X[1] < PRIME_Q, "verifier-bX1-gte-prime-q"); - require(proof.B.Y[1] < PRIME_Q, "verifier-bY1-gte-prime-q"); + y0 = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); + y1 = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); - require(proof.C.X < PRIME_Q, "verifier-cX-gte-prime-q"); - require(proof.C.Y < PRIME_Q, "verifier-cY-gte-prime-q"); + // Note: sqrt_Fp2 reverts if there is no solution, i.e. the point is not on the curve. + // Note: (X³ + 3/(9 + i)) is irreducible in Fp2, so y can not be zero. + // But y0 or y1 may still independently be zero. + (y0, y1) = sqrt_Fp2(y0, y1, hint); + if (negate_point) { + y0 = negate(y0); + y1 = negate(y1); + } + } - // Make sure that every input is less than the snark scalar field - for (uint256 i = 0; i < input.length; i++) { - require(input[i] < SNARK_SCALAR_FIELD, "verifier-gte-snark-scalar-field"); - vk_x = Pairing.plus(vk_x, Pairing.scalar_mul(vk.IC[i + 1], input[i])); + /// Compute the public input linear combination. + /// @notice Reverts with PublicInputNotInField if the input is not in the field. + /// @notice Computes the multi-scalar-multiplication of the public input + /// elements and the verification key including the constant term. + /// @param input The public inputs. These are elements of the scalar field Fr. + /// @return x The X coordinate of the resulting G1 point. + /// @return y The Y coordinate of the resulting G1 point. + function publicInputMSM(uint256[1] calldata input) + internal view returns (uint256 x, uint256 y) { + // Note: The ECMUL precompile does not reject unreduced values, so we check this. + // Note: Unrolling this loop does not cost much extra in code-size, the bulk of the + // code-size is in the PUB_ constants. + // ECMUL has input (x, y, scalar) and output (x', y'). + // ECADD has input (x1, y1, x2, y2) and output (x', y'). + // We call them such that ecmul output is already in the second point + // argument to ECADD so we can have a tight loop. + bool success = true; + assembly ("memory-safe") { + let f := mload(0x40) + let g := add(f, 0x40) + let s + mstore(f, CONSTANT_X) + mstore(add(f, 0x20), CONSTANT_Y) + mstore(g, PUB_0_X) + mstore(add(g, 0x20), PUB_0_Y) + s := calldataload(input) + mstore(add(g, 0x40), s) + success := and(success, lt(s, R)) + success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) + success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) + x := mload(f) + y := mload(add(f, 0x20)) + } + if (!success) { + // Either Public input not in field, or verification key invalid. + // We assume the contract is correctly generated, so the verification key is valid. + revert PublicInputNotInField(); } + } + + /// Compress a proof. + /// @notice Will revert with InvalidProof if the curve points are invalid, + /// but does not verify the proof itself. + /// @param proof The uncompressed Groth16 proof. Elements are in the same order as for + /// verifyProof. I.e. Groth16 points (A, B, C) encoded as in EIP-197. + /// @return compressed The compressed proof. Elements are in the same order as for + /// verifyCompressedProof. I.e. points (A, B, C) in compressed format. + function compressProof(uint256[8] calldata proof) + public view returns (uint256[4] memory compressed) { + compressed[0] = compress_g1(proof[0], proof[1]); + (compressed[2], compressed[1]) = compress_g2(proof[3], proof[2], proof[5], proof[4]); + compressed[3] = compress_g1(proof[6], proof[7]); + } - vk_x = Pairing.plus(vk_x, vk.IC[0]); - - require( - Pairing.pairing( - Pairing.negate(proof.A), - proof.B, - vk.alfa1, - vk.beta2, - vk_x, - vk.gamma2, - proof.C, - vk.delta2 - ), - "Invalid proof" - ); + /// Verify a Groth16 proof with compressed points. + /// @notice Reverts with InvalidProof if the proof is invalid or + /// with PublicInputNotInField the public input is not reduced. + /// @notice There is no return value. If the function does not revert, the + /// proof was succesfully verified. + /// @param compressedProof the points (A, B, C) in compressed format + /// matching the output of compressProof. + /// @param input the public input field elements in the scalar field Fr. + /// Elements must be reduced. + function verifyCompressedProof( + uint256[4] calldata compressedProof, + uint256[1] calldata input + ) public view { + (uint256 Ax, uint256 Ay) = decompress_g1(compressedProof[0]); + (uint256 Bx0, uint256 Bx1, uint256 By0, uint256 By1) = decompress_g2( + compressedProof[2], compressedProof[1]); + (uint256 Cx, uint256 Cy) = decompress_g1(compressedProof[3]); + (uint256 Lx, uint256 Ly) = publicInputMSM(input); + + // Verify the pairing + // Note: The precompile expects the F2 coefficients in big-endian order. + // Note: The pairing precompile rejects unreduced values, so we won't check that here. + uint256[24] memory pairings; + // e(A, B) + pairings[ 0] = Ax; + pairings[ 1] = Ay; + pairings[ 2] = Bx1; + pairings[ 3] = Bx0; + pairings[ 4] = By1; + pairings[ 5] = By0; + // e(C, -δ) + pairings[ 6] = Cx; + pairings[ 7] = Cy; + pairings[ 8] = DELTA_NEG_X_1; + pairings[ 9] = DELTA_NEG_X_0; + pairings[10] = DELTA_NEG_Y_1; + pairings[11] = DELTA_NEG_Y_0; + // e(α, -β) + pairings[12] = ALPHA_X; + pairings[13] = ALPHA_Y; + pairings[14] = BETA_NEG_X_1; + pairings[15] = BETA_NEG_X_0; + pairings[16] = BETA_NEG_Y_1; + pairings[17] = BETA_NEG_Y_0; + // e(L_pub, -γ) + pairings[18] = Lx; + pairings[19] = Ly; + pairings[20] = GAMMA_NEG_X_1; + pairings[21] = GAMMA_NEG_X_0; + pairings[22] = GAMMA_NEG_Y_1; + pairings[23] = GAMMA_NEG_Y_0; + + // Check pairing equation. + bool success; + uint256[1] memory output; + assembly ("memory-safe") { + success := staticcall(gas(), PRECOMPILE_VERIFY, pairings, 0x300, output, 0x20) + } + if (!success || output[0] != 1) { + // Either proof or verification key invalid. + // We assume the contract is correctly generated, so the verification key is valid. + revert ProofInvalid(); + } + } + + /// Verify an uncompressed Groth16 proof. + /// @notice Reverts with InvalidProof if the proof is invalid or + /// with PublicInputNotInField the public input is not reduced. + /// @notice There is no return value. If the function does not revert, the + /// proof was succesfully verified. + /// @param proof the points (A, B, C) in EIP-197 format matching the output + /// of compressProof. + /// @param input the public input field elements in the scalar field Fr. + /// Elements must be reduced. + function verifyProof( + uint256[8] calldata proof, + uint256[1] calldata input + ) public view { + (uint256 x, uint256 y) = publicInputMSM(input); + + // Note: The precompile expects the F2 coefficients in big-endian order. + // Note: The pairing precompile rejects unreduced values, so we won't check that here. + + bool success; + assembly ("memory-safe") { + let f := mload(0x40) // Free memory pointer. + + // Copy points (A, B, C) to memory. They are already in correct encoding. + // This is pairing e(A, B) and G1 of e(C, -δ). + calldatacopy(f, proof, 0x100) + + // Complete e(C, -δ) and write e(α, -β), e(L_pub, -γ) to memory. + // OPT: This could be better done using a single codecopy, but + // Solidity (unlike standalone Yul) doesn't provide a way to + // to do this. + mstore(add(f, 0x100), DELTA_NEG_X_1) + mstore(add(f, 0x120), DELTA_NEG_X_0) + mstore(add(f, 0x140), DELTA_NEG_Y_1) + mstore(add(f, 0x160), DELTA_NEG_Y_0) + mstore(add(f, 0x180), ALPHA_X) + mstore(add(f, 0x1a0), ALPHA_Y) + mstore(add(f, 0x1c0), BETA_NEG_X_1) + mstore(add(f, 0x1e0), BETA_NEG_X_0) + mstore(add(f, 0x200), BETA_NEG_Y_1) + mstore(add(f, 0x220), BETA_NEG_Y_0) + mstore(add(f, 0x240), x) + mstore(add(f, 0x260), y) + mstore(add(f, 0x280), GAMMA_NEG_X_1) + mstore(add(f, 0x2a0), GAMMA_NEG_X_0) + mstore(add(f, 0x2c0), GAMMA_NEG_Y_1) + mstore(add(f, 0x2e0), GAMMA_NEG_Y_0) + + // Check pairing equation. + success := staticcall(gas(), PRECOMPILE_VERIFY, f, 0x300, f, 0x20) + // Also check returned value (both are either 1 or 0). + success := and(success, mload(f)) + } + if (!success) { + // Either proof or verification key invalid. + // We assume the contract is correctly generated, so the verification key is valid. + revert ProofInvalid(); + } } } From 6b1635ded2ca2addb8734d832feff932680bdd7a Mon Sep 17 00:00:00 2001 From: "dcbuilder.eth" Date: Mon, 25 Sep 2023 19:35:25 +0200 Subject: [PATCH 08/20] fix incorrect import of insertion verifier for tests --- .../WorldIDIdentityManagerIdentityRegistration.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol b/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol index 8618852..7628ae6 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol @@ -8,7 +8,7 @@ import {WorldIDIdentityManagerTest} from "./WorldIDIdentityManagerTest.sol"; import {ITreeVerifier} from "../../interfaces/ITreeVerifier.sol"; import {SimpleVerifier, SimpleVerify} from "../mock/SimpleVerifier.sol"; import {TypeConverter as TC} from "../utils/TypeConverter.sol"; -import {Verifier as TreeVerifier} from "src/Verifier.sol"; +import {Verifier as TreeVerifier} from "src/test/mock/InsertionTreeVerifier.sol"; import {VerifierLookupTable} from "../../data/VerifierLookupTable.sol"; import {WorldIDIdentityManager as IdentityManager} from "../../WorldIDIdentityManager.sol"; From 44476b246fb842616d580609a80e5143ec10969f Mon Sep 17 00:00:00 2001 From: "dcbuilder.eth" Date: Mon, 25 Sep 2023 20:31:52 +0200 Subject: [PATCH 09/20] fix registration/deletion tests --- .../WorldIDIdentityManagerIdentityDeletion.t.sol | 13 ++++++------- ...WorldIDIdentityManagerIdentityRegistration.t.sol | 12 +++++------- src/test/mock/DeletionTreeVerifier.sol | 2 +- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/test/identity-manager/WorldIDIdentityManagerIdentityDeletion.t.sol b/src/test/identity-manager/WorldIDIdentityManagerIdentityDeletion.t.sol index 24ab2c7..5aec167 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerIdentityDeletion.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerIdentityDeletion.t.sol @@ -216,20 +216,19 @@ contract WorldIDIdentityManagerIdentityDeletion is WorldIDIdentityManagerTest { function testCannotDeleteIdentitiesWithIncorrectInputs( uint128[8] memory prf, uint128 newPreRoot, - bytes calldata packedDeletionIndices, uint128 newPostRoot ) public { // Setup vm.assume(!SimpleVerify.isValidInput(uint256(prf[0]))); vm.assume(newPreRoot != newPostRoot); - vm.assume(packedDeletionIndices.length >0 && packedDeletionIndices.length <= 1000); - uint32 indicesLength = uint32(packedDeletionIndices.length * 8); - + ITreeVerifier actualVerifier = new TreeVerifier(); + uint32 indicesLength = uint32(packedDeletionIndices.length); ( VerifierLookupTable insertVerifiers, VerifierLookupTable deletionVerifiers, VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([indicesLength])); + ) = makeVerifierLookupTables(TC.makeDynArray([70])); + deletionVerifiers.addVerifier(indicesLength, actualVerifier); makeNewIdentityManager( treeDepth, newPreRoot, @@ -259,8 +258,8 @@ contract WorldIDIdentityManagerIdentityDeletion is WorldIDIdentityManagerTest { VerifierLookupTable insertVerifiers, VerifierLookupTable deletionVerifiers, VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([deletionBatchSize])); - deletionVerifiers.addVerifier(identityCommitmentsSize, actualVerifier); + ) = makeVerifierLookupTables(TC.makeDynArray([70])); + deletionVerifiers.addVerifier(deletionBatchSize, actualVerifier); makeNewIdentityManager( treeDepth, deletionPreRoot, diff --git a/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol b/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol index 7628ae6..f1543e9 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol @@ -213,17 +213,17 @@ contract WorldIDIdentityManagerIdentityRegistration is WorldIDIdentityManagerTes uint128[8] memory prf, uint32 newStartIndex, uint128 newPreRoot, - uint128 newPostRoot, - uint128[] memory identities + uint128 newPostRoot ) public { // Setup vm.assume(!SimpleVerify.isValidInput(uint256(prf[0]))); - vm.assume(newPreRoot != newPostRoot); + ITreeVerifier actualVerifier = new TreeVerifier(); ( VerifierLookupTable insertVerifiers, VerifierLookupTable deletionVerifiers, VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([identities.length])); + ) = makeVerifierLookupTables(TC.makeDynArray([70])); + insertVerifiers.addVerifier(identityCommitments.length, actualVerifier); makeNewIdentityManager( treeDepth, newPreRoot, @@ -232,11 +232,9 @@ contract WorldIDIdentityManagerIdentityRegistration is WorldIDIdentityManagerTes updateVerifiers, semaphoreVerifier ); - (uint256[] memory preparedIdents, uint256[8] memory actualProof) = - prepareInsertIdentitiesTestCase(identities, prf); bytes memory callData = abi.encodeCall( ManagerImplV1.registerIdentities, - (actualProof, newPreRoot, newStartIndex, preparedIdents, newPostRoot) + (insertionProof, newPreRoot, newStartIndex, identityCommitments, newPostRoot) ); bytes memory expectedError = abi.encodeWithSelector(ManagerImplV1.ProofValidationFailure.selector); diff --git a/src/test/mock/DeletionTreeVerifier.sol b/src/test/mock/DeletionTreeVerifier.sol index 804651d..2bb2731 100644 --- a/src/test/mock/DeletionTreeVerifier.sol +++ b/src/test/mock/DeletionTreeVerifier.sol @@ -118,7 +118,7 @@ contract Verifier is ITreeVerifier { } } - /// Invertsion in Fp. + /// Inversion in Fp. /// @notice Returns a number x such that a * x = 1 in Fp. /// @notice The input does not need to be reduced. /// @notice Reverts with ProofInvalid() if the inverse does not exist From db8be57e88c72bd3544eb8bb91cec6bec8cdbe70 Mon Sep 17 00:00:00 2001 From: "dcbuilder.eth" Date: Mon, 25 Sep 2023 20:45:21 +0200 Subject: [PATCH 10/20] delete all update logic and unused functions in V1 --- src/WorldIDIdentityManagerImplV1.sol | 338 ------------- .../WorldIDIdentityManagerCalculation.t.sol | 63 --- ...WorldIDIdentityManagerGettersSetters.t.sol | 69 --- ...WorldIDIdentityManagerIdentityUpdate.t.sol | 478 ------------------ .../WorldIDIdentityManagerUninit.t.sol | 54 -- 5 files changed, 1002 deletions(-) delete mode 100644 src/test/identity-manager/WorldIDIdentityManagerIdentityUpdate.t.sol diff --git a/src/WorldIDIdentityManagerImplV1.sol b/src/WorldIDIdentityManagerImplV1.sol index 7d9b95a..e7ac1fb 100644 --- a/src/WorldIDIdentityManagerImplV1.sol +++ b/src/WorldIDIdentityManagerImplV1.sol @@ -372,9 +372,6 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { revert UnreducedElement(UnreducedElementType.PostRoot, postRoot); } - // We can only operate on identities that are valid and in reduced form. - validateIdentityCommitmentsForRegistration(identityCommitments); - // Having validated the preconditions we can now check the proof itself. bytes32 inputHash = calculateIdentityRegistrationInputHash( startIndex, preRoot, postRoot, identityCommitments @@ -410,144 +407,6 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { } } - /// @notice Updates identities in the WorldID system. - /// @dev Can only be called by the identity operator. - /// @dev The update is performed off-chain and verified on-chain via the `updateProof`. This - /// saves gas and time over removing identities one at a time. - /// @dev This function can perform arbitrary identity alterations and does not require any - /// preconditions on the inputs other than that the identities are in reduced form. - /// - /// @param updateProof The proof that, given the conditions (`preRoot`, `startIndex` and - /// `removedIdentities`), updates in the tree results in `postRoot`. Elements 0 and 1 are - /// the `x` and `y` coordinates for `ar` respectively. Elements 2 and 3 are the `x` - /// coordinate for `bs`, and elements 4 and 5 are the `y` coordinate for `bs`. Elements 6 - /// and 7 are the `x` and `y` coordinates for `krs`. - /// @param preRoot The value for the root of the tree before the `updatedIdentities` have been - //// altered. Must be an element of the field `Kr`. - /// @param leafIndices The array of leaf indices at which the update operations take place in - /// the tree. Elements in this array are extended to 256 bits when encoding. - /// @param oldIdentities The array of old values for the identities. Length must match that of - /// `leafIndices`. - /// @param newIdentities The array of new values for the identities. Length must match that of - /// `leafIndices`. - /// @param postRoot The root obtained after removing all of `removedIdentities` from the tree - /// described by `preRoot`. Must be an element of the field `Kr`. - /// - /// The arrays `leafIndices`, `oldIdentities` and `newIdentities` are arranged such that the - /// triple at an element `i` in those arrays corresponds to one update operation. - /// - /// @custom:reverts Unauthorized If the message sender is not authorised to update identities. - /// @custom:reverts NotLatestRoot If the provided `preRoot` is not the latest root. - /// @custom:reverts MismatchedInputLengths If the provided arrays for `leafIndices`, - /// `oldIdentities` and `newIdentities` do not match in length. - /// @custom:reverts ProofValidationFailure If `removalProof` cannot be verified using the - /// provided inputs. - /// @custom:reverts UnreducedElement If any of the `preRoot`, `postRoot` and `identities` is not - /// an element of the field `Kr`. It describes the type and value of the - /// unreduced element. - /// @custom:reverts NoSuchVerifier If the batch sizes doesn't match a known verifier. - function updateIdentities( - uint256[8] calldata updateProof, - uint256 preRoot, - uint32[] calldata leafIndices, - uint256[] calldata oldIdentities, - uint256[] calldata newIdentities, - uint256 postRoot - ) public virtual onlyProxy onlyInitialized onlyIdentityOperator { - // We can only operate on the latest root in reduced form. - if (preRoot >= SNARK_SCALAR_FIELD) { - revert UnreducedElement(UnreducedElementType.PreRoot, preRoot); - } - if (preRoot != _latestRoot) { - revert NotLatestRoot(preRoot, _latestRoot); - } - - // We also need the post root to be in reduced form. - if (postRoot >= SNARK_SCALAR_FIELD) { - revert UnreducedElement(UnreducedElementType.PostRoot, postRoot); - } - - // We also need the arrays to be of the same length. - if ( - leafIndices.length != oldIdentities.length || leafIndices.length != newIdentities.length - ) { - revert MismatchedInputLengths(); - } - - // We only operate on identities that are in reduced form. - validateIdentitiesForUpdate(oldIdentities, newIdentities); - - // With valid preconditions we can calculate the input to the proof. - bytes32 inputHash = calculateIdentityUpdateInputHash( - preRoot, postRoot, leafIndices, oldIdentities, newIdentities - ); - - // No matter what, the input hashing process can result in a hash that is not an element of - // the field Fr. We reduce it into the field to give it safely to the verifier. - uint256 reducedInputHash = uint256(inputHash) % SNARK_SCALAR_FIELD; - - // We have to look up the correct verifier before we can verify. - ITreeVerifier updateVerifier = identityUpdateVerifiers.getVerifierFor(leafIndices.length); - - // Now we delegate to another function in order to avoid the limit on stack variables. - performIdentityUpdate(updateVerifier, updateProof, reducedInputHash, preRoot, postRoot); - } - - /// @notice Performs the verification of the identity update proof. - /// @dev This function only exists because `updateIdentities` ended up with more than 16 local - /// variables, and hence ran into the limit on the EVM. It will be called as a direct call - /// and is hence relatively cheap. - /// @dev Can only be called by the owner. - /// @dev The update is performed off-chain and verified on-chain via the `updateProof`. This - /// saves gas and time over removing identities one at a time. - /// @dev This function can perform arbitrary identity alterations and does not require any - /// preconditions on the inputs other than that the identities are in reduced form. - /// - /// @param updateVerifier The merkle tree verifier to use for updates of the correct batch size. - /// @param updateProof The proof that, given the conditions (`preRoot`, `startIndex` and - /// `removedIdentities`), updates in the tree results in `postRoot`. Elements 0 and 1 are - /// the `x` and `y` coordinates for `ar` respectively. Elements 2 and 3 are the `x` - /// coordinate for `bs`, and elements 4 and 5 are the `y` coordinate for `bs`. Elements 6 - /// and 7 are the `x` and `y` coordinates for `krs`. - /// @param inputHash The input hash for the update operation. - /// @param preRoot The value for the root of the tree before the `updatedIdentities` have been - //// altered. Must be an element of the field `Kr`. - /// @param postRoot The root obtained after removing all of `removedIdentities` from the tree - /// described by `preRoot`. Must be an element of the field `Kr`. - /// - /// @custom:reverts ProofValidationFailure If `removalProof` cannot be verified using the - /// provided inputs. - function performIdentityUpdate( - ITreeVerifier updateVerifier, - uint256[8] calldata updateProof, - uint256 inputHash, - uint256 preRoot, - uint256 postRoot - ) internal virtual onlyProxy onlyInitialized onlyIdentityOperator { - // Pull out the verifier input. - uint256[1] memory proofInput = [inputHash]; - - // Now it's possible to verify the proof. - try updateVerifier.verifyProof(updateProof, proofInput) { - // If it did verify, we need to update the contract's state. We set the currently valid - // root to the root after the insertions. - _latestRoot = postRoot; - - // We also need to add the previous root to the history, and set the timestamp at which - // it was expired. - rootHistory[preRoot] = uint128(block.timestamp); - - emit TreeChanged(preRoot, TreeChange.Update, postRoot); - } catch Error(string memory errString) { - /// This is not the revert we're looking for. - revert(errString); - } catch { - // If we reach here we know it's the internal error, as the tree verifier only uses - // `require`s otherwise, which will be re-thrown above. - revert ProofValidationFailure(); - } - } - /////////////////////////////////////////////////////////////////////////////// /// UTILITY FUNCTIONS /// /////////////////////////////////////////////////////////////////////////////// @@ -578,47 +437,6 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { hash = keccak256(bytesToHash); } - /// @notice Calculates the input hash for the identity update verifier. - /// @dev Implements the computation described below. - /// - /// @param preRoot The root value of the tree before the updates were made. - /// @param postRoot The root value of the tree after the updates were made. - /// @param leafIndices The array of leaf indices at which the update operations take place in - /// the tree. Elements in this array are extended to 256 bits when encoding. - /// @param oldIdentities The array of old values for the identities. Length must match that of - /// `leafIndices`. - /// @param newIdentities The array of new values for the identities. Length must match that of - /// `leafIndices`. - /// - /// @return hash The input hash calculated as described below. - /// - /// The arrays `leafIndices`, `oldIdentities` and `newIdentities` are arranged such that the - /// triple at an element `i` in those arrays corresponds to one update operation. - /// - /// We keccak hash all input to save verification gas. The inputs are arranged as follows: - /// - /// preRoot || postRoot || ix[0] || ... || ix[n] || oi[0] || ... || oi[n] || ni[0] || ... || ni[n] || - /// 256 || 256 || 256 || ... || 256 || 256 || ... || 256 || 256 || ... || 256 || - /// - /// where: - /// - `ix[i] == leafIndices[i]` - /// - `oi[i] == oldIdentities[i]` - /// - `ni[i] == newIdentities[i]` - /// - `id[i] == identities[i]` - /// - `n == batchSize - 1` - function calculateIdentityUpdateInputHash( - uint256 preRoot, - uint256 postRoot, - uint32[] calldata leafIndices, - uint256[] calldata oldIdentities, - uint256[] calldata newIdentities - ) public view virtual onlyProxy onlyInitialized returns (bytes32 hash) { - bytes memory bytesToHash = - abi.encodePacked(preRoot, postRoot, leafIndices, oldIdentities, newIdentities); - - hash = keccak256(bytesToHash); - } - /// @notice Allows a caller to query the latest root. /// /// @return root The value of the latest tree root. @@ -655,126 +473,6 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { } } - /// @notice Validates an array of identity commitments, reverting if it finds one that is - /// invalid or has not been reduced. - /// @dev Identities are not valid if an identity is a non-zero element that occurs after a zero - /// element in the array. - /// - /// @param identityCommitments The array of identity commitments to be validated. - /// - /// @custom:reverts Reverts with `InvalidCommitment` if one or more of the provided commitments - /// is invalid. - /// @custom:reverts Reverts with `UnreducedElement` if one or more of the provided commitments - /// is not in reduced form. - function validateIdentityCommitmentsForRegistration(uint256[] calldata identityCommitments) - internal - view - virtual - { - uint256 offset; - uint256 max; - assembly ("memory-safe") { - // offset of the first element in the array - offset := identityCommitments.offset - // offset one byte after the end of argument section - max := add(offset, shl(5, identityCommitments.length)) - - // increment offset until either of the following happens: - // - offset is equal to max, meaning we've reached the end of the array - // - the element at offset is zero - // - the element at offset is greater than or equal to SNARK_SCALAR_FIELD - // The latter two conditions are combined into a single check of the form - // `element - 1 < SNARK_SCALAR_FIELD - 1`. This abuses unsigned comparisons: - // if `element` is zero, it will underflow and be greater than SNARK_SCALAR_FIELD - 1. - // If `element` is non-zero, we can cancel the -1 terms and the comparison becomes what - // we want: `element < SNARK_SCALAR_FIELD`. - for {} and( - lt(offset, max), lt(sub(calldataload(offset), 1), SNARK_SCALAR_FIELD_MIN_ONE) - ) { offset := add(offset, 32) } {} - } - - // check if the loop terminated before end of array and handle the remaining elements - if (offset < max) { - uint256 index = identityCommitments.length - ((max - offset) >> 5); - uint256 element = identityCommitments[index]; - - // Check if the loop terminated because it found a zero - // this means we need to finish looping and make sure all remaining elements are zero. - if (element == 0) { - assembly ("memory-safe") { - // we just confirmed this element is zero - offset := add(offset, 32) - - // increment offset until either of the following happens: - // - offset is equal to max, meaning we've reached the end of the array - // - the element at offset is non-zero - for {} and(lt(offset, max), iszero(calldataload(offset))) { - offset := add(offset, 32) - } {} - } - - // check if the loop terminated because it found a non-zero element - // if yes, revert. - if (offset < max) { - index = identityCommitments.length - ((max - offset) >> 5); - revert InvalidCommitment(index); - } - } - // otherwise check if the loop terminated because it found an unreduced element - // if so, revert - else if (element >= SNARK_SCALAR_FIELD) { - revert UnreducedElement(UnreducedElementType.IdentityCommitment, element); - } - } - } - - /// @notice Validates that an array of identity commitments is within bounds of the SNARK_SCALAR_FIELD - /// @param identityCommitments The array of identity commitments to be validated. - function validateArrayIsInReducedForm(uint256[] calldata identityCommitments) - internal - view - virtual - { - uint256 offset; - uint256 max; - assembly ("memory-safe") { - // offset of the first element in the array - offset := identityCommitments.offset - // offset one byte after the end of argument section - max := add(offset, shl(5, identityCommitments.length)) - - // increment offset until either of the following happens: - // - offset is equal to max, meaning we've reached the end of the array - // - the element at offset is greater than or equal to SNARK_SCALAR_FIELD - for {} and(lt(offset, max), lt(calldataload(offset), SNARK_SCALAR_FIELD)) { - offset := add(offset, 32) - } {} - } - // check if the loop terminated before end of array and revert if so - if (offset < max) { - uint256 index = identityCommitments.length - ((max - offset) >> 5); - uint256 element = identityCommitments[index]; - revert UnreducedElement(UnreducedElementType.IdentityCommitment, element); - } - } - - /// @notice Validates the array of identities for each of the old and new commitments being in - /// reduced form. - /// @dev Must be called with arrays of the same length. - /// - /// @param oldIdentities The array of old values for the identities. - /// @param newIdentities The array of new values for the identities. - /// - /// @custom:reverts UnreducedElement If one or more of the provided commitments is not in - //// reduced form. - function validateIdentitiesForUpdate( - uint256[] calldata oldIdentities, - uint256[] calldata newIdentities - ) internal view virtual { - validateArrayIsInReducedForm(oldIdentities); - validateArrayIsInReducedForm(newIdentities); - } - /// @notice Reverts if the provided root value is not valid. /// @dev A root is valid if it is either the latest root, or not the latest root but has not /// expired. @@ -838,42 +536,6 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { ); } - /// @notice Gets the address for the lookup table of merkle tree verifiers used for identity - /// updates. - /// @dev The update verifier is also used for member removals. - /// - /// @return addr The address of the contract being used as the verifier lookup table. - function getIdentityUpdateVerifierLookupTableAddress() - public - view - virtual - onlyProxy - onlyInitialized - returns (address) - { - return address(identityUpdateVerifiers); - } - - /// @notice Sets the address for the lookup table of merkle tree verifiers to be used for - /// verification of identity updates. - /// @dev Only the owner of the contract can call this function. - /// @dev The update verifier is also used for member removals. - /// - /// @param newTable The new lookup table instance to be used for verifying identity updates. - function setIdentityUpdateVerifierLookupTable(VerifierLookupTable newTable) - public - virtual - onlyProxy - onlyInitialized - onlyOwner - { - VerifierLookupTable oldTable = identityUpdateVerifiers; - identityUpdateVerifiers = newTable; - emit DependencyUpdated( - Dependency.UpdateVerifierLookupTable, address(oldTable), address(newTable) - ); - } - /// @notice Gets the address of the verifier used for verification of semaphore proofs. /// /// @return addr The address of the contract being used as the verifier. diff --git a/src/test/identity-manager/WorldIDIdentityManagerCalculation.t.sol b/src/test/identity-manager/WorldIDIdentityManagerCalculation.t.sol index e89000c..5c89ad2 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerCalculation.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerCalculation.t.sol @@ -62,67 +62,4 @@ contract WorldIDIdentityManagerCalculation is WorldIDIdentityManagerTest { ); } - /// @notice Check whether it's possible to caculate the identity update input hash. - function testCanCalculateIdentityUpdateInputHash( - uint256 insertionPreRoot, - uint256 insertionPostRoot, - uint32 startIndex1, - uint32 startIndex2, - uint256 oldIdent1, - uint256 oldIdent2, - uint256 newIdent1, - uint256 newIdent2 - ) public { - // Setup - uint32[] memory leafIndices = new uint32[](2); - leafIndices[0] = startIndex1; - leafIndices[1] = startIndex2; - - uint256[] memory oldIdents = new uint256[](2); - oldIdents[0] = oldIdent1; - oldIdents[1] = oldIdent2; - - uint256[] memory newIdents = new uint256[](2); - newIdents[0] = newIdent1; - newIdents[1] = newIdent2; - - bytes32 expectedResult = keccak256( - abi.encodePacked( - insertionPreRoot, - insertionPostRoot, - uint256(startIndex1), - uint256(startIndex2), - oldIdent1, - oldIdent2, - newIdent1, - newIdent2 - ) - ); - bytes memory callData = abi.encodeCall( - ManagerImplV1.calculateIdentityUpdateInputHash, - (insertionPreRoot, insertionPostRoot, leafIndices, oldIdents, newIdents) - ); - bytes memory expectedReturn = abi.encode(expectedResult); - - // Test - assertCallSucceedsOn(identityManagerAddress, callData, expectedReturn); - } - - /// @notice Ensures that the identity update hash can only be calculated when called via the - /// proxy. - function testCannotCalculateIdentityUpdateHashIfNotViaProxy( - uint256 insertionPreRoot, - uint256 insertionPostRoot, - uint32[] memory leafIndices, - uint256[] memory oldIdents, - uint256[] memory newIdents - ) public { - // Setup - vm.expectRevert("Function must be called through delegatecall"); - - // Test - managerImpl.calculateIdentityUpdateInputHash( - insertionPreRoot, insertionPostRoot, leafIndices, oldIdents, newIdents - ); - } } diff --git a/src/test/identity-manager/WorldIDIdentityManagerGettersSetters.t.sol b/src/test/identity-manager/WorldIDIdentityManagerGettersSetters.t.sol index 852137b..76bc324 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerGettersSetters.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerGettersSetters.t.sol @@ -165,75 +165,6 @@ contract WorldIDIdentityManagerGettersSetters is WorldIDIdentityManagerTest { managerImpl.setDeleteIdentitiesVerifierLookupTable(deletionVerifiers); } - /// @notice Checks that it is possible to get the address of the lookup table currently being - /// used to verify identity update proofs. - function testCanGetIdentityUpdateVerifierLookupTableAddress() public { - // Setup - bytes memory callData = - abi.encodeCall(ManagerImplV1.getIdentityUpdateVerifierLookupTableAddress, ()); - bytes memory expectedReturn = abi.encode(defaultUpdateVerifiers); - - // Test - assertCallSucceedsOn(identityManagerAddress, callData, expectedReturn); - } - - /// @notice Ensures that it is not possible to get the address of the verifier lookup table for - /// identity updates unless called via the proxy. - function testCannotGetIdentityUpdateVerifierLookupTableAddressUnlessViaProxy() public { - // Setup - vm.expectRevert("Function must be called through delegatecall"); - - // Test - managerImpl.getIdentityUpdateVerifierLookupTableAddress(); - } - - /// @notice Checks that it is possible to set the lookup table currently being used to verify - /// identity update proofs. - function testCanSetIdentityUpdateVerifierLookupTable() public { - // Setup - (,, VerifierLookupTable updateVerifiers) = makeVerifierLookupTables(TC.makeDynArray([40])); - address newVerifierAddress = address(updateVerifiers); - bytes memory callData = - abi.encodeCall(ManagerImplV1.setIdentityUpdateVerifierLookupTable, (updateVerifiers)); - bytes memory checkCallData = - abi.encodeCall(ManagerImplV1.getIdentityUpdateVerifierLookupTableAddress, ()); - bytes memory expectedReturn = abi.encode(newVerifierAddress); - vm.expectEmit(true, false, true, true); - emit DependencyUpdated( - ManagerImplV1.Dependency.UpdateVerifierLookupTable, nullAddress, newVerifierAddress - ); - - // Test - assertCallSucceedsOn(identityManagerAddress, callData); - assertCallSucceedsOn(identityManagerAddress, checkCallData, expectedReturn); - } - - /// @notice Checks that the identity update verifier lookup table cannot be set except by the - /// owner. - function testCannotSetIdentityUpdateVerifierLookupTableUnlessOwner(address notOwner) public { - // Setup - vm.assume(notOwner != address(this) && notOwner != address(0x0)); - (,, VerifierLookupTable updateVerifiers) = makeVerifierLookupTables(TC.makeDynArray([40])); - bytes memory callData = - abi.encodeCall(ManagerImplV1.setIdentityUpdateVerifierLookupTable, (updateVerifiers)); - bytes memory errorData = encodeStringRevert("Ownable: caller is not the owner"); - vm.prank(notOwner); - - // Test - assertCallFailsOn(identityManagerAddress, callData, errorData); - } - - /// @notice Ensures that it is not possible to set the address of the verifier lookup table for - /// identity removal unless called via the proxy. - function testCannotSetIdentityUpdateVerifierLookupTableUnlessViaProxy() public { - // Setup - (,, VerifierLookupTable updateVerifiers) = makeVerifierLookupTables(TC.makeDynArray([40])); - vm.expectRevert("Function must be called through delegatecall"); - - // Test - managerImpl.setIdentityUpdateVerifierLookupTable(updateVerifiers); - } - /// @notice Ensures that we can get the address of the semaphore verifier. function testCanGetSemaphoreVerifierAddress() public { // Setup diff --git a/src/test/identity-manager/WorldIDIdentityManagerIdentityUpdate.t.sol b/src/test/identity-manager/WorldIDIdentityManagerIdentityUpdate.t.sol deleted file mode 100644 index 2a7c9ca..0000000 --- a/src/test/identity-manager/WorldIDIdentityManagerIdentityUpdate.t.sol +++ /dev/null @@ -1,478 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.21; - -import {WorldIDIdentityManagerTest} from "./WorldIDIdentityManagerTest.sol"; - -import {ITreeVerifier} from "../../interfaces/ITreeVerifier.sol"; -import {SimpleVerifier, SimpleVerify} from "../mock/SimpleVerifier.sol"; -import {TypeConverter as TC} from "../utils/TypeConverter.sol"; -import {Verifier as TreeVerifier} from "../mock/InsertionTreeVerifier.sol"; -import {VerifierLookupTable} from "../../data/VerifierLookupTable.sol"; - -import {WorldIDIdentityManager as IdentityManager} from "../../WorldIDIdentityManager.sol"; -import {WorldIDIdentityManagerImplV1 as ManagerImpl} from "../../WorldIDIdentityManagerImplV1.sol"; - -/// @title World ID Identity Manager Identity Update Tests -/// @notice Contains tests for the WorldID identity manager. -/// @author Worldcoin -/// @dev This test suite tests both the proxy and the functionality of the underlying implementation -/// so as to test everything in the context of how it will be deployed. -contract WorldIDIdentityManagerIdentityUpdate is WorldIDIdentityManagerTest { - /// Taken from SimpleVerifier.sol - event VerifiedProof(uint256 batchSize); - - /// Taken from WorldIDIdentityManagerImplV1.sol - event TreeChanged( - uint256 indexed insertionPreRoot, - ManagerImpl.TreeChange indexed kind, - uint256 indexed insertionPostRoot - ); - - /// @notice Checks that the proof validates properly with correct inputs. - function testUpdateIdentitiesWithCorrectInputs( - uint128[8] memory prf, - uint128 newPreRoot, - uint128 newPostRoot, - uint128[] memory identities, - address identityOperator - ) public { - // Setup - vm.assume(SimpleVerify.isValidInput(uint256(prf[0]))); - vm.assume(newPreRoot != newPostRoot); - vm.assume(identities.length <= 1000); // Keeps the test time sane-ish. - vm.assume(identityOperator != nullAddress && identityOperator != thisAddress); - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([identities.length])); - makeNewIdentityManager( - treeDepth, - newPreRoot, - insertVerifiers, - deletionVerifiers, - updateVerifiers, - semaphoreVerifier - ); - ( - uint32[] memory leafIndices, - uint256[] memory oldIdents, - uint256[] memory newIdents, - uint256[8] memory actualProof - ) = prepareUpdateIdentitiesTestCase(identities, prf); - bytes memory callData = abi.encodeCall( - ManagerImpl.updateIdentities, - (actualProof, newPreRoot, leafIndices, oldIdents, newIdents, newPostRoot) - ); - - bytes memory setupCallData = - abi.encodeCall(ManagerImpl.setIdentityOperator, identityOperator); - (bool success,) = identityManagerAddress.call(setupCallData); - assert(success); - - // Expect that the state root was sent to the state bridge - vm.expectEmit(true, true, true, true); - emit TreeChanged(newPreRoot, ManagerImpl.TreeChange.Update, newPostRoot); - vm.prank(identityOperator); - - // Test - assertCallSucceedsOn(identityManagerAddress, callData); - } - - /// @notice Ensures that identity updates select the correct verifier when updating identities. - function testUpdateIdentitiesSelectsCorrectVerifier( - uint128[8] memory prf, - uint128 newPreRoot, - uint128 newPostRoot, - uint128[] memory identities - ) public { - vm.assume(SimpleVerify.isValidInput(uint256(prf[0]))); - vm.assume(newPreRoot != newPostRoot); - vm.assume(identities.length <= 1000 && identities.length > 0); - uint256 secondIdentsLength = identities.length / 2; - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([identities.length, secondIdentsLength])); - makeNewIdentityManager( - treeDepth, - newPreRoot, - insertVerifiers, - deletionVerifiers, - updateVerifiers, - semaphoreVerifier - ); - ( - uint32[] memory leafIndices, - uint256[] memory oldIdents, - uint256[] memory newIdents, - uint256[8] memory actualProof - ) = prepareUpdateIdentitiesTestCase(identities, prf); - - part2TestUpdateIdentitiesSelectsCorrectVerifier( - actualProof, newPreRoot, leafIndices, oldIdents, newIdents, newPostRoot - ); - } - - /// @notice Exists to work around local variable limits. - function part2TestUpdateIdentitiesSelectsCorrectVerifier( - uint256[8] memory actualProof, - uint256 newPreRoot, - uint32[] memory leafIndices, - uint256[] memory oldIdents, - uint256[] memory newIdents, - uint256 newPostRoot - ) public { - uint256 secondIdentsLength = leafIndices.length / 2; - uint32[] memory secondLeafIndices = new uint32[](secondIdentsLength); - uint256[] memory secondOldIdents = new uint256[](secondIdentsLength); - uint256[] memory secondNewIdents = new uint256[](secondIdentsLength); - for (uint256 i = 0; i < secondIdentsLength; ++i) { - secondLeafIndices[i] = leafIndices[i]; - secondOldIdents[i] = oldIdents[i]; - secondNewIdents[i] = newIdents[i]; - } - - part3TestUpdateIdentitiesSelectsCorrectVerifier( - actualProof, - newPreRoot, - leafIndices, - oldIdents, - newIdents, - newPostRoot, - secondLeafIndices, - secondOldIdents, - secondNewIdents - ); - } - - /// @notice Exists to work around local variable limits. - function part3TestUpdateIdentitiesSelectsCorrectVerifier( - uint256[8] memory actualProof, - uint256 newPreRoot, - uint32[] memory leafIndices, - uint256[] memory oldIdents, - uint256[] memory newIdents, - uint256 newPostRoot, - uint32[] memory secondLeafIndices, - uint256[] memory secondOldIdents, - uint256[] memory secondNewIdents - ) public { - bytes memory firstCallData = abi.encodeCall( - ManagerImpl.updateIdentities, - (actualProof, newPreRoot, leafIndices, oldIdents, newIdents, newPostRoot) - ); - uint256 secondPostRoot = uint256(newPostRoot) + 1; - bytes memory secondCallData = abi.encodeCall( - ManagerImpl.updateIdentities, - ( - actualProof, - newPostRoot, - secondLeafIndices, - secondOldIdents, - secondNewIdents, - secondPostRoot - ) - ); - - vm.expectEmit(true, true, true, true); - emit VerifiedProof(oldIdents.length); - - // Test - assertCallSucceedsOn(identityManagerAddress, firstCallData); - - vm.expectEmit(true, true, true, true); - emit VerifiedProof(secondOldIdents.length); - - assertCallSucceedsOn(identityManagerAddress, secondCallData); - } - - /// @notice Ensures that the contract reverts if passed a batch size it doesn't know about. - function testCannotUpdateIdentitiesWithInvalidBatchSize( - uint128[8] memory prf, - uint128 newPreRoot, - uint128 newPostRoot, - uint128[] memory identities - ) public { - vm.assume(SimpleVerify.isValidInput(uint256(prf[0]))); - vm.assume(newPreRoot != newPostRoot); - vm.assume(identities.length > 0); - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([identities.length - 1])); - makeNewIdentityManager( - treeDepth, - newPreRoot, - insertVerifiers, - deletionVerifiers, - updateVerifiers, - semaphoreVerifier - ); - ( - uint32[] memory leafIndices, - uint256[] memory oldIdents, - uint256[] memory newIdents, - uint256[8] memory actualProof - ) = prepareUpdateIdentitiesTestCase(identities, prf); - bytes memory callData = abi.encodeCall( - ManagerImpl.updateIdentities, - (actualProof, newPreRoot, leafIndices, oldIdents, newIdents, newPostRoot) - ); - bytes memory errorData = abi.encodeWithSelector(VerifierLookupTable.NoSuchVerifier.selector); - - // Test - assertCallFailsOn(identityManagerAddress, callData, errorData); - } - - /// @notice Checks that it reverts if the provided proof is incorrect for the public inputs. - function testCannotUpdateIdentitiesWithIncorrectInputs( - uint128[8] memory prf, - uint128 newPreRoot, - uint128 newPostRoot, - uint128[] memory identities - ) public { - // Setup - vm.assume(!SimpleVerify.isValidInput(uint256(prf[0]))); - vm.assume(newPreRoot != newPostRoot); - vm.assume(identities.length <= 1000); - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([identities.length])); - makeNewIdentityManager( - treeDepth, - newPreRoot, - insertVerifiers, - deletionVerifiers, - updateVerifiers, - semaphoreVerifier - ); - ( - uint32[] memory leafIndices, - uint256[] memory oldIdents, - uint256[] memory newIdents, - uint256[8] memory actualProof - ) = prepareUpdateIdentitiesTestCase(identities, prf); - bytes memory callData = abi.encodeCall( - ManagerImpl.updateIdentities, - (actualProof, newPreRoot, leafIndices, oldIdents, newIdents, newPostRoot) - ); - bytes memory expectedError = - abi.encodeWithSelector(ManagerImpl.ProofValidationFailure.selector); - - // Test - assertCallFailsOn(identityManagerAddress, callData, expectedError); - } - - /// @notice Tests that it reverts if an attempt is made to update identities as an address that - /// is not the identity operator address. - function testCannotUpdateIdentitiesAsNonIdentityOperator( - address nonOperator, - uint128[] memory identities, - uint128[8] memory prf - ) public { - // Setup - vm.assume(nonOperator != address(this) && nonOperator != address(0x0)); - ( - uint32[] memory leafIndices, - uint256[] memory oldIdents, - uint256[] memory newIdents, - uint256[8] memory actualProof - ) = prepareUpdateIdentitiesTestCase(identities, prf); - bytes memory callData = abi.encodeCall( - ManagerImpl.updateIdentities, - (actualProof, insertionPreRoot, leafIndices, oldIdents, newIdents, insertionPostRoot) - ); - bytes memory errorData = - abi.encodeWithSelector(ManagerImpl.Unauthorized.selector, nonOperator); - vm.prank(nonOperator); - - // Test - assertCallFailsOn(identityManagerAddress, callData, errorData); - } - - /// @notice Tests that it reverts if an attempt is made to update identities with an outdated - /// root. - function testCannotRegisterIdentitiesWithOutdatedRoot( - uint256 currentPreRoot, - uint256 actualRoot, - uint128[] memory identities, - uint128[8] memory prf - ) public { - // Setup - vm.assume( - currentPreRoot != actualRoot && currentPreRoot < SNARK_SCALAR_FIELD - && actualRoot < SNARK_SCALAR_FIELD - ); - vm.assume(identities.length <= 1000); - ( - uint32[] memory leafIndices, - uint256[] memory oldIdents, - uint256[] memory newIdents, - uint256[8] memory actualProof - ) = prepareUpdateIdentitiesTestCase(identities, prf); - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([identities.length])); - makeNewIdentityManager( - treeDepth, - uint256(currentPreRoot), - insertVerifiers, - deletionVerifiers, - updateVerifiers, - semaphoreVerifier - ); - bytes memory callData = abi.encodeCall( - ManagerImpl.updateIdentities, - (actualProof, actualRoot, leafIndices, oldIdents, newIdents, insertionPostRoot) - ); - bytes memory expectedError = abi.encodeWithSelector( - ManagerImpl.NotLatestRoot.selector, actualRoot, uint256(currentPreRoot) - ); - - // Test - assertCallFailsOn(identityManagerAddress, callData, expectedError); - } - - /// @notice Tests that it reverts if an attempt is made to update identity commitments that - /// are not in reduced form. - function testCannotRegisterIdentitiesWithUnreducedIdentities( - uint128 i, - uint256 position, - uint128 newPreRoot, - uint128[] memory identities, - uint128[8] memory prf, - bool changeOld - ) public { - // Setup - vm.assume(position < identities.length); - vm.assume(identities.length <= 1000); - ( - uint32[] memory leafIndices, - uint256[] memory oldIdents, - uint256[] memory newIdents, - uint256[8] memory actualProof - ) = prepareUpdateIdentitiesTestCase(identities, prf); - // scoping to avoid stack too deep errors - { - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([identities.length])); - makeNewIdentityManager( - treeDepth, - newPreRoot, - insertVerifiers, - deletionVerifiers, - updateVerifiers, - semaphoreVerifier - ); - } - - if (changeOld) { - oldIdents[position] = SNARK_SCALAR_FIELD + i; - } else { - newIdents[position] = SNARK_SCALAR_FIELD + i; - } - bytes memory callData = abi.encodeCall( - ManagerImpl.updateIdentities, - (actualProof, newPreRoot, leafIndices, oldIdents, newIdents, insertionPostRoot) - ); - - bytes memory expectedError = abi.encodeWithSelector( - ManagerImpl.UnreducedElement.selector, - ManagerImpl.UnreducedElementType.IdentityCommitment, - SNARK_SCALAR_FIELD + i - ); - - // Test - assertCallFailsOn(identityManagerAddress, callData, expectedError); - } - - /// @notice Tests that it reverts if an attempt is made to update identities with a pre root - /// that is not in reduced form. - function testCannotUpdateIdentitiesWithUnreducedPreRoot( - uint128 i, - uint128[] memory identities, - uint128[8] memory prf - ) public { - // Setup - uint256 newPreRoot = SNARK_SCALAR_FIELD + i; - ( - uint32[] memory leafIndices, - uint256[] memory oldIdents, - uint256[] memory newIdents, - uint256[8] memory actualProof - ) = prepareUpdateIdentitiesTestCase(identities, prf); - bytes memory callData = abi.encodeCall( - ManagerImpl.updateIdentities, - (actualProof, newPreRoot, leafIndices, oldIdents, newIdents, insertionPostRoot) - ); - bytes memory expectedError = abi.encodeWithSelector( - ManagerImpl.UnreducedElement.selector, - ManagerImpl.UnreducedElementType.PreRoot, - newPreRoot - ); - - // Test - assertCallFailsOn(identityManagerAddress, callData, expectedError); - } - - /// @notice Tests that it reverts if an attempt is made to update identities with a insertionPostRoot - /// that is not in reduced form. - function testCannotUpdateIdentitiesWithUnreducedPostRoot( - uint128 i, - uint128[] memory identities, - uint128[8] memory prf - ) public { - // Setup - uint256 newPostRoot = SNARK_SCALAR_FIELD + i; - ( - uint32[] memory leafIndices, - uint256[] memory oldIdents, - uint256[] memory newIdents, - uint256[8] memory actualProof - ) = prepareUpdateIdentitiesTestCase(identities, prf); - bytes memory callData = abi.encodeCall( - ManagerImpl.updateIdentities, - (actualProof, initialRoot, leafIndices, oldIdents, newIdents, newPostRoot) - ); - bytes memory expectedError = abi.encodeWithSelector( - ManagerImpl.UnreducedElement.selector, - ManagerImpl.UnreducedElementType.PostRoot, - newPostRoot - ); - - // Test - assertCallFailsOn(identityManagerAddress, callData, expectedError); - } - - /// @notice Tests that identities can only be updated through the proxy. - function testCannotUpdateIdentitiesIfNotViaProxy( - uint128[] memory identities, - uint128[8] memory prf - ) public { - // Setup - address expectedOwner = managerImpl.owner(); - ( - uint32[] memory leafIndices, - uint256[] memory oldIdents, - uint256[] memory newIdents, - uint256[8] memory actualProof - ) = prepareUpdateIdentitiesTestCase(identities, prf); - vm.expectRevert("Function must be called through delegatecall"); - vm.prank(expectedOwner); - - // Test - managerImpl.updateIdentities( - actualProof, initialRoot, leafIndices, oldIdents, newIdents, insertionPostRoot - ); - } -} diff --git a/src/test/identity-manager/WorldIDIdentityManagerUninit.t.sol b/src/test/identity-manager/WorldIDIdentityManagerUninit.t.sol index 594f70c..84c5792 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerUninit.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerUninit.t.sol @@ -58,31 +58,6 @@ contract WorldIDIdentityManagerUninit is WorldIDIdentityManagerTest { assertCallFailsOn(identityManagerAddress, callData, expectedError); } - /// @notice Checks that it is impossible to call `updateIdentities` while the contract is not - /// initialised. - function testShouldNotCallUpdateIdentitiesWhileUninit( - uint128[] memory identities, - uint128[8] memory prf - ) public { - // Setup - makeUninitIdentityManager(); - ( - uint32[] memory leafIndices, - uint256[] memory oldIdents, - uint256[] memory newIdents, - uint256[8] memory actualProof - ) = prepareUpdateIdentitiesTestCase(identities, prf); - bytes memory callData = abi.encodeCall( - ManagerImplV1.updateIdentities, - (actualProof, initialRoot, leafIndices, oldIdents, newIdents, insertionPostRoot) - ); - bytes memory expectedError = - abi.encodeWithSelector(CheckInitialized.ImplementationNotInitialized.selector); - - // Test - assertCallFailsOn(identityManagerAddress, callData, expectedError); - } - /// @notice Checks that it is impossible to call `calculateIdentityRegistrationInputHash` while /// the contract is not initialised. function testShouldNotCallCalculateIdentityRegistrationInputHash() public { @@ -197,35 +172,6 @@ contract WorldIDIdentityManagerUninit is WorldIDIdentityManagerTest { assertCallFailsOn(identityManagerAddress, callData, expectedError); } - /// @notice Checks that it is impossible to call `getIdentityUpdateVerifierLookupTableAddress` - /// while the contract is not initialized. - function testShouldNotCallGetIdentityUpdateVerifierLookupTableAddressWhileUninit() public { - // Setup - makeUninitIdentityManager(); - bytes memory callData = - abi.encodeCall(ManagerImplV1.getIdentityUpdateVerifierLookupTableAddress, ()); - bytes memory expectedError = - abi.encodeWithSelector(CheckInitialized.ImplementationNotInitialized.selector); - - // Test - assertCallFailsOn(identityManagerAddress, callData, expectedError); - } - - /// @notice Checks that it is impossible to call `setIdentityUpdateVerifierLookupTable` while - /// the contract is not initialized. - function testShouldNotCallSetIdentityUpdateVerifierLookupTableWhileUninit() public { - // Setup - makeUninitIdentityManager(); - (,, VerifierLookupTable updateVerifiers) = makeVerifierLookupTables(TC.makeDynArray([75])); - bytes memory callData = - abi.encodeCall(ManagerImplV1.setIdentityUpdateVerifierLookupTable, (updateVerifiers)); - bytes memory expectedError = - abi.encodeWithSelector(CheckInitialized.ImplementationNotInitialized.selector); - - // Test - assertCallFailsOn(identityManagerAddress, callData, expectedError); - } - /// @notice Checks that it is impossible to call `getSemaphoreVerifierAddress` while the /// contract is not initialized. function testShouldNotCallGetSemaphoreVerifierAddressWhileUninit() public { From e8ff2fb3d089fa014ddb82fec9406af72582977e Mon Sep 17 00:00:00 2001 From: "dcbuilder.eth" Date: Mon, 25 Sep 2023 21:49:12 +0200 Subject: [PATCH 11/20] remove invalid commitment test we no longer need it because we make sure the commitments are valid off-chain within the circuit --- src/WorldIDIdentityManagerImplV1.sol | 3 +- ...DIdentityManagerIdentityRegistration.t.sol | 28 ------------------- 2 files changed, 2 insertions(+), 29 deletions(-) diff --git a/src/WorldIDIdentityManagerImplV1.sol b/src/WorldIDIdentityManagerImplV1.sol index e7ac1fb..7a2ec41 100644 --- a/src/WorldIDIdentityManagerImplV1.sol +++ b/src/WorldIDIdentityManagerImplV1.sol @@ -168,6 +168,8 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { /// /// @param index The index in the array of identity commitments where the invalid commitment was /// found. + /// @dev This error is no longer in use as we now verify the commitments off-chain within the circuit + /// no need to check for reduced elements or invalid commitments. error InvalidCommitment(uint256 index); /// @notice Thrown when the provided proof cannot be verified for the accompanying inputs. @@ -336,7 +338,6 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { /// described by `preRoot`. Must be an element of the field `Kr`. /// /// @custom:reverts Unauthorized If the message sender is not authorised to add identities. - /// @custom:reverts InvalidCommitment If one or more of the provided commitments is invalid. /// @custom:reverts NotLatestRoot If the provided `preRoot` is not the latest root. /// @custom:reverts ProofValidationFailure If `insertionProof` cannot be verified using the /// provided inputs. diff --git a/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol b/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol index f1543e9..9aafd61 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol @@ -403,34 +403,6 @@ contract WorldIDIdentityManagerIdentityRegistration is WorldIDIdentityManagerTes assertCallFailsOn(identityManagerAddress, callData, expectedError); } - /// @notice Tests that it reverts if an attempt is made to register identity commitments - /// containing an invalid identity. - function testCannotRegisterIdentitiesWithInvalidIdentities( - uint8 identitiesLength, - uint8 invalidPosition - ) public { - // Setup - vm.assume(identitiesLength != 0); - vm.assume(invalidPosition < (identitiesLength - 1)); - uint256[] memory invalidCommitments = new uint256[](identitiesLength); - - for (uint256 i = 0; i < identitiesLength; ++i) { - invalidCommitments[i] = i + 1; - } - invalidCommitments[invalidPosition] = 0x0; - - bytes memory callData = abi.encodeCall( - ManagerImplV1.registerIdentities, - (insertionProof, initialRoot, startIndex, invalidCommitments, insertionPostRoot) - ); - bytes memory expectedError = abi.encodeWithSelector( - ManagerImplV1.InvalidCommitment.selector, uint256(invalidPosition + 1) - ); - - // Test - assertCallFailsOn(identityManagerAddress, callData, expectedError); - } - /// @notice Tests that runs of zeroes are accepted by the `registerIdentities` function as valid /// arrays of identity commitments. function testRegisterIdentitiesWithRunsOfZeroes(uint8 identitiesLength, uint8 zeroPosition) From 85b3bc65ec7e3699e3d7ff96c62800c8bdb2d6bf Mon Sep 17 00:00:00 2001 From: "dcbuilder.eth" Date: Mon, 25 Sep 2023 21:49:56 +0200 Subject: [PATCH 12/20] remove unreduced element test we no longer need it because we make sure elements are reduced off-chain within the circuit --- ...DIdentityManagerIdentityRegistration.t.sol | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol b/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol index 9aafd61..b100e7e 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol @@ -448,27 +448,6 @@ contract WorldIDIdentityManagerIdentityRegistration is WorldIDIdentityManagerTes assertCallSucceedsOn(identityManagerAddress, callData, new bytes(0)); } - /// @notice Tests that it reverts if an attempt is made to register identity commitments that - /// are not in reduced form. - function testCannotRegisterIdentitiesWithUnreducedIdentities(uint128 i) public { - // Setup - uint256 position = rotateSlot(); - uint256[] memory unreducedCommitments = new uint256[](identityCommitments.length); - unreducedCommitments[position] = SNARK_SCALAR_FIELD + i; - bytes memory callData = abi.encodeCall( - ManagerImplV1.registerIdentities, - (insertionProof, initialRoot, startIndex, unreducedCommitments, insertionPostRoot) - ); - bytes memory expectedError = abi.encodeWithSelector( - ManagerImplV1.UnreducedElement.selector, - ManagerImplV1.UnreducedElementType.IdentityCommitment, - SNARK_SCALAR_FIELD + i - ); - - // Test - assertCallFailsOn(identityManagerAddress, callData, expectedError); - } - /// @notice Tests that it reverts if an attempt is made to register new identities with a pre /// root that is not in reduced form. function testCannotRegisterIdentitiesWithUnreducedPreRoot(uint128 i) public { From 67d3bd3bfa93c2cda40cd89a8859dee41ad9d620 Mon Sep 17 00:00:00 2001 From: "dcbuilder.eth" Date: Mon, 25 Sep 2023 23:37:44 +0200 Subject: [PATCH 13/20] fix tests optimized verifier test still broken --- src/test/data/InclusionProof.json | 22 ++++++++++++++ src/test/data/Proof.json | 20 ------------- ...IdentityManagerSemaphoreVerification.t.sol | 12 ++++---- .../WorldIDIdentityManagerTest.sol | 30 +++++++++++++++++++ 4 files changed, 58 insertions(+), 26 deletions(-) create mode 100644 src/test/data/InclusionProof.json delete mode 100644 src/test/data/Proof.json diff --git a/src/test/data/InclusionProof.json b/src/test/data/InclusionProof.json new file mode 100644 index 0000000..35636f0 --- /dev/null +++ b/src/test/data/InclusionProof.json @@ -0,0 +1,22 @@ +{ + "proof": [ + [ + "0x27d70bdecb420a7322a0e44ef68345fc67e9903a3980762c23dfda5cf4d65715", + "0x1aba064ef272dd53b498d856c711890249a63a46825fe6d332fc5868ad854ef4" + ], + [ + [ + "0x23a76f9777710f268d2092d859344cdc8d7f77abef35695f89d1ebf771d8a520", + "0x295ab87eb7c0ad9470ec2b56b35309f5e4576679ef6180ed78124e3f549f125d" + ], + [ + "0x1da63a007225659d3a70a2dfe807df5c3e8423bfd8e059d72909a1def161573f", + "0x2578db76ee9f64ff4eb0b532cb796dfa27d86ae8cd29e2d6b32f9428c71acb8b" + ] + ], + [ + "0xd00d49d5db4c5b11a13aca379f5c3c627a6e8fc1c4470e7a56017307aca51a2", + "0xf6ee8db704ecb5c149e5a046a03e8767ba5a818c08320f6245070e4c0e99b77" + ] + ] +} \ No newline at end of file diff --git a/src/test/data/Proof.json b/src/test/data/Proof.json deleted file mode 100644 index ba5e67b..0000000 --- a/src/test/data/Proof.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "ar": [ - "0x2a45bf326884bbf13c821a5e4f30690a391156cccf80a2922fb24250111dd7eb", - "0x23a7376a159513e6d0e22d43fcdca9d0c8a5c54a73b59fce6962a41e71355894" - ], - "bs": [ - [ - "0x21b9fc7c2d1f76c2e1a972b00f18728a57a34d7e4ae040811bf1626132ff3658", - "0x2a7c3c660190a33ab92cd84e4b2540e49ea80bdc766eb3aeec49806a78071c75" - ], - [ - "0x2fc9a52a7f4bcc29faab28a8d8ec126b4fe604a7b41e7d2b3efe92422951d706", - "0x110740f0b21fb329de682dffc95a5ede11c11c6328606fe254b6ba469b15f68" - ] - ], - "krs": [ - "0x23115ff1573808639f19724479b195b7894a45c9868242ad2a416767359c6c78", - "0x23f3fa30273c7f38e360496e7f9790450096d4a9592e1fe6e0a996cb05b8fb28" - ] -} diff --git a/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol b/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol index 892c03c..5989a46 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol @@ -31,7 +31,7 @@ contract WorldIDIdentityManagerSemaphoreValidation is WorldIDIdentityManagerTest vm.assume(prf[0] != 0); makeNewIdentityManager( actualTreeDepth, - insertionPreRoot, + inclusionRoot, defaultInsertVerifiers, defaultDeletionVerifiers, defaultUpdateVerifiers, @@ -39,7 +39,7 @@ contract WorldIDIdentityManagerSemaphoreValidation is WorldIDIdentityManagerTest ); bytes memory verifyProofCallData = abi.encodeCall( ManagerImplV1.verifyProof, - (insertionPreRoot, nullifierHash, signalHash, externalNullifierHash, insertionProof) + (inclusionRoot, nullifierHash, signalHash, externalNullifierHash, inclusionProof) ); // Test @@ -60,7 +60,7 @@ contract WorldIDIdentityManagerSemaphoreValidation is WorldIDIdentityManagerTest vm.assume(prf[0] % 2 == 0); makeNewIdentityManager( actualTreeDepth, - insertionPreRoot, + inclusionRoot, defaultInsertVerifiers, defaultDeletionVerifiers, defaultUpdateVerifiers, @@ -68,7 +68,7 @@ contract WorldIDIdentityManagerSemaphoreValidation is WorldIDIdentityManagerTest ); bytes memory verifyProofCallData = abi.encodeCall( ManagerImplV1.verifyProof, - (insertionPreRoot, nullifierHash, signalHash, externalNullifierHash, prf) + (inclusionRoot, nullifierHash, signalHash, externalNullifierHash, prf) ); vm.expectRevert("Semaphore__InvalidProof()"); @@ -90,7 +90,7 @@ contract WorldIDIdentityManagerSemaphoreValidation is WorldIDIdentityManagerTest vm.assume(prf[0] != 0); makeNewIdentityManager( actualTreeDepth, - insertionPreRoot, + inclusionRoot, defaultInsertVerifiers, defaultDeletionVerifiers, defaultUpdateVerifiers, @@ -98,7 +98,7 @@ contract WorldIDIdentityManagerSemaphoreValidation is WorldIDIdentityManagerTest ); bytes memory verifyProofCallData = abi.encodeCall( ManagerImplV1.verifyProof, - (insertionPreRoot, nullifierHash, signalHash, externalNullifierHash, insertionProof) + (inclusionRoot, nullifierHash, signalHash, externalNullifierHash, inclusionProof) ); // Test diff --git a/src/test/identity-manager/WorldIDIdentityManagerTest.sol b/src/test/identity-manager/WorldIDIdentityManagerTest.sol index 76c4b47..0bc6e67 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerTest.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerTest.sol @@ -83,6 +83,18 @@ contract WorldIDIdentityManagerTest is WorldIDTest { uint32 deletionBatchSize = 8; uint256[8] deletionProof; + /////////////////////////////////////////////////////////////////// + /// INCLUSION /// + /////////////////////////////////////////////////////////////////// + /// @dev generated using https://github.com/worldcoin/semaphore-mock + /// steps: + /// 1. cargo run --release generate-identities --identities 10 + /// 2. cargo run --release prove-inclusion --identities out/random_identities.json --tree-depth 16 --identity-index 3 + uint256 internal constant inclusionRoot = + 0xdf9f0cb5a3afe2129e349c1435bfbe9e6f091832fdfa7b739b61c5db2cbdde9; + + uint256[8] inclusionProof; + // Needed for testing things. uint256 internal constant SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617; @@ -138,6 +150,24 @@ contract WorldIDIdentityManagerTest is WorldIDTest { 0x280894db66e6a9f9bf8aa48ffa1de98b755adadcf5962fb308cd1802a1101a0c, 0x1484814b74243a07930c6af61079f94eefd843efe95e2388d9d49956cfacf3ab ]; + + // Create the inclusion proof term. + // output from semaphore-mtb prove in src/test/data/InclusionProof.json + // + /// @dev generated using https://github.com/worldcoin/semaphore-mock + /// steps: + /// 1. cargo run --release generate-identities --identities 10 + /// 2. cargo run --release prove-inclusion --identities out/random_identities.json --tree-depth 16 --identity-index 3 + inclusionProof = [ + 0x27d70bdecb420a7322a0e44ef68345fc67e9903a3980762c23dfda5cf4d65715, + 0x1aba064ef272dd53b498d856c711890249a63a46825fe6d332fc5868ad854ef4, + 0x23a76f9777710f268d2092d859344cdc8d7f77abef35695f89d1ebf771d8a520, + 0x295ab87eb7c0ad9470ec2b56b35309f5e4576679ef6180ed78124e3f549f125d, + 0x1da63a007225659d3a70a2dfe807df5c3e8423bfd8e059d72909a1def161573f, + 0x2578db76ee9f64ff4eb0b532cb796dfa27d86ae8cd29e2d6b32f9428c71acb8b, + 0xd00d49d5db4c5b11a13aca379f5c3c627a6e8fc1c4470e7a56017307aca51a2, + 0xf6ee8db704ecb5c149e5a046a03e8767ba5a818c08320f6245070e4c0e99b77 + ]; } /// @notice This function runs before every single test. From b6eb7c612b0ad4574c7e70c62dde8db94a742f1d Mon Sep 17 00:00:00 2001 From: "dcbuilder.eth" Date: Tue, 26 Sep 2023 17:29:08 +0200 Subject: [PATCH 14/20] new optimized verifier test still broken --- src/{test/mock => }/DeletionTreeVerifier.sol | 2 +- src/{test/mock => }/InsertionTreeVerifier.sol | 2 +- src/SemaphoreVerifier.sol | 655 ++++++++++++++++++ src/interfaces/ISemaphoreVerifier.sol | 18 + .../SemaphoreVerifier16.sol} | 195 +++--- src/test/data/semaphore_16.json | 0 src/test/data/semaphore_30.json | 109 +++ ...rldIDIdentityManagerIdentityDeletion.t.sol | 2 +- ...DIdentityManagerIdentityRegistration.t.sol | 2 +- ...IDIdentityManagerOwnershipManagement.t.sol | 2 +- ...IdentityManagerSemaphoreVerification.t.sol | 4 +- .../WorldIDIdentityManagerUninit.t.sol | 2 +- 12 files changed, 883 insertions(+), 110 deletions(-) rename src/{test/mock => }/DeletionTreeVerifier.sol (99%) rename src/{test/mock => }/InsertionTreeVerifier.sol (99%) create mode 100644 src/SemaphoreVerifier.sol create mode 100644 src/interfaces/ISemaphoreVerifier.sol rename src/{Verifier.sol => test/SemaphoreVerifier16.sol} (76%) create mode 100644 src/test/data/semaphore_16.json create mode 100644 src/test/data/semaphore_30.json diff --git a/src/test/mock/DeletionTreeVerifier.sol b/src/DeletionTreeVerifier.sol similarity index 99% rename from src/test/mock/DeletionTreeVerifier.sol rename to src/DeletionTreeVerifier.sol index 2bb2731..6c1673e 100644 --- a/src/test/mock/DeletionTreeVerifier.sol +++ b/src/DeletionTreeVerifier.sol @@ -1,4 +1,4 @@ -import {ITreeVerifier} from "../../interfaces/ITreeVerifier.sol"; +import {ITreeVerifier} from "src/interfaces/ITreeVerifier.sol"; // SPDX-License-Identifier: MIT diff --git a/src/test/mock/InsertionTreeVerifier.sol b/src/InsertionTreeVerifier.sol similarity index 99% rename from src/test/mock/InsertionTreeVerifier.sol rename to src/InsertionTreeVerifier.sol index 53125f8..417b874 100644 --- a/src/test/mock/InsertionTreeVerifier.sol +++ b/src/InsertionTreeVerifier.sol @@ -1,4 +1,4 @@ -import {ITreeVerifier} from "../../interfaces/ITreeVerifier.sol"; +import {ITreeVerifier} from "src/interfaces/ITreeVerifier.sol"; // SPDX-License-Identifier: MIT diff --git a/src/SemaphoreVerifier.sol b/src/SemaphoreVerifier.sol new file mode 100644 index 0000000..d800487 --- /dev/null +++ b/src/SemaphoreVerifier.sol @@ -0,0 +1,655 @@ +// SPDX-License-Identifier: MIT + +import {ISemaphoreVerifier} from "src/interfaces/ISemaphoreVerifier.sol"; + +pragma solidity ^0.8.0; + +/// @title Groth16 verifier template. +/// @author Remco Bloemen +/// @notice Supports verifying Groth16 proofs. Proofs can be in uncompressed +/// (256 bytes) and compressed (128 bytes) format. A view function is provided +/// to compress proofs. +/// @notice See for further explanation. +contract Verifier is ISemaphoreVerifier{ + /// Some of the provided public input values are larger than the field modulus. + /// @dev Public input elements are not automatically reduced, as this is can be + /// a dangerous source of bugs. + error PublicInputNotInField(); + + /// The proof is invalid. + /// @dev This can mean that provided Groth16 proof points are not on their + /// curves, that pairing equation fails, or that the proof is not for the + /// provided public input. + error ProofInvalid(); + + // Addresses of precompiles + uint256 constant PRECOMPILE_MODEXP = 0x05; + uint256 constant PRECOMPILE_ADD = 0x06; + uint256 constant PRECOMPILE_MUL = 0x07; + uint256 constant PRECOMPILE_VERIFY = 0x08; + + // Base field Fp order P and scalar field Fr order R. + // For BN254 these are computed as follows: + // t = 4965661367192848881 + // P = 36⋅t⁴ + 36⋅t³ + 24⋅t² + 6⋅t + 1 + // R = 36⋅t⁴ + 36⋅t³ + 18⋅t² + 6⋅t + 1 + uint256 constant P = + 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; + uint256 constant R = + 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001; + + // Extension field Fp2 = Fp[i] / (i² + 1) + // Note: This is the complex extension field of Fp with i² = -1. + // Values in Fp2 are represented as a pair of Fp elements (a₀, a₁) as a₀ + a₁⋅i. + // Note: The order of Fp2 elements is *opposite* that of the pairing contract, which + // expects Fp2 elements in order (a₁, a₀). This is also the order in which + // Fp2 elements are encoded in the public interface as this became convention. + + // Constants in Fp + uint256 constant FRACTION_1_2_FP = + 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4; + uint256 constant FRACTION_27_82_FP = + 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; + uint256 constant FRACTION_3_82_FP = + 0x2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e775; + + // Exponents for inversions and square roots mod P + uint256 constant EXP_INVERSE_FP = + 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45; // P - 2 + uint256 constant EXP_SQRT_FP = + 0xC19139CB84C680A6E14116DA060561765E05AA45A1C72A34F082305B61F3F52; // (P + 1) / 4; + + // Groth16 alpha point in G1 + uint256 constant ALPHA_X = + 20491192805390485299153009773594534940189261866228447918068658471970481763042; + uint256 constant ALPHA_Y = + 9383485363053290200918347156157836566562967994039712273449902621266178545958; + + // Groth16 beta point in G2 in powers of i + uint256 constant BETA_NEG_X_0 = + 6375614351688725206403948262868962793625744043794305715222011528459656738731; + uint256 constant BETA_NEG_X_1 = + 4252822878758300859123897981450591353533073413197771768651442665752259397132; + uint256 constant BETA_NEG_Y_0 = + 11383000245469012944693504663162918391286475477077232690815866754273895001727; + uint256 constant BETA_NEG_Y_1 = + 41207766310529818958173054109690360505148424997958324311878202295167071904; + + // Groth16 gamma point in G2 in powers of i + uint256 constant GAMMA_NEG_X_0 = + 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 constant GAMMA_NEG_X_1 = + 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 constant GAMMA_NEG_Y_0 = + 13392588948715843804641432497768002650278120570034223513918757245338268106653; + uint256 constant GAMMA_NEG_Y_1 = + 17805874995975841540914202342111839520379459829704422454583296818431106115052; + + // Groth16 delta point in G2 in powers of i + uint256 constant DELTA_NEG_X_0 = + 15028154694713144242204861571552635520290993855826554325002991692907421516918; + uint256 constant DELTA_NEG_X_1 = + 10202326166286888893675634318107715186834588694714750762952081034135561546271; + uint256 constant DELTA_NEG_Y_0 = + 9121952986466441409625823112409402110610350380222160673756836983949377617226; + uint256 constant DELTA_NEG_Y_1 = + 3402203030459169245973828223647408421795734658790470725360311404592929738724; + + // Constant and public input points + uint256 constant CONSTANT_X = + 1452272927738590248356371174422184656932731110936062990115610832462181634644; + uint256 constant CONSTANT_Y = + 3608050114233210789542189629343107890943266759827387991788718454179833288695; + uint256 constant PUB_0_X = + 14798240452388909327945424685903532333765637883272751382037716636327236955001; + uint256 constant PUB_0_Y = + 10773894897711848209682368488916121016695006898681985691467605219098835500201; + uint256 constant PUB_1_X = + 17204267933132009093604099819536245144503489322639121825381131096467570698650; + uint256 constant PUB_1_Y = + 7704298975420304156332734115679983371345754866278811368869074990486717531131; + uint256 constant PUB_2_X = + 8060465662017324080560848316478407038163145149983639907596180500095598669247; + uint256 constant PUB_2_Y = + 20475082166427284188002500222093571716651248980245637602667562336751029856573; + uint256 constant PUB_3_X = + 7457566682692308112726332096733260585025339741083447785327706250123165087868; + uint256 constant PUB_3_Y = + 11904519443874922292602150685069370036383697877657723976244907400392778002614; + + /// Negation in Fp. + /// @notice Returns a number x such that a + x = 0 in Fp. + /// @notice The input does not need to be reduced. + /// @param a the base + /// @return x the result + function negate(uint256 a) internal pure returns (uint256 x) { + unchecked { + x = (P - (a % P)) % P; // Modulo is cheaper than branching + } + } + + /// Exponentiation in Fp. + /// @notice Returns a number x such that a ^ e = x in Fp. + /// @notice The input does not need to be reduced. + /// @param a the base + /// @param e the exponent + /// @return x the result + function exp(uint256 a, uint256 e) internal view returns (uint256 x) { + bool success; + assembly ("memory-safe") { + let f := mload(0x40) + mstore(f, 0x20) + mstore(add(f, 0x20), 0x20) + mstore(add(f, 0x40), 0x20) + mstore(add(f, 0x60), a) + mstore(add(f, 0x80), e) + mstore(add(f, 0xa0), P) + success := staticcall(gas(), PRECOMPILE_MODEXP, f, 0xc0, f, 0x20) + x := mload(f) + } + if (!success) { + // Exponentiation failed. + // Should not happen. + revert ProofInvalid(); + } + } + + /// Invertsion in Fp. + /// @notice Returns a number x such that a * x = 1 in Fp. + /// @notice The input does not need to be reduced. + /// @notice Reverts with ProofInvalid() if the inverse does not exist + /// @param a the input + /// @return x the solution + function invert_Fp(uint256 a) internal view returns (uint256 x) { + x = exp(a, EXP_INVERSE_FP); + if (mulmod(a, x, P) != 1) { + // Inverse does not exist. + // Can only happen during G2 point decompression. + revert ProofInvalid(); + } + } + + /// Square root in Fp. + /// @notice Returns a number x such that x * x = a in Fp. + /// @notice Will revert with InvalidProof() if the input is not a square + /// or not reduced. + /// @param a the square + /// @return x the solution + function sqrt_Fp(uint256 a) internal view returns (uint256 x) { + x = exp(a, EXP_SQRT_FP); + if (mulmod(x, x, P) != a) { + // Square root does not exist or a is not reduced. + // Happens when G1 point is not on curve. + revert ProofInvalid(); + } + } + + /// Square test in Fp. + /// @notice Returns wheter a number x exists such that x * x = a in Fp. + /// @notice Will revert with InvalidProof() if the input is not a square + /// or not reduced. + /// @param a the square + /// @return x the solution + function isSquare_Fp(uint256 a) internal view returns (bool) { + uint256 x = exp(a, EXP_SQRT_FP); + return mulmod(x, x, P) == a; + } + + /// Square root in Fp2. + /// @notice Fp2 is the complex extension Fp[i]/(i^2 + 1). The input is + /// a0 + a1 ⋅ i and the result is x0 + x1 ⋅ i. + /// @notice Will revert with InvalidProof() if + /// * the input is not a square, + /// * the hint is incorrect, or + /// * the input coefficents are not reduced. + /// @param a0 The real part of the input. + /// @param a1 The imaginary part of the input. + /// @param hint A hint which of two possible signs to pick in the equation. + /// @return x0 The real part of the square root. + /// @return x1 The imaginary part of the square root. + function sqrt_Fp2( + uint256 a0, + uint256 a1, + bool hint + ) internal view returns (uint256 x0, uint256 x1) { + // If this square root reverts there is no solution in Fp2. + uint256 d = sqrt_Fp(addmod(mulmod(a0, a0, P), mulmod(a1, a1, P), P)); + if (hint) { + d = negate(d); + } + // If this square root reverts there is no solution in Fp2. + x0 = sqrt_Fp(mulmod(addmod(a0, d, P), FRACTION_1_2_FP, P)); + x1 = mulmod(a1, invert_Fp(mulmod(x0, 2, P)), P); + + // Check result to make sure we found a root. + // Note: this also fails if a0 or a1 is not reduced. + if ( + a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P) || + a1 != mulmod(2, mulmod(x0, x1, P), P) + ) { + revert ProofInvalid(); + } + } + + /// Compress a G1 point. + /// @notice Reverts with InvalidProof if the coordinates are not reduced + /// or if the point is not on the curve. + /// @notice The point at infinity is encoded as (0,0) and compressed to 0. + /// @param x The X coordinate in Fp. + /// @param y The Y coordinate in Fp. + /// @return c The compresed point (x with one signal bit). + function compress_g1( + uint256 x, + uint256 y + ) internal view returns (uint256 c) { + if (x >= P || y >= P) { + // G1 point not in field. + revert ProofInvalid(); + } + if (x == 0 && y == 0) { + // Point at infinity + return 0; + } + + // Note: sqrt_Fp reverts if there is no solution, i.e. the x coordinate is invalid. + uint256 y_pos = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P)); + if (y == y_pos) { + return (x << 1) | 0; + } else if (y == negate(y_pos)) { + return (x << 1) | 1; + } else { + // G1 point not on curve. + revert ProofInvalid(); + } + } + + /// Decompress a G1 point. + /// @notice Reverts with InvalidProof if the input does not represent a valid point. + /// @notice The point at infinity is encoded as (0,0) and compressed to 0. + /// @param c The compresed point (x with one signal bit). + /// @return x The X coordinate in Fp. + /// @return y The Y coordinate in Fp. + function decompress_g1( + uint256 c + ) internal view returns (uint256 x, uint256 y) { + // Note that X = 0 is not on the curve since 0³ + 3 = 3 is not a square. + // so we can use it to represent the point at infinity. + if (c == 0) { + // Point at infinity as encoded in EIP196 and EIP197. + return (0, 0); + } + bool negate_point = c & 1 == 1; + x = c >> 1; + if (x >= P) { + // G1 x coordinate not in field. + revert ProofInvalid(); + } + + // Note: (x³ + 3) is irreducible in Fp, so it can not be zero and therefore + // y can not be zero. + // Note: sqrt_Fp reverts if there is no solution, i.e. the point is not on the curve. + y = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P)); + if (negate_point) { + y = negate(y); + } + } + + /// Compress a G2 point. + /// @notice Reverts with InvalidProof if the coefficients are not reduced + /// or if the point is not on the curve. + /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). + /// @param x0 The real part of the X coordinate. + /// @param x1 The imaginary poart of the X coordinate. + /// @param y0 The real part of the Y coordinate. + /// @param y1 The imaginary part of the Y coordinate. + /// @return c0 The first half of the compresed point (x0 with two signal bits). + /// @return c1 The second half of the compressed point (x1 unmodified). + function compress_g2( + uint256 x0, + uint256 x1, + uint256 y0, + uint256 y1 + ) internal view returns (uint256 c0, uint256 c1) { + if (x0 >= P || x1 >= P || y0 >= P || y1 >= P) { + // G2 point not in field. + revert ProofInvalid(); + } + if ((x0 | x1 | y0 | y1) == 0) { + // Point at infinity + return (0, 0); + } + + // Compute y^2 + // Note: shadowing variables and scoping to avoid stack-to-deep. + uint256 y0_pos; + uint256 y1_pos; + { + uint256 n3ab = mulmod(mulmod(x0, x1, P), P - 3, P); + uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); + uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); + y0_pos = addmod( + FRACTION_27_82_FP, + addmod(a_3, mulmod(n3ab, x1, P), P), + P + ); + y1_pos = negate( + addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P) + ); + } + + // Determine hint bit + // If this sqrt fails the x coordinate is not on the curve. + bool hint; + { + uint256 d = sqrt_Fp( + addmod(mulmod(y0_pos, y0_pos, P), mulmod(y1_pos, y1_pos, P), P) + ); + hint = !isSquare_Fp( + mulmod(addmod(y0_pos, d, P), FRACTION_1_2_FP, P) + ); + } + + // Recover y + (y0_pos, y1_pos) = sqrt_Fp2(y0_pos, y1_pos, hint); + if (y0 == y0_pos && y1 == y1_pos) { + c0 = (x0 << 2) | (hint ? 2 : 0) | 0; + c1 = x1; + } else if (y0 == negate(y0_pos) && y1 == negate(y1_pos)) { + c0 = (x0 << 2) | (hint ? 2 : 0) | 1; + c1 = x1; + } else { + // G1 point not on curve. + revert ProofInvalid(); + } + } + + /// Decompress a G2 point. + /// @notice Reverts with InvalidProof if the input does not represent a valid point. + /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). + /// @param c0 The first half of the compresed point (x0 with two signal bits). + /// @param c1 The second half of the compressed point (x1 unmodified). + /// @return x0 The real part of the X coordinate. + /// @return x1 The imaginary poart of the X coordinate. + /// @return y0 The real part of the Y coordinate. + /// @return y1 The imaginary part of the Y coordinate. + function decompress_g2( + uint256 c0, + uint256 c1 + ) internal view returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1) { + // Note that X = (0, 0) is not on the curve since 0³ + 3/(9 + i) is not a square. + // so we can use it to represent the point at infinity. + if (c0 == 0 && c1 == 0) { + // Point at infinity as encoded in EIP197. + return (0, 0, 0, 0); + } + bool negate_point = c0 & 1 == 1; + bool hint = c0 & 2 == 2; + x0 = c0 >> 2; + x1 = c1; + if (x0 >= P || x1 >= P) { + // G2 x0 or x1 coefficient not in field. + revert ProofInvalid(); + } + + uint256 n3ab = mulmod(mulmod(x0, x1, P), P - 3, P); + uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); + uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); + + y0 = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); + y1 = negate( + addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P) + ); + + // Note: sqrt_Fp2 reverts if there is no solution, i.e. the point is not on the curve. + // Note: (X³ + 3/(9 + i)) is irreducible in Fp2, so y can not be zero. + // But y0 or y1 may still independently be zero. + (y0, y1) = sqrt_Fp2(y0, y1, hint); + if (negate_point) { + y0 = negate(y0); + y1 = negate(y1); + } + } + + /// Compute the public input linear combination. + /// @notice Reverts with PublicInputNotInField if the input is not in the field. + /// @notice Computes the multi-scalar-multiplication of the public input + /// elements and the verification key including the constant term. + /// @param input The public inputs. These are elements of the scalar field Fr. + /// @return x The X coordinate of the resulting G1 point. + /// @return y The Y coordinate of the resulting G1 point. + function publicInputMSM( + uint256[4] calldata input + ) internal view returns (uint256 x, uint256 y) { + // Note: The ECMUL precompile does not reject unreduced values, so we check this. + // Note: Unrolling this loop does not cost much extra in code-size, the bulk of the + // code-size is in the PUB_ constants. + // ECMUL has input (x, y, scalar) and output (x', y'). + // ECADD has input (x1, y1, x2, y2) and output (x', y'). + // We call them such that ecmul output is already in the second point + // argument to ECADD so we can have a tight loop. + bool success = true; + assembly ("memory-safe") { + let f := mload(0x40) + let g := add(f, 0x40) + let s + mstore(f, CONSTANT_X) + mstore(add(f, 0x20), CONSTANT_Y) + mstore(g, PUB_0_X) + mstore(add(g, 0x20), PUB_0_Y) + s := calldataload(input) + mstore(add(g, 0x40), s) + success := and(success, lt(s, R)) + success := and( + success, + staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40) + ) + success := and( + success, + staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40) + ) + mstore(g, PUB_1_X) + mstore(add(g, 0x20), PUB_1_Y) + s := calldataload(add(input, 32)) + mstore(add(g, 0x40), s) + success := and(success, lt(s, R)) + success := and( + success, + staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40) + ) + success := and( + success, + staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40) + ) + mstore(g, PUB_2_X) + mstore(add(g, 0x20), PUB_2_Y) + s := calldataload(add(input, 64)) + mstore(add(g, 0x40), s) + success := and(success, lt(s, R)) + success := and( + success, + staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40) + ) + success := and( + success, + staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40) + ) + mstore(g, PUB_3_X) + mstore(add(g, 0x20), PUB_3_Y) + s := calldataload(add(input, 96)) + mstore(add(g, 0x40), s) + success := and(success, lt(s, R)) + success := and( + success, + staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40) + ) + success := and( + success, + staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40) + ) + x := mload(f) + y := mload(add(f, 0x20)) + } + if (!success) { + // Either Public input not in field, or verification key invalid. + // We assume the contract is correctly generated, so the verification key is valid. + revert PublicInputNotInField(); + } + } + + /// Compress a proof. + /// @notice Will revert with InvalidProof if the curve points are invalid, + /// but does not verify the proof itself. + /// @param proof The uncompressed Groth16 proof. Elements are in the same order as for + /// verifyProof. I.e. Groth16 points (A, B, C) encoded as in EIP-197. + /// @return compressed The compressed proof. Elements are in the same order as for + /// verifyCompressedProof. I.e. points (A, B, C) in compressed format. + function compressProof( + uint256[8] calldata proof + ) public view returns (uint256[4] memory compressed) { + compressed[0] = compress_g1(proof[0], proof[1]); + (compressed[2], compressed[1]) = compress_g2( + proof[3], + proof[2], + proof[5], + proof[4] + ); + compressed[3] = compress_g1(proof[6], proof[7]); + } + + /// Verify a Groth16 proof with compressed points. + /// @notice Reverts with InvalidProof if the proof is invalid or + /// with PublicInputNotInField the public input is not reduced. + /// @notice There is no return value. If the function does not revert, the + /// proof was succesfully verified. + /// @param compressedProof the points (A, B, C) in compressed format + /// matching the output of compressProof. + /// @param input the public input field elements in the scalar field Fr. + /// Elements must be reduced. + function verifyCompressedProof( + uint256[4] calldata compressedProof, + uint256[4] calldata input + ) public view { + (uint256 Ax, uint256 Ay) = decompress_g1(compressedProof[0]); + (uint256 Bx0, uint256 Bx1, uint256 By0, uint256 By1) = decompress_g2( + compressedProof[2], + compressedProof[1] + ); + (uint256 Cx, uint256 Cy) = decompress_g1(compressedProof[3]); + (uint256 Lx, uint256 Ly) = publicInputMSM(input); + + // Verify the pairing + // Note: The precompile expects the F2 coefficients in big-endian order. + // Note: The pairing precompile rejects unreduced values, so we won't check that here. + uint256[24] memory pairings; + // e(A, B) + pairings[0] = Ax; + pairings[1] = Ay; + pairings[2] = Bx1; + pairings[3] = Bx0; + pairings[4] = By1; + pairings[5] = By0; + // e(C, -δ) + pairings[6] = Cx; + pairings[7] = Cy; + pairings[8] = DELTA_NEG_X_1; + pairings[9] = DELTA_NEG_X_0; + pairings[10] = DELTA_NEG_Y_1; + pairings[11] = DELTA_NEG_Y_0; + // e(α, -β) + pairings[12] = ALPHA_X; + pairings[13] = ALPHA_Y; + pairings[14] = BETA_NEG_X_1; + pairings[15] = BETA_NEG_X_0; + pairings[16] = BETA_NEG_Y_1; + pairings[17] = BETA_NEG_Y_0; + // e(L_pub, -γ) + pairings[18] = Lx; + pairings[19] = Ly; + pairings[20] = GAMMA_NEG_X_1; + pairings[21] = GAMMA_NEG_X_0; + pairings[22] = GAMMA_NEG_Y_1; + pairings[23] = GAMMA_NEG_Y_0; + + // Check pairing equation. + bool success; + uint256[1] memory output; + assembly ("memory-safe") { + success := staticcall( + gas(), + PRECOMPILE_VERIFY, + pairings, + 0x300, + output, + 0x20 + ) + } + if (!success || output[0] != 1) { + // Either proof or verification key invalid. + // We assume the contract is correctly generated, so the verification key is valid. + revert ProofInvalid(); + } + } + + /// Verify an uncompressed Groth16 proof. + /// @notice Reverts with InvalidProof if the proof is invalid or + /// with PublicInputNotInField the public input is not reduced. + /// @notice There is no return value. If the function does not revert, the + /// proof was succesfully verified. + /// @param proof the points (A, B, C) in EIP-197 format matching the output + /// of compressProof. + /// @param input the public input field elements in the scalar field Fr. + /// Elements must be reduced. + function verifyProof( + uint256[8] calldata proof, + uint256[4] calldata input + ) public view { + (uint256 x, uint256 y) = publicInputMSM(input); + + // Note: The precompile expects the F2 coefficients in big-endian order. + // Note: The pairing precompile rejects unreduced values, so we won't check that here. + + bool success; + assembly ("memory-safe") { + let f := mload(0x40) // Free memory pointer. + + // Copy points (A, B, C) to memory. They are already in correct encoding. + // This is pairing e(A, B) and G1 of e(C, -δ). + calldatacopy(f, proof, 0x100) + + // Complete e(C, -δ) and write e(α, -β), e(L_pub, -γ) to memory. + // OPT: This could be better done using a single codecopy, but + // Solidity (unlike standalone Yul) doesn't provide a way to + // to do this. + mstore(add(f, 0x100), DELTA_NEG_X_1) + mstore(add(f, 0x120), DELTA_NEG_X_0) + mstore(add(f, 0x140), DELTA_NEG_Y_1) + mstore(add(f, 0x160), DELTA_NEG_Y_0) + mstore(add(f, 0x180), ALPHA_X) + mstore(add(f, 0x1a0), ALPHA_Y) + mstore(add(f, 0x1c0), BETA_NEG_X_1) + mstore(add(f, 0x1e0), BETA_NEG_X_0) + mstore(add(f, 0x200), BETA_NEG_Y_1) + mstore(add(f, 0x220), BETA_NEG_Y_0) + mstore(add(f, 0x240), x) + mstore(add(f, 0x260), y) + mstore(add(f, 0x280), GAMMA_NEG_X_1) + mstore(add(f, 0x2a0), GAMMA_NEG_X_0) + mstore(add(f, 0x2c0), GAMMA_NEG_Y_1) + mstore(add(f, 0x2e0), GAMMA_NEG_Y_0) + + // Check pairing equation. + success := staticcall(gas(), PRECOMPILE_VERIFY, f, 0x300, f, 0x20) + // Also check returned value (both are either 1 or 0). + success := and(success, mload(f)) + } + if (!success) { + // Either proof or verification key invalid. + // We assume the contract is correctly generated, so the verification key is valid. + revert ProofInvalid(); + } + } +} diff --git a/src/interfaces/ISemaphoreVerifier.sol b/src/interfaces/ISemaphoreVerifier.sol new file mode 100644 index 0000000..9676049 --- /dev/null +++ b/src/interfaces/ISemaphoreVerifier.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +/// @title Tree Verifier Interface +/// @author Worldcoin +/// @notice An interface representing a merkle tree verifier. +interface ISemaphoreVerifier { + /// @notice Verify an uncompressed Groth16 proof. + /// @notice Reverts with InvalidProof if the proof is invalid or + /// with PublicInputNotInField the public input is not reduced. + /// @notice There is no return value. If the function does not revert, the + /// proof was succesfully verified. + /// @param proof the points (A, B, C) in EIP-197 format matching the output + /// of compressProof. + /// @param input the public input field elements in the scalar field Fr. + /// Elements must be reduced. + function verifyProof(uint256[8] calldata proof, uint256[4] calldata input) external; +} diff --git a/src/Verifier.sol b/src/test/SemaphoreVerifier16.sol similarity index 76% rename from src/Verifier.sol rename to src/test/SemaphoreVerifier16.sol index 7e76a6d..2acc45a 100644 --- a/src/Verifier.sol +++ b/src/test/SemaphoreVerifier16.sol @@ -1,16 +1,17 @@ +import {ISemaphoreVerifier} from "src/interfaces/ISemaphoreVerifier.sol"; + // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {ITreeVerifier} from "src/interfaces/ITreeVerifier.sol"; - /// @title Groth16 verifier template. /// @author Remco Bloemen /// @notice Supports verifying Groth16 proofs. Proofs can be in uncompressed /// (256 bytes) and compressed (128 bytes) format. A view function is provided /// to compress proofs. /// @notice See for further explanation. -contract Verifier is ITreeVerifier { +contract Verifier is ISemaphoreVerifier { + /// Some of the provided public input values are larger than the field modulus. /// @dev Public input elements are not automatically reduced, as this is can be /// a dangerous source of bugs. @@ -44,63 +45,47 @@ contract Verifier is ITreeVerifier { // Fp2 elements are encoded in the public interface as this became convention. // Constants in Fp - uint256 constant FRACTION_1_2_FP = - 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4; - uint256 constant FRACTION_27_82_FP = - 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; - uint256 constant FRACTION_3_82_FP = - 0x2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e775; + uint256 constant FRACTION_1_2_FP = 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4; + uint256 constant FRACTION_27_82_FP = 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; + uint256 constant FRACTION_3_82_FP = 0x2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e775; // Exponents for inversions and square roots mod P - uint256 constant EXP_INVERSE_FP = - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45; // P - 2 + uint256 constant EXP_INVERSE_FP = 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45; // P - 2 uint256 constant EXP_SQRT_FP = 0xC19139CB84C680A6E14116DA060561765E05AA45A1C72A34F082305B61F3F52; // (P + 1) / 4; // Groth16 alpha point in G1 - uint256 constant ALPHA_X = - 21238094701099767992181020574097576567248669231933303288906499800492665245646; - uint256 constant ALPHA_Y = - 17835722675051865338243836993818778396240839209371887976268210291316141770442; + uint256 constant ALPHA_X = 20491192805390485299153009773594534940189261866228447918068658471970481763042; + uint256 constant ALPHA_Y = 9383485363053290200918347156157836566562967994039712273449902621266178545958; // Groth16 beta point in G2 in powers of i - uint256 constant BETA_NEG_X_0 = - 17328241642904145666695767828612798629577735363797370984204279730597246374999; - uint256 constant BETA_NEG_X_1 = - 3860372090784917774458215217940662090649652140977504592804245872521163657018; - uint256 constant BETA_NEG_Y_0 = - 10271963604709049923747050159865377390989698297635199231422940911768575903284; - uint256 constant BETA_NEG_Y_1 = - 15652994649667530601973737381462460227196275508968367336119989296260301572852; + uint256 constant BETA_NEG_X_0 = 6375614351688725206403948262868962793625744043794305715222011528459656738731; + uint256 constant BETA_NEG_X_1 = 4252822878758300859123897981450591353533073413197771768651442665752259397132; + uint256 constant BETA_NEG_Y_0 = 11383000245469012944693504663162918391286475477077232690815866754273895001727; + uint256 constant BETA_NEG_Y_1 = 41207766310529818958173054109690360505148424997958324311878202295167071904; // Groth16 gamma point in G2 in powers of i - uint256 constant GAMMA_NEG_X_0 = - 13072135626714886665743299238380777558485915182186148044840030375227830297167; - uint256 constant GAMMA_NEG_X_1 = - 15728445629349648373029025074665124823451612105079365074482822873179926876367; - uint256 constant GAMMA_NEG_Y_0 = - 574425249090245226726507580262558613212067342295152730122360551738055447720; - uint256 constant GAMMA_NEG_Y_1 = - 14984396635537120619265753845843371019305161489040275839272534175355415469117; + uint256 constant GAMMA_NEG_X_0 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 constant GAMMA_NEG_X_1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 constant GAMMA_NEG_Y_0 = 13392588948715843804641432497768002650278120570034223513918757245338268106653; + uint256 constant GAMMA_NEG_Y_1 = 17805874995975841540914202342111839520379459829704422454583296818431106115052; // Groth16 delta point in G2 in powers of i - uint256 constant DELTA_NEG_X_0 = - 11641495633934915211954655655028364970241467353476707112972519067148042156236; - uint256 constant DELTA_NEG_X_1 = - 2862574649712655062568935342174214330174596565118547226721602157017065773404; - uint256 constant DELTA_NEG_Y_0 = - 19077360648152442030655834928434260675626758144638095983278277208712230645850; - uint256 constant DELTA_NEG_Y_1 = - 14251475007345879229751465975664358189685218843746247716834082671723536271198; + uint256 constant DELTA_NEG_X_0 = 16243966861079634958125511652590761846958471358623040426599000904006426210032; + uint256 constant DELTA_NEG_X_1 = 13406811599156507528361773763681356312643537981039994686313383243831956396116; + uint256 constant DELTA_NEG_Y_0 = 6200159192601353057572886987075813506094457284081503951532640457043392212361; + uint256 constant DELTA_NEG_Y_1 = 10106646337257131644126001022517996571132285659724751907435065628753338091209; // Constant and public input points - uint256 constant CONSTANT_X = - 16288867954761114276402155371115785036905027315050399275668719978219442996927; - uint256 constant CONSTANT_Y = - 15611120916366083495836852937429193652107718465176612711134377435436192859809; - uint256 constant PUB_0_X = - 4147432231571547026123777586694593106967411953796535970917709407254986235876; - uint256 constant PUB_0_Y = - 10326630917030366329673551361728562727322815783171171513266831996416185441581; + uint256 constant CONSTANT_X = 1964404930528116823793003656764176108669615750422202377358993070935069307720; + uint256 constant CONSTANT_Y = 2137714996673694828207437580381836490878070731768805974506391024595988817424; + uint256 constant PUB_0_X = 19568893707760843340848992184233194433177372925415116053368211122719346671126; + uint256 constant PUB_0_Y = 11639469568629189918046964192305250472192697612201524135560178632824282818614; + uint256 constant PUB_1_X = 5317268879687484957437879782519918549127939892210247573193613900261494313825; + uint256 constant PUB_1_Y = 528174394975085006443543773707702838726735933116136102590448357278717993744; + uint256 constant PUB_2_X = 14865918005176722116473730206622066845866539143554731094374354951675249722731; + uint256 constant PUB_2_Y = 3197770568483953664363740385883457803041685902965668289308665954510373380344; + uint256 constant PUB_3_X = 6863358721495494421022713667808247652425178970453300712435830652679038918987; + uint256 constant PUB_3_Y = 15025816433373311798308762709072064417001390853103872064614174594927359131281; /// Negation in Fp. /// @notice Returns a number x such that a + x = 0 in Fp. @@ -136,7 +121,7 @@ contract Verifier is ITreeVerifier { // Exponentiation failed. // Should not happen. revert ProofInvalid(); - } + } } /// Invertsion in Fp. @@ -192,11 +177,7 @@ contract Verifier is ITreeVerifier { /// @param hint A hint which of two possible signs to pick in the equation. /// @return x0 The real part of the square root. /// @return x1 The imaginary part of the square root. - function sqrt_Fp2(uint256 a0, uint256 a1, bool hint) - internal - view - returns (uint256 x0, uint256 x1) - { + function sqrt_Fp2(uint256 a0, uint256 a1, bool hint) internal view returns (uint256 x0, uint256 x1) { // If this square root reverts there is no solution in Fp2. uint256 d = sqrt_Fp(addmod(mulmod(a0, a0, P), mulmod(a1, a1, P), P)); if (hint) { @@ -208,10 +189,8 @@ contract Verifier is ITreeVerifier { // Check result to make sure we found a root. // Note: this also fails if a0 or a1 is not reduced. - if ( - a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P) - || a1 != mulmod(2, mulmod(x0, x1, P), P) - ) { + if (a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P) + || a1 != mulmod(2, mulmod(x0, x1, P), P)) { revert ProofInvalid(); } } @@ -232,7 +211,7 @@ contract Verifier is ITreeVerifier { // Point at infinity return 0; } - + // Note: sqrt_Fp reverts if there is no solution, i.e. the x coordinate is invalid. uint256 y_pos = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P)); if (y == y_pos) { @@ -278,7 +257,7 @@ contract Verifier is ITreeVerifier { /// @notice Reverts with InvalidProof if the coefficients are not reduced /// or if the point is not on the curve. /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) - /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). /// @param x0 The real part of the X coordinate. /// @param x1 The imaginary poart of the X coordinate. @@ -287,10 +266,7 @@ contract Verifier is ITreeVerifier { /// @return c0 The first half of the compresed point (x0 with two signal bits). /// @return c1 The second half of the compressed point (x1 unmodified). function compress_g2(uint256 x0, uint256 x1, uint256 y0, uint256 y1) - internal - view - returns (uint256 c0, uint256 c1) - { + internal view returns (uint256 c0, uint256 c1) { if (x0 >= P || x1 >= P || y0 >= P || y1 >= P) { // G2 point not in field. revert ProofInvalid(); @@ -305,11 +281,11 @@ contract Verifier is ITreeVerifier { uint256 y0_pos; uint256 y1_pos; { - uint256 n3ab = mulmod(mulmod(x0, x1, P), P - 3, P); + uint256 n3ab = mulmod(mulmod(x0, x1, P), P-3, P); uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); y0_pos = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); - y1_pos = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); + y1_pos = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); } // Determine hint bit @@ -323,10 +299,10 @@ contract Verifier is ITreeVerifier { // Recover y (y0_pos, y1_pos) = sqrt_Fp2(y0_pos, y1_pos, hint); if (y0 == y0_pos && y1 == y1_pos) { - c0 = (x0 << 2) | (hint ? 2 : 0) | 0; + c0 = (x0 << 2) | (hint ? 2 : 0) | 0; c1 = x1; } else if (y0 == negate(y0_pos) && y1 == negate(y1_pos)) { - c0 = (x0 << 2) | (hint ? 2 : 0) | 1; + c0 = (x0 << 2) | (hint ? 2 : 0) | 1; c1 = x1; } else { // G1 point not on curve. @@ -337,7 +313,7 @@ contract Verifier is ITreeVerifier { /// Decompress a G2 point. /// @notice Reverts with InvalidProof if the input does not represent a valid point. /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) - /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). /// @param c0 The first half of the compresed point (x0 with two signal bits). /// @param c1 The second half of the compressed point (x1 unmodified). @@ -346,10 +322,7 @@ contract Verifier is ITreeVerifier { /// @return y0 The real part of the Y coordinate. /// @return y1 The imaginary part of the Y coordinate. function decompress_g2(uint256 c0, uint256 c1) - internal - view - returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1) - { + internal view returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1) { // Note that X = (0, 0) is not on the curve since 0³ + 3/(9 + i) is not a square. // so we can use it to represent the point at infinity. if (c0 == 0 && c1 == 0) { @@ -365,12 +338,12 @@ contract Verifier is ITreeVerifier { revert ProofInvalid(); } - uint256 n3ab = mulmod(mulmod(x0, x1, P), P - 3, P); + uint256 n3ab = mulmod(mulmod(x0, x1, P), P-3, P); uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); y0 = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); - y1 = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); + y1 = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); // Note: sqrt_Fp2 reverts if there is no solution, i.e. the point is not on the curve. // Note: (X³ + 3/(9 + i)) is irreducible in Fp2, so y can not be zero. @@ -389,11 +362,8 @@ contract Verifier is ITreeVerifier { /// @param input The public inputs. These are elements of the scalar field Fr. /// @return x The X coordinate of the resulting G1 point. /// @return y The Y coordinate of the resulting G1 point. - function publicInputMSM(uint256[1] calldata input) - internal - view - returns (uint256 x, uint256 y) - { + function publicInputMSM(uint256[4] calldata input) + internal view returns (uint256 x, uint256 y) { // Note: The ECMUL precompile does not reject unreduced values, so we check this. // Note: Unrolling this loop does not cost much extra in code-size, the bulk of the // code-size is in the PUB_ constants. @@ -410,7 +380,28 @@ contract Verifier is ITreeVerifier { mstore(add(f, 0x20), CONSTANT_Y) mstore(g, PUB_0_X) mstore(add(g, 0x20), PUB_0_Y) - s := calldataload(input) + s := calldataload(input) + mstore(add(g, 0x40), s) + success := and(success, lt(s, R)) + success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) + success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) + mstore(g, PUB_1_X) + mstore(add(g, 0x20), PUB_1_Y) + s := calldataload(add(input, 32)) + mstore(add(g, 0x40), s) + success := and(success, lt(s, R)) + success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) + success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) + mstore(g, PUB_2_X) + mstore(add(g, 0x20), PUB_2_Y) + s := calldataload(add(input, 64)) + mstore(add(g, 0x40), s) + success := and(success, lt(s, R)) + success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) + success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) + mstore(g, PUB_3_X) + mstore(add(g, 0x20), PUB_3_Y) + s := calldataload(add(input, 96)) mstore(add(g, 0x40), s) success := and(success, lt(s, R)) success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) @@ -433,10 +424,7 @@ contract Verifier is ITreeVerifier { /// @return compressed The compressed proof. Elements are in the same order as for /// verifyCompressedProof. I.e. points (A, B, C) in compressed format. function compressProof(uint256[8] calldata proof) - public - view - returns (uint256[4] memory compressed) - { + public view returns (uint256[4] memory compressed) { compressed[0] = compress_g1(proof[0], proof[1]); (compressed[2], compressed[1]) = compress_g2(proof[3], proof[2], proof[5], proof[4]); compressed[3] = compress_g1(proof[6], proof[7]); @@ -451,13 +439,13 @@ contract Verifier is ITreeVerifier { /// matching the output of compressProof. /// @param input the public input field elements in the scalar field Fr. /// Elements must be reduced. - function verifyCompressedProof(uint256[4] calldata compressedProof, uint256[1] calldata input) - public - view - { + function verifyCompressedProof( + uint256[4] calldata compressedProof, + uint256[4] calldata input + ) public view { (uint256 Ax, uint256 Ay) = decompress_g1(compressedProof[0]); - (uint256 Bx0, uint256 Bx1, uint256 By0, uint256 By1) = - decompress_g2(compressedProof[2], compressedProof[1]); + (uint256 Bx0, uint256 Bx1, uint256 By0, uint256 By1) = decompress_g2( + compressedProof[2], compressedProof[1]); (uint256 Cx, uint256 Cy) = decompress_g1(compressedProof[3]); (uint256 Lx, uint256 Ly) = publicInputMSM(input); @@ -466,17 +454,17 @@ contract Verifier is ITreeVerifier { // Note: The pairing precompile rejects unreduced values, so we won't check that here. uint256[24] memory pairings; // e(A, B) - pairings[0] = Ax; - pairings[1] = Ay; - pairings[2] = Bx1; - pairings[3] = Bx0; - pairings[4] = By1; - pairings[5] = By0; + pairings[ 0] = Ax; + pairings[ 1] = Ay; + pairings[ 2] = Bx1; + pairings[ 3] = Bx0; + pairings[ 4] = By1; + pairings[ 5] = By0; // e(C, -δ) - pairings[6] = Cx; - pairings[7] = Cy; - pairings[8] = DELTA_NEG_X_1; - pairings[9] = DELTA_NEG_X_0; + pairings[ 6] = Cx; + pairings[ 7] = Cy; + pairings[ 8] = DELTA_NEG_X_1; + pairings[ 9] = DELTA_NEG_X_0; pairings[10] = DELTA_NEG_Y_1; pairings[11] = DELTA_NEG_Y_0; // e(α, -β) @@ -516,12 +504,15 @@ contract Verifier is ITreeVerifier { /// of compressProof. /// @param input the public input field elements in the scalar field Fr. /// Elements must be reduced. - function verifyProof(uint256[8] calldata proof, uint256[1] calldata input) public view { + function verifyProof( + uint256[8] calldata proof, + uint256[4] calldata input + ) public view { (uint256 x, uint256 y) = publicInputMSM(input); // Note: The precompile expects the F2 coefficients in big-endian order. // Note: The pairing precompile rejects unreduced values, so we won't check that here. - + bool success; assembly ("memory-safe") { let f := mload(0x40) // Free memory pointer. diff --git a/src/test/data/semaphore_16.json b/src/test/data/semaphore_16.json new file mode 100644 index 0000000..e69de29 diff --git a/src/test/data/semaphore_30.json b/src/test/data/semaphore_30.json new file mode 100644 index 0000000..b4b61a5 --- /dev/null +++ b/src/test/data/semaphore_30.json @@ -0,0 +1,109 @@ +{ + "protocol": "groth16", + "curve": "bn128", + "nPublic": 4, + "vk_alpha_1": [ + "20491192805390485299153009773594534940189261866228447918068658471970481763042", + "9383485363053290200918347156157836566562967994039712273449902621266178545958", + "1" + ], + "vk_beta_2": [ + [ + "6375614351688725206403948262868962793625744043794305715222011528459656738731", + "4252822878758300859123897981450591353533073413197771768651442665752259397132" + ], + [ + "10505242626370262277552901082094356697409835680220590971873171140371331206856", + "21847035105528745403288232691147584728191162732299865338377159692350059136679" + ], + [ + "1", + "0" + ] + ], + "vk_gamma_2": [ + [ + "10857046999023057135944570762232829481370756359578518086990519993285655852781", + "11559732032986387107991004021392285783925812861821192530917403151452391805634" + ], + [ + "8495653923123431417604973247489272438418190587263600148770280649306958101930", + "4082367875863433681332203403145435568316851327593401208105741076214120093531" + ], + [ + "1", + "0" + ] + ], + "vk_delta_2": [ + [ + "15028154694713144242204861571552635520290993855826554325002991692907421516918", + "10202326166286888893675634318107715186834588694714750762952081034135561546271" + ], + [ + "12766289885372833812620582632847872978085960777075662988932200910695848591357", + "18486039841380105976272577521609866666900576498507352937328726490052296469859" + ], + [ + "1", + "0" + ] + ], + "vk_alphabeta_12": [ + [ + [ + "2029413683389138792403550203267699914886160938906632433982220835551125967885", + "21072700047562757817161031222997517981543347628379360635925549008442030252106" + ], + [ + "5940354580057074848093997050200682056184807770593307860589430076672439820312", + "12156638873931618554171829126792193045421052652279363021382169897324752428276" + ], + [ + "7898200236362823042373859371574133993780991612861777490112507062703164551277", + "7074218545237549455313236346927434013100842096812539264420499035217050630853" + ] + ], + [ + [ + "7077479683546002997211712695946002074877511277312570035766170199895071832130", + "10093483419865920389913245021038182291233451549023025229112148274109565435465" + ], + [ + "4595479056700221319381530156280926371456704509942304414423590385166031118820", + "19831328484489333784475432780421641293929726139240675179672856274388269393268" + ], + [ + "11934129596455521040620786944827826205713621633706285934057045369193958244500", + "8037395052364110730298837004334506829870972346962140206007064471173334027475" + ] + ] + ], + "IC": [ + [ + "1452272927738590248356371174422184656932731110936062990115610832462181634644", + "3608050114233210789542189629343107890943266759827387991788718454179833288695", + "1" + ], + [ + "14798240452388909327945424685903532333765637883272751382037716636327236955001", + "10773894897711848209682368488916121016695006898681985691467605219098835500201", + "1" + ], + [ + "17204267933132009093604099819536245144503489322639121825381131096467570698650", + "7704298975420304156332734115679983371345754866278811368869074990486717531131", + "1" + ], + [ + "8060465662017324080560848316478407038163145149983639907596180500095598669247", + "20475082166427284188002500222093571716651248980245637602667562336751029856573", + "1" + ], + [ + "7457566682692308112726332096733260585025339741083447785327706250123165087868", + "11904519443874922292602150685069370036383697877657723976244907400392778002614", + "1" + ] + ] +} \ No newline at end of file diff --git a/src/test/identity-manager/WorldIDIdentityManagerIdentityDeletion.t.sol b/src/test/identity-manager/WorldIDIdentityManagerIdentityDeletion.t.sol index 5aec167..62fc33c 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerIdentityDeletion.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerIdentityDeletion.t.sol @@ -6,7 +6,7 @@ import {WorldIDIdentityManagerTest} from "./WorldIDIdentityManagerTest.sol"; import {ITreeVerifier} from "../../interfaces/ITreeVerifier.sol"; import {SimpleVerifier, SimpleVerify} from "../mock/SimpleVerifier.sol"; import {TypeConverter as TC} from "../utils/TypeConverter.sol"; -import {Verifier as TreeVerifier} from "../mock/DeletionTreeVerifier.sol"; +import {Verifier as TreeVerifier} from "src/DeletionTreeVerifier.sol"; import {VerifierLookupTable} from "../../data/VerifierLookupTable.sol"; import {WorldIDIdentityManager as IdentityManager} from "../../WorldIDIdentityManager.sol"; diff --git a/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol b/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol index b100e7e..fc4f508 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol @@ -8,7 +8,7 @@ import {WorldIDIdentityManagerTest} from "./WorldIDIdentityManagerTest.sol"; import {ITreeVerifier} from "../../interfaces/ITreeVerifier.sol"; import {SimpleVerifier, SimpleVerify} from "../mock/SimpleVerifier.sol"; import {TypeConverter as TC} from "../utils/TypeConverter.sol"; -import {Verifier as TreeVerifier} from "src/test/mock/InsertionTreeVerifier.sol"; +import {Verifier as TreeVerifier} from "src/InsertionTreeVerifier.sol"; import {VerifierLookupTable} from "../../data/VerifierLookupTable.sol"; import {WorldIDIdentityManager as IdentityManager} from "../../WorldIDIdentityManager.sol"; diff --git a/src/test/identity-manager/WorldIDIdentityManagerOwnershipManagement.t.sol b/src/test/identity-manager/WorldIDIdentityManagerOwnershipManagement.t.sol index 04e1557..efda6e0 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerOwnershipManagement.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerOwnershipManagement.t.sol @@ -11,7 +11,7 @@ import {Ownable2StepUpgradeable} from "contracts-upgradeable/access/Ownable2Step import {SemaphoreVerifier} from "semaphore/base/SemaphoreVerifier.sol"; import {SimpleVerifier, SimpleVerify} from "../mock/SimpleVerifier.sol"; import {UUPSUpgradeable} from "contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import {Verifier as TreeVerifier} from "../mock/InsertionTreeVerifier.sol"; +import {Verifier as TreeVerifier} from "src/InsertionTreeVerifier.sol"; import {WorldIDIdentityManagerImplMock} from "../mock/WorldIDIdentityManagerImplMock.sol"; import {WorldIDImpl} from "../../abstract/WorldIDImpl.sol"; diff --git a/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol b/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol index 5989a46..6f9c8cf 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol @@ -6,7 +6,7 @@ import {WorldIDIdentityManagerTest} from "./WorldIDIdentityManagerTest.sol"; import {ISemaphoreVerifier} from "semaphore/interfaces/ISemaphoreVerifier.sol"; import {SemaphoreTreeDepthValidator} from "../../utils/SemaphoreTreeDepthValidator.sol"; import {SimpleSemaphoreVerifier} from "../mock/SimpleSemaphoreVerifier.sol"; -import {Verifier} from "src/Verifier.sol"; +import {Verifier} from "src/test/SemaphoreVerifier16.sol"; import {WorldIDIdentityManager as IdentityManager} from "../../WorldIDIdentityManager.sol"; import {WorldIDIdentityManagerImplV2 as ManagerImpl} from "../../WorldIDIdentityManagerImplV2.sol"; import {WorldIDIdentityManagerImplV1 as ManagerImplV1} from "../../WorldIDIdentityManagerImplV1.sol"; @@ -16,7 +16,7 @@ import {WorldIDIdentityManagerImplV1 as ManagerImplV1} from "../../WorldIDIdenti /// @author Worldcoin /// @dev This test suite tests both the proxy and the functionality of the underlying implementation /// so as to test everything in the context of how it will be deployed. -contract WorldIDIdentityManagerSemaphoreValidation is WorldIDIdentityManagerTest { +contract WorldIDIdentityManagerSemaphoreVerification is WorldIDIdentityManagerTest { /// @notice Checks that the proof validates properly with the correct inputs. function testProofVerificationWithCorrectInputs( uint8 actualTreeDepth, diff --git a/src/test/identity-manager/WorldIDIdentityManagerUninit.t.sol b/src/test/identity-manager/WorldIDIdentityManagerUninit.t.sol index 84c5792..0fe9486 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerUninit.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerUninit.t.sol @@ -8,7 +8,7 @@ import {ITreeVerifier} from "../../interfaces/ITreeVerifier.sol"; import {CheckInitialized} from "../../utils/CheckInitialized.sol"; import {SemaphoreVerifier} from "semaphore/base/SemaphoreVerifier.sol"; import {TypeConverter as TC} from "../utils/TypeConverter.sol"; -import {Verifier as TreeVerifier} from "../mock/InsertionTreeVerifier.sol"; +import {Verifier as TreeVerifier} from "src/InsertionTreeVerifier.sol"; import {VerifierLookupTable} from "../../data/VerifierLookupTable.sol"; import {WorldIDIdentityManagerImplV2 as ManagerImpl} from "../../WorldIDIdentityManagerImplV2.sol"; From f8690147c2d32419a2030f1fb4ca8ae850b839e9 Mon Sep 17 00:00:00 2001 From: "dcbuilder.eth" Date: Wed, 27 Sep 2023 13:23:39 +0200 Subject: [PATCH 15/20] fix interface errors proof invalid error --- src/DeletionTreeVerifier.sol | 160 ++++++++------ src/InsertionTreeVerifier.sol | 160 ++++++++------ src/SemaphoreVerifier.sol | 167 +++++---------- src/WorldIDIdentityManagerImplV1.sol | 6 +- src/interfaces/ISemaphoreVerifier.sol | 2 +- src/test/SemaphoreVerifier16.sol | 186 ++++++++++------- src/test/data/DeletionProof.json | 2 +- src/test/data/InclusionProof.json | 2 +- src/test/data/InsertionProof.json | 2 +- src/test/data/semaphore_30.json | 195 +++++++++--------- .../WorldIDIdentityManagerCalculation.t.sol | 1 - ...WorldIDIdentityManagerGettersSetters.t.sol | 2 +- ...IdentityManagerSemaphoreVerification.t.sol | 11 +- .../WorldIDIdentityManagerTest.sol | 12 +- .../WorldIDIdentityManagerUninit.t.sol | 2 +- src/test/mock/SimpleSemaphoreVerifier.sol | 17 +- src/test/mock/SimpleVerifier.sol | 1 + 17 files changed, 483 insertions(+), 445 deletions(-) diff --git a/src/DeletionTreeVerifier.sol b/src/DeletionTreeVerifier.sol index 6c1673e..6973702 100644 --- a/src/DeletionTreeVerifier.sol +++ b/src/DeletionTreeVerifier.sol @@ -11,7 +11,6 @@ pragma solidity ^0.8.0; /// to compress proofs. /// @notice See for further explanation. contract Verifier is ITreeVerifier { - /// Some of the provided public input values are larger than the field modulus. /// @dev Public input elements are not automatically reduced, as this is can be /// a dangerous source of bugs. @@ -45,41 +44,63 @@ contract Verifier is ITreeVerifier { // Fp2 elements are encoded in the public interface as this became convention. // Constants in Fp - uint256 constant FRACTION_1_2_FP = 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4; - uint256 constant FRACTION_27_82_FP = 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; - uint256 constant FRACTION_3_82_FP = 0x2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e775; + uint256 constant FRACTION_1_2_FP = + 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4; + uint256 constant FRACTION_27_82_FP = + 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; + uint256 constant FRACTION_3_82_FP = + 0x2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e775; // Exponents for inversions and square roots mod P - uint256 constant EXP_INVERSE_FP = 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45; // P - 2 + uint256 constant EXP_INVERSE_FP = + 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45; // P - 2 uint256 constant EXP_SQRT_FP = 0xC19139CB84C680A6E14116DA060561765E05AA45A1C72A34F082305B61F3F52; // (P + 1) / 4; // Groth16 alpha point in G1 - uint256 constant ALPHA_X = 10540806559887167104588299892241621164255487150461496341626858664281664558273; - uint256 constant ALPHA_Y = 18696534755308846201677397222792953217528992421163095056941057317509629524722; + uint256 constant ALPHA_X = + 10540806559887167104588299892241621164255487150461496341626858664281664558273; + uint256 constant ALPHA_Y = + 18696534755308846201677397222792953217528992421163095056941057317509629524722; // Groth16 beta point in G2 in powers of i - uint256 constant BETA_NEG_X_0 = 9289144700177602508224796928305680819301212642031268209898370879595438760917; - uint256 constant BETA_NEG_X_1 = 10547839143610067056656195135986672966679578259812908443118403685682429861701; - uint256 constant BETA_NEG_Y_0 = 12915174887173266773990929270621008261886710141330627051375005843032324032816; - uint256 constant BETA_NEG_Y_1 = 20204556387398089691578608866734867938413428713768226934160981536705045926684; + uint256 constant BETA_NEG_X_0 = + 9289144700177602508224796928305680819301212642031268209898370879595438760917; + uint256 constant BETA_NEG_X_1 = + 10547839143610067056656195135986672966679578259812908443118403685682429861701; + uint256 constant BETA_NEG_Y_0 = + 12915174887173266773990929270621008261886710141330627051375005843032324032816; + uint256 constant BETA_NEG_Y_1 = + 20204556387398089691578608866734867938413428713768226934160981536705045926684; // Groth16 gamma point in G2 in powers of i - uint256 constant GAMMA_NEG_X_0 = 12125917802287751299598970306127232317234244065863085706149700341692909445135; - uint256 constant GAMMA_NEG_X_1 = 12148123627061259167145600433069418819234101862686655936951485568840483717123; - uint256 constant GAMMA_NEG_Y_0 = 5988769189388486153600786474675906426283316229539620228678444567681682777498; - uint256 constant GAMMA_NEG_Y_1 = 5738750612292361316155676119434475570600542711558566821477860588397762422487; + uint256 constant GAMMA_NEG_X_0 = + 12125917802287751299598970306127232317234244065863085706149700341692909445135; + uint256 constant GAMMA_NEG_X_1 = + 12148123627061259167145600433069418819234101862686655936951485568840483717123; + uint256 constant GAMMA_NEG_Y_0 = + 5988769189388486153600786474675906426283316229539620228678444567681682777498; + uint256 constant GAMMA_NEG_Y_1 = + 5738750612292361316155676119434475570600542711558566821477860588397762422487; // Groth16 delta point in G2 in powers of i - uint256 constant DELTA_NEG_X_0 = 1634881525453069381539064669198838164371392344721377194711591834696682352477; - uint256 constant DELTA_NEG_X_1 = 17279327023445589453594845467776134123782766121800267509419424589125110740187; - uint256 constant DELTA_NEG_Y_0 = 11432830246305367590277540894794860588641953149336980701683372253417473334750; - uint256 constant DELTA_NEG_Y_1 = 4004758155600074163048436555321639796581387612468627300293241737049808612532; + uint256 constant DELTA_NEG_X_0 = + 1634881525453069381539064669198838164371392344721377194711591834696682352477; + uint256 constant DELTA_NEG_X_1 = + 17279327023445589453594845467776134123782766121800267509419424589125110740187; + uint256 constant DELTA_NEG_Y_0 = + 11432830246305367590277540894794860588641953149336980701683372253417473334750; + uint256 constant DELTA_NEG_Y_1 = + 4004758155600074163048436555321639796581387612468627300293241737049808612532; // Constant and public input points - uint256 constant CONSTANT_X = 7065344469851661926793643372215217621315658305837861579101548147761825653088; - uint256 constant CONSTANT_Y = 3816842911808951106840635623036133479452547290738636365883439290420360457712; - uint256 constant PUB_0_X = 3600420742132324519200509028486624821285996242068377631973021819080430036637; - uint256 constant PUB_0_Y = 372418442062690161950901896514875127229125198024957013636072554100336848194; + uint256 constant CONSTANT_X = + 7065344469851661926793643372215217621315658305837861579101548147761825653088; + uint256 constant CONSTANT_Y = + 3816842911808951106840635623036133479452547290738636365883439290420360457712; + uint256 constant PUB_0_X = + 3600420742132324519200509028486624821285996242068377631973021819080430036637; + uint256 constant PUB_0_Y = + 372418442062690161950901896514875127229125198024957013636072554100336848194; /// Negation in Fp. /// @notice Returns a number x such that a + x = 0 in Fp. @@ -115,7 +136,7 @@ contract Verifier is ITreeVerifier { // Exponentiation failed. // Should not happen. revert ProofInvalid(); - } + } } /// Inversion in Fp. @@ -171,7 +192,11 @@ contract Verifier is ITreeVerifier { /// @param hint A hint which of two possible signs to pick in the equation. /// @return x0 The real part of the square root. /// @return x1 The imaginary part of the square root. - function sqrt_Fp2(uint256 a0, uint256 a1, bool hint) internal view returns (uint256 x0, uint256 x1) { + function sqrt_Fp2(uint256 a0, uint256 a1, bool hint) + internal + view + returns (uint256 x0, uint256 x1) + { // If this square root reverts there is no solution in Fp2. uint256 d = sqrt_Fp(addmod(mulmod(a0, a0, P), mulmod(a1, a1, P), P)); if (hint) { @@ -183,8 +208,10 @@ contract Verifier is ITreeVerifier { // Check result to make sure we found a root. // Note: this also fails if a0 or a1 is not reduced. - if (a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P) - || a1 != mulmod(2, mulmod(x0, x1, P), P)) { + if ( + a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P) + || a1 != mulmod(2, mulmod(x0, x1, P), P) + ) { revert ProofInvalid(); } } @@ -205,7 +232,7 @@ contract Verifier is ITreeVerifier { // Point at infinity return 0; } - + // Note: sqrt_Fp reverts if there is no solution, i.e. the x coordinate is invalid. uint256 y_pos = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P)); if (y == y_pos) { @@ -251,7 +278,7 @@ contract Verifier is ITreeVerifier { /// @notice Reverts with InvalidProof if the coefficients are not reduced /// or if the point is not on the curve. /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) - /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). /// @param x0 The real part of the X coordinate. /// @param x1 The imaginary poart of the X coordinate. @@ -260,7 +287,10 @@ contract Verifier is ITreeVerifier { /// @return c0 The first half of the compresed point (x0 with two signal bits). /// @return c1 The second half of the compressed point (x1 unmodified). function compress_g2(uint256 x0, uint256 x1, uint256 y0, uint256 y1) - internal view returns (uint256 c0, uint256 c1) { + internal + view + returns (uint256 c0, uint256 c1) + { if (x0 >= P || x1 >= P || y0 >= P || y1 >= P) { // G2 point not in field. revert ProofInvalid(); @@ -275,11 +305,11 @@ contract Verifier is ITreeVerifier { uint256 y0_pos; uint256 y1_pos; { - uint256 n3ab = mulmod(mulmod(x0, x1, P), P-3, P); + uint256 n3ab = mulmod(mulmod(x0, x1, P), P - 3, P); uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); y0_pos = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); - y1_pos = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); + y1_pos = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); } // Determine hint bit @@ -293,10 +323,10 @@ contract Verifier is ITreeVerifier { // Recover y (y0_pos, y1_pos) = sqrt_Fp2(y0_pos, y1_pos, hint); if (y0 == y0_pos && y1 == y1_pos) { - c0 = (x0 << 2) | (hint ? 2 : 0) | 0; + c0 = (x0 << 2) | (hint ? 2 : 0) | 0; c1 = x1; } else if (y0 == negate(y0_pos) && y1 == negate(y1_pos)) { - c0 = (x0 << 2) | (hint ? 2 : 0) | 1; + c0 = (x0 << 2) | (hint ? 2 : 0) | 1; c1 = x1; } else { // G1 point not on curve. @@ -307,7 +337,7 @@ contract Verifier is ITreeVerifier { /// Decompress a G2 point. /// @notice Reverts with InvalidProof if the input does not represent a valid point. /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) - /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). /// @param c0 The first half of the compresed point (x0 with two signal bits). /// @param c1 The second half of the compressed point (x1 unmodified). @@ -316,7 +346,10 @@ contract Verifier is ITreeVerifier { /// @return y0 The real part of the Y coordinate. /// @return y1 The imaginary part of the Y coordinate. function decompress_g2(uint256 c0, uint256 c1) - internal view returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1) { + internal + view + returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1) + { // Note that X = (0, 0) is not on the curve since 0³ + 3/(9 + i) is not a square. // so we can use it to represent the point at infinity. if (c0 == 0 && c1 == 0) { @@ -332,12 +365,12 @@ contract Verifier is ITreeVerifier { revert ProofInvalid(); } - uint256 n3ab = mulmod(mulmod(x0, x1, P), P-3, P); + uint256 n3ab = mulmod(mulmod(x0, x1, P), P - 3, P); uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); y0 = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); - y1 = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); + y1 = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); // Note: sqrt_Fp2 reverts if there is no solution, i.e. the point is not on the curve. // Note: (X³ + 3/(9 + i)) is irreducible in Fp2, so y can not be zero. @@ -357,7 +390,10 @@ contract Verifier is ITreeVerifier { /// @return x The X coordinate of the resulting G1 point. /// @return y The Y coordinate of the resulting G1 point. function publicInputMSM(uint256[1] calldata input) - internal view returns (uint256 x, uint256 y) { + internal + view + returns (uint256 x, uint256 y) + { // Note: The ECMUL precompile does not reject unreduced values, so we check this. // Note: Unrolling this loop does not cost much extra in code-size, the bulk of the // code-size is in the PUB_ constants. @@ -374,7 +410,7 @@ contract Verifier is ITreeVerifier { mstore(add(f, 0x20), CONSTANT_Y) mstore(g, PUB_0_X) mstore(add(g, 0x20), PUB_0_Y) - s := calldataload(input) + s := calldataload(input) mstore(add(g, 0x40), s) success := and(success, lt(s, R)) success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) @@ -397,7 +433,10 @@ contract Verifier is ITreeVerifier { /// @return compressed The compressed proof. Elements are in the same order as for /// verifyCompressedProof. I.e. points (A, B, C) in compressed format. function compressProof(uint256[8] calldata proof) - public view returns (uint256[4] memory compressed) { + public + view + returns (uint256[4] memory compressed) + { compressed[0] = compress_g1(proof[0], proof[1]); (compressed[2], compressed[1]) = compress_g2(proof[3], proof[2], proof[5], proof[4]); compressed[3] = compress_g1(proof[6], proof[7]); @@ -412,13 +451,13 @@ contract Verifier is ITreeVerifier { /// matching the output of compressProof. /// @param input the public input field elements in the scalar field Fr. /// Elements must be reduced. - function verifyCompressedProof( - uint256[4] calldata compressedProof, - uint256[1] calldata input - ) public view { + function verifyCompressedProof(uint256[4] calldata compressedProof, uint256[1] calldata input) + public + view + { (uint256 Ax, uint256 Ay) = decompress_g1(compressedProof[0]); - (uint256 Bx0, uint256 Bx1, uint256 By0, uint256 By1) = decompress_g2( - compressedProof[2], compressedProof[1]); + (uint256 Bx0, uint256 Bx1, uint256 By0, uint256 By1) = + decompress_g2(compressedProof[2], compressedProof[1]); (uint256 Cx, uint256 Cy) = decompress_g1(compressedProof[3]); (uint256 Lx, uint256 Ly) = publicInputMSM(input); @@ -427,17 +466,17 @@ contract Verifier is ITreeVerifier { // Note: The pairing precompile rejects unreduced values, so we won't check that here. uint256[24] memory pairings; // e(A, B) - pairings[ 0] = Ax; - pairings[ 1] = Ay; - pairings[ 2] = Bx1; - pairings[ 3] = Bx0; - pairings[ 4] = By1; - pairings[ 5] = By0; + pairings[0] = Ax; + pairings[1] = Ay; + pairings[2] = Bx1; + pairings[3] = Bx0; + pairings[4] = By1; + pairings[5] = By0; // e(C, -δ) - pairings[ 6] = Cx; - pairings[ 7] = Cy; - pairings[ 8] = DELTA_NEG_X_1; - pairings[ 9] = DELTA_NEG_X_0; + pairings[6] = Cx; + pairings[7] = Cy; + pairings[8] = DELTA_NEG_X_1; + pairings[9] = DELTA_NEG_X_0; pairings[10] = DELTA_NEG_Y_1; pairings[11] = DELTA_NEG_Y_0; // e(α, -β) @@ -477,15 +516,12 @@ contract Verifier is ITreeVerifier { /// of compressProof. /// @param input the public input field elements in the scalar field Fr. /// Elements must be reduced. - function verifyProof( - uint256[8] calldata proof, - uint256[1] calldata input - ) public view { + function verifyProof(uint256[8] calldata proof, uint256[1] calldata input) public view { (uint256 x, uint256 y) = publicInputMSM(input); // Note: The precompile expects the F2 coefficients in big-endian order. // Note: The pairing precompile rejects unreduced values, so we won't check that here. - + bool success; assembly ("memory-safe") { let f := mload(0x40) // Free memory pointer. diff --git a/src/InsertionTreeVerifier.sol b/src/InsertionTreeVerifier.sol index 417b874..adc76f5 100644 --- a/src/InsertionTreeVerifier.sol +++ b/src/InsertionTreeVerifier.sol @@ -11,7 +11,6 @@ pragma solidity ^0.8.0; /// to compress proofs. /// @notice See for further explanation. contract Verifier is ITreeVerifier { - /// Some of the provided public input values are larger than the field modulus. /// @dev Public input elements are not automatically reduced, as this is can be /// a dangerous source of bugs. @@ -45,41 +44,63 @@ contract Verifier is ITreeVerifier { // Fp2 elements are encoded in the public interface as this became convention. // Constants in Fp - uint256 constant FRACTION_1_2_FP = 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4; - uint256 constant FRACTION_27_82_FP = 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; - uint256 constant FRACTION_3_82_FP = 0x2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e775; + uint256 constant FRACTION_1_2_FP = + 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4; + uint256 constant FRACTION_27_82_FP = + 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; + uint256 constant FRACTION_3_82_FP = + 0x2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e775; // Exponents for inversions and square roots mod P - uint256 constant EXP_INVERSE_FP = 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45; // P - 2 + uint256 constant EXP_INVERSE_FP = + 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45; // P - 2 uint256 constant EXP_SQRT_FP = 0xC19139CB84C680A6E14116DA060561765E05AA45A1C72A34F082305B61F3F52; // (P + 1) / 4; // Groth16 alpha point in G1 - uint256 constant ALPHA_X = 15976828965750791468988686334447811489136097632219377308651062054699114822216; - uint256 constant ALPHA_Y = 17571185980075114535712506428928541014285966203133597604199833733334669062947; + uint256 constant ALPHA_X = + 15976828965750791468988686334447811489136097632219377308651062054699114822216; + uint256 constant ALPHA_Y = + 17571185980075114535712506428928541014285966203133597604199833733334669062947; // Groth16 beta point in G2 in powers of i - uint256 constant BETA_NEG_X_0 = 3774880652569784103101780290655946736778806829747412095447973768235447935627; - uint256 constant BETA_NEG_X_1 = 16140359218528844177661226532636814578832873241049546877451825018737878894415; - uint256 constant BETA_NEG_Y_0 = 4640315682033452018918186416519285362905365110971338138159134713745786498132; - uint256 constant BETA_NEG_Y_1 = 3591449710587988205963826035685830100614411910678456443129126525230663369361; + uint256 constant BETA_NEG_X_0 = + 3774880652569784103101780290655946736778806829747412095447973768235447935627; + uint256 constant BETA_NEG_X_1 = + 16140359218528844177661226532636814578832873241049546877451825018737878894415; + uint256 constant BETA_NEG_Y_0 = + 4640315682033452018918186416519285362905365110971338138159134713745786498132; + uint256 constant BETA_NEG_Y_1 = + 3591449710587988205963826035685830100614411910678456443129126525230663369361; // Groth16 gamma point in G2 in powers of i - uint256 constant GAMMA_NEG_X_0 = 17637628216749009270364930684841675593008642019322849310939298209033912045480; - uint256 constant GAMMA_NEG_X_1 = 16778406356398287643368954352598191898790242877372845103320576982242500933852; - uint256 constant GAMMA_NEG_Y_0 = 8224931393918135298340454914349355511588649579654293187876560463421379935080; - uint256 constant GAMMA_NEG_Y_1 = 8652273155883398190128238325969441551390725833029677073788838240017751601738; + uint256 constant GAMMA_NEG_X_0 = + 17637628216749009270364930684841675593008642019322849310939298209033912045480; + uint256 constant GAMMA_NEG_X_1 = + 16778406356398287643368954352598191898790242877372845103320576982242500933852; + uint256 constant GAMMA_NEG_Y_0 = + 8224931393918135298340454914349355511588649579654293187876560463421379935080; + uint256 constant GAMMA_NEG_Y_1 = + 8652273155883398190128238325969441551390725833029677073788838240017751601738; // Groth16 delta point in G2 in powers of i - uint256 constant DELTA_NEG_X_0 = 2280566876154873121729228932237318721748751798277472636568974908993913843953; - uint256 constant DELTA_NEG_X_1 = 20174276210268561659837178440442037241955176983492602351856686097389113846193; - uint256 constant DELTA_NEG_Y_0 = 2810000783863410994886532472239034393068688066928911515614533202542952344359; - uint256 constant DELTA_NEG_Y_1 = 3434233964589686342320626243495267687470464612485221367834666549409219897059; + uint256 constant DELTA_NEG_X_0 = + 2280566876154873121729228932237318721748751798277472636568974908993913843953; + uint256 constant DELTA_NEG_X_1 = + 20174276210268561659837178440442037241955176983492602351856686097389113846193; + uint256 constant DELTA_NEG_Y_0 = + 2810000783863410994886532472239034393068688066928911515614533202542952344359; + uint256 constant DELTA_NEG_Y_1 = + 3434233964589686342320626243495267687470464612485221367834666549409219897059; // Constant and public input points - uint256 constant CONSTANT_X = 19016103526843875126395145525124455195546522337268670813255409672571795425684; - uint256 constant CONSTANT_Y = 463262142987534641028551863519809496575105875074564654761628432482686134324; - uint256 constant PUB_0_X = 1379748789921737417066313098910363913169509602099243602246914882900464880825; - uint256 constant PUB_0_Y = 943304887426072701494501907600571048821602999217792043874057384381177139312; + uint256 constant CONSTANT_X = + 19016103526843875126395145525124455195546522337268670813255409672571795425684; + uint256 constant CONSTANT_Y = + 463262142987534641028551863519809496575105875074564654761628432482686134324; + uint256 constant PUB_0_X = + 1379748789921737417066313098910363913169509602099243602246914882900464880825; + uint256 constant PUB_0_Y = + 943304887426072701494501907600571048821602999217792043874057384381177139312; /// Negation in Fp. /// @notice Returns a number x such that a + x = 0 in Fp. @@ -115,7 +136,7 @@ contract Verifier is ITreeVerifier { // Exponentiation failed. // Should not happen. revert ProofInvalid(); - } + } } /// Invertsion in Fp. @@ -171,7 +192,11 @@ contract Verifier is ITreeVerifier { /// @param hint A hint which of two possible signs to pick in the equation. /// @return x0 The real part of the square root. /// @return x1 The imaginary part of the square root. - function sqrt_Fp2(uint256 a0, uint256 a1, bool hint) internal view returns (uint256 x0, uint256 x1) { + function sqrt_Fp2(uint256 a0, uint256 a1, bool hint) + internal + view + returns (uint256 x0, uint256 x1) + { // If this square root reverts there is no solution in Fp2. uint256 d = sqrt_Fp(addmod(mulmod(a0, a0, P), mulmod(a1, a1, P), P)); if (hint) { @@ -183,8 +208,10 @@ contract Verifier is ITreeVerifier { // Check result to make sure we found a root. // Note: this also fails if a0 or a1 is not reduced. - if (a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P) - || a1 != mulmod(2, mulmod(x0, x1, P), P)) { + if ( + a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P) + || a1 != mulmod(2, mulmod(x0, x1, P), P) + ) { revert ProofInvalid(); } } @@ -205,7 +232,7 @@ contract Verifier is ITreeVerifier { // Point at infinity return 0; } - + // Note: sqrt_Fp reverts if there is no solution, i.e. the x coordinate is invalid. uint256 y_pos = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P)); if (y == y_pos) { @@ -251,7 +278,7 @@ contract Verifier is ITreeVerifier { /// @notice Reverts with InvalidProof if the coefficients are not reduced /// or if the point is not on the curve. /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) - /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). /// @param x0 The real part of the X coordinate. /// @param x1 The imaginary poart of the X coordinate. @@ -260,7 +287,10 @@ contract Verifier is ITreeVerifier { /// @return c0 The first half of the compresed point (x0 with two signal bits). /// @return c1 The second half of the compressed point (x1 unmodified). function compress_g2(uint256 x0, uint256 x1, uint256 y0, uint256 y1) - internal view returns (uint256 c0, uint256 c1) { + internal + view + returns (uint256 c0, uint256 c1) + { if (x0 >= P || x1 >= P || y0 >= P || y1 >= P) { // G2 point not in field. revert ProofInvalid(); @@ -275,11 +305,11 @@ contract Verifier is ITreeVerifier { uint256 y0_pos; uint256 y1_pos; { - uint256 n3ab = mulmod(mulmod(x0, x1, P), P-3, P); + uint256 n3ab = mulmod(mulmod(x0, x1, P), P - 3, P); uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); y0_pos = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); - y1_pos = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); + y1_pos = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); } // Determine hint bit @@ -293,10 +323,10 @@ contract Verifier is ITreeVerifier { // Recover y (y0_pos, y1_pos) = sqrt_Fp2(y0_pos, y1_pos, hint); if (y0 == y0_pos && y1 == y1_pos) { - c0 = (x0 << 2) | (hint ? 2 : 0) | 0; + c0 = (x0 << 2) | (hint ? 2 : 0) | 0; c1 = x1; } else if (y0 == negate(y0_pos) && y1 == negate(y1_pos)) { - c0 = (x0 << 2) | (hint ? 2 : 0) | 1; + c0 = (x0 << 2) | (hint ? 2 : 0) | 1; c1 = x1; } else { // G1 point not on curve. @@ -307,7 +337,7 @@ contract Verifier is ITreeVerifier { /// Decompress a G2 point. /// @notice Reverts with InvalidProof if the input does not represent a valid point. /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) - /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). /// @param c0 The first half of the compresed point (x0 with two signal bits). /// @param c1 The second half of the compressed point (x1 unmodified). @@ -316,7 +346,10 @@ contract Verifier is ITreeVerifier { /// @return y0 The real part of the Y coordinate. /// @return y1 The imaginary part of the Y coordinate. function decompress_g2(uint256 c0, uint256 c1) - internal view returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1) { + internal + view + returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1) + { // Note that X = (0, 0) is not on the curve since 0³ + 3/(9 + i) is not a square. // so we can use it to represent the point at infinity. if (c0 == 0 && c1 == 0) { @@ -332,12 +365,12 @@ contract Verifier is ITreeVerifier { revert ProofInvalid(); } - uint256 n3ab = mulmod(mulmod(x0, x1, P), P-3, P); + uint256 n3ab = mulmod(mulmod(x0, x1, P), P - 3, P); uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); y0 = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); - y1 = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); + y1 = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); // Note: sqrt_Fp2 reverts if there is no solution, i.e. the point is not on the curve. // Note: (X³ + 3/(9 + i)) is irreducible in Fp2, so y can not be zero. @@ -357,7 +390,10 @@ contract Verifier is ITreeVerifier { /// @return x The X coordinate of the resulting G1 point. /// @return y The Y coordinate of the resulting G1 point. function publicInputMSM(uint256[1] calldata input) - internal view returns (uint256 x, uint256 y) { + internal + view + returns (uint256 x, uint256 y) + { // Note: The ECMUL precompile does not reject unreduced values, so we check this. // Note: Unrolling this loop does not cost much extra in code-size, the bulk of the // code-size is in the PUB_ constants. @@ -374,7 +410,7 @@ contract Verifier is ITreeVerifier { mstore(add(f, 0x20), CONSTANT_Y) mstore(g, PUB_0_X) mstore(add(g, 0x20), PUB_0_Y) - s := calldataload(input) + s := calldataload(input) mstore(add(g, 0x40), s) success := and(success, lt(s, R)) success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) @@ -397,7 +433,10 @@ contract Verifier is ITreeVerifier { /// @return compressed The compressed proof. Elements are in the same order as for /// verifyCompressedProof. I.e. points (A, B, C) in compressed format. function compressProof(uint256[8] calldata proof) - public view returns (uint256[4] memory compressed) { + public + view + returns (uint256[4] memory compressed) + { compressed[0] = compress_g1(proof[0], proof[1]); (compressed[2], compressed[1]) = compress_g2(proof[3], proof[2], proof[5], proof[4]); compressed[3] = compress_g1(proof[6], proof[7]); @@ -412,13 +451,13 @@ contract Verifier is ITreeVerifier { /// matching the output of compressProof. /// @param input the public input field elements in the scalar field Fr. /// Elements must be reduced. - function verifyCompressedProof( - uint256[4] calldata compressedProof, - uint256[1] calldata input - ) public view { + function verifyCompressedProof(uint256[4] calldata compressedProof, uint256[1] calldata input) + public + view + { (uint256 Ax, uint256 Ay) = decompress_g1(compressedProof[0]); - (uint256 Bx0, uint256 Bx1, uint256 By0, uint256 By1) = decompress_g2( - compressedProof[2], compressedProof[1]); + (uint256 Bx0, uint256 Bx1, uint256 By0, uint256 By1) = + decompress_g2(compressedProof[2], compressedProof[1]); (uint256 Cx, uint256 Cy) = decompress_g1(compressedProof[3]); (uint256 Lx, uint256 Ly) = publicInputMSM(input); @@ -427,17 +466,17 @@ contract Verifier is ITreeVerifier { // Note: The pairing precompile rejects unreduced values, so we won't check that here. uint256[24] memory pairings; // e(A, B) - pairings[ 0] = Ax; - pairings[ 1] = Ay; - pairings[ 2] = Bx1; - pairings[ 3] = Bx0; - pairings[ 4] = By1; - pairings[ 5] = By0; + pairings[0] = Ax; + pairings[1] = Ay; + pairings[2] = Bx1; + pairings[3] = Bx0; + pairings[4] = By1; + pairings[5] = By0; // e(C, -δ) - pairings[ 6] = Cx; - pairings[ 7] = Cy; - pairings[ 8] = DELTA_NEG_X_1; - pairings[ 9] = DELTA_NEG_X_0; + pairings[6] = Cx; + pairings[7] = Cy; + pairings[8] = DELTA_NEG_X_1; + pairings[9] = DELTA_NEG_X_0; pairings[10] = DELTA_NEG_Y_1; pairings[11] = DELTA_NEG_Y_0; // e(α, -β) @@ -477,15 +516,12 @@ contract Verifier is ITreeVerifier { /// of compressProof. /// @param input the public input field elements in the scalar field Fr. /// Elements must be reduced. - function verifyProof( - uint256[8] calldata proof, - uint256[1] calldata input - ) public view { + function verifyProof(uint256[8] calldata proof, uint256[1] calldata input) public view { (uint256 x, uint256 y) = publicInputMSM(input); // Note: The precompile expects the F2 coefficients in big-endian order. // Note: The pairing precompile rejects unreduced values, so we won't check that here. - + bool success; assembly ("memory-safe") { let f := mload(0x40) // Free memory pointer. diff --git a/src/SemaphoreVerifier.sol b/src/SemaphoreVerifier.sol index d800487..12d123f 100644 --- a/src/SemaphoreVerifier.sol +++ b/src/SemaphoreVerifier.sol @@ -10,7 +10,7 @@ pragma solidity ^0.8.0; /// (256 bytes) and compressed (128 bytes) format. A view function is provided /// to compress proofs. /// @notice See for further explanation. -contract Verifier is ISemaphoreVerifier{ +contract SemaphoreVerifier is ISemaphoreVerifier { /// Some of the provided public input values are larger than the field modulus. /// @dev Public input elements are not automatically reduced, as this is can be /// a dangerous source of bugs. @@ -33,10 +33,8 @@ contract Verifier is ISemaphoreVerifier{ // t = 4965661367192848881 // P = 36⋅t⁴ + 36⋅t³ + 24⋅t² + 6⋅t + 1 // R = 36⋅t⁴ + 36⋅t³ + 18⋅t² + 6⋅t + 1 - uint256 constant P = - 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; - uint256 constant R = - 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001; + uint256 constant P = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; + uint256 constant R = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001; // Extension field Fp2 = Fp[i] / (i² + 1) // Note: This is the complex extension field of Fp with i² = -1. @@ -56,8 +54,7 @@ contract Verifier is ISemaphoreVerifier{ // Exponents for inversions and square roots mod P uint256 constant EXP_INVERSE_FP = 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45; // P - 2 - uint256 constant EXP_SQRT_FP = - 0xC19139CB84C680A6E14116DA060561765E05AA45A1C72A34F082305B61F3F52; // (P + 1) / 4; + uint256 constant EXP_SQRT_FP = 0xC19139CB84C680A6E14116DA060561765E05AA45A1C72A34F082305B61F3F52; // (P + 1) / 4; // Groth16 alpha point in G1 uint256 constant ALPHA_X = @@ -207,11 +204,11 @@ contract Verifier is ISemaphoreVerifier{ /// @param hint A hint which of two possible signs to pick in the equation. /// @return x0 The real part of the square root. /// @return x1 The imaginary part of the square root. - function sqrt_Fp2( - uint256 a0, - uint256 a1, - bool hint - ) internal view returns (uint256 x0, uint256 x1) { + function sqrt_Fp2(uint256 a0, uint256 a1, bool hint) + internal + view + returns (uint256 x0, uint256 x1) + { // If this square root reverts there is no solution in Fp2. uint256 d = sqrt_Fp(addmod(mulmod(a0, a0, P), mulmod(a1, a1, P), P)); if (hint) { @@ -224,8 +221,8 @@ contract Verifier is ISemaphoreVerifier{ // Check result to make sure we found a root. // Note: this also fails if a0 or a1 is not reduced. if ( - a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P) || - a1 != mulmod(2, mulmod(x0, x1, P), P) + a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P) + || a1 != mulmod(2, mulmod(x0, x1, P), P) ) { revert ProofInvalid(); } @@ -238,10 +235,7 @@ contract Verifier is ISemaphoreVerifier{ /// @param x The X coordinate in Fp. /// @param y The Y coordinate in Fp. /// @return c The compresed point (x with one signal bit). - function compress_g1( - uint256 x, - uint256 y - ) internal view returns (uint256 c) { + function compress_g1(uint256 x, uint256 y) internal view returns (uint256 c) { if (x >= P || y >= P) { // G1 point not in field. revert ProofInvalid(); @@ -269,9 +263,7 @@ contract Verifier is ISemaphoreVerifier{ /// @param c The compresed point (x with one signal bit). /// @return x The X coordinate in Fp. /// @return y The Y coordinate in Fp. - function decompress_g1( - uint256 c - ) internal view returns (uint256 x, uint256 y) { + function decompress_g1(uint256 c) internal view returns (uint256 x, uint256 y) { // Note that X = 0 is not on the curve since 0³ + 3 = 3 is not a square. // so we can use it to represent the point at infinity. if (c == 0) { @@ -306,12 +298,11 @@ contract Verifier is ISemaphoreVerifier{ /// @param y1 The imaginary part of the Y coordinate. /// @return c0 The first half of the compresed point (x0 with two signal bits). /// @return c1 The second half of the compressed point (x1 unmodified). - function compress_g2( - uint256 x0, - uint256 x1, - uint256 y0, - uint256 y1 - ) internal view returns (uint256 c0, uint256 c1) { + function compress_g2(uint256 x0, uint256 x1, uint256 y0, uint256 y1) + internal + view + returns (uint256 c0, uint256 c1) + { if (x0 >= P || x1 >= P || y0 >= P || y1 >= P) { // G2 point not in field. revert ProofInvalid(); @@ -329,26 +320,16 @@ contract Verifier is ISemaphoreVerifier{ uint256 n3ab = mulmod(mulmod(x0, x1, P), P - 3, P); uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); - y0_pos = addmod( - FRACTION_27_82_FP, - addmod(a_3, mulmod(n3ab, x1, P), P), - P - ); - y1_pos = negate( - addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P) - ); + y0_pos = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); + y1_pos = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); } // Determine hint bit // If this sqrt fails the x coordinate is not on the curve. bool hint; { - uint256 d = sqrt_Fp( - addmod(mulmod(y0_pos, y0_pos, P), mulmod(y1_pos, y1_pos, P), P) - ); - hint = !isSquare_Fp( - mulmod(addmod(y0_pos, d, P), FRACTION_1_2_FP, P) - ); + uint256 d = sqrt_Fp(addmod(mulmod(y0_pos, y0_pos, P), mulmod(y1_pos, y1_pos, P), P)); + hint = !isSquare_Fp(mulmod(addmod(y0_pos, d, P), FRACTION_1_2_FP, P)); } // Recover y @@ -376,10 +357,11 @@ contract Verifier is ISemaphoreVerifier{ /// @return x1 The imaginary poart of the X coordinate. /// @return y0 The real part of the Y coordinate. /// @return y1 The imaginary part of the Y coordinate. - function decompress_g2( - uint256 c0, - uint256 c1 - ) internal view returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1) { + function decompress_g2(uint256 c0, uint256 c1) + internal + view + returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1) + { // Note that X = (0, 0) is not on the curve since 0³ + 3/(9 + i) is not a square. // so we can use it to represent the point at infinity. if (c0 == 0 && c1 == 0) { @@ -400,9 +382,7 @@ contract Verifier is ISemaphoreVerifier{ uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); y0 = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); - y1 = negate( - addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P) - ); + y1 = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); // Note: sqrt_Fp2 reverts if there is no solution, i.e. the point is not on the curve. // Note: (X³ + 3/(9 + i)) is irreducible in Fp2, so y can not be zero. @@ -421,9 +401,11 @@ contract Verifier is ISemaphoreVerifier{ /// @param input The public inputs. These are elements of the scalar field Fr. /// @return x The X coordinate of the resulting G1 point. /// @return y The Y coordinate of the resulting G1 point. - function publicInputMSM( - uint256[4] calldata input - ) internal view returns (uint256 x, uint256 y) { + function publicInputMSM(uint256[4] calldata input) + internal + view + returns (uint256 x, uint256 y) + { // Note: The ECMUL precompile does not reject unreduced values, so we check this. // Note: Unrolling this loop does not cost much extra in code-size, the bulk of the // code-size is in the PUB_ constants. @@ -443,53 +425,29 @@ contract Verifier is ISemaphoreVerifier{ s := calldataload(input) mstore(add(g, 0x40), s) success := and(success, lt(s, R)) - success := and( - success, - staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40) - ) - success := and( - success, - staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40) - ) + success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) + success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) mstore(g, PUB_1_X) mstore(add(g, 0x20), PUB_1_Y) s := calldataload(add(input, 32)) mstore(add(g, 0x40), s) success := and(success, lt(s, R)) - success := and( - success, - staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40) - ) - success := and( - success, - staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40) - ) + success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) + success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) mstore(g, PUB_2_X) mstore(add(g, 0x20), PUB_2_Y) s := calldataload(add(input, 64)) mstore(add(g, 0x40), s) success := and(success, lt(s, R)) - success := and( - success, - staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40) - ) - success := and( - success, - staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40) - ) + success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) + success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) mstore(g, PUB_3_X) mstore(add(g, 0x20), PUB_3_Y) s := calldataload(add(input, 96)) mstore(add(g, 0x40), s) success := and(success, lt(s, R)) - success := and( - success, - staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40) - ) - success := and( - success, - staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40) - ) + success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) + success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) x := mload(f) y := mload(add(f, 0x20)) } @@ -507,16 +465,13 @@ contract Verifier is ISemaphoreVerifier{ /// verifyProof. I.e. Groth16 points (A, B, C) encoded as in EIP-197. /// @return compressed The compressed proof. Elements are in the same order as for /// verifyCompressedProof. I.e. points (A, B, C) in compressed format. - function compressProof( - uint256[8] calldata proof - ) public view returns (uint256[4] memory compressed) { + function compressProof(uint256[8] calldata proof) + public + view + returns (uint256[4] memory compressed) + { compressed[0] = compress_g1(proof[0], proof[1]); - (compressed[2], compressed[1]) = compress_g2( - proof[3], - proof[2], - proof[5], - proof[4] - ); + (compressed[2], compressed[1]) = compress_g2(proof[3], proof[2], proof[5], proof[4]); compressed[3] = compress_g1(proof[6], proof[7]); } @@ -529,15 +484,13 @@ contract Verifier is ISemaphoreVerifier{ /// matching the output of compressProof. /// @param input the public input field elements in the scalar field Fr. /// Elements must be reduced. - function verifyCompressedProof( - uint256[4] calldata compressedProof, - uint256[4] calldata input - ) public view { + function verifyCompressedProof(uint256[4] calldata compressedProof, uint256[4] calldata input) + public + view + { (uint256 Ax, uint256 Ay) = decompress_g1(compressedProof[0]); - (uint256 Bx0, uint256 Bx1, uint256 By0, uint256 By1) = decompress_g2( - compressedProof[2], - compressedProof[1] - ); + (uint256 Bx0, uint256 Bx1, uint256 By0, uint256 By1) = + decompress_g2(compressedProof[2], compressedProof[1]); (uint256 Cx, uint256 Cy) = decompress_g1(compressedProof[3]); (uint256 Lx, uint256 Ly) = publicInputMSM(input); @@ -578,14 +531,7 @@ contract Verifier is ISemaphoreVerifier{ bool success; uint256[1] memory output; assembly ("memory-safe") { - success := staticcall( - gas(), - PRECOMPILE_VERIFY, - pairings, - 0x300, - output, - 0x20 - ) + success := staticcall(gas(), PRECOMPILE_VERIFY, pairings, 0x300, output, 0x20) } if (!success || output[0] != 1) { // Either proof or verification key invalid. @@ -603,10 +549,7 @@ contract Verifier is ISemaphoreVerifier{ /// of compressProof. /// @param input the public input field elements in the scalar field Fr. /// Elements must be reduced. - function verifyProof( - uint256[8] calldata proof, - uint256[4] calldata input - ) public view { + function verifyProof(uint256[8] calldata proof, uint256[4] calldata input) public view { (uint256 x, uint256 y) = publicInputMSM(input); // Note: The precompile expects the F2 coefficients in big-endian order. diff --git a/src/WorldIDIdentityManagerImplV1.sol b/src/WorldIDIdentityManagerImplV1.sol index 7a2ec41..94d140a 100644 --- a/src/WorldIDIdentityManagerImplV1.sol +++ b/src/WorldIDIdentityManagerImplV1.sol @@ -5,7 +5,7 @@ import {WorldIDImpl} from "./abstract/WorldIDImpl.sol"; import {IWorldID} from "./interfaces/IWorldID.sol"; import {ITreeVerifier} from "./interfaces/ITreeVerifier.sol"; -import {ISemaphoreVerifier} from "semaphore/interfaces/ISemaphoreVerifier.sol"; +import {ISemaphoreVerifier} from "src/interfaces/ISemaphoreVerifier.sol"; import {IBridge} from "./interfaces/IBridge.sol"; import {SemaphoreTreeDepthValidator} from "./utils/SemaphoreTreeDepthValidator.sol"; @@ -647,11 +647,11 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { /// @dev Note that a double-signaling check is not included here, and should be carried by the /// caller. /// + /// @param proof The zero-knowledge proof /// @param root The of the Merkle tree /// @param signalHash A keccak256 hash of the Semaphore signal /// @param nullifierHash The nullifier hash /// @param externalNullifierHash A keccak256 hash of the external nullifier - /// @param proof The zero-knowledge proof /// /// @custom:reverts string If the zero-knowledge proof cannot be verified for the public inputs. function verifyProof( @@ -666,7 +666,7 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { // With that done we can now verify the proof. semaphoreVerifier.verifyProof( - root, nullifierHash, signalHash, externalNullifierHash, proof, treeDepth + proof, [root, nullifierHash, signalHash, externalNullifierHash] ); } diff --git a/src/interfaces/ISemaphoreVerifier.sol b/src/interfaces/ISemaphoreVerifier.sol index 9676049..6213bec 100644 --- a/src/interfaces/ISemaphoreVerifier.sol +++ b/src/interfaces/ISemaphoreVerifier.sol @@ -14,5 +14,5 @@ interface ISemaphoreVerifier { /// of compressProof. /// @param input the public input field elements in the scalar field Fr. /// Elements must be reduced. - function verifyProof(uint256[8] calldata proof, uint256[4] calldata input) external; + function verifyProof(uint256[8] calldata proof, uint256[4] calldata input) external view; } diff --git a/src/test/SemaphoreVerifier16.sol b/src/test/SemaphoreVerifier16.sol index 2acc45a..bc6b98b 100644 --- a/src/test/SemaphoreVerifier16.sol +++ b/src/test/SemaphoreVerifier16.sol @@ -10,8 +10,7 @@ pragma solidity ^0.8.0; /// (256 bytes) and compressed (128 bytes) format. A view function is provided /// to compress proofs. /// @notice See for further explanation. -contract Verifier is ISemaphoreVerifier { - +contract SemaphoreVerifier is ISemaphoreVerifier { /// Some of the provided public input values are larger than the field modulus. /// @dev Public input elements are not automatically reduced, as this is can be /// a dangerous source of bugs. @@ -45,47 +44,75 @@ contract Verifier is ISemaphoreVerifier { // Fp2 elements are encoded in the public interface as this became convention. // Constants in Fp - uint256 constant FRACTION_1_2_FP = 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4; - uint256 constant FRACTION_27_82_FP = 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; - uint256 constant FRACTION_3_82_FP = 0x2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e775; + uint256 constant FRACTION_1_2_FP = + 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4; + uint256 constant FRACTION_27_82_FP = + 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; + uint256 constant FRACTION_3_82_FP = + 0x2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e775; // Exponents for inversions and square roots mod P - uint256 constant EXP_INVERSE_FP = 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45; // P - 2 + uint256 constant EXP_INVERSE_FP = + 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45; // P - 2 uint256 constant EXP_SQRT_FP = 0xC19139CB84C680A6E14116DA060561765E05AA45A1C72A34F082305B61F3F52; // (P + 1) / 4; // Groth16 alpha point in G1 - uint256 constant ALPHA_X = 20491192805390485299153009773594534940189261866228447918068658471970481763042; - uint256 constant ALPHA_Y = 9383485363053290200918347156157836566562967994039712273449902621266178545958; + uint256 constant ALPHA_X = + 20491192805390485299153009773594534940189261866228447918068658471970481763042; + uint256 constant ALPHA_Y = + 9383485363053290200918347156157836566562967994039712273449902621266178545958; // Groth16 beta point in G2 in powers of i - uint256 constant BETA_NEG_X_0 = 6375614351688725206403948262868962793625744043794305715222011528459656738731; - uint256 constant BETA_NEG_X_1 = 4252822878758300859123897981450591353533073413197771768651442665752259397132; - uint256 constant BETA_NEG_Y_0 = 11383000245469012944693504663162918391286475477077232690815866754273895001727; - uint256 constant BETA_NEG_Y_1 = 41207766310529818958173054109690360505148424997958324311878202295167071904; + uint256 constant BETA_NEG_X_0 = + 6375614351688725206403948262868962793625744043794305715222011528459656738731; + uint256 constant BETA_NEG_X_1 = + 4252822878758300859123897981450591353533073413197771768651442665752259397132; + uint256 constant BETA_NEG_Y_0 = + 11383000245469012944693504663162918391286475477077232690815866754273895001727; + uint256 constant BETA_NEG_Y_1 = + 41207766310529818958173054109690360505148424997958324311878202295167071904; // Groth16 gamma point in G2 in powers of i - uint256 constant GAMMA_NEG_X_0 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; - uint256 constant GAMMA_NEG_X_1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; - uint256 constant GAMMA_NEG_Y_0 = 13392588948715843804641432497768002650278120570034223513918757245338268106653; - uint256 constant GAMMA_NEG_Y_1 = 17805874995975841540914202342111839520379459829704422454583296818431106115052; + uint256 constant GAMMA_NEG_X_0 = + 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 constant GAMMA_NEG_X_1 = + 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 constant GAMMA_NEG_Y_0 = + 13392588948715843804641432497768002650278120570034223513918757245338268106653; + uint256 constant GAMMA_NEG_Y_1 = + 17805874995975841540914202342111839520379459829704422454583296818431106115052; // Groth16 delta point in G2 in powers of i - uint256 constant DELTA_NEG_X_0 = 16243966861079634958125511652590761846958471358623040426599000904006426210032; - uint256 constant DELTA_NEG_X_1 = 13406811599156507528361773763681356312643537981039994686313383243831956396116; - uint256 constant DELTA_NEG_Y_0 = 6200159192601353057572886987075813506094457284081503951532640457043392212361; - uint256 constant DELTA_NEG_Y_1 = 10106646337257131644126001022517996571132285659724751907435065628753338091209; + uint256 constant DELTA_NEG_X_0 = + 16243966861079634958125511652590761846958471358623040426599000904006426210032; + uint256 constant DELTA_NEG_X_1 = + 13406811599156507528361773763681356312643537981039994686313383243831956396116; + uint256 constant DELTA_NEG_Y_0 = + 6200159192601353057572886987075813506094457284081503951532640457043392212361; + uint256 constant DELTA_NEG_Y_1 = + 10106646337257131644126001022517996571132285659724751907435065628753338091209; // Constant and public input points - uint256 constant CONSTANT_X = 1964404930528116823793003656764176108669615750422202377358993070935069307720; - uint256 constant CONSTANT_Y = 2137714996673694828207437580381836490878070731768805974506391024595988817424; - uint256 constant PUB_0_X = 19568893707760843340848992184233194433177372925415116053368211122719346671126; - uint256 constant PUB_0_Y = 11639469568629189918046964192305250472192697612201524135560178632824282818614; - uint256 constant PUB_1_X = 5317268879687484957437879782519918549127939892210247573193613900261494313825; - uint256 constant PUB_1_Y = 528174394975085006443543773707702838726735933116136102590448357278717993744; - uint256 constant PUB_2_X = 14865918005176722116473730206622066845866539143554731094374354951675249722731; - uint256 constant PUB_2_Y = 3197770568483953664363740385883457803041685902965668289308665954510373380344; - uint256 constant PUB_3_X = 6863358721495494421022713667808247652425178970453300712435830652679038918987; - uint256 constant PUB_3_Y = 15025816433373311798308762709072064417001390853103872064614174594927359131281; + uint256 constant CONSTANT_X = + 1964404930528116823793003656764176108669615750422202377358993070935069307720; + uint256 constant CONSTANT_Y = + 2137714996673694828207437580381836490878070731768805974506391024595988817424; + uint256 constant PUB_0_X = + 19568893707760843340848992184233194433177372925415116053368211122719346671126; + uint256 constant PUB_0_Y = + 11639469568629189918046964192305250472192697612201524135560178632824282818614; + uint256 constant PUB_1_X = + 5317268879687484957437879782519918549127939892210247573193613900261494313825; + uint256 constant PUB_1_Y = + 528174394975085006443543773707702838726735933116136102590448357278717993744; + uint256 constant PUB_2_X = + 14865918005176722116473730206622066845866539143554731094374354951675249722731; + uint256 constant PUB_2_Y = + 3197770568483953664363740385883457803041685902965668289308665954510373380344; + uint256 constant PUB_3_X = + 6863358721495494421022713667808247652425178970453300712435830652679038918987; + uint256 constant PUB_3_Y = + 15025816433373311798308762709072064417001390853103872064614174594927359131281; /// Negation in Fp. /// @notice Returns a number x such that a + x = 0 in Fp. @@ -121,7 +148,7 @@ contract Verifier is ISemaphoreVerifier { // Exponentiation failed. // Should not happen. revert ProofInvalid(); - } + } } /// Invertsion in Fp. @@ -177,7 +204,11 @@ contract Verifier is ISemaphoreVerifier { /// @param hint A hint which of two possible signs to pick in the equation. /// @return x0 The real part of the square root. /// @return x1 The imaginary part of the square root. - function sqrt_Fp2(uint256 a0, uint256 a1, bool hint) internal view returns (uint256 x0, uint256 x1) { + function sqrt_Fp2(uint256 a0, uint256 a1, bool hint) + internal + view + returns (uint256 x0, uint256 x1) + { // If this square root reverts there is no solution in Fp2. uint256 d = sqrt_Fp(addmod(mulmod(a0, a0, P), mulmod(a1, a1, P), P)); if (hint) { @@ -189,8 +220,10 @@ contract Verifier is ISemaphoreVerifier { // Check result to make sure we found a root. // Note: this also fails if a0 or a1 is not reduced. - if (a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P) - || a1 != mulmod(2, mulmod(x0, x1, P), P)) { + if ( + a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P) + || a1 != mulmod(2, mulmod(x0, x1, P), P) + ) { revert ProofInvalid(); } } @@ -211,7 +244,7 @@ contract Verifier is ISemaphoreVerifier { // Point at infinity return 0; } - + // Note: sqrt_Fp reverts if there is no solution, i.e. the x coordinate is invalid. uint256 y_pos = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P)); if (y == y_pos) { @@ -257,7 +290,7 @@ contract Verifier is ISemaphoreVerifier { /// @notice Reverts with InvalidProof if the coefficients are not reduced /// or if the point is not on the curve. /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) - /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). /// @param x0 The real part of the X coordinate. /// @param x1 The imaginary poart of the X coordinate. @@ -266,7 +299,10 @@ contract Verifier is ISemaphoreVerifier { /// @return c0 The first half of the compresed point (x0 with two signal bits). /// @return c1 The second half of the compressed point (x1 unmodified). function compress_g2(uint256 x0, uint256 x1, uint256 y0, uint256 y1) - internal view returns (uint256 c0, uint256 c1) { + internal + view + returns (uint256 c0, uint256 c1) + { if (x0 >= P || x1 >= P || y0 >= P || y1 >= P) { // G2 point not in field. revert ProofInvalid(); @@ -281,11 +317,11 @@ contract Verifier is ISemaphoreVerifier { uint256 y0_pos; uint256 y1_pos; { - uint256 n3ab = mulmod(mulmod(x0, x1, P), P-3, P); + uint256 n3ab = mulmod(mulmod(x0, x1, P), P - 3, P); uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); y0_pos = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); - y1_pos = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); + y1_pos = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); } // Determine hint bit @@ -299,10 +335,10 @@ contract Verifier is ISemaphoreVerifier { // Recover y (y0_pos, y1_pos) = sqrt_Fp2(y0_pos, y1_pos, hint); if (y0 == y0_pos && y1 == y1_pos) { - c0 = (x0 << 2) | (hint ? 2 : 0) | 0; + c0 = (x0 << 2) | (hint ? 2 : 0) | 0; c1 = x1; } else if (y0 == negate(y0_pos) && y1 == negate(y1_pos)) { - c0 = (x0 << 2) | (hint ? 2 : 0) | 1; + c0 = (x0 << 2) | (hint ? 2 : 0) | 1; c1 = x1; } else { // G1 point not on curve. @@ -313,7 +349,7 @@ contract Verifier is ISemaphoreVerifier { /// Decompress a G2 point. /// @notice Reverts with InvalidProof if the input does not represent a valid point. /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) - /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). /// @param c0 The first half of the compresed point (x0 with two signal bits). /// @param c1 The second half of the compressed point (x1 unmodified). @@ -322,7 +358,10 @@ contract Verifier is ISemaphoreVerifier { /// @return y0 The real part of the Y coordinate. /// @return y1 The imaginary part of the Y coordinate. function decompress_g2(uint256 c0, uint256 c1) - internal view returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1) { + internal + view + returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1) + { // Note that X = (0, 0) is not on the curve since 0³ + 3/(9 + i) is not a square. // so we can use it to represent the point at infinity. if (c0 == 0 && c1 == 0) { @@ -338,12 +377,12 @@ contract Verifier is ISemaphoreVerifier { revert ProofInvalid(); } - uint256 n3ab = mulmod(mulmod(x0, x1, P), P-3, P); + uint256 n3ab = mulmod(mulmod(x0, x1, P), P - 3, P); uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); y0 = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); - y1 = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); + y1 = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); // Note: sqrt_Fp2 reverts if there is no solution, i.e. the point is not on the curve. // Note: (X³ + 3/(9 + i)) is irreducible in Fp2, so y can not be zero. @@ -363,7 +402,10 @@ contract Verifier is ISemaphoreVerifier { /// @return x The X coordinate of the resulting G1 point. /// @return y The Y coordinate of the resulting G1 point. function publicInputMSM(uint256[4] calldata input) - internal view returns (uint256 x, uint256 y) { + internal + view + returns (uint256 x, uint256 y) + { // Note: The ECMUL precompile does not reject unreduced values, so we check this. // Note: Unrolling this loop does not cost much extra in code-size, the bulk of the // code-size is in the PUB_ constants. @@ -380,28 +422,28 @@ contract Verifier is ISemaphoreVerifier { mstore(add(f, 0x20), CONSTANT_Y) mstore(g, PUB_0_X) mstore(add(g, 0x20), PUB_0_Y) - s := calldataload(input) + s := calldataload(input) mstore(add(g, 0x40), s) success := and(success, lt(s, R)) success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) mstore(g, PUB_1_X) mstore(add(g, 0x20), PUB_1_Y) - s := calldataload(add(input, 32)) + s := calldataload(add(input, 32)) mstore(add(g, 0x40), s) success := and(success, lt(s, R)) success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) mstore(g, PUB_2_X) mstore(add(g, 0x20), PUB_2_Y) - s := calldataload(add(input, 64)) + s := calldataload(add(input, 64)) mstore(add(g, 0x40), s) success := and(success, lt(s, R)) success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) mstore(g, PUB_3_X) mstore(add(g, 0x20), PUB_3_Y) - s := calldataload(add(input, 96)) + s := calldataload(add(input, 96)) mstore(add(g, 0x40), s) success := and(success, lt(s, R)) success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) @@ -424,7 +466,10 @@ contract Verifier is ISemaphoreVerifier { /// @return compressed The compressed proof. Elements are in the same order as for /// verifyCompressedProof. I.e. points (A, B, C) in compressed format. function compressProof(uint256[8] calldata proof) - public view returns (uint256[4] memory compressed) { + public + view + returns (uint256[4] memory compressed) + { compressed[0] = compress_g1(proof[0], proof[1]); (compressed[2], compressed[1]) = compress_g2(proof[3], proof[2], proof[5], proof[4]); compressed[3] = compress_g1(proof[6], proof[7]); @@ -439,13 +484,13 @@ contract Verifier is ISemaphoreVerifier { /// matching the output of compressProof. /// @param input the public input field elements in the scalar field Fr. /// Elements must be reduced. - function verifyCompressedProof( - uint256[4] calldata compressedProof, - uint256[4] calldata input - ) public view { + function verifyCompressedProof(uint256[4] calldata compressedProof, uint256[4] calldata input) + public + view + { (uint256 Ax, uint256 Ay) = decompress_g1(compressedProof[0]); - (uint256 Bx0, uint256 Bx1, uint256 By0, uint256 By1) = decompress_g2( - compressedProof[2], compressedProof[1]); + (uint256 Bx0, uint256 Bx1, uint256 By0, uint256 By1) = + decompress_g2(compressedProof[2], compressedProof[1]); (uint256 Cx, uint256 Cy) = decompress_g1(compressedProof[3]); (uint256 Lx, uint256 Ly) = publicInputMSM(input); @@ -454,17 +499,17 @@ contract Verifier is ISemaphoreVerifier { // Note: The pairing precompile rejects unreduced values, so we won't check that here. uint256[24] memory pairings; // e(A, B) - pairings[ 0] = Ax; - pairings[ 1] = Ay; - pairings[ 2] = Bx1; - pairings[ 3] = Bx0; - pairings[ 4] = By1; - pairings[ 5] = By0; + pairings[0] = Ax; + pairings[1] = Ay; + pairings[2] = Bx1; + pairings[3] = Bx0; + pairings[4] = By1; + pairings[5] = By0; // e(C, -δ) - pairings[ 6] = Cx; - pairings[ 7] = Cy; - pairings[ 8] = DELTA_NEG_X_1; - pairings[ 9] = DELTA_NEG_X_0; + pairings[6] = Cx; + pairings[7] = Cy; + pairings[8] = DELTA_NEG_X_1; + pairings[9] = DELTA_NEG_X_0; pairings[10] = DELTA_NEG_Y_1; pairings[11] = DELTA_NEG_Y_0; // e(α, -β) @@ -504,15 +549,12 @@ contract Verifier is ISemaphoreVerifier { /// of compressProof. /// @param input the public input field elements in the scalar field Fr. /// Elements must be reduced. - function verifyProof( - uint256[8] calldata proof, - uint256[4] calldata input - ) public view { + function verifyProof(uint256[8] calldata proof, uint256[4] calldata input) public view { (uint256 x, uint256 y) = publicInputMSM(input); // Note: The precompile expects the F2 coefficients in big-endian order. // Note: The pairing precompile rejects unreduced values, so we won't check that here. - + bool success; assembly ("memory-safe") { let f := mload(0x40) // Free memory pointer. diff --git a/src/test/data/DeletionProof.json b/src/test/data/DeletionProof.json index f495ddd..605db00 100644 --- a/src/test/data/DeletionProof.json +++ b/src/test/data/DeletionProof.json @@ -17,4 +17,4 @@ "0x280894db66e6a9f9bf8aa48ffa1de98b755adadcf5962fb308cd1802a1101a0c", "0x1484814b74243a07930c6af61079f94eefd843efe95e2388d9d49956cfacf3ab" ] -} \ No newline at end of file +} diff --git a/src/test/data/InclusionProof.json b/src/test/data/InclusionProof.json index 35636f0..92c7278 100644 --- a/src/test/data/InclusionProof.json +++ b/src/test/data/InclusionProof.json @@ -19,4 +19,4 @@ "0xf6ee8db704ecb5c149e5a046a03e8767ba5a818c08320f6245070e4c0e99b77" ] ] -} \ No newline at end of file +} diff --git a/src/test/data/InsertionProof.json b/src/test/data/InsertionProof.json index 3f61e76..e9832a3 100644 --- a/src/test/data/InsertionProof.json +++ b/src/test/data/InsertionProof.json @@ -17,4 +17,4 @@ "0x2f5f37e84d6acff8cfd7988d33aaea072dbe5071093b2df022d23047f134ac45", "0x24830332559eada283d4473b17091b239443e75e9e09f0ebce8e72c235ee665d" ] -} \ No newline at end of file +} diff --git a/src/test/data/semaphore_30.json b/src/test/data/semaphore_30.json index b4b61a5..ddcb814 100644 --- a/src/test/data/semaphore_30.json +++ b/src/test/data/semaphore_30.json @@ -1,109 +1,100 @@ { - "protocol": "groth16", - "curve": "bn128", - "nPublic": 4, - "vk_alpha_1": [ - "20491192805390485299153009773594534940189261866228447918068658471970481763042", - "9383485363053290200918347156157836566562967994039712273449902621266178545958", - "1" + "protocol": "groth16", + "curve": "bn128", + "nPublic": 4, + "vk_alpha_1": [ + "20491192805390485299153009773594534940189261866228447918068658471970481763042", + "9383485363053290200918347156157836566562967994039712273449902621266178545958", + "1" + ], + "vk_beta_2": [ + [ + "6375614351688725206403948262868962793625744043794305715222011528459656738731", + "4252822878758300859123897981450591353533073413197771768651442665752259397132" ], - "vk_beta_2": [ - [ - "6375614351688725206403948262868962793625744043794305715222011528459656738731", - "4252822878758300859123897981450591353533073413197771768651442665752259397132" - ], - [ - "10505242626370262277552901082094356697409835680220590971873171140371331206856", - "21847035105528745403288232691147584728191162732299865338377159692350059136679" - ], - [ - "1", - "0" - ] + [ + "10505242626370262277552901082094356697409835680220590971873171140371331206856", + "21847035105528745403288232691147584728191162732299865338377159692350059136679" ], - "vk_gamma_2": [ - [ - "10857046999023057135944570762232829481370756359578518086990519993285655852781", - "11559732032986387107991004021392285783925812861821192530917403151452391805634" - ], - [ - "8495653923123431417604973247489272438418190587263600148770280649306958101930", - "4082367875863433681332203403145435568316851327593401208105741076214120093531" - ], - [ - "1", - "0" - ] + ["1", "0"] + ], + "vk_gamma_2": [ + [ + "10857046999023057135944570762232829481370756359578518086990519993285655852781", + "11559732032986387107991004021392285783925812861821192530917403151452391805634" ], - "vk_delta_2": [ - [ - "15028154694713144242204861571552635520290993855826554325002991692907421516918", - "10202326166286888893675634318107715186834588694714750762952081034135561546271" - ], - [ - "12766289885372833812620582632847872978085960777075662988932200910695848591357", - "18486039841380105976272577521609866666900576498507352937328726490052296469859" - ], - [ - "1", - "0" - ] + [ + "8495653923123431417604973247489272438418190587263600148770280649306958101930", + "4082367875863433681332203403145435568316851327593401208105741076214120093531" ], - "vk_alphabeta_12": [ - [ - [ - "2029413683389138792403550203267699914886160938906632433982220835551125967885", - "21072700047562757817161031222997517981543347628379360635925549008442030252106" - ], - [ - "5940354580057074848093997050200682056184807770593307860589430076672439820312", - "12156638873931618554171829126792193045421052652279363021382169897324752428276" - ], - [ - "7898200236362823042373859371574133993780991612861777490112507062703164551277", - "7074218545237549455313236346927434013100842096812539264420499035217050630853" - ] - ], - [ - [ - "7077479683546002997211712695946002074877511277312570035766170199895071832130", - "10093483419865920389913245021038182291233451549023025229112148274109565435465" - ], - [ - "4595479056700221319381530156280926371456704509942304414423590385166031118820", - "19831328484489333784475432780421641293929726139240675179672856274388269393268" - ], - [ - "11934129596455521040620786944827826205713621633706285934057045369193958244500", - "8037395052364110730298837004334506829870972346962140206007064471173334027475" - ] - ] + ["1", "0"] + ], + "vk_delta_2": [ + [ + "15028154694713144242204861571552635520290993855826554325002991692907421516918", + "10202326166286888893675634318107715186834588694714750762952081034135561546271" ], - "IC": [ - [ - "1452272927738590248356371174422184656932731110936062990115610832462181634644", - "3608050114233210789542189629343107890943266759827387991788718454179833288695", - "1" - ], - [ - "14798240452388909327945424685903532333765637883272751382037716636327236955001", - "10773894897711848209682368488916121016695006898681985691467605219098835500201", - "1" - ], - [ - "17204267933132009093604099819536245144503489322639121825381131096467570698650", - "7704298975420304156332734115679983371345754866278811368869074990486717531131", - "1" - ], - [ - "8060465662017324080560848316478407038163145149983639907596180500095598669247", - "20475082166427284188002500222093571716651248980245637602667562336751029856573", - "1" - ], - [ - "7457566682692308112726332096733260585025339741083447785327706250123165087868", - "11904519443874922292602150685069370036383697877657723976244907400392778002614", - "1" - ] + [ + "12766289885372833812620582632847872978085960777075662988932200910695848591357", + "18486039841380105976272577521609866666900576498507352937328726490052296469859" + ], + ["1", "0"] + ], + "vk_alphabeta_12": [ + [ + [ + "2029413683389138792403550203267699914886160938906632433982220835551125967885", + "21072700047562757817161031222997517981543347628379360635925549008442030252106" + ], + [ + "5940354580057074848093997050200682056184807770593307860589430076672439820312", + "12156638873931618554171829126792193045421052652279363021382169897324752428276" + ], + [ + "7898200236362823042373859371574133993780991612861777490112507062703164551277", + "7074218545237549455313236346927434013100842096812539264420499035217050630853" + ] + ], + [ + [ + "7077479683546002997211712695946002074877511277312570035766170199895071832130", + "10093483419865920389913245021038182291233451549023025229112148274109565435465" + ], + [ + "4595479056700221319381530156280926371456704509942304414423590385166031118820", + "19831328484489333784475432780421641293929726139240675179672856274388269393268" + ], + [ + "11934129596455521040620786944827826205713621633706285934057045369193958244500", + "8037395052364110730298837004334506829870972346962140206007064471173334027475" + ] + ] + ], + "IC": [ + [ + "1452272927738590248356371174422184656932731110936062990115610832462181634644", + "3608050114233210789542189629343107890943266759827387991788718454179833288695", + "1" + ], + [ + "14798240452388909327945424685903532333765637883272751382037716636327236955001", + "10773894897711848209682368488916121016695006898681985691467605219098835500201", + "1" + ], + [ + "17204267933132009093604099819536245144503489322639121825381131096467570698650", + "7704298975420304156332734115679983371345754866278811368869074990486717531131", + "1" + ], + [ + "8060465662017324080560848316478407038163145149983639907596180500095598669247", + "20475082166427284188002500222093571716651248980245637602667562336751029856573", + "1" + ], + [ + "7457566682692308112726332096733260585025339741083447785327706250123165087868", + "11904519443874922292602150685069370036383697877657723976244907400392778002614", + "1" ] -} \ No newline at end of file + ] +} diff --git a/src/test/identity-manager/WorldIDIdentityManagerCalculation.t.sol b/src/test/identity-manager/WorldIDIdentityManagerCalculation.t.sol index 5c89ad2..f9f4c1f 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerCalculation.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerCalculation.t.sol @@ -61,5 +61,4 @@ contract WorldIDIdentityManagerCalculation is WorldIDIdentityManagerTest { packedDeletionIndices, deletionPreRoot, deletionPostRoot, deletionBatchSize ); } - } diff --git a/src/test/identity-manager/WorldIDIdentityManagerGettersSetters.t.sol b/src/test/identity-manager/WorldIDIdentityManagerGettersSetters.t.sol index 76bc324..921e387 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerGettersSetters.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerGettersSetters.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.21; import {WorldIDIdentityManagerTest} from "./WorldIDIdentityManagerTest.sol"; import {ITreeVerifier} from "../../interfaces/ITreeVerifier.sol"; -import {SemaphoreVerifier} from "semaphore/base/SemaphoreVerifier.sol"; +import {SemaphoreVerifier} from "src/SemaphoreVerifier.sol"; import {SimpleVerifier, SimpleVerify} from "../mock/SimpleVerifier.sol"; import {TypeConverter as TC} from "../utils/TypeConverter.sol"; import {VerifierLookupTable} from "../../data/VerifierLookupTable.sol"; diff --git a/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol b/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol index 6f9c8cf..c90a86f 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.21; import {WorldIDIdentityManagerTest} from "./WorldIDIdentityManagerTest.sol"; -import {ISemaphoreVerifier} from "semaphore/interfaces/ISemaphoreVerifier.sol"; +import {ISemaphoreVerifier} from "src/interfaces/ISemaphoreVerifier.sol"; import {SemaphoreTreeDepthValidator} from "../../utils/SemaphoreTreeDepthValidator.sol"; import {SimpleSemaphoreVerifier} from "../mock/SimpleSemaphoreVerifier.sol"; -import {Verifier} from "src/test/SemaphoreVerifier16.sol"; +import {SemaphoreVerifier} from "src/test/SemaphoreVerifier16.sol"; import {WorldIDIdentityManager as IdentityManager} from "../../WorldIDIdentityManager.sol"; import {WorldIDIdentityManagerImplV2 as ManagerImpl} from "../../WorldIDIdentityManagerImplV2.sol"; import {WorldIDIdentityManagerImplV1 as ManagerImplV1} from "../../WorldIDIdentityManagerImplV1.sol"; @@ -29,9 +29,10 @@ contract WorldIDIdentityManagerSemaphoreVerification is WorldIDIdentityManagerTe ISemaphoreVerifier actualSemaphoreVerifier = new SimpleSemaphoreVerifier(); vm.assume(SemaphoreTreeDepthValidator.validate(actualTreeDepth)); vm.assume(prf[0] != 0); + vm.assume(prf[0] % 2 != 0); makeNewIdentityManager( actualTreeDepth, - inclusionRoot, + insertionPreRoot, defaultInsertVerifiers, defaultDeletionVerifiers, defaultUpdateVerifiers, @@ -39,7 +40,7 @@ contract WorldIDIdentityManagerSemaphoreVerification is WorldIDIdentityManagerTe ); bytes memory verifyProofCallData = abi.encodeCall( ManagerImplV1.verifyProof, - (inclusionRoot, nullifierHash, signalHash, externalNullifierHash, inclusionProof) + (insertionPreRoot, nullifierHash, signalHash, externalNullifierHash, prf) ); // Test @@ -85,7 +86,7 @@ contract WorldIDIdentityManagerSemaphoreVerification is WorldIDIdentityManagerTe uint256[8] memory prf ) public { // Setup - ISemaphoreVerifier actualSemaphoreVerifier = ISemaphoreVerifier(address(new Verifier())); + ISemaphoreVerifier actualSemaphoreVerifier = new SemaphoreVerifier(); vm.assume(SemaphoreTreeDepthValidator.validate(actualTreeDepth)); vm.assume(prf[0] != 0); makeNewIdentityManager( diff --git a/src/test/identity-manager/WorldIDIdentityManagerTest.sol b/src/test/identity-manager/WorldIDIdentityManagerTest.sol index 0bc6e67..d91d2b2 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerTest.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerTest.sol @@ -6,13 +6,13 @@ import {UUPSUpgradeable} from "contracts-upgradeable/proxy/utils/UUPSUpgradeable import {WorldIDTest} from "../WorldIDTest.sol"; import {ITreeVerifier} from "../../interfaces/ITreeVerifier.sol"; -import {ISemaphoreVerifier} from "semaphore/interfaces/ISemaphoreVerifier.sol"; +import {ISemaphoreVerifier} from "src/interfaces/ISemaphoreVerifier.sol"; import {IBridge} from "../../interfaces/IBridge.sol"; import {SimpleStateBridge} from "../mock/SimpleStateBridge.sol"; import {SimpleVerifier, SimpleVerify} from "../mock/SimpleVerifier.sol"; import {UnimplementedTreeVerifier} from "../../utils/UnimplementedTreeVerifier.sol"; -import {SemaphoreVerifier} from "semaphore/base/SemaphoreVerifier.sol"; +import {SemaphoreVerifier} from "src/SemaphoreVerifier.sol"; import {VerifierLookupTable} from "../../data/VerifierLookupTable.sol"; import {WorldIDIdentityManager as IdentityManager} from "../../WorldIDIdentityManager.sol"; @@ -87,7 +87,7 @@ contract WorldIDIdentityManagerTest is WorldIDTest { /// INCLUSION /// /////////////////////////////////////////////////////////////////// /// @dev generated using https://github.com/worldcoin/semaphore-mock - /// steps: + /// steps: /// 1. cargo run --release generate-identities --identities 10 /// 2. cargo run --release prove-inclusion --identities out/random_identities.json --tree-depth 16 --identity-index 3 uint256 internal constant inclusionRoot = @@ -125,7 +125,7 @@ contract WorldIDIdentityManagerTest is WorldIDTest { // output from semaphore-mtb prove in src/test/data/InsertionProof.json /// @dev test_insertion.ps is generated using semaphore-mtb: `./gnark-mbu setup --mode insertion --batch-size 3 --tree-depth 16 --output test_insertion.ps` /// @dev generated using semaphore-mtb: `./gnark-mbu gen-test-params --mode insertion --tree-depth 16 --batch-size 3 | ./gnark-mbu prove --mode insertion --keys-file test_insertion.ps` - insertionProof = [ + insertionProof = [ 0x18491e665bc7128f0113b3cf187502311cf5a82b0304e02464099782483b14ba, 0x1dace8033bc22eda25b483b2a260195b67ee5bef07990bf0e2c5f7923423fe, 0x1d4489b99a91a972e878bef7a147251c8d4941b415bb7b36a9740e714f995b7e, @@ -153,9 +153,9 @@ contract WorldIDIdentityManagerTest is WorldIDTest { // Create the inclusion proof term. // output from semaphore-mtb prove in src/test/data/InclusionProof.json - // + // /// @dev generated using https://github.com/worldcoin/semaphore-mock - /// steps: + /// steps: /// 1. cargo run --release generate-identities --identities 10 /// 2. cargo run --release prove-inclusion --identities out/random_identities.json --tree-depth 16 --identity-index 3 inclusionProof = [ diff --git a/src/test/identity-manager/WorldIDIdentityManagerUninit.t.sol b/src/test/identity-manager/WorldIDIdentityManagerUninit.t.sol index 0fe9486..6ff13ef 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerUninit.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerUninit.t.sol @@ -6,7 +6,7 @@ import {WorldIDIdentityManagerTest} from "./WorldIDIdentityManagerTest.sol"; import {ITreeVerifier} from "../../interfaces/ITreeVerifier.sol"; import {CheckInitialized} from "../../utils/CheckInitialized.sol"; -import {SemaphoreVerifier} from "semaphore/base/SemaphoreVerifier.sol"; +import {SemaphoreVerifier} from "src/SemaphoreVerifier.sol"; import {TypeConverter as TC} from "../utils/TypeConverter.sol"; import {Verifier as TreeVerifier} from "src/InsertionTreeVerifier.sol"; import {VerifierLookupTable} from "../../data/VerifierLookupTable.sol"; diff --git a/src/test/mock/SimpleSemaphoreVerifier.sol b/src/test/mock/SimpleSemaphoreVerifier.sol index 17b9229..2e80cd7 100644 --- a/src/test/mock/SimpleSemaphoreVerifier.sol +++ b/src/test/mock/SimpleSemaphoreVerifier.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.21; -import {ISemaphoreVerifier} from "semaphore/interfaces/ISemaphoreVerifier.sol"; +import {ISemaphoreVerifier} from "src/interfaces/ISemaphoreVerifier.sol"; /// @title Simple Verifier /// @author Worldcoin @@ -9,19 +9,8 @@ import {ISemaphoreVerifier} from "semaphore/interfaces/ISemaphoreVerifier.sol"; contract SimpleSemaphoreVerifier is ISemaphoreVerifier { error Semaphore__InvalidProof(); - function verifyProof( - uint256 merkleTreeRoot, - uint256 nullifierHash, - uint256 signal, - uint256 externalNullifier, - uint256[8] calldata proof, - uint256 merkleTreeDepth - ) external pure { - delete merkleTreeRoot; - delete nullifierHash; - delete signal; - delete externalNullifier; - delete merkleTreeDepth; + function verifyProof(uint256[8] calldata proof, uint256[4] memory input) external pure { + delete input; if (proof[0] % 2 == 0) { revert Semaphore__InvalidProof(); diff --git a/src/test/mock/SimpleVerifier.sol b/src/test/mock/SimpleVerifier.sol index 90f9cd4..ad4e1d0 100644 --- a/src/test/mock/SimpleVerifier.sol +++ b/src/test/mock/SimpleVerifier.sol @@ -18,6 +18,7 @@ contract SimpleVerifier is ITreeVerifier { function verifyProof(uint256[8] memory proof, uint256[1] memory input) external { bool result = proof[0] % 2 == 0; + input[0] = 0; if (result) { emit VerifiedProof(batchSize); } From 892fc802bf2fba3b040d98cc7e27414a5b5761d6 Mon Sep 17 00:00:00 2001 From: "dcbuilder.eth" Date: Wed, 27 Sep 2023 14:14:37 +0200 Subject: [PATCH 16/20] fix optimized semaphore verifier test --- src/test/data/InclusionProof.json | 20 +++++++++++-------- ...IdentityManagerSemaphoreVerification.t.sol | 19 +++++++++--------- .../WorldIDIdentityManagerTest.sol | 6 ++++++ 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/test/data/InclusionProof.json b/src/test/data/InclusionProof.json index 92c7278..5729333 100644 --- a/src/test/data/InclusionProof.json +++ b/src/test/data/InclusionProof.json @@ -1,22 +1,26 @@ { + "root": "0xdf9f0cb5a3afe2129e349c1435bfbe9e6f091832fdfa7b739b61c5db2cbdde9", + "signal_hash": "0xbc6bb462e38af7da48e0ae7b5cbae860141c04e5af2cf92328cd6548df111f", + "external_nullifier_hash": "0xfd3a1e9736c12a5d4a31f26362b577ccafbd523d358daf40cdc04d90e17f77", + "nullifier_hash": "0x2887375654a2f83868b277f3836678aa55475fd5c840b117913ea4a7c9ded6fc", "proof": [ [ - "0x27d70bdecb420a7322a0e44ef68345fc67e9903a3980762c23dfda5cf4d65715", - "0x1aba064ef272dd53b498d856c711890249a63a46825fe6d332fc5868ad854ef4" + "0x12bca28c242e87a12637280e957ee6eefa29446e11cd4e49f86df9fd320fbdba", + "0x1f399a7953de230a6da3a70e49c284b60ee5040cd71ee668994f70f49ff73489" ], [ [ - "0x23a76f9777710f268d2092d859344cdc8d7f77abef35695f89d1ebf771d8a520", - "0x295ab87eb7c0ad9470ec2b56b35309f5e4576679ef6180ed78124e3f549f125d" + "0x9b28c42a4b34507ae0ae61f72342888b64a2f89c1c45e2e897a53ee2b946406", + "0x28a5825073e62d01c9f5c8172664349180df8a09d68026a29df91500964a0a72" ], [ - "0x1da63a007225659d3a70a2dfe807df5c3e8423bfd8e059d72909a1def161573f", - "0x2578db76ee9f64ff4eb0b532cb796dfa27d86ae8cd29e2d6b32f9428c71acb8b" + "0x24356f7d8e4565514fc8a575834b6b37bd7df399ae99424187f30591557e4c4a", + "0x177dc415a6d6e866c0125a0f73f41e7a5b291f18a90864e5fbe106c67df82f67" ] ], [ - "0xd00d49d5db4c5b11a13aca379f5c3c627a6e8fc1c4470e7a56017307aca51a2", - "0xf6ee8db704ecb5c149e5a046a03e8767ba5a818c08320f6245070e4c0e99b77" + "0x1fb872833d2373abaa26cca40f498b878cef8134cae18cc703e68f99b9938fa5", + "0x1487bc571a30416ad9124f5f8ef9e2efe5a0d720a710f6abf0095004f250b306" ] ] } diff --git a/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol b/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol index c90a86f..2de8c13 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol @@ -78,19 +78,12 @@ contract WorldIDIdentityManagerSemaphoreVerification is WorldIDIdentityManagerTe } /// @notice Checks that the proof validates properly with the correct inputs. - function testOptimizedProofVerificationWithCorrectInputs( - uint8 actualTreeDepth, - uint256 nullifierHash, - uint256 signalHash, - uint256 externalNullifierHash, - uint256[8] memory prf - ) public { + function testOptimizedProofVerificationWithCorrectInputs(uint256[8] memory prf) public { // Setup ISemaphoreVerifier actualSemaphoreVerifier = new SemaphoreVerifier(); - vm.assume(SemaphoreTreeDepthValidator.validate(actualTreeDepth)); vm.assume(prf[0] != 0); makeNewIdentityManager( - actualTreeDepth, + treeDepth, inclusionRoot, defaultInsertVerifiers, defaultDeletionVerifiers, @@ -99,7 +92,13 @@ contract WorldIDIdentityManagerSemaphoreVerification is WorldIDIdentityManagerTe ); bytes memory verifyProofCallData = abi.encodeCall( ManagerImplV1.verifyProof, - (inclusionRoot, nullifierHash, signalHash, externalNullifierHash, inclusionProof) + ( + inclusionRoot, + inclusionSignalHash, + inclusionNullifierHash, + inclusionExternalNullifierHash, + inclusionProof + ) ); // Test diff --git a/src/test/identity-manager/WorldIDIdentityManagerTest.sol b/src/test/identity-manager/WorldIDIdentityManagerTest.sol index d91d2b2..7f1437e 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerTest.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerTest.sol @@ -92,6 +92,12 @@ contract WorldIDIdentityManagerTest is WorldIDTest { /// 2. cargo run --release prove-inclusion --identities out/random_identities.json --tree-depth 16 --identity-index 3 uint256 internal constant inclusionRoot = 0xdf9f0cb5a3afe2129e349c1435bfbe9e6f091832fdfa7b739b61c5db2cbdde9; + uint256 internal constant inclusionSignalHash = + 0xbc6bb462e38af7da48e0ae7b5cbae860141c04e5af2cf92328cd6548df111f; + uint256 internal constant inclusionNullifierHash = + 0x2887375654a2f83868b277f3836678aa55475fd5c840b117913ea4a7c9ded6fc; + uint256 internal constant inclusionExternalNullifierHash = + 0xfd3a1e9736c12a5d4a31f26362b577ccafbd523d358daf40cdc04d90e17f77; uint256[8] inclusionProof; From 5d863293dc449320291da2b32f8f89258846c6d8 Mon Sep 17 00:00:00 2001 From: "dcbuilder.eth" Date: Wed, 27 Sep 2023 14:19:25 +0200 Subject: [PATCH 17/20] add natspec explainer --- src/test/identity-manager/WorldIDIdentityManagerTest.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/identity-manager/WorldIDIdentityManagerTest.sol b/src/test/identity-manager/WorldIDIdentityManagerTest.sol index 7f1437e..5273278 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerTest.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerTest.sol @@ -90,6 +90,7 @@ contract WorldIDIdentityManagerTest is WorldIDTest { /// steps: /// 1. cargo run --release generate-identities --identities 10 /// 2. cargo run --release prove-inclusion --identities out/random_identities.json --tree-depth 16 --identity-index 3 + /// @dev params from `src/test/data/InclusionProof.json` (output of step 2.) uint256 internal constant inclusionRoot = 0xdf9f0cb5a3afe2129e349c1435bfbe9e6f091832fdfa7b739b61c5db2cbdde9; uint256 internal constant inclusionSignalHash = From 4ea11854858f062c213d2470a26e0086f60ecb91 Mon Sep 17 00:00:00 2001 From: "dcbuilder.eth" Date: Wed, 27 Sep 2023 19:01:38 +0200 Subject: [PATCH 18/20] remove unused old enums, events and errors --- src/WorldIDIdentityManagerImplV1.sol | 51 +++------------------------- 1 file changed, 4 insertions(+), 47 deletions(-) diff --git a/src/WorldIDIdentityManagerImplV1.sol b/src/WorldIDIdentityManagerImplV1.sol index a42afa7..35529dd 100644 --- a/src/WorldIDIdentityManagerImplV1.sol +++ b/src/WorldIDIdentityManagerImplV1.sol @@ -82,15 +82,18 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { VerifierLookupTable internal batchInsertionVerifiers; /// @notice The table of verifiers for verifying identity updates. + /// @dev preserved for storage reasons, no longer used VerifierLookupTable internal identityUpdateVerifiers; /// @notice The verifier instance needed for operating within the semaphore protocol. ISemaphoreVerifier internal semaphoreVerifier; /// @notice The interface of the bridge contract from L1 to supported target chains. + /// @dev preserved for storage reasons, no longer used IBridge internal _stateBridge; /// @notice Boolean flag to enable/disable the state bridge. + /// @dev preserved for storage reasons, no longer used bool internal _isStateBridgeEnabled; /// @notice The depth of the Semaphore merkle tree. @@ -113,18 +116,10 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { bool isValid; } - /// @notice Represents the kind of element that has not been provided in reduced form. - enum UnreducedElementType { - PreRoot, - IdentityCommitment, - PostRoot - } - /// @notice Represents the kind of change that is made to the root of the tree. enum TreeChange { Insertion, - Deletion, - Update + Deletion } /// @notice Represents the kinds of dependencies that can be updated. @@ -132,7 +127,6 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { StateBridge, InsertionVerifierLookupTable, DeletionVerifierLookupTable, - UpdateVerifierLookupTable, SemaphoreVerifier } @@ -150,28 +144,12 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { /// ERRORS /// /////////////////////////////////////////////////////////////////////////////// - /// @notice Thrown when encountering an element that should be reduced as a member of `Fr` but - /// is not. - /// @dev `r` in this case is given by `SNARK_SCALAR_FIELD`. - /// - /// @param elementType The kind of element that was encountered unreduced. - /// @param element The value of that element. - error UnreducedElement(UnreducedElementType elementType, uint256 element); - /// @notice Thrown when trying to execute a privileged action without being the contract /// manager. /// /// @param user The user that attempted the action that they were not authorised for. error Unauthorized(address user); - /// @notice Thrown when one or more of the identity commitments to be inserted is invalid. - /// - /// @param index The index in the array of identity commitments where the invalid commitment was - /// found. - /// @dev This error is no longer in use as we now verify the commitments off-chain within the circuit - /// no need to check for reduced elements or invalid commitments. - error InvalidCommitment(uint256 index); - /// @notice Thrown when the provided proof cannot be verified for the accompanying inputs. error ProofValidationFailure(); @@ -181,24 +159,11 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { /// @param latestRoot The actual latest root at the time of the transaction. error NotLatestRoot(uint256 providedRoot, uint256 latestRoot); - /// @notice Thrown when attempting to enable the bridge when it is already enabled. - error StateBridgeAlreadyEnabled(); - - /// @notice Thrown when attempting to disable the bridge when it is already disabled. - error StateBridgeAlreadyDisabled(); - - /// @notice Thrown when attempting to set the state bridge address to the zero address. - error InvalidStateBridgeAddress(); - /// @notice Thrown when Semaphore tree depth is not supported. /// /// @param depth Passed tree depth. error UnsupportedTreeDepth(uint8 depth); - /// @notice Thrown when the inputs to `removeIdentities` do not match in - /// length. - error MismatchedInputLengths(); - /////////////////////////////////////////////////////////////////////////////// /// EVENTS /// /////////////////////////////////////////////////////////////////////////////// @@ -220,12 +185,6 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { Dependency indexed kind, address indexed oldAddress, address indexed newAddress ); - /// @notice Emitted when the state bridge is enabled or disabled. - /// - /// @param isEnabled Set to `true` if the event comes from the state bridge being enabled, - /// `false` otherwise. - event StateBridgeStateChange(bool indexed isEnabled); - /// @notice Emitted when the root history expiry time is changed. /// /// @param oldExpiryTime The expiry time prior to the change. @@ -343,8 +302,6 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { /// provided inputs. /// @custom:reverts VerifierLookupTable.NoSuchVerifier If the batch sizes doesn't match a known /// verifier. - /// @custom:reverts VerifierLookupTable.BatchTooLarge If the batch size exceeds the maximum - /// batch size. function registerIdentities( uint256[8] calldata insertionProof, uint256 preRoot, From 0c922ed1bc1a62a5f15251c87175acc6eb404e5e Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xKitsune@protonmail.com> Date: Wed, 27 Sep 2023 14:20:49 -0400 Subject: [PATCH 19/20] Revert "remove unused old enums, events and errors" This reverts commit 4ea11854858f062c213d2470a26e0086f60ecb91. --- src/WorldIDIdentityManagerImplV1.sol | 51 +++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/src/WorldIDIdentityManagerImplV1.sol b/src/WorldIDIdentityManagerImplV1.sol index 35529dd..a42afa7 100644 --- a/src/WorldIDIdentityManagerImplV1.sol +++ b/src/WorldIDIdentityManagerImplV1.sol @@ -82,18 +82,15 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { VerifierLookupTable internal batchInsertionVerifiers; /// @notice The table of verifiers for verifying identity updates. - /// @dev preserved for storage reasons, no longer used VerifierLookupTable internal identityUpdateVerifiers; /// @notice The verifier instance needed for operating within the semaphore protocol. ISemaphoreVerifier internal semaphoreVerifier; /// @notice The interface of the bridge contract from L1 to supported target chains. - /// @dev preserved for storage reasons, no longer used IBridge internal _stateBridge; /// @notice Boolean flag to enable/disable the state bridge. - /// @dev preserved for storage reasons, no longer used bool internal _isStateBridgeEnabled; /// @notice The depth of the Semaphore merkle tree. @@ -116,10 +113,18 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { bool isValid; } + /// @notice Represents the kind of element that has not been provided in reduced form. + enum UnreducedElementType { + PreRoot, + IdentityCommitment, + PostRoot + } + /// @notice Represents the kind of change that is made to the root of the tree. enum TreeChange { Insertion, - Deletion + Deletion, + Update } /// @notice Represents the kinds of dependencies that can be updated. @@ -127,6 +132,7 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { StateBridge, InsertionVerifierLookupTable, DeletionVerifierLookupTable, + UpdateVerifierLookupTable, SemaphoreVerifier } @@ -144,12 +150,28 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { /// ERRORS /// /////////////////////////////////////////////////////////////////////////////// + /// @notice Thrown when encountering an element that should be reduced as a member of `Fr` but + /// is not. + /// @dev `r` in this case is given by `SNARK_SCALAR_FIELD`. + /// + /// @param elementType The kind of element that was encountered unreduced. + /// @param element The value of that element. + error UnreducedElement(UnreducedElementType elementType, uint256 element); + /// @notice Thrown when trying to execute a privileged action without being the contract /// manager. /// /// @param user The user that attempted the action that they were not authorised for. error Unauthorized(address user); + /// @notice Thrown when one or more of the identity commitments to be inserted is invalid. + /// + /// @param index The index in the array of identity commitments where the invalid commitment was + /// found. + /// @dev This error is no longer in use as we now verify the commitments off-chain within the circuit + /// no need to check for reduced elements or invalid commitments. + error InvalidCommitment(uint256 index); + /// @notice Thrown when the provided proof cannot be verified for the accompanying inputs. error ProofValidationFailure(); @@ -159,11 +181,24 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { /// @param latestRoot The actual latest root at the time of the transaction. error NotLatestRoot(uint256 providedRoot, uint256 latestRoot); + /// @notice Thrown when attempting to enable the bridge when it is already enabled. + error StateBridgeAlreadyEnabled(); + + /// @notice Thrown when attempting to disable the bridge when it is already disabled. + error StateBridgeAlreadyDisabled(); + + /// @notice Thrown when attempting to set the state bridge address to the zero address. + error InvalidStateBridgeAddress(); + /// @notice Thrown when Semaphore tree depth is not supported. /// /// @param depth Passed tree depth. error UnsupportedTreeDepth(uint8 depth); + /// @notice Thrown when the inputs to `removeIdentities` do not match in + /// length. + error MismatchedInputLengths(); + /////////////////////////////////////////////////////////////////////////////// /// EVENTS /// /////////////////////////////////////////////////////////////////////////////// @@ -185,6 +220,12 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { Dependency indexed kind, address indexed oldAddress, address indexed newAddress ); + /// @notice Emitted when the state bridge is enabled or disabled. + /// + /// @param isEnabled Set to `true` if the event comes from the state bridge being enabled, + /// `false` otherwise. + event StateBridgeStateChange(bool indexed isEnabled); + /// @notice Emitted when the root history expiry time is changed. /// /// @param oldExpiryTime The expiry time prior to the change. @@ -302,6 +343,8 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { /// provided inputs. /// @custom:reverts VerifierLookupTable.NoSuchVerifier If the batch sizes doesn't match a known /// verifier. + /// @custom:reverts VerifierLookupTable.BatchTooLarge If the batch size exceeds the maximum + /// batch size. function registerIdentities( uint256[8] calldata insertionProof, uint256 preRoot, From 0f92d6ca9dbfa1837ba1c450d696362827e0c195 Mon Sep 17 00:00:00 2001 From: "dcbuilder.eth" Date: Wed, 27 Sep 2023 21:30:12 +0200 Subject: [PATCH 20/20] add dev comments --- src/WorldIDIdentityManagerImplV1.sol | 20 +++++++++++++++----- src/WorldIDIdentityManagerImplV2.sol | 3 +-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/WorldIDIdentityManagerImplV1.sol b/src/WorldIDIdentityManagerImplV1.sol index a42afa7..5c69a23 100644 --- a/src/WorldIDIdentityManagerImplV1.sol +++ b/src/WorldIDIdentityManagerImplV1.sol @@ -82,12 +82,14 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { VerifierLookupTable internal batchInsertionVerifiers; /// @notice The table of verifiers for verifying identity updates. + /// @dev preserved for storage reasons, no longer used VerifierLookupTable internal identityUpdateVerifiers; /// @notice The verifier instance needed for operating within the semaphore protocol. ISemaphoreVerifier internal semaphoreVerifier; /// @notice The interface of the bridge contract from L1 to supported target chains. + /// @dev preserved for storage reasons, no longer used IBridge internal _stateBridge; /// @notice Boolean flag to enable/disable the state bridge. @@ -114,6 +116,8 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { } /// @notice Represents the kind of element that has not been provided in reduced form. + /// @dev preserved for ABI backwards compatibility with V1, no longer used + /// all elements come out reduced from the circuit enum UnreducedElementType { PreRoot, IdentityCommitment, @@ -121,10 +125,10 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { } /// @notice Represents the kind of change that is made to the root of the tree. + /// @dev TreeChange.Update preserved for ABI backwards compatibility with V1, no longer used enum TreeChange { Insertion, - Deletion, - Update + Deletion } /// @notice Represents the kinds of dependencies that can be updated. @@ -132,7 +136,6 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { StateBridge, InsertionVerifierLookupTable, DeletionVerifierLookupTable, - UpdateVerifierLookupTable, SemaphoreVerifier } @@ -156,6 +159,8 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { /// /// @param elementType The kind of element that was encountered unreduced. /// @param element The value of that element. + /// @dev preserved for ABI backwards compatibility with V1, no longer used, + /// all elements come out reduced from the circuit error UnreducedElement(UnreducedElementType elementType, uint256 element); /// @notice Thrown when trying to execute a privileged action without being the contract @@ -170,6 +175,8 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { /// found. /// @dev This error is no longer in use as we now verify the commitments off-chain within the circuit /// no need to check for reduced elements or invalid commitments. + /// @dev preserved for ABI backwards compatibility with V1, no longer used, + /// all elements are validated by the circuit error InvalidCommitment(uint256 index); /// @notice Thrown when the provided proof cannot be verified for the accompanying inputs. @@ -182,12 +189,15 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { error NotLatestRoot(uint256 providedRoot, uint256 latestRoot); /// @notice Thrown when attempting to enable the bridge when it is already enabled. + /// @dev preserved for ABI backwards compatibility with V1, no longer used error StateBridgeAlreadyEnabled(); /// @notice Thrown when attempting to disable the bridge when it is already disabled. + /// @dev preserved for ABI backwards compatibility with V1, no longer used error StateBridgeAlreadyDisabled(); /// @notice Thrown when attempting to set the state bridge address to the zero address. + /// @dev preserved for ABI backwards compatibility with V1, no longer used error InvalidStateBridgeAddress(); /// @notice Thrown when Semaphore tree depth is not supported. @@ -197,6 +207,7 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { /// @notice Thrown when the inputs to `removeIdentities` do not match in /// length. + /// @dev preserved for ABI backwards compatibility with V1, no longer used error MismatchedInputLengths(); /////////////////////////////////////////////////////////////////////////////// @@ -224,6 +235,7 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { /// /// @param isEnabled Set to `true` if the event comes from the state bridge being enabled, /// `false` otherwise. + /// @dev preserved for ABI backwards compatibility with V1, no longer used event StateBridgeStateChange(bool indexed isEnabled); /// @notice Emitted when the root history expiry time is changed. @@ -343,8 +355,6 @@ contract WorldIDIdentityManagerImplV1 is WorldIDImpl, IWorldID { /// provided inputs. /// @custom:reverts VerifierLookupTable.NoSuchVerifier If the batch sizes doesn't match a known /// verifier. - /// @custom:reverts VerifierLookupTable.BatchTooLarge If the batch size exceeds the maximum - /// batch size. function registerIdentities( uint256[8] calldata insertionProof, uint256 preRoot, diff --git a/src/WorldIDIdentityManagerImplV2.sol b/src/WorldIDIdentityManagerImplV2.sol index 5880f8f..2064b2d 100644 --- a/src/WorldIDIdentityManagerImplV2.sol +++ b/src/WorldIDIdentityManagerImplV2.sol @@ -51,6 +51,7 @@ contract WorldIDIdentityManagerImplV2 is WorldIDIdentityManagerImplV1 { VerifierLookupTable internal batchDeletionVerifiers; /// @notice Initializes the V2 implementation contract. + /// @param _batchUpdateVerifiers The table of verifiers for verifying batch identity deletions. /// @dev Must be called exactly once /// @dev This is marked `reinitializer()` to allow for updated initialisation steps when working /// with upgrades based upon this contract. Be aware that there are only 256 (zero-indexed) @@ -91,8 +92,6 @@ contract WorldIDIdentityManagerImplV2 is WorldIDIdentityManagerImplV1 { /// provided inputs. /// @custom:reverts VerifierLookupTable.NoSuchVerifier If the batch sizes doesn't match a known /// verifier. - /// @custom:reverts VerifierLookupTable.BatchTooLarge If the batch size exceeds the maximum - /// batch size. function deleteIdentities( uint256[8] calldata deletionProof, uint32 batchSize,