diff --git a/audits/zellic_audit_october_2024_remediation.pdf b/audits/zellic_audit_october_2024_remediation.pdf new file mode 100644 index 0000000..776bd2b Binary files /dev/null and b/audits/zellic_audit_october_2024_remediation.pdf differ diff --git a/src/BLS.sol b/src/BLS.sol index 79f2827..7807909 100644 --- a/src/BLS.sol +++ b/src/BLS.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.23; -// TODO(Gas golf these vs inline precompiles) import {ModexpInverse, ModexpSqrt} from "./ModExp.sol"; /// @title Boneh–Lynn–Shacham (BLS) signature scheme on Barreto-Naehrig 254 bit curve (BN-254) @@ -10,6 +9,7 @@ import {ModexpInverse, ModexpSqrt} from "./ModExp.sol"; /// @dev Adapted from https://github.com/thehubbleproject/hubble-contracts /// @dev Leveraging additional documentation from https://github.com/kevincharm/bls-bn254/blob/master/contracts/BLS.sol /// @dev A long form article: https://hackmd.io/@liangcc/bls-solidity +/// @dev This contract has been audited by Zellic, and implements all remediations of that report. library BLS { // Field order of BN254 curve uint256 private constant N = 21888242871839275222246405745257275088696311157297823662689037894645226208583; @@ -57,12 +57,11 @@ library BLS { /// @param signature The signature to verify (G1 point) /// @param pubkey The public key (G2 point) /// @param message The message that was signed (G1 point) - /// @return pairingSuccess True if the pairing check succeeds - /// @return callSuccess True if the precompile call to verify the signature succeeds + /// @return success True if the pairing check succeeds and if the precompile call to verify the signature succeeds function verifySingle(uint256[2] memory signature, uint256[4] memory pubkey, uint256[2] memory message) internal view - returns (bool pairingSuccess, bool callSuccess) + returns (bool) { // Prepare input for the pairing check uint256[12] memory input = [ @@ -80,12 +79,13 @@ library BLS { pubkey[2] ]; // Use the SNARKV precompile to verify the pairing + bool callSuccess; uint256[1] memory out; // slither-disable-next-line assembly assembly { callSuccess := staticcall(sub(gas(), 2000), 8, input, 384, out, 0x20) } - return (out[0] != 0, callSuccess); + return (callSuccess && (out[0] != 0)); } /// @notice Hash a message to a point on the BN254 G1 curve @@ -126,14 +126,17 @@ library BLS { } } - /// @notice Check if a given public key is valid (i.e., on the curve) + /// @notice Check if a given public key is valid (i.e., on the curve AND in + /// the correct r-torsion). The subgroup membership check critically involves + /// identifying if $[r]Q = O$ for a point $Q$ on the curve defined over the + /// quadratic extension, and $O$ the point at infinity. /// @param publicKey The public key to check /// @return True if the public key is valid - function isValidPublicKey(uint256[4] memory publicKey) internal pure returns (bool) { + function isValidPublicKey(uint256[4] memory publicKey) internal view returns (bool) { if ((publicKey[0] >= N) || (publicKey[1] >= N) || (publicKey[2] >= N || (publicKey[3] >= N))) { return false; } else { - return isOnCurveG2(publicKey); + return isElementOfG2(publicKey); } } @@ -155,57 +158,41 @@ library BLS { } } - /// @notice Check if a point is on the G2 curve + /// @notice Check if a point is in G2, namely the r-torsion of the elliptic curve over the quadratic extension. Note + /// that this necessitates curve membership, which is also checked by the precompile. The verification of a public key + /// should not be done often given the current expected lifetime of Warlock public keys, which should mitigate + /// the gas of the precompile static call. /// @param point The point to check - /// @return isOnCurve True if the point is on the curve - function isOnCurveG2(uint256[4] memory point) internal pure returns (bool isOnCurve) { + /// @return bool True if the point is in the r-torsion G2 + function isElementOfG2(uint256[4] memory point) internal view returns (bool) { + uint256[6] memory input = [0, 0, point[1], point[0], point[3], point[2]]; + bool callSuccess; + uint256[1] memory out; // slither-disable-next-line assembly assembly { - // x0, x1 - let t0 := mload(point) - let t1 := mload(add(point, 32)) - // x0 ^ 2 - let t2 := mulmod(t0, t0, N) - // x1 ^ 2 - let t3 := mulmod(t1, t1, N) - // 3 * x0 ^ 2 - let t4 := add(add(t2, t2), t2) - // 3 * x1 ^ 2 - let t5 := addmod(add(t3, t3), t3, N) - // x0 * (x0 ^ 2 - 3 * x1 ^ 2) - t2 := mulmod(add(t2, sub(N, t5)), t0, N) - // x1 * (3 * x0 ^ 2 - x1 ^ 2) - t3 := mulmod(add(t4, sub(N, t3)), t1, N) - - // x ^ 3 + b - t0 := addmod(t2, 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5, N) - t1 := addmod(t3, 0x009713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d2, N) - - // y0, y1 - t2 := mload(add(point, 64)) - t3 := mload(add(point, 96)) - // y ^ 2 - t4 := mulmod(addmod(t2, t3, N), addmod(t2, sub(N, t3), N), N) - t3 := mulmod(shl(1, t2), t3, N) - - // y ^ 2 == x ^ 3 + b - isOnCurve := and(eq(t0, t4), eq(t1, t3)) + callSuccess := staticcall(sub(gas(), 2000), 8, input, 192, out, 0x20) } + return (callSuccess && (out[0] == 1)); } - /// @notice Compute the square root of a field element + /// @notice Compute the square root of a field element. Note that + /// because we accept inputs larger than the modulus, simply doing + /// the check `mulmod(x, x, N) == xx` is ambiguous, as this will always be false + /// for $xx >= N$. We therefore check for comparison against $xx % N$, as this + /// creates a boolean purely indicative of the presence of a valid moduluar square + /// root mod N. /// @param xx The element to compute the square root of /// @return x The square root /// @return hasRoot True if the square root exists - function sqrt(uint256 xx) internal pure returns (uint256 x, bool hasRoot) { + function sqrt(uint256 xx) internal view returns (uint256 x, bool hasRoot) { x = ModexpSqrt.run(xx); - hasRoot = mulmod(x, x, N) == xx; + hasRoot = mulmod(x, x, N) == (xx % N); } /// @notice Compute the modular multiplicative inverse of a field element /// @param a The element to invert /// @return The inverse of a - function inverse(uint256 a) internal pure returns (uint256) { + function inverse(uint256 a) internal view returns (uint256) { return ModexpInverse.run(a); } @@ -243,7 +230,7 @@ library BLS { /// @param message Message to expand function expandMsgTo96(bytes memory dst, bytes memory message) internal pure returns (bytes memory) { uint256 domainLen = dst.length; - if (domainLen > 255) { + if ((domainLen > 255) || (domainLen == 0)) { revert InvalidDSTLength(dst); } bytes memory zpad = new bytes(136); @@ -292,22 +279,16 @@ library BLS { uint256 x3 = addmod(Z, mulmod(C4, mulmod(tv8, tv8, N), N), N); bool hasRoot; - uint256 gx; - if (legendre(g(x1)) == 1) { - p[0] = x1; - gx = g(x1); - (p[1], hasRoot) = sqrt(gx); - if (!hasRoot) revert MapToPointFailed(gx); - } else if (legendre(g(x2)) == 1) { + p[0] = x1; + (p[1], hasRoot) = sqrt(g(p[0])); + if (!hasRoot) { p[0] = x2; - gx = g(x2); - (p[1], hasRoot) = sqrt(gx); - if (!hasRoot) revert MapToPointFailed(gx); - } else { - p[0] = x3; - gx = g(x3); - (p[1], hasRoot) = sqrt(gx); - if (!hasRoot) revert MapToPointFailed(gx); + (p[1], hasRoot) = sqrt(g(p[0])); + if (!hasRoot) { + p[0] = x3; + (p[1], hasRoot) = sqrt(g(p[0])); + if (!hasRoot) revert MapToPointFailed(p[1]); + } } if (sgn0(u) != sgn0(p[1])) { p[1] = N - p[1]; @@ -326,56 +307,4 @@ library BLS { function sgn0(uint256 x) private pure returns (uint256) { return x % 2; } - - /// @notice Compute the Legendre symbol of a field element - /// @param u The field element - /// @return 1 if u is a quadratic residue, -1 if not, or 0 if u = 0 (mod p) - function legendre(uint256 u) private view returns (int8) { - uint256 x = expModLegendre(u); - if (x == N - 1) { - return -1; - } - if (x != 0 && x != 1) { - revert MapToPointFailed(u); - } - return int8(int256(x)); - } - - /// @notice Compute (u^((N-1)/2) mod N) using the EXPMOD precompile - /// @dev This is cheaper than an addchain for exponent (N-1)/2 - /// @param u The base - /// @return output The result of the modular exponentiation - function expModLegendre(uint256 u) private view returns (uint256 output) { - bytes memory input = new bytes(192); - bool success; - // slither-disable-next-line assembly - assembly { - let p := add(input, 32) - mstore(p, 32) // len(u) - p := add(p, 32) - mstore(p, 32) // len(exp) - p := add(p, 32) - mstore(p, 32) // len(mod) - p := add(p, 32) - mstore(p, u) // u - p := add(p, 32) - mstore(p, C5) // (N-1)/2 - p := add(p, 32) - mstore(p, N) // N - - success := - staticcall( - gas(), - 5, - add(input, 32), - 192, - 0x00, // scratch space <- result - 32 - ) - output := mload(0x00) // output <- result - } - if (!success) { - revert ModExpFailed(u, C5, N); - } - } } diff --git a/src/ModExp.sol b/src/ModExp.sol index b5251b7..164c78a 100644 --- a/src/ModExp.sol +++ b/src/ModExp.sol @@ -1,654 +1,71 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.23; +pragma solidity ^0.8; /** * @dev This file contains two libraries: ModexpInverse and ModexpSqrt - * Both use optimized addition chain methods for efficient computation + * Both use the `modexp` precompile at 0x05 for efficient computation * of modular exponentiation operations on the BN254 curve's base field. + * It should be noted that these two libraries accept inputs that are + * larger than the modulus, and reduces accordingly. */ /** * @title Compute Inverse by Modular Exponentiation - * @notice Compute $input^(N - 2) mod N$ using Addition Chain method. + * @notice Compute base^(N - 2) mod N$. * Where N = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 - * and N - 2 = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd45 - * @dev the function body is generated with the modified addchain script - * see https://github.com/kobigurk/addchain/commit/2c37a2ace567a9bdc680b4e929c94aaaa3ec700f + * and N - 2 = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd45. + * It should be noted here that the inverse function itself works by the fact that, + * by Fermat's little theorem, $x^{-1} mod N = x^{N-2} mod N$, and by this merit, + * consistent with the specification of `inv0` from RFC 9380, an input of 0 will + * return 0 from this calculation, despite the modular inverse of 0 not existing, + * consistent with the specification of EIP-198 that defines this precompile. */ library ModexpInverse { - function run(uint256 t2) internal pure returns (uint256 t0) { + function run(uint256 base) internal view returns (uint256 result) { + bool success; // slither-disable-next-line assembly assembly { - let n := 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 - t0 := mulmod(t2, t2, n) - let t5 := mulmod(t0, t2, n) - let t1 := mulmod(t5, t0, n) - let t3 := mulmod(t5, t5, n) - let t8 := mulmod(t1, t0, n) - let t4 := mulmod(t3, t5, n) - let t6 := mulmod(t3, t1, n) - t0 := mulmod(t3, t3, n) - let t7 := mulmod(t8, t3, n) - t3 := mulmod(t4, t3, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t5, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t2, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t2, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t8, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t8, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t2, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t8, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t2, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t5, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t7, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t1, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t5, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t8, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t1, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t2, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t6, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t7, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t1, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t5, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t1, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t5, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t6, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t6, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t1, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t8, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t6, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t1, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t4, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t6, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t2, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t8, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t8, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t1, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t2, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t7, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t3, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t2, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t2, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t5, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t6, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t5, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t5, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t3, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t4, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t3, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t1, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t2, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t1, n) + let memPtr := mload(0x40) + mstore(memPtr, 0x20) + mstore(add(memPtr, 0x20), 0x20) + mstore(add(memPtr, 0x40), 0x20) + mstore(add(memPtr, 0x60), base) + mstore(add(memPtr, 0x80), 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd45) + mstore(add(memPtr, 0xa0), 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47) + success := staticcall(sub(gas(), 2000), 0x05, memPtr, 0xc0, memPtr, 0x20) + result := mload(memPtr) } } } /** * @title Compute Square Root by Modular Exponentiation - * @notice Compute $input^{(N + 1) / 4} mod N$ using Addition Chain method. + * @notice Compute $input^{(N + 1) / 4} mod N$. * Where N = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 * and (N + 1) / 4 = 0xc19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52 + * It should be noted that if the base's Legendre symbol is 1, $s^2 mod N = x$, yet if + * it is -1, $s$ is not a modular sqrt of x, though $x$ also does not have a sqrt, as $s$ + * is a sqrt of $-x$. This fact, combined with the acceptance of inputs larger that the + * modulus means that the check `mulmod(x, x, N) == xx`, where `x = ModexpSqrt.run(xx)` + * will always be false if `xx >= N`, or if there is no sqrt existing mod N. Checks + * on this point are done in the main `sqrt` function in `BLS.sol`, and therefore + * users should only interact with that function, not this library itself, so that the + * user need not be aware of this subtle point. */ library ModexpSqrt { - function run(uint256 t6) internal pure returns (uint256 t0) { + function run(uint256 base) internal view returns (uint256 result) { + bool success; // slither-disable-next-line assembly assembly { - let n := 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 - - t0 := mulmod(t6, t6, n) - let t4 := mulmod(t0, t6, n) - let t2 := mulmod(t4, t0, n) - let t3 := mulmod(t4, t4, n) - let t8 := mulmod(t2, t0, n) - let t1 := mulmod(t3, t4, n) - let t5 := mulmod(t3, t2, n) - t0 := mulmod(t3, t3, n) - let t7 := mulmod(t8, t3, n) - t3 := mulmod(t1, t3, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t4, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t6, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t6, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t8, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t8, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t6, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t8, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t6, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t4, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t7, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t2, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t4, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t8, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t2, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t6, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t5, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t7, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t2, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t4, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t2, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t4, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t5, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t5, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t2, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t8, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t5, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t2, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t1, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t5, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t6, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t8, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t8, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t2, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t6, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t7, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t3, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t6, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t6, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t4, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t5, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t4, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t4, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t3, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t1, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t3, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t2, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t0, n) - t0 := mulmod(t0, t1, n) - t0 := mulmod(t0, t0, n) + let memPtr := mload(0x40) + mstore(memPtr, 0x20) + mstore(add(memPtr, 0x20), 0x20) + mstore(add(memPtr, 0x40), 0x20) + mstore(add(memPtr, 0x60), base) + mstore(add(memPtr, 0x80), 0xc19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52) + mstore(add(memPtr, 0xa0), 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47) + success := staticcall(sub(gas(), 2000), 0x05, memPtr, 0xc0, memPtr, 0x20) + result := mload(memPtr) } } } diff --git a/test/solbls.unit.t.sol b/test/solbls.unit.t.sol index 75450d2..87eabe2 100644 --- a/test/solbls.unit.t.sol +++ b/test/solbls.unit.t.sol @@ -142,15 +142,6 @@ contract BLSUnitTest is Test { } } - // Check that E'(Fp2) reference points that are not in the r-torsion are correctly rejected from G2 - function testFail_E2noG2() public noGasMetering { - for (uint256 i = 0; i < 1000; i++) { - uint256[] memory iq = e2_non_g2[i]; - uint256[4] memory iqOw = [iq[0], iq[1], iq[2], iq[3]]; - assert(BLS.isValidPublicKey(iqOw) == false); - } - } - // Test SvdW (Shallue-van de Woestijne) algorithm implementation function testSVDW() public noGasMetering { for (uint256 i = 0; i < 1000; i++) { @@ -173,31 +164,16 @@ contract BLSUnitTest is Test { uint256[4] memory iqTwoOw = [iqTwo[0], iqTwo[1], iqTwo[2], iqTwo[3]]; assert(BLS.isValidPublicKey(iqTwoOw)); assert(BLS.isValidSignature(iqOw)); - (bool pairingSuccess, bool callSuccess) = BLS.verifySingle(iqOw, iqTwoOw, pt); - if (callSuccess) { - assert(pairingSuccess); - } else { - assert(pairingSuccess == false); - } + assert(BLS.verifySingle(iqOw, iqTwoOw, pt)); } } // Check internal consistency of the library using E2 points not in G2 - function testLibConsistentTwo() public noGasMetering { - uint256[2] memory pt = BLS.hashToPoint(bytes(domain), bytes("Hello world!")); + function testRejectInvalidSubgroupG2() public noGasMetering { for (uint256 i = 0; i < 1000; i++) { - uint256[] memory iq = g1_signatures[i]; - uint256[] memory iqTwo = e2_non_g2[i]; - uint256[2] memory iqOw = [iq[0], iq[1]]; - uint256[4] memory iqTwoOw = [iqTwo[0], iqTwo[1], iqTwo[2], iqTwo[3]]; - assert(BLS.isValidPublicKey(iqTwoOw)); - assert(BLS.isValidSignature(iqOw)); - (bool pairingSuccess, bool callSuccess) = BLS.verifySingle(iqOw, iqTwoOw, pt); - if (callSuccess) { - assert(pairingSuccess); - } else { - assert(pairingSuccess == false); - } + uint256[] memory iq = e2_non_g2[i]; + uint256[4] memory iqOw = [iq[0], iq[1], iq[2], iq[3]]; + assertFalse(BLS.isValidPublicKey(iqOw)); } } }