From 5929f8090d48c98a7b9ac56c36378335e4009456 Mon Sep 17 00:00:00 2001 From: Alex <18387287+wadealexc@users.noreply.github.com> Date: Tue, 7 Nov 2023 11:22:29 -0500 Subject: [PATCH] chore: move BitmapUtils and BN254 libraries to middleware (#55) * chore: move BitmapUtils and BN254 libraries to middleware * fix: actually commit migrated files * chore: update submodule --- lib/eigenlayer-contracts | 2 +- src/BLSOperatorStateRetriever.sol | 3 +- src/BLSPubkeyRegistry.sol | 3 +- src/BLSPubkeyRegistryStorage.sol | 3 +- src/BLSPublicKeyCompendium.sol | 3 +- src/BLSRegistryCoordinatorWithIndices.sol | 5 +- src/BLSSignatureChecker.sol | 7 +- src/IndexRegistry.sol | 1 - src/ServiceManagerBase.sol | 10 +- src/StakeRegistry.sol | 4 +- src/interfaces/IBLSPubkeyRegistry.sol | 2 +- src/interfaces/IBLSPublicKeyCompendium.sol | 2 +- src/interfaces/IBLSRegistry.sol | 2 +- .../IBLSRegistryCoordinatorWithIndices.sol | 2 +- src/interfaces/IBLSSignatureChecker.sol | 2 +- src/libraries/BN254.sol | 346 ++++++++++++++++++ src/libraries/BitmapUtils.sol | 283 ++++++++++++++ test/ffi/BLSSignatureCheckerFFI.t.sol | 4 +- test/ffi/util/G2Operations.sol | 2 +- test/harnesses/BitmapUtilsWrapper.sol | 2 +- test/mocks/BLSPublicKeyCompendiumMock.sol | 2 +- test/unit/StakeRegistryUnit.t.sol | 2 +- test/utils/BLSMockAVSDeployer.sol | 4 +- test/utils/MockAVSDeployer.sol | 4 +- test/utils/Operators.sol | 2 +- test/utils/ProofParsing.sol | 2 +- 26 files changed, 671 insertions(+), 33 deletions(-) create mode 100644 src/libraries/BN254.sol create mode 100644 src/libraries/BitmapUtils.sol diff --git a/lib/eigenlayer-contracts b/lib/eigenlayer-contracts index 1a8f17fa..bccae530 160000 --- a/lib/eigenlayer-contracts +++ b/lib/eigenlayer-contracts @@ -1 +1 @@ -Subproject commit 1a8f17fad68a1cd09ba2adf0df0ba8a903410f34 +Subproject commit bccae530de8bcbf17cfd3e0a04c7b8b13679d34d diff --git a/src/BLSOperatorStateRetriever.sol b/src/BLSOperatorStateRetriever.sol index 85656e4c..7b891923 100644 --- a/src/BLSOperatorStateRetriever.sol +++ b/src/BLSOperatorStateRetriever.sol @@ -5,7 +5,8 @@ import "src/interfaces/IStakeRegistry.sol"; import "src/interfaces/IBLSPubkeyRegistry.sol"; import "src/interfaces/IIndexRegistry.sol"; import "src/interfaces/IBLSRegistryCoordinatorWithIndices.sol"; -import "eigenlayer-contracts/src/contracts/libraries/BitmapUtils.sol"; + +import "src/libraries/BitmapUtils.sol"; /** * @title BLSOperatorStateRetriever with view functions that allow to retrieve the state of an AVSs registry system. diff --git a/src/BLSPubkeyRegistry.sol b/src/BLSPubkeyRegistry.sol index 34875bd6..f98af12b 100644 --- a/src/BLSPubkeyRegistry.sol +++ b/src/BLSPubkeyRegistry.sol @@ -1,8 +1,9 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity =0.8.12; +import "src/libraries/BN254.sol"; + import "src/BLSPubkeyRegistryStorage.sol"; -import "eigenlayer-contracts/src/contracts/libraries/BN254.sol"; contract BLSPubkeyRegistry is BLSPubkeyRegistryStorage { using BN254 for BN254.G1Point; diff --git a/src/BLSPubkeyRegistryStorage.sol b/src/BLSPubkeyRegistryStorage.sol index 22d4751a..10e2fe51 100644 --- a/src/BLSPubkeyRegistryStorage.sol +++ b/src/BLSPubkeyRegistryStorage.sol @@ -4,7 +4,8 @@ pragma solidity =0.8.12; import "src/interfaces/IBLSPubkeyRegistry.sol"; import "src/interfaces/IRegistryCoordinator.sol"; import "src/interfaces/IBLSPublicKeyCompendium.sol"; -import "eigenlayer-contracts/src/contracts/libraries/BN254.sol"; + +import "src/libraries/BN254.sol"; import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; diff --git a/src/BLSPublicKeyCompendium.sol b/src/BLSPublicKeyCompendium.sol index a5a95159..88d47163 100644 --- a/src/BLSPublicKeyCompendium.sol +++ b/src/BLSPublicKeyCompendium.sol @@ -2,7 +2,8 @@ pragma solidity =0.8.12; import "src/interfaces/IBLSPublicKeyCompendium.sol"; -import "eigenlayer-contracts/src/contracts/libraries/BN254.sol"; + +import "src/libraries/BN254.sol"; /** * @title A shared contract for EigenLayer operators to register their BLS public keys. diff --git a/src/BLSRegistryCoordinatorWithIndices.sol b/src/BLSRegistryCoordinatorWithIndices.sol index 45787e0f..7b49eadf 100644 --- a/src/BLSRegistryCoordinatorWithIndices.sol +++ b/src/BLSRegistryCoordinatorWithIndices.sol @@ -6,9 +6,7 @@ import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; import "eigenlayer-contracts/src/contracts/interfaces/IPauserRegistry.sol"; import "eigenlayer-contracts/src/contracts/interfaces/ISlasher.sol"; -import "eigenlayer-contracts/src/contracts/libraries/BN254.sol"; import "eigenlayer-contracts/src/contracts/libraries/EIP1271SignatureUtils.sol"; -import "eigenlayer-contracts/src/contracts/libraries/BitmapUtils.sol"; import "eigenlayer-contracts/src/contracts/permissions/Pausable.sol"; import "src/interfaces/IBLSRegistryCoordinatorWithIndices.sol"; @@ -20,6 +18,9 @@ import "src/interfaces/IStakeRegistry.sol"; import "src/interfaces/IIndexRegistry.sol"; import "src/interfaces/IRegistryCoordinator.sol"; +import "src/libraries/BitmapUtils.sol"; +import "src/libraries/BN254.sol"; + /** diff --git a/src/BLSSignatureChecker.sol b/src/BLSSignatureChecker.sol index 734f9148..86d44680 100644 --- a/src/BLSSignatureChecker.sol +++ b/src/BLSSignatureChecker.sol @@ -1,13 +1,14 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity =0.8.12; -import "src/interfaces/IBLSSignatureChecker.sol"; -import "eigenlayer-contracts/src/contracts/libraries/BitmapUtils.sol"; -import "eigenlayer-contracts/src/contracts/libraries/BN254.sol"; import "src/interfaces/IRegistryCoordinator.sol"; import "src/interfaces/IStakeRegistry.sol"; import "src/interfaces/IBLSPubkeyRegistry.sol"; import "src/interfaces/IBLSRegistryCoordinatorWithIndices.sol"; +import "src/interfaces/IBLSSignatureChecker.sol"; + +import "src/libraries/BitmapUtils.sol"; +import "src/libraries/BN254.sol"; /** * @title Used for checking BLS aggregate signatures from the operators of a `BLSRegistry`. diff --git a/src/IndexRegistry.sol b/src/IndexRegistry.sol index 2453e509..3c814e98 100644 --- a/src/IndexRegistry.sol +++ b/src/IndexRegistry.sol @@ -2,7 +2,6 @@ pragma solidity =0.8.12; import "src/IndexRegistryStorage.sol"; -import "eigenlayer-contracts/src/contracts/libraries/BN254.sol"; /** * @title A `Registry` that keeps track of an ordered list of operators for each quorum diff --git a/src/ServiceManagerBase.sol b/src/ServiceManagerBase.sol index 2e1f1cbe..ae300c43 100644 --- a/src/ServiceManagerBase.sol +++ b/src/ServiceManagerBase.sol @@ -3,13 +3,15 @@ pragma solidity ^0.8.9; import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; -import "src/BLSSignatureChecker.sol"; -import "src/interfaces/IBLSRegistryCoordinatorWithIndices.sol"; -import "src/interfaces/IServiceManager.sol"; -import "eigenlayer-contracts/src/contracts/permissions/Pausable.sol"; import "eigenlayer-contracts/src/contracts/interfaces/IPauserRegistry.sol"; import "eigenlayer-contracts/src/contracts/interfaces/ISlasher.sol"; +import "eigenlayer-contracts/src/contracts/permissions/Pausable.sol"; + +import "src/interfaces/IBLSRegistryCoordinatorWithIndices.sol"; +import "src/interfaces/IServiceManager.sol"; + +import "src/BLSSignatureChecker.sol"; /** * @title Base implementation of `IServiceManager` interface, designed to be inherited from by more complex ServiceManagers. diff --git a/src/StakeRegistry.sol b/src/StakeRegistry.sol index 1dd6d9d0..5d32852a 100644 --- a/src/StakeRegistry.sol +++ b/src/StakeRegistry.sol @@ -1,10 +1,12 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity =0.8.12; -import "eigenlayer-contracts/src/contracts/libraries/BitmapUtils.sol"; import "src/interfaces/IServiceManager.sol"; import "src/interfaces/IStakeRegistry.sol"; import "src/interfaces/IRegistryCoordinator.sol"; + +import "src/libraries/BitmapUtils.sol"; + import "src/StakeRegistryStorage.sol"; import {VoteWeigherBase} from "src/VoteWeigherBase.sol"; diff --git a/src/interfaces/IBLSPubkeyRegistry.sol b/src/interfaces/IBLSPubkeyRegistry.sol index 42ec2251..f0fa5ec2 100644 --- a/src/interfaces/IBLSPubkeyRegistry.sol +++ b/src/interfaces/IBLSPubkeyRegistry.sol @@ -2,7 +2,7 @@ pragma solidity =0.8.12; import {IRegistry} from "src/interfaces/IRegistry.sol"; -import {BN254} from "eigenlayer-contracts/src/contracts/libraries/BN254.sol"; +import {BN254} from "src/libraries/BN254.sol"; /** * @title Minimal interface for a registry that keeps track of aggregate operator public keys for among many quorums. diff --git a/src/interfaces/IBLSPublicKeyCompendium.sol b/src/interfaces/IBLSPublicKeyCompendium.sol index 51705d57..3a6d6ff6 100644 --- a/src/interfaces/IBLSPublicKeyCompendium.sol +++ b/src/interfaces/IBLSPublicKeyCompendium.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.5.0; -import {BN254}from"eigenlayer-contracts/src/contracts/libraries/BN254.sol"; +import {BN254} from "src/libraries/BN254.sol"; /** * @title Minimal interface for the `BLSPublicKeyCompendium` contract. diff --git a/src/interfaces/IBLSRegistry.sol b/src/interfaces/IBLSRegistry.sol index 95509aec..8cecc073 100644 --- a/src/interfaces/IBLSRegistry.sol +++ b/src/interfaces/IBLSRegistry.sol @@ -2,7 +2,7 @@ pragma solidity >=0.5.0; import {IQuorumRegistry} from "./IQuorumRegistry.sol"; -import {BN254} from "eigenlayer-contracts/src/contracts/libraries/BN254.sol"; +import {BN254} from "src/libraries/BN254.sol"; /** diff --git a/src/interfaces/IBLSRegistryCoordinatorWithIndices.sol b/src/interfaces/IBLSRegistryCoordinatorWithIndices.sol index 821f00f1..f8ad8452 100644 --- a/src/interfaces/IBLSRegistryCoordinatorWithIndices.sol +++ b/src/interfaces/IBLSRegistryCoordinatorWithIndices.sol @@ -6,7 +6,7 @@ import {IRegistryCoordinator} from "src/interfaces/IRegistryCoordinator.sol"; import {IStakeRegistry} from "src/interfaces/IStakeRegistry.sol"; import {IBLSPubkeyRegistry} from "src/interfaces/IBLSPubkeyRegistry.sol"; import {IIndexRegistry} from "src/interfaces/IIndexRegistry.sol"; -import {BN254} from "eigenlayer-contracts/src/contracts/libraries/BN254.sol"; +import {BN254} from "src/libraries/BN254.sol"; /** * @title Minimal interface for the `IBLSStakeRegistryCoordinator` contract. diff --git a/src/interfaces/IBLSSignatureChecker.sol b/src/interfaces/IBLSSignatureChecker.sol index 131f9cee..bf098acc 100644 --- a/src/interfaces/IBLSSignatureChecker.sol +++ b/src/interfaces/IBLSSignatureChecker.sol @@ -5,7 +5,7 @@ import {IBLSRegistryCoordinatorWithIndices} from "src/interfaces/IBLSRegistryCoo import {IBLSPubkeyRegistry} from "src/interfaces/IBLSPubkeyRegistry.sol"; import {IRegistryCoordinator} from "src/interfaces/IRegistryCoordinator.sol"; import {IStakeRegistry} from "src/interfaces/IStakeRegistry.sol"; -import {BN254} from "eigenlayer-contracts/src/contracts/libraries/BN254.sol"; +import {BN254} from "src/libraries/BN254.sol"; /** * @title Used for checking BLS aggregate signatures from the operators of a EigenLayer AVS with the RegistryCoordinator/BLSPubkeyRegistry/StakeRegistry architechture. diff --git a/src/libraries/BN254.sol b/src/libraries/BN254.sol new file mode 100644 index 00000000..42703bda --- /dev/null +++ b/src/libraries/BN254.sol @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: BUSL-1.1 AND MIT +// several functions are taken or adapted from https://github.com/HarryR/solcrypto/blob/master/contracts/altbn128.sol (MIT license): +// 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. + +// The remainder of the code is written by LayrLabs Inc. and is under the BUSL-1.1 license + +pragma solidity =0.8.12; + +/** + * @title Library for operations on the BN254 elliptic curve. + * @author Layr Labs, Inc. + * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service + * @notice Contains BN254 parameters, common operations (addition, scalar mul, pairing), and BLS signature functionality. + */ +library BN254 { + // modulus for the underlying field F_p of the elliptic curve + uint256 internal constant FP_MODULUS = + 21888242871839275222246405745257275088696311157297823662689037894645226208583; + // modulus for the underlying field F_r of the elliptic curve + uint256 internal constant FR_MODULUS = + 21888242871839275222246405745257275088548364400416034343698204186575808495617; + + struct G1Point { + uint256 X; + uint256 Y; + } + + // Encoding of field elements is: X[1] * i + X[0] + struct G2Point { + uint256[2] X; + uint256[2] Y; + } + + function generatorG1() internal pure returns (G1Point memory) { + return G1Point(1, 2); + } + + // generator of group G2 + /// @dev Generator point in F_q2 is of the form: (x0 + ix1, y0 + iy1). + uint256 internal constant G2x1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 internal constant G2x0 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 internal constant G2y1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; + uint256 internal constant G2y0 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; + + /// @notice returns the G2 generator + /// @dev mind the ordering of the 1s and 0s! + /// this is because of the (unknown to us) convention used in the bn254 pairing precompile contract + /// "Elements a * i + b of F_p^2 are encoded as two elements of F_p, (a, b)." + /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-197.md#encoding + function generatorG2() internal pure returns (G2Point memory) { + return G2Point([G2x1, G2x0], [G2y1, G2y0]); + } + + // negation of the generator of group G2 + /// @dev Generator point in F_q2 is of the form: (x0 + ix1, y0 + iy1). + uint256 internal constant nG2x1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 internal constant nG2x0 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 internal constant nG2y1 = 17805874995975841540914202342111839520379459829704422454583296818431106115052; + uint256 internal constant nG2y0 = 13392588948715843804641432497768002650278120570034223513918757245338268106653; + + function negGeneratorG2() internal pure returns (G2Point memory) { + return G2Point([nG2x1, nG2x0], [nG2y1, nG2y0]); + } + + bytes32 internal constant powersOfTauMerkleRoot = + 0x22c998e49752bbb1918ba87d6d59dd0e83620a311ba91dd4b2cc84990b31b56f; + + /** + * @param p Some point in G1. + * @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, FP_MODULUS - (p.Y % FP_MODULUS)); + } + } + + /** + * @return r 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; + bool success; + + // solium-disable-next-line security/no-inline-assembly + assembly { + success := staticcall(sub(gas(), 2000), 6, input, 0x80, r, 0x40) + // Use "invalid" to make gas estimation work + switch success + case 0 { + invalid() + } + } + + require(success, "ec-add-failed"); + } + + /** + * @notice an optimized ecMul implementation that takes O(log_2(s)) ecAdds + * @param p the point to multiply + * @param s the scalar to multiply by + * @dev this function is only safe to use if the scalar is 9 bits or less + */ + function scalar_mul_tiny(BN254.G1Point memory p, uint16 s) internal view returns (BN254.G1Point memory) { + require(s < 2**9, "scalar-too-large"); + + // if s is 1 return p + if(s == 1) { + return p; + } + + // the accumulated product to return + BN254.G1Point memory acc = BN254.G1Point(0, 0); + // the 2^n*p to add to the accumulated product in each iteration + BN254.G1Point memory p2n = p; + // value of most significant bit + uint16 m = 1; + // index of most significant bit + uint8 i = 0; + + //loop until we reach the most significant bit + while(s > m){ + unchecked { + // if the current bit is 1, add the 2^n*p to the accumulated product + if ((s >> i) & 1 == 1) { + acc = plus(acc, p2n); + } + // double the 2^n*p for the next iteration + p2n = plus(p2n, p2n); + + // increment the index and double the value of the most significant bit + m <<= 1; + ++i; + } + } + + // return the accumulated product + return acc; + } + + /** + * @return r 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, 0x60, r, 0x40) + // Use "invalid" to make gas estimation work + switch success + case 0 { + invalid() + } + } + require(success, "ec-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 + ) internal view returns (bool) { + G1Point[2] memory p1 = [a1, b1]; + G2Point[2] memory p2 = [a2, b2]; + + uint256[12] memory input; + + for (uint256 i = 0; i < 2; 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]; + } + + uint256[1] memory out; + bool success; + + // solium-disable-next-line security/no-inline-assembly + assembly { + success := staticcall(sub(gas(), 2000), 8, input, mul(12, 0x20), out, 0x20) + // Use "invalid" to make gas estimation work + switch success + case 0 { + invalid() + } + } + + require(success, "pairing-opcode-failed"); + + return out[0] != 0; + } + + /** + * @notice This function is functionally the same as pairing(), however it specifies a gas limit + * the user can set, as a precompile may use the entire gas budget if it reverts. + */ + function safePairing( + G1Point memory a1, + G2Point memory a2, + G1Point memory b1, + G2Point memory b2, + uint256 pairingGas + ) internal view returns (bool, bool) { + G1Point[2] memory p1 = [a1, b1]; + G2Point[2] memory p2 = [a2, b2]; + + uint256[12] memory input; + + for (uint256 i = 0; i < 2; 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]; + } + + uint256[1] memory out; + bool success; + + // solium-disable-next-line security/no-inline-assembly + assembly { + success := staticcall(pairingGas, 8, input, mul(12, 0x20), out, 0x20) + } + + //Out is the output of the pairing precompile, either 0 or 1 based on whether the two pairings are equal. + //Success is true if the precompile actually goes through (aka all inputs are valid) + + return (success, out[0] != 0); + } + + /// @return the keccak256 hash of the G1 Point + /// @dev used for BLS signatures + function hashG1Point(BN254.G1Point memory pk) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(pk.X, pk.Y)); + } + + /// @return the keccak256 hash of the G2 Point + /// @dev used for BLS signatures + function hashG2Point( + BN254.G2Point memory pk + ) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(pk.X[0], pk.X[1], pk.Y[0], pk.Y[1])); + } + + /** + * @notice adapted from https://github.com/HarryR/solcrypto/blob/master/contracts/altbn128.sol + */ + function hashToG1(bytes32 _x) internal view returns (G1Point memory) { + uint256 beta = 0; + uint256 y = 0; + + uint256 x = uint256(_x) % FP_MODULUS; + + while (true) { + (beta, y) = findYFromX(x); + + // y^2 == beta + if( beta == mulmod(y, y, FP_MODULUS) ) { + return G1Point(x, y); + } + + x = addmod(x, 1, FP_MODULUS); + } + return G1Point(0, 0); + } + + /** + * Given X, find Y + * + * where y = sqrt(x^3 + b) + * + * Returns: (x^3 + b), y + */ + function findYFromX(uint256 x) internal view returns (uint256, uint256) { + // beta = (x^3 + b) % p + uint256 beta = addmod(mulmod(mulmod(x, x, FP_MODULUS), x, FP_MODULUS), 3, FP_MODULUS); + + // y^2 = x^3 + b + // this acts like: y = sqrt(beta) = beta^((p+1) / 4) + uint256 y = expMod(beta, 0xc19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52, FP_MODULUS); + + return (beta, y); + } + + function expMod(uint256 _base, uint256 _exponent, uint256 _modulus) internal view returns (uint256 retval) { + bool success; + uint256[1] memory output; + uint[6] memory input; + input[0] = 0x20; // baseLen = new(big.Int).SetBytes(getData(input, 0, 32)) + input[1] = 0x20; // expLen = new(big.Int).SetBytes(getData(input, 32, 32)) + input[2] = 0x20; // modLen = new(big.Int).SetBytes(getData(input, 64, 32)) + input[3] = _base; + input[4] = _exponent; + input[5] = _modulus; + assembly { + success := staticcall(sub(gas(), 2000), 5, input, 0xc0, output, 0x20) + // Use "invalid" to make gas estimation work + switch success + case 0 { + invalid() + } + } + require(success, "BN254.expMod: call failure"); + return output[0]; + } +} diff --git a/src/libraries/BitmapUtils.sol b/src/libraries/BitmapUtils.sol new file mode 100644 index 00000000..1db53985 --- /dev/null +++ b/src/libraries/BitmapUtils.sol @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity =0.8.12; + +/** + * @title Library for Bitmap utilities such as converting between an array of bytes and a bitmap and finding the number of 1s in a bitmap. + * @author Layr Labs, Inc. + */ +library BitmapUtils { + /** + * @notice Byte arrays are meant to contain unique bytes. + * If the array length exceeds 256, then it's impossible for all entries to be unique. + * This constant captures the max allowed array length (inclusive, i.e. 256 is allowed). + */ + uint256 internal constant MAX_BYTE_ARRAY_LENGTH = 256; + + /** + * @notice Converts an array of bytes into a bitmap. + * @param bytesArray The array of bytes to convert/compress into a bitmap. + * @return The resulting bitmap. + * @dev Each byte in the input is processed as indicating a single bit to flip in the bitmap + * @dev This function will also revert if the `bytesArray` input contains any duplicate entries (i.e. duplicate bytes). + */ + function bytesArrayToBitmap(bytes memory bytesArray) internal pure returns (uint256) { + // sanity-check on input. a too-long input would fail later on due to having duplicate entry(s) + require(bytesArray.length <= MAX_BYTE_ARRAY_LENGTH, + "BitmapUtils.bytesArrayToBitmap: bytesArray is too long"); + + // return empty bitmap early if length of array is 0 + if (bytesArray.length == 0) { + return uint256(0); + } + + // initialize the empty bitmap, to be built inside the loop + uint256 bitmap; + // initialize an empty uint256 to be used as a bitmask inside the loop + uint256 bitMask; + + // perform the 0-th loop iteration with the duplicate check *omitted* (since it is unnecessary / will always pass) + // construct a single-bit mask from the numerical value of the 0th byte of the array, and immediately add it to the bitmap + bitmap = uint256(1 << uint8(bytesArray[0])); + + // loop through each byte in the array to construct the bitmap + for (uint256 i = 1; i < bytesArray.length; ++i) { + // construct a single-bit mask from the numerical value of the next byte out of the array + bitMask = uint256(1 << uint8(bytesArray[i])); + // check that the entry is not a repeat + require(bitmap & bitMask == 0, "BitmapUtils.bytesArrayToBitmap: repeat entry in bytesArray"); + // add the entry to the bitmap + bitmap = (bitmap | bitMask); + } + return bitmap; + } + + /** + * @notice Converts an ordered array of bytes into a bitmap. + * @param orderedBytesArray The array of bytes to convert/compress into a bitmap. Must be in strictly ascending order. + * @return The resulting bitmap. + * @dev Each byte in the input is processed as indicating a single bit to flip in the bitmap. + * @dev This function will eventually revert in the event that the `orderedBytesArray` is not properly ordered (in ascending order). + * @dev This function will also revert if the `orderedBytesArray` input contains any duplicate entries (i.e. duplicate bytes). + */ + function orderedBytesArrayToBitmap(bytes memory orderedBytesArray) internal pure returns (uint256) { + // sanity-check on input. a too-long input would fail later on due to having duplicate entry(s) + require(orderedBytesArray.length <= MAX_BYTE_ARRAY_LENGTH, + "BitmapUtils.orderedBytesArrayToBitmap: orderedBytesArray is too long"); + + // return empty bitmap early if length of array is 0 + if (orderedBytesArray.length == 0) { + return uint256(0); + } + + // initialize the empty bitmap, to be built inside the loop + uint256 bitmap; + // initialize an empty uint256 to be used as a bitmask inside the loop + uint256 bitMask; + + // perform the 0-th loop iteration with the ordering check *omitted* (since it is unnecessary / will always pass) + // construct a single-bit mask from the numerical value of the 0th byte of the array, and immediately add it to the bitmap + bitmap = uint256(1 << uint8(orderedBytesArray[0])); + + // loop through each byte in the array to construct the bitmap + for (uint256 i = 1; i < orderedBytesArray.length; ++i) { + // construct a single-bit mask from the numerical value of the next byte of the array + bitMask = uint256(1 << uint8(orderedBytesArray[i])); + // check strictly ascending array ordering by comparing the mask to the bitmap so far (revert if mask isn't greater than bitmap) + require(bitMask > bitmap, "BitmapUtils.orderedBytesArrayToBitmap: orderedBytesArray is not ordered"); + // add the entry to the bitmap + bitmap = (bitmap | bitMask); + } + return bitmap; + } + + /** + * @notice Converts an ordered array of bytes into a bitmap. Optimized, Yul-heavy version of `orderedBytesArrayToBitmap`. + * @param orderedBytesArray The array of bytes to convert/compress into a bitmap. Must be in strictly ascending order. + * @return bitmap The resulting bitmap. + * @dev Each byte in the input is processed as indicating a single bit to flip in the bitmap. + * @dev This function will eventually revert in the event that the `orderedBytesArray` is not properly ordered (in ascending order). + * @dev This function will also revert if the `orderedBytesArray` input contains any duplicate entries (i.e. duplicate bytes). + */ + function orderedBytesArrayToBitmap_Yul(bytes calldata orderedBytesArray) internal pure returns (uint256 bitmap) { + // sanity-check on input. a too-long input would fail later on due to having duplicate entry(s) + require(orderedBytesArray.length <= MAX_BYTE_ARRAY_LENGTH, + "BitmapUtils.orderedBytesArrayToBitmap: orderedBytesArray is too long"); + + // return empty bitmap early if length of array is 0 + if (orderedBytesArray.length == 0) { + return uint256(0); + } + + assembly { + // get first entry in bitmap (single byte => single-bit mask) + bitmap := + shl( + // extract single byte to get the correct value for the left shift + shr( + 248, + calldataload( + orderedBytesArray.offset + ) + ), + 1 + ) + // loop through other entries (byte by byte) + for { let i := 1 } lt(i, orderedBytesArray.length) { i := add(i, 1) } { + // first construct the single-bit mask by left-shifting a '1' + let bitMask := + shl( + // extract single byte to get the correct value for the left shift + shr( + 248, + calldataload( + add( + orderedBytesArray.offset, + i + ) + ) + ), + 1 + ) + // check strictly ascending ordering by comparing the mask to the bitmap so far (revert if mask isn't greater than bitmap) + // TODO: revert with a good message instead of using `revert(0, 0)` + // REFERENCE: require(bitMask > bitmap, "BitmapUtils.orderedBytesArrayToBitmap: orderedBytesArray is not ordered"); + if iszero(gt(bitMask, bitmap)) { revert(0, 0) } + // update the bitmap by adding the single bit in the mask + bitmap := or(bitmap, bitMask) + } + } + } + + /** + * @notice Converts an array of bytes into a bitmap. Optimized, Yul-heavy version of `bytesArrayToBitmap`. + * @param bytesArray The array of bytes to convert/compress into a bitmap. + * @return bitmap The resulting bitmap. + * @dev Each byte in the input is processed as indicating a single bit to flip in the bitmap. + * @dev This function will eventually revert in the event that the `bytesArray` is not properly ordered (in ascending order). + * @dev This function will also revert if the `bytesArray` input contains any duplicate entries (i.e. duplicate bytes). + */ + function bytesArrayToBitmap_Yul(bytes calldata bytesArray) internal pure returns (uint256 bitmap) { + // sanity-check on input. a too-long input would fail later on due to having duplicate entry(s) + require(bytesArray.length <= MAX_BYTE_ARRAY_LENGTH, + "BitmapUtils.bytesArrayToBitmap: bytesArray is too long"); + + // return empty bitmap early if length of array is 0 + if (bytesArray.length == 0) { + return uint256(0); + } + + assembly { + // get first entry in bitmap (single byte => single-bit mask) + bitmap := + shl( + // extract single byte to get the correct value for the left shift + shr( + 248, + calldataload( + bytesArray.offset + ) + ), + 1 + ) + // loop through other entries (byte by byte) + for { let i := 1 } lt(i, bytesArray.length) { i := add(i, 1) } { + // first construct the single-bit mask by left-shifting a '1' + let bitMask := + shl( + // extract single byte to get the correct value for the left shift + shr( + 248, + calldataload( + add( + bytesArray.offset, + i + ) + ) + ), + 1 + ) + // check against duplicates by comparing the bitmask and bitmap (revert if the bitmap already contains the entry, i.e. bitmap & bitMask != 0) + // TODO: revert with a good message instead of using `revert(0, 0)` + // REFERENCE: require(bitmap & bitMask == 0, "BitmapUtils.bytesArrayToBitmap: repeat entry in bytesArray"); + if gt(and(bitmap, bitMask), 0) { revert(0, 0) } + // update the bitmap by adding the single bit in the mask + bitmap := or(bitmap, bitMask) + } + } + } + + /** + * @notice Utility function for checking if a bytes array is strictly ordered, in ascending order. + * @param bytesArray the bytes array of interest + * @return Returns 'true' if the array is ordered in strictly ascending order, and 'false' otherwise. + * @dev This function returns 'true' for the edge case of the `bytesArray` having zero length. + * It also returns 'false' early for arrays with length in excess of MAX_BYTE_ARRAY_LENGTH (i.e. so long that they cannot be strictly ordered) + */ + function isArrayStrictlyAscendingOrdered(bytes calldata bytesArray) internal pure returns (bool) { + // return 'false' early for too-long (i.e. unorderable) arrays + if (bytesArray.length > MAX_BYTE_ARRAY_LENGTH) { + return false; + } + + // return 'true' early if length of array is 0 + if (bytesArray.length == 0) { + return true; + } + + // initialize an empty byte object, to be re-used inside the loop + bytes1 singleByte; + + // perform the 0-th loop iteration with the ordering check *omitted* (otherwise it will break with an out-of-bounds error) + // pull the 0th byte out of the array + singleByte = bytesArray[0]; + + // loop through each byte in the array to construct the bitmap + for (uint256 i = 1; i < bytesArray.length; ++i) { + // check if the entry is *less than or equal to* the previous entry. if it is, then the array isn't strictly ordered! + if (uint256(uint8(bytesArray[i])) <= uint256(uint8(singleByte))) { + return false; + } + // pull the next byte out of the array + singleByte = bytesArray[i]; + } + return true; + } + + /** + * @notice Converts a bitmap into an array of bytes. + * @param bitmap The bitmap to decompress/convert to an array of bytes. + * @return bytesArray The resulting bitmap array of bytes. + * @dev Each byte in the input is processed as indicating a single bit to flip in the bitmap + */ + function bitmapToBytesArray(uint256 bitmap) internal pure returns (bytes memory bytesArray) { + // initialize an empty uint256 to be used as a bitmask inside the loop + uint256 bitMask; + // loop through each index in the bitmap to construct the array + for (uint256 i = 0; i < 256; ++i) { + // construct a single-bit mask for the i-th bit + bitMask = uint256(1 << i); + // check if the i-th bit is flipped in the bitmap + if (bitmap & bitMask != 0) { + // if the i-th bit is flipped, then add a byte encoding the value 'i' to the `bytesArray` + bytesArray = bytes.concat(bytesArray, bytes1(uint8(i))); + } + } + return bytesArray; + } + + /// @return count number of ones in binary representation of `n` + function countNumOnes(uint256 n) internal pure returns (uint16) { + uint16 count = 0; + while (n > 0) { + n &= (n - 1); + count++; + } + return count; + } + + // @notice returns 'true' if `numberToCheckForInclusion` is in `bitmap` and 'false' otherwise. + function numberIsInBitmap(uint256 bitmap, uint8 numberToCheckForInclusion) internal pure returns (bool) { + return (((bitmap >> numberToCheckForInclusion) & 1) == 1); + } +} \ No newline at end of file diff --git a/test/ffi/BLSSignatureCheckerFFI.t.sol b/test/ffi/BLSSignatureCheckerFFI.t.sol index 1511dcaa..dce7b281 100644 --- a/test/ffi/BLSSignatureCheckerFFI.t.sol +++ b/test/ffi/BLSSignatureCheckerFFI.t.sol @@ -4,9 +4,9 @@ pragma solidity =0.8.12; import {G2Operations} from "test/ffi/util/G2Operations.sol"; import {MockAVSDeployer} from "test/utils/MockAVSDeployer.sol"; import {BLSSignatureChecker} from "src/BLSSignatureChecker.sol"; -import {BN254} from "eigenlayer-contracts/src/contracts/libraries/BN254.sol"; import {BLSOperatorStateRetriever} from "src/BLSOperatorStateRetriever.sol"; -import {BitmapUtils} from "eigenlayer-contracts/src/contracts/libraries/BitmapUtils.sol"; +import {BN254} from "src/libraries/BN254.sol"; +import {BitmapUtils} from "src/libraries/BitmapUtils.sol"; contract BLSSignatureCheckerFFITests is MockAVSDeployer, G2Operations { diff --git a/test/ffi/util/G2Operations.sol b/test/ffi/util/G2Operations.sol index bc4d67d8..488c30b2 100644 --- a/test/ffi/util/G2Operations.sol +++ b/test/ffi/util/G2Operations.sol @@ -3,7 +3,7 @@ pragma solidity =0.8.12; import "forge-std/Test.sol"; import "openzeppelin-contracts/contracts/utils/Strings.sol"; -import "eigenlayer-contracts/src/contracts/libraries/BN254.sol"; +import "src/libraries/BN254.sol"; contract G2Operations is Test { using Strings for uint256; diff --git a/test/harnesses/BitmapUtilsWrapper.sol b/test/harnesses/BitmapUtilsWrapper.sol index f0d3be24..3f4e6d8c 100644 --- a/test/harnesses/BitmapUtilsWrapper.sol +++ b/test/harnesses/BitmapUtilsWrapper.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity =0.8.12; -import "eigenlayer-contracts/src/contracts/libraries/BitmapUtils.sol"; +import "src/libraries/BitmapUtils.sol"; // wrapper around the BitmapUtils library that exposes the internal functions contract BitmapUtilsWrapper { diff --git a/test/mocks/BLSPublicKeyCompendiumMock.sol b/test/mocks/BLSPublicKeyCompendiumMock.sol index 1373ed4c..ee79d3fe 100644 --- a/test/mocks/BLSPublicKeyCompendiumMock.sol +++ b/test/mocks/BLSPublicKeyCompendiumMock.sol @@ -2,7 +2,7 @@ pragma solidity =0.8.12; import "src/interfaces/IBLSPublicKeyCompendium.sol"; -import "eigenlayer-contracts/src/contracts/libraries/BN254.sol"; +import "src/libraries/BN254.sol"; /** * @title A shared contract for EigenLayer operators to register their BLS public keys. * @author Layr Labs, Inc. diff --git a/test/unit/StakeRegistryUnit.t.sol b/test/unit/StakeRegistryUnit.t.sol index 64599d44..91e7e3fb 100644 --- a/test/unit/StakeRegistryUnit.t.sol +++ b/test/unit/StakeRegistryUnit.t.sol @@ -17,7 +17,7 @@ import {IIndexRegistry} from "src/interfaces/IIndexRegistry.sol"; import {IRegistryCoordinator} from "src/interfaces/IRegistryCoordinator.sol"; import {IBLSPubkeyRegistry} from "src/interfaces/IBLSPubkeyRegistry.sol"; -import {BitmapUtils} from "eigenlayer-contracts/src/contracts/libraries/BitmapUtils.sol"; +import {BitmapUtils} from "src/libraries/BitmapUtils.sol"; import {StrategyManagerMock} from "eigenlayer-contracts/src/test/mocks/StrategyManagerMock.sol"; import {EigenPodManagerMock} from "eigenlayer-contracts/src/test/mocks/EigenPodManagerMock.sol"; diff --git a/test/utils/BLSMockAVSDeployer.sol b/test/utils/BLSMockAVSDeployer.sol index 3e839f1e..5c935814 100644 --- a/test/utils/BLSMockAVSDeployer.sol +++ b/test/utils/BLSMockAVSDeployer.sol @@ -3,9 +3,9 @@ pragma solidity =0.8.12; import {BLSSignatureChecker} from "src/BLSSignatureChecker.sol"; import {MockAVSDeployer} from "test/utils/MockAVSDeployer.sol"; -import {BN254} from "eigenlayer-contracts/src/contracts/libraries/BN254.sol"; +import {BN254} from "src/libraries/BN254.sol"; import {BLSOperatorStateRetriever} from "src/BLSOperatorStateRetriever.sol"; -import {BitmapUtils} from "eigenlayer-contracts/src/contracts/libraries/BitmapUtils.sol"; +import {BitmapUtils} from "src/libraries/BitmapUtils.sol"; contract BLSMockAVSDeployer is MockAVSDeployer { using BN254 for BN254.G1Point; diff --git a/test/utils/MockAVSDeployer.sol b/test/utils/MockAVSDeployer.sol index 64611832..7f3981d7 100644 --- a/test/utils/MockAVSDeployer.sol +++ b/test/utils/MockAVSDeployer.sol @@ -10,8 +10,8 @@ import {PauserRegistry} from "eigenlayer-contracts/src/contracts/permissions/Pau import {IStrategyManager} from "eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol"; import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; -import {BitmapUtils} from "eigenlayer-contracts/src/contracts/libraries/BitmapUtils.sol"; -import {BN254} from "eigenlayer-contracts/src/contracts/libraries/BN254.sol"; +import {BitmapUtils} from "src/libraries/BitmapUtils.sol"; +import {BN254} from "src/libraries/BN254.sol"; import {BLSPublicKeyCompendium} from "src/BLSPublicKeyCompendium.sol"; import {BLSOperatorStateRetriever} from "src/BLSOperatorStateRetriever.sol"; diff --git a/test/utils/Operators.sol b/test/utils/Operators.sol index 9f7f6236..910165e7 100644 --- a/test/utils/Operators.sol +++ b/test/utils/Operators.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity =0.8.12; -import "eigenlayer-contracts/src/contracts/libraries/BN254.sol"; +import "src/libraries/BN254.sol"; import "forge-std/Test.sol"; import "forge-std/StdJson.sol"; diff --git a/test/utils/ProofParsing.sol b/test/utils/ProofParsing.sol index 54ced2d9..f73f74b8 100644 --- a/test/utils/ProofParsing.sol +++ b/test/utils/ProofParsing.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity =0.8.12; -import "eigenlayer-contracts/src/contracts/libraries/BN254.sol"; +import "src/libraries/BN254.sol"; import "forge-std/Test.sol"; import "forge-std/StdJson.sol";