From 5aff78706733ae7acf6115f83f8cc9e6e456da8c Mon Sep 17 00:00:00 2001 From: alrxy Date: Tue, 24 Dec 2024 05:16:40 +0400 Subject: [PATCH 01/21] fix: rm approvalselfregisteroperators and add power threshold to selfregisteroperator --- .../SelfRegisterEd25519Middleware.sol | 2 +- .../SelfRegisterMiddleware.sol | 2 +- .../SelfRegisterSqrtTaskMiddleware.sol | 2 +- .../operators/ApprovalRegisterOperators.sol | 110 ------------------ .../ForcePauseApprovalRegisterOperators.sol | 61 ---------- .../operators/SelfRegisterOperators.sol | 39 +++++-- .../operators/ISelfRegisterOperators.sol | 1 + 7 files changed, 33 insertions(+), 184 deletions(-) delete mode 100644 src/extensions/operators/ApprovalRegisterOperators.sol delete mode 100644 src/extensions/operators/ForcePauseApprovalRegisterOperators.sol diff --git a/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol index 8f64090..cdf77f0 100644 --- a/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol +++ b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol @@ -52,7 +52,7 @@ contract SelfRegisterEd25519Middleware is address owner ) internal initializer { __BaseMiddleware_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, reader); - __SelfRegisterOperators_init("SelfRegisterEd25519Middleware"); + __SelfRegisterOperators_init("SelfRegisterEd25519Middleware", 0); __OwnableAccessManager_init(owner); } } diff --git a/src/examples/self-register-network/SelfRegisterMiddleware.sol b/src/examples/self-register-network/SelfRegisterMiddleware.sol index d205782..ef3cedf 100644 --- a/src/examples/self-register-network/SelfRegisterMiddleware.sol +++ b/src/examples/self-register-network/SelfRegisterMiddleware.sol @@ -52,7 +52,7 @@ contract SelfRegisterMiddleware is address owner ) internal initializer { __BaseMiddleware_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptIn, reader); - __SelfRegisterOperators_init("SelfRegisterMiddleware"); + __SelfRegisterOperators_init("SelfRegisterMiddleware", 0); __OwnableAccessManager_init(owner); } } diff --git a/src/examples/sqrt-task-network/SelfRegisterSqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SelfRegisterSqrtTaskMiddleware.sol index ae6ed35..0874c9f 100644 --- a/src/examples/sqrt-task-network/SelfRegisterSqrtTaskMiddleware.sol +++ b/src/examples/sqrt-task-network/SelfRegisterSqrtTaskMiddleware.sol @@ -85,7 +85,7 @@ contract SelfRegisterSqrtTaskMiddleware is INetworkRegistry(networkRegistry).registerNetwork(); __BaseMiddleware_init(address(this), slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, reader); __OwnableAccessManager_init(owner); - __SelfRegisterOperators_init("SelfRegisterSqrtTaskMiddleware"); + __SelfRegisterOperators_init("SelfRegisterSqrtTaskMiddleware", 0); } function createTask(uint256 value, address validator) external returns (uint256 taskIndex) { diff --git a/src/extensions/operators/ApprovalRegisterOperators.sol b/src/extensions/operators/ApprovalRegisterOperators.sol deleted file mode 100644 index dda290e..0000000 --- a/src/extensions/operators/ApprovalRegisterOperators.sol +++ /dev/null @@ -1,110 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {SelfRegisterOperators} from "./SelfRegisterOperators.sol"; -import {IApprovalRegisterOperators} from "../../interfaces/extensions/operators/IApprovalRegisterOperators.sol"; -/** - * @title ApprovalRegisterOperators - * @notice Extends SelfRegisterOperators to add approval-based registration - */ - -abstract contract ApprovalRegisterOperators is SelfRegisterOperators, IApprovalRegisterOperators { - uint64 public constant ApprovalRegisterOperators_VERSION = 1; - - struct ApprovalRegisterOperatorsStorage { - RegistrationRequest[] requests; - } - - // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.ApprovalRegisterOperators")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant ApprovalRegisterOperators_STORAGE_LOCATION = - 0x8d3c0d900c3fcfbc53470fac03a90d5cf6aa7b77c3f1ed10e6c6bd4d192eaf00; - - function _getApprovalRegisterOperatorsStorage() private pure returns (ApprovalRegisterOperatorsStorage storage $) { - bytes32 location = ApprovalRegisterOperators_STORAGE_LOCATION; - assembly { - $.slot := location - } - } - - /** - * @inheritdoc IApprovalRegisterOperators - */ - function getRegistrationRequestCount() public view returns (uint256) { - return _getApprovalRegisterOperatorsStorage().requests.length; - } - - /** - * @inheritdoc IApprovalRegisterOperators - */ - function getRegistrationRequest( - uint256 index - ) public view returns (RegistrationRequest memory) { - return _getApprovalRegisterOperatorsStorage().requests[index]; - } - - /** - * @inheritdoc IApprovalRegisterOperators - */ - function registerOperator( - uint256 requestIndex - ) external checkAccess { - RegistrationRequest memory request = getRegistrationRequest(requestIndex); - _registerOperatorImpl(request.operator, request.key, request.vault); - ApprovalRegisterOperatorsStorage storage $ = _getApprovalRegisterOperatorsStorage(); - uint256 lastIndex = $.requests.length - 1; - if (requestIndex != lastIndex) { - $.requests[requestIndex] = $.requests[lastIndex]; - } - $.requests.pop(); - } - - /** - * @notice Override to prevent direct registration - */ - function registerOperator(bytes memory key, address vault, bytes memory signature) external virtual override { - revert DirectRegistrationNotAllowed(); - } - - /** - * @notice Override to prevent direct registration - */ - function registerOperator( - address operator, - bytes memory key, - address vault, - bytes memory signature, - bytes memory keySignature - ) public virtual override { - revert DirectRegistrationNotAllowed(); - } - - /** - * @inheritdoc IApprovalRegisterOperators - */ - function requestRegisterOperator(bytes memory key, address vault, bytes memory signature) external { - _verifyKey(msg.sender, key, signature); - ApprovalRegisterOperatorsStorage storage $ = _getApprovalRegisterOperatorsStorage(); - $.requests.push(RegistrationRequest({operator: msg.sender, vault: vault, key: key})); - } - - /** - * @inheritdoc IApprovalRegisterOperators - */ - function requestRegisterOperator( - address operator, - bytes memory key, - address vault, - bytes memory signature, - bytes memory keySignature - ) public { - SelfRegisterOperatorsStorage storage s = _getSelfRegisterOperatorsStorage(); - _verifyEIP712( - operator, - keccak256(abi.encode(REGISTER_OPERATOR_TYPEHASH, operator, keccak256(key), vault, s.nonces[operator]++)), - signature - ); - _verifyKey(operator, key, keySignature); - ApprovalRegisterOperatorsStorage storage $ = _getApprovalRegisterOperatorsStorage(); - $.requests.push(RegistrationRequest({operator: operator, vault: vault, key: key})); - } -} diff --git a/src/extensions/operators/ForcePauseApprovalRegisterOperators.sol b/src/extensions/operators/ForcePauseApprovalRegisterOperators.sol deleted file mode 100644 index 5ec1e29..0000000 --- a/src/extensions/operators/ForcePauseApprovalRegisterOperators.sol +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {ApprovalRegisterOperators} from "./ApprovalRegisterOperators.sol"; -import {SelfRegisterOperators} from "./SelfRegisterOperators.sol"; -import {ForcePauseSelfRegisterOperators} from "./ForcePauseSelfRegisterOperators.sol"; -import {BaseOperators} from "./BaseOperators.sol"; -/** - * @title ForcePauseSelfRegisterOperators - * @notice Extension of SelfRegisterOperators that allows authorized addresses to forcefully pause operators - * @dev Implements force pause functionality and prevents unpausing of force-paused operators - */ - -abstract contract ForcePauseApprovalRegisterOperators is ForcePauseSelfRegisterOperators, ApprovalRegisterOperators { - uint64 public constant ForcePauseApprovalRegisterOperators_VERSION = 1; - - function registerOperator( - bytes memory key, - address vault, - bytes memory signature - ) external override(ApprovalRegisterOperators, SelfRegisterOperators) { - revert DirectRegistrationNotAllowed(); - } - - // Override the registerOperator function to resolve ambiguity - function registerOperator( - address operator, - bytes memory key, - address vault, - bytes memory signature, - bytes memory keySignature - ) public override(ApprovalRegisterOperators, SelfRegisterOperators) { - revert DirectRegistrationNotAllowed(); - } - - function _beforeUnpauseOperator( - address operator - ) internal virtual override(ForcePauseSelfRegisterOperators, BaseOperators) { - super._beforeUnpauseOperator(operator); - } - - function _beforeUnpauseOperatorVault( - address operator, - address vault - ) internal virtual override(ForcePauseSelfRegisterOperators, BaseOperators) { - super._beforeUnpauseOperatorVault(operator, vault); - } - - function _beforeUnregisterOperator( - address operator - ) internal virtual override(ForcePauseSelfRegisterOperators, BaseOperators) { - super._beforeUnregisterOperator(operator); - } - - function _beforeUnregisterOperatorVault( - address operator, - address vault - ) internal virtual override(BaseOperators, ForcePauseSelfRegisterOperators) { - super._beforeUnregisterOperatorVault(operator, vault); - } -} diff --git a/src/extensions/operators/SelfRegisterOperators.sol b/src/extensions/operators/SelfRegisterOperators.sol index 385bd7d..ea7d4f9 100644 --- a/src/extensions/operators/SelfRegisterOperators.sol +++ b/src/extensions/operators/SelfRegisterOperators.sol @@ -35,6 +35,7 @@ abstract contract SelfRegisterOperators is BaseOperators, SigManager, EIP712Upgr keccak256("UnpauseOperatorVault(address operator,address vault,uint256 nonce)"); struct SelfRegisterOperatorsStorage { + uint256 minPower; mapping(address => uint256) nonces; } @@ -49,20 +50,21 @@ abstract contract SelfRegisterOperators is BaseOperators, SigManager, EIP712Upgr } } - function nonces( - address operator - ) public view returns (uint256) { - return _getSelfRegisterOperatorsStorage().nonces[operator]; - } - /** - * @notice Initializes the contract with EIP712 domain separator + * @notice Initializes the contract with EIP712 domain separator and minimum power threshold * @param name The name to use for the EIP712 domain separator + * @param _minPower The minimum power threshold */ - function __SelfRegisterOperators_init( - string memory name - ) internal onlyInitializing { + function __SelfRegisterOperators_init(string memory name, uint256 _minPower) internal onlyInitializing { __EIP712_init(name, "1"); + SelfRegisterOperatorsStorage storage $ = _getSelfRegisterOperatorsStorage(); + $.minPower = _minPower; + } + + function nonces( + address operator + ) public view returns (uint256) { + return _getSelfRegisterOperatorsStorage().nonces[operator]; } /** @@ -70,6 +72,7 @@ abstract contract SelfRegisterOperators is BaseOperators, SigManager, EIP712Upgr */ function registerOperator(bytes memory key, address vault, bytes memory signature) external virtual { _verifyKey(msg.sender, key, signature); + _checkMinPower(msg.sender, vault); _registerOperatorImpl(msg.sender, key, vault); } @@ -84,6 +87,7 @@ abstract contract SelfRegisterOperators is BaseOperators, SigManager, EIP712Upgr bytes memory keySignature ) public virtual { _verifyKey(operator, key, keySignature); + _checkMinPower(operator, vault); SelfRegisterOperatorsStorage storage $ = _getSelfRegisterOperatorsStorage(); _verifyEIP712( operator, @@ -265,6 +269,21 @@ abstract contract SelfRegisterOperators is BaseOperators, SigManager, EIP712Upgr _unpauseOperatorVaultImpl(operator, vault); } + function _checkMinPower(address operator, address vault) internal view { + SelfRegisterOperatorsStorage storage $ = _getSelfRegisterOperatorsStorage(); + address[] memory vaults = _activeVaults(); + uint160[] memory subnetworks = _activeSubnetworks(); + uint256 power = _getOperatorPower(operator, vaults, subnetworks); + if (address(vault) != address(0)) { + vaults = new address[](1); + vaults[0] = vault; + power += _getOperatorPower(operator, vaults, subnetworks); + } + if (power < $.minPower) { + revert NotEnoughPower(); + } + } + /** * @notice Verifies a key signature * @param operator The address of the operator diff --git a/src/interfaces/extensions/operators/ISelfRegisterOperators.sol b/src/interfaces/extensions/operators/ISelfRegisterOperators.sol index 9a6fe8b..c55c018 100644 --- a/src/interfaces/extensions/operators/ISelfRegisterOperators.sol +++ b/src/interfaces/extensions/operators/ISelfRegisterOperators.sol @@ -7,6 +7,7 @@ pragma solidity ^0.8.25; */ interface ISelfRegisterOperators { error InvalidSignature(); + error NotEnoughPower(); /** * @notice Returns the nonce for an operator address From 8aef438046d4dd37a0141c7bdd3fee201c537311 Mon Sep 17 00:00:00 2001 From: alrxy Date: Tue, 24 Dec 2024 05:22:01 +0400 Subject: [PATCH 02/21] chore: rm iapprovalregsiteroperators interface --- .../operators/IApprovalRegisterOperators.sol | 63 ------------------- 1 file changed, 63 deletions(-) delete mode 100644 src/interfaces/extensions/operators/IApprovalRegisterOperators.sol diff --git a/src/interfaces/extensions/operators/IApprovalRegisterOperators.sol b/src/interfaces/extensions/operators/IApprovalRegisterOperators.sol deleted file mode 100644 index 5aa416d..0000000 --- a/src/interfaces/extensions/operators/IApprovalRegisterOperators.sol +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -/** - * @title IApprovalRegisterOperators - * @notice Interface for approval-based operator registration - */ -interface IApprovalRegisterOperators { - struct RegistrationRequest { - address operator; - address vault; - bytes key; - } - - error DirectRegistrationNotAllowed(); - - /** - * @notice Get the total number of pending registration requests - * @return The number of requests - */ - function getRegistrationRequestCount() external view returns (uint256); - - /** - * @notice Get a specific registration request by index - * @param index The index of the request to retrieve - * @return The registration request details - */ - function getRegistrationRequest( - uint256 index - ) external view returns (RegistrationRequest memory); - - /** - * @notice Register an operator based on a pending request - * @param requestIndex The index of the request to register - */ - function registerOperator( - uint256 requestIndex - ) external; - - /** - * @notice Request registration as an operator - * @param key The operator's public key - * @param vault Optional vault address to associate - * @param signature Signature proving ownership of the key - */ - function requestRegisterOperator(bytes memory key, address vault, bytes memory signature) external; - - /** - * @notice Request registration on behalf of another operator - * @param operator The address of the operator to register - * @param key The operator's public key - * @param vault Optional vault address to associate - * @param signature EIP712 signature authorizing registration - * @param keySignature Signature proving ownership of the key - */ - function requestRegisterOperator( - address operator, - bytes memory key, - address vault, - bytes memory signature, - bytes memory keySignature - ) external; -} From c96f3ce01ccf2e01219f7d431e97f9d856402458 Mon Sep 17 00:00:00 2001 From: alrxy Date: Wed, 25 Dec 2024 12:02:58 +0400 Subject: [PATCH 03/21] wip: bls --- .../managers/keys/KeyManager256.sol | 65 ++- .../managers/keys/KeyManagerAddress.sol | 62 +- .../managers/keys/KeyManagerBLS.sol | 152 +++++ .../managers/keys/KeyManagerBytes.sol | 77 ++- src/extensions/managers/sigs/BLSSig.sol | 60 ++ src/libraries/BN254.sol | 328 +++++++++++ src/libraries/PauseableEnumerableSet.sol | 542 +++--------------- 7 files changed, 717 insertions(+), 569 deletions(-) create mode 100644 src/extensions/managers/keys/KeyManagerBLS.sol create mode 100644 src/extensions/managers/sigs/BLSSig.sol create mode 100644 src/libraries/BN254.sol diff --git a/src/extensions/managers/keys/KeyManager256.sol b/src/extensions/managers/keys/KeyManager256.sol index b0a39b2..e590039 100644 --- a/src/extensions/managers/keys/KeyManager256.sol +++ b/src/extensions/managers/keys/KeyManager256.sol @@ -12,19 +12,15 @@ import {PauseableEnumerableSet} from "../../../libraries/PauseableEnumerableSet. abstract contract KeyManager256 is KeyManager { uint64 public constant KeyManager256_VERSION = 1; - using PauseableEnumerableSet for PauseableEnumerableSet.Bytes32Set; + using PauseableEnumerableSet for PauseableEnumerableSet.Status; error DuplicateKey(); - error MaxDisabledKeysReached(); - - bytes32 private constant ZERO_BYTES32 = bytes32(0); - uint256 private constant MAX_DISABLED_KEYS = 1; + error PreviousKeySlashable(); struct KeyManager256Storage { - /// @notice Mapping from operator addresses to their keys - mapping(address => PauseableEnumerableSet.Bytes32Set) _keys; - /// @notice Mapping from keys to operator addresses - mapping(bytes32 => address) _keyToOperator; + mapping(address => bytes32) _key; + mapping(address => bytes32) _prevKey; + mapping(bytes32 => PauseableEnumerableSet.InnerAddress) _keyData; } // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.KeyManager256")) - 1)) & ~bytes32(uint256(0xff)) @@ -47,7 +43,7 @@ abstract contract KeyManager256 is KeyManager { bytes memory key ) public view override returns (address) { KeyManager256Storage storage $ = _getKeyManager256Storage(); - return $._keyToOperator[abi.decode(key, (bytes32))]; + return $._keyData[abi.decode(key, (bytes32))].value; } /** @@ -59,11 +55,16 @@ abstract contract KeyManager256 is KeyManager { address operator ) public view override returns (bytes memory) { KeyManager256Storage storage $ = _getKeyManager256Storage(); - bytes32[] memory active = $._keys[operator].getActive(getCaptureTimestamp()); - if (active.length == 0) { - return abi.encode(ZERO_BYTES32); + uint48 timestamp = getCaptureTimestamp(); + bytes32 key = $._key[operator]; + if (key != bytes32(0) && $._keyData[key].status.wasActiveAt(timestamp)) { + return abi.encode(key); + } + key = $._prevKey[operator]; + if (key != bytes32(0) && $._keyData[key].status.wasActiveAt(timestamp)) { + return abi.encode(key); } - return abi.encode(active[0]); + return abi.encode(bytes32(0)); } /** @@ -75,7 +76,7 @@ abstract contract KeyManager256 is KeyManager { function keyWasActiveAt(uint48 timestamp, bytes memory key_) public view override returns (bool) { KeyManager256Storage storage $ = _getKeyManager256Storage(); bytes32 key = abi.decode(key_, (bytes32)); - return $._keys[$._keyToOperator[key]].wasActiveAt(timestamp, key); + return $._keyData[key].status.wasActiveAt(timestamp); } /** @@ -89,31 +90,29 @@ abstract contract KeyManager256 is KeyManager { bytes32 key = abi.decode(key_, (bytes32)); uint48 timestamp = _now(); - if ($._keyToOperator[key] != address(0)) { + if ($._keyData[key].value != address(0)) { revert DuplicateKey(); } - // check if we have reached the max number of disabled keys - // this allow us to limit the number times we can change the key - if (key != ZERO_BYTES32 && $._keys[operator].length() > MAX_DISABLED_KEYS + 1) { - revert MaxDisabledKeysReached(); + bytes32 prevKey = $._prevKey[operator]; + if (prevKey != bytes32(0)) { + if (!$._keyData[prevKey].status.checkUnregister(timestamp, _SLASHING_WINDOW())) { + revert PreviousKeySlashable(); + } + delete $._keyData[prevKey]; } - if ($._keys[operator].length() > 0) { - // try to remove disabled keys - bytes32 prevKey = $._keys[operator].array[0].value; - if ($._keys[operator].checkUnregister(timestamp, _SLASHING_WINDOW(), prevKey)) { - $._keys[operator].unregister(timestamp, _SLASHING_WINDOW(), prevKey); - delete $._keyToOperator[prevKey]; - } else if ($._keys[operator].wasActiveAt(timestamp, prevKey)) { - $._keys[operator].pause(timestamp, prevKey); - } + bytes32 currentKey = $._key[operator]; + if (currentKey != bytes32(0)) { + $._keyData[currentKey].status.disable(timestamp); } - if (key != ZERO_BYTES32) { - // register the new key - $._keys[operator].register(timestamp, key); - $._keyToOperator[key] = operator; + $._prevKey[operator] = currentKey; + $._key[operator] = key; + + if (key != bytes32(0)) { + $._keyData[key].value = operator; + $._keyData[key].status.set(timestamp); } } } diff --git a/src/extensions/managers/keys/KeyManagerAddress.sol b/src/extensions/managers/keys/KeyManagerAddress.sol index fe889dc..0025824 100644 --- a/src/extensions/managers/keys/KeyManagerAddress.sol +++ b/src/extensions/managers/keys/KeyManagerAddress.sol @@ -12,18 +12,15 @@ import {PauseableEnumerableSet} from "../../../libraries/PauseableEnumerableSet. abstract contract KeyManagerAddress is KeyManager { uint64 public constant KeyManagerAddress_VERSION = 1; - using PauseableEnumerableSet for PauseableEnumerableSet.AddressSet; + using PauseableEnumerableSet for PauseableEnumerableSet.Status; error DuplicateKey(); - error MaxDisabledKeysReached(); - - uint256 private constant MAX_DISABLED_KEYS = 1; + error PreviousKeySlashable(); struct KeyManagerAddressStorage { - /// @notice Mapping from operator addresses to their keys - mapping(address => PauseableEnumerableSet.AddressSet) _keys; - /// @notice Mapping from keys to operator addresses - mapping(address => address) _keyToOperator; + mapping(address => address) _key; + mapping(address => address) _prevKey; + mapping(address => PauseableEnumerableSet.InnerAddress) _keyData; } // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.KeyManagerAddress")) - 1)) & ~bytes32(uint256(0xff)) @@ -46,7 +43,7 @@ abstract contract KeyManagerAddress is KeyManager { bytes memory key ) public view override returns (address) { KeyManagerAddressStorage storage $ = _getKeyManagerAddressStorage(); - return $._keyToOperator[abi.decode(key, (address))]; + return $._keyData[abi.decode(key, (address))].value; } /** @@ -58,11 +55,16 @@ abstract contract KeyManagerAddress is KeyManager { address operator ) public view override returns (bytes memory) { KeyManagerAddressStorage storage $ = _getKeyManagerAddressStorage(); - address[] memory active = $._keys[operator].getActive(getCaptureTimestamp()); - if (active.length == 0) { - return abi.encode(address(0)); + uint48 timestamp = getCaptureTimestamp(); + address key = $._key[operator]; + if (key != address(0) && $._keyData[key].status.wasActiveAt(timestamp)) { + return abi.encode(key); + } + key = $._prevKey[operator]; + if (key != address(0) && $._keyData[key].status.wasActiveAt(timestamp)) { + return abi.encode(key); } - return abi.encode(active[0]); + return abi.encode(address(0)); } /** @@ -74,7 +76,7 @@ abstract contract KeyManagerAddress is KeyManager { function keyWasActiveAt(uint48 timestamp, bytes memory key_) public view override returns (bool) { KeyManagerAddressStorage storage $ = _getKeyManagerAddressStorage(); address key = abi.decode(key_, (address)); - return $._keys[$._keyToOperator[key]].wasActiveAt(timestamp, key); + return $._keyData[key].status.wasActiveAt(timestamp); } /** @@ -88,31 +90,29 @@ abstract contract KeyManagerAddress is KeyManager { address key = abi.decode(key_, (address)); uint48 timestamp = _now(); - if ($._keyToOperator[key] != address(0)) { + if ($._keyData[key].value != address(0)) { revert DuplicateKey(); } - // check if we have reached the max number of disabled keys - // this allow us to limit the number times we can change the key - if (key != address(0) && $._keys[operator].length() > MAX_DISABLED_KEYS + 1) { - revert MaxDisabledKeysReached(); + address prevKey = $._prevKey[operator]; + if (prevKey != address(0)) { + if (!$._keyData[prevKey].status.checkUnregister(timestamp, _SLASHING_WINDOW())) { + revert PreviousKeySlashable(); + } + delete $._keyData[prevKey]; } - if ($._keys[operator].length() > 0) { - // try to remove disabled keys - address prevKey = address($._keys[operator].set.array[0].value); - if ($._keys[operator].checkUnregister(timestamp, _SLASHING_WINDOW(), prevKey)) { - $._keys[operator].unregister(timestamp, _SLASHING_WINDOW(), prevKey); - delete $._keyToOperator[prevKey]; - } else if ($._keys[operator].wasActiveAt(timestamp, prevKey)) { - $._keys[operator].pause(timestamp, prevKey); - } + address currentKey = $._key[operator]; + if (currentKey != address(0)) { + $._keyData[currentKey].status.disable(timestamp); } + $._prevKey[operator] = currentKey; + $._key[operator] = key; + if (key != address(0)) { - // register the new key - $._keys[operator].register(timestamp, key); - $._keyToOperator[key] = operator; + $._keyData[key].value = operator; + $._keyData[key].status.set(timestamp); } } } diff --git a/src/extensions/managers/keys/KeyManagerBLS.sol b/src/extensions/managers/keys/KeyManagerBLS.sol new file mode 100644 index 0000000..857f00a --- /dev/null +++ b/src/extensions/managers/keys/KeyManagerBLS.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {KeyManager} from "../../../managers/extendable/KeyManager.sol"; +import {PauseableEnumerableSet} from "../../../libraries/PauseableEnumerableSet.sol"; +import {BN254} from "../../../libraries/BN254.sol"; +import {BLSSig} from "../sigs/BLSSig.sol"; + +/** + * @title KeyManagerBLS + * @notice Manages storage and validation of operator keys using BLS G1 points + * @dev Extends KeyManager to provide key management functionality + */ +abstract contract KeyManagerBLS is KeyManager, BLSSig { + using BN254 for BN254.G1Point; + using PauseableEnumerableSet for PauseableEnumerableSet.Status; + + uint64 public constant KeyManagerBLS_VERSION = 1; + + error DuplicateKey(); + error PreviousKeySlashable(); + + struct KeyManagerBLSStorage { + mapping(address => BN254.G1Point) _key; + mapping(address => BN254.G1Point) _prevKey; + mapping(uint256 => uint48) _aggregatedKeyTimestamp; + mapping(uint256 => PauseableEnumerableSet.InnerAddress) _keyData; + BN254.G1Point _aggregatedKey; + } + + // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.KeyManagerBLS")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant KeyManagerBLSStorageLocation = + 0x4da47716e6090d5a5545e03387f4dac112d37cd069a5573bb81de8579bd9dc00; + + function _getKeyManagerBLSStorage() internal pure returns (KeyManagerBLSStorage storage s) { + bytes32 location = KeyManagerBLSStorageLocation; + assembly { + s.slot := location + } + } + + function verifyAggregate( + uint48 timestamp, + BN254.G1Point memory aggregateG1Key, + BN254.G2Point memory aggregateG2Key, + BN254.G1Point memory signature, + bytes32 messageHash, + BN254.G1Point[] memory nonSigningKeys + ) public view returns (bool) { + KeyManagerBLSStorage storage $ = _getKeyManagerBLSStorage(); + if ($._aggregatedKeyTimestamp[aggregateG1Key.X] != timestamp) { + return false; + } + BN254.G1Point memory aggregatedNonSigningKey = BN254.G1Point(0, 0); + for (uint256 i = 0; i < nonSigningKeys.length; i++) { + if (!$._keyData[nonSigningKeys[i].X].status.wasActiveAt(timestamp)) { + return false; + } + if (true) { + aggregatedNonSigningKey = aggregatedNonSigningKey.plus(nonSigningKeys[i]); + } + } + aggregateG1Key = aggregateG1Key.plus(aggregatedNonSigningKey.negate()); + return BLSSig.verify(aggregateG1Key, aggregateG2Key, signature, messageHash); + } + + /** + * @notice Gets the operator address associated with a key + * @param key The key to lookup + * @return The operator address that owns the key, or zero address if none + */ + function operatorByKey( + bytes memory key + ) public view override returns (address) { + KeyManagerBLSStorage storage $ = _getKeyManagerBLSStorage(); + BN254.G1Point memory g1Point = abi.decode(key, (BN254.G1Point)); + return $._keyData[g1Point.X].value; + } + + /** + * @notice Gets an operator's active key at the current capture timestamp + * @param operator The operator address to lookup + * @return The operator's active key encoded as bytes, or encoded zero bytes if none + */ + function operatorKey( + address operator + ) public view override returns (bytes memory) { + KeyManagerBLSStorage storage $ = _getKeyManagerBLSStorage(); + uint48 timestamp = getCaptureTimestamp(); + BN254.G1Point memory key = $._key[operator]; + if ((key.X != 0 || key.Y != 0) && $._keyData[key.X].status.wasActiveAt(timestamp)) { + return abi.encode(key); + } + key = $._prevKey[operator]; + if ((key.X != 0 || key.Y != 0) && $._keyData[key.X].status.wasActiveAt(timestamp)) { + return abi.encode(key); + } + return abi.encode(BN254.G1Point(0, 0)); + } + + /** + * @notice Checks if a key was active at a specific timestamp + * @param timestamp The timestamp to check + * @param key_ The key to check + * @return True if the key was active at the timestamp, false otherwise + */ + function keyWasActiveAt(uint48 timestamp, bytes memory key_) public view override returns (bool) { + KeyManagerBLSStorage storage $ = _getKeyManagerBLSStorage(); + BN254.G1Point memory key = abi.decode(key_, (BN254.G1Point)); + return $._keyData[key.X].status.wasActiveAt(timestamp); + } + + /** + * @notice Updates an operator's key + * @dev Handles key rotation by disabling old key and registering new one + * @param operator The operator address to update + * @param key_ The new key to register, encoded as bytes + */ + function _updateKey(address operator, bytes memory key_) internal override { + KeyManagerBLSStorage storage $ = _getKeyManagerBLSStorage(); + BN254.G1Point memory key = abi.decode(key_, (BN254.G1Point)); + uint48 timestamp = _now(); + + if ($._keyData[key.X].value != address(0)) { + revert DuplicateKey(); + } + + BN254.G1Point memory prevKey = $._prevKey[operator]; + if (prevKey.X != 0 || prevKey.Y != 0) { + if (!$._keyData[prevKey.X].status.checkUnregister(timestamp, _SLASHING_WINDOW())) { + revert PreviousKeySlashable(); + } + delete $._keyData[prevKey.X]; + $.aggregateKey = $.aggregateKey.plus(prevKey.negate()); + } + + BN254.G1Point memory currentKey = $._key[operator]; + if (currentKey.X != 0 || currentKey.Y != 0) { + $._keyData[currentKey.X].status.disable(timestamp); + $.aggregateKey = $.aggregateKey.plus(currentKey.negate()); + } + + $._prevKey[operator] = currentKey; + $._key[operator] = key; + + if (key.X != 0 || key.Y != 0) { + $._keyData[key.X].value = operator; + $._keyData[key.X].status.set(timestamp); + $.aggregateKey = $.aggregateKey.plus(key); + } + } +} diff --git a/src/extensions/managers/keys/KeyManagerBytes.sol b/src/extensions/managers/keys/KeyManagerBytes.sol index dbc3839..57e0d64 100644 --- a/src/extensions/managers/keys/KeyManagerBytes.sol +++ b/src/extensions/managers/keys/KeyManagerBytes.sol @@ -6,24 +6,21 @@ import {PauseableEnumerableSet} from "../../../libraries/PauseableEnumerableSet. /** * @title KeyManagerBytes - * @notice Manages storage and validation of operator keys + * @notice Manages storage and validation of operator keys using bytes values * @dev Extends KeyManager to provide key management functionality */ abstract contract KeyManagerBytes is KeyManager { uint64 public constant KeyManagerBytes_VERSION = 1; - using PauseableEnumerableSet for PauseableEnumerableSet.BytesSet; + using PauseableEnumerableSet for PauseableEnumerableSet.Status; error DuplicateKey(); - error MaxDisabledKeysReached(); - - uint256 private constant MAX_DISABLED_KEYS = 1; - bytes private constant ZERO_BYTES = ""; - bytes32 private constant ZERO_BYTES_HASH = keccak256(""); + error PreviousKeySlashable(); struct KeyManagerBytesStorage { - mapping(address => PauseableEnumerableSet.BytesSet) _keys; - mapping(bytes => address) _keyToOperator; + mapping(address => bytes) _key; + mapping(address => bytes) _prevKey; + mapping(bytes => PauseableEnumerableSet.InnerAddress) _keyData; } // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.KeyManagerBytes")) - 1)) & ~bytes32(uint256(0xff)) @@ -46,72 +43,74 @@ abstract contract KeyManagerBytes is KeyManager { bytes memory key ) public view override returns (address) { KeyManagerBytesStorage storage $ = _getKeyManagerBytesStorage(); - return $._keyToOperator[key]; + return $._keyData[key].value; } /** * @notice Gets an operator's active key at the current capture timestamp * @param operator The operator address to lookup - * @return The operator's active key, or empty bytes if none + * @return The operator's active key encoded as bytes, or empty bytes if none */ function operatorKey( address operator ) public view override returns (bytes memory) { KeyManagerBytesStorage storage $ = _getKeyManagerBytesStorage(); - bytes[] memory active = $._keys[operator].getActive(getCaptureTimestamp()); - if (active.length == 0) { - return ZERO_BYTES; + uint48 timestamp = getCaptureTimestamp(); + bytes memory key = $._key[operator]; + if (keccak256(key) != keccak256("") && $._keyData[key].status.wasActiveAt(timestamp)) { + return key; + } + key = $._prevKey[operator]; + if (keccak256(key) != keccak256("") && $._keyData[key].status.wasActiveAt(timestamp)) { + return key; } - return active[0]; + return ""; } /** * @notice Checks if a key was active at a specific timestamp * @param timestamp The timestamp to check - * @param key The key to check + * @param key_ The key to check * @return True if the key was active at the timestamp, false otherwise */ - function keyWasActiveAt(uint48 timestamp, bytes memory key) public view override returns (bool) { + function keyWasActiveAt(uint48 timestamp, bytes memory key_) public view override returns (bool) { KeyManagerBytesStorage storage $ = _getKeyManagerBytesStorage(); - return $._keys[$._keyToOperator[key]].wasActiveAt(timestamp, key); + return $._keyData[key_].status.wasActiveAt(timestamp); } /** * @notice Updates an operator's key * @dev Handles key rotation by disabling old key and registering new one * @param operator The operator address to update - * @param key The new key to register + * @param key_ The new key to register, encoded as bytes */ - function _updateKey(address operator, bytes memory key) internal override { + function _updateKey(address operator, bytes memory key_) internal override { KeyManagerBytesStorage storage $ = _getKeyManagerBytesStorage(); - bytes32 keyHash = keccak256(key); uint48 timestamp = _now(); - if ($._keyToOperator[key] != address(0)) { + if ($._keyData[key_].value != address(0)) { revert DuplicateKey(); } - // check if we have reached the max number of disabled keys - // this allow us to limit the number times we can change the key - if (keyHash != ZERO_BYTES_HASH && $._keys[operator].length() > MAX_DISABLED_KEYS + 1) { - revert MaxDisabledKeysReached(); + bytes memory prevKey = $._prevKey[operator]; + if (keccak256(prevKey) != keccak256("")) { + if (!$._keyData[prevKey].status.checkUnregister(timestamp, _SLASHING_WINDOW())) { + revert PreviousKeySlashable(); + } + delete $._keyData[prevKey]; } - if ($._keys[operator].length() > 0) { - // try to remove disabled keys - bytes memory prevKey = $._keys[operator].array[0].value; - if ($._keys[operator].checkUnregister(timestamp, _SLASHING_WINDOW(), prevKey)) { - $._keys[operator].unregister(timestamp, _SLASHING_WINDOW(), prevKey); - delete $._keyToOperator[prevKey]; - } else if ($._keys[operator].wasActiveAt(timestamp, prevKey)) { - $._keys[operator].pause(timestamp, prevKey); - } + bytes memory currentKey = $._key[operator]; + if (keccak256(currentKey) != keccak256("")) { + $._keyData[currentKey].status.disable(timestamp); } - if (keyHash != ZERO_BYTES_HASH) { - // register the new key - $._keys[operator].register(timestamp, key); - $._keyToOperator[key] = operator; + $._prevKey[operator] = currentKey; + $._key[operator] = key_; + + if (keccak256(key_) != keccak256("")) { + $._keyData[key_].value = operator; + $._keyData[key_].status.set(timestamp); } } } diff --git a/src/extensions/managers/sigs/BLSSig.sol b/src/extensions/managers/sigs/BLSSig.sol new file mode 100644 index 0000000..2cfaebe --- /dev/null +++ b/src/extensions/managers/sigs/BLSSig.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {SigManager} from "../../../managers/extendable/SigManager.sol"; +import {BN254} from "../../../libraries/BN254.sol"; + +/** + * @title BLSSig + * @notice Manages BLS public keys and signature verification + */ +contract BLSSig is SigManager { + using BN254 for BN254.G1Point; + + uint64 public constant BLSSig_VERSION = 1; + + /** + * @notice Verifies that a signature was created by the owner of a key + * @param operator The address of the operator that owns the key + * @param key_ The BLS public key encoded as bytes + * @param signature The BLS signature to verify + * @return True if the signature was created by the key owner, false otherwise + * @dev The key is expected to be ABI encoded (G1Point, G2Point) tuple + */ + function _verifyKeySignature( + address operator, + bytes memory key_, + bytes memory signature + ) internal view override returns (bool) { + (BN254.G1Point memory pubkeyG1, BN254.G2Point memory pubkeyG2) = + abi.decode(key_, (BN254.G1Point, BN254.G2Point)); + BN254.G1Point memory sig = abi.decode(signature, (BN254.G1Point)); + bytes32 messageHash = + keccak256(abi.encodePacked(operator, BN254.hashG1Point(pubkeyG1), BN254.hashG2Point(pubkeyG2))); + return verify(pubkeyG1, pubkeyG2, sig, messageHash); + } + + /** + * @notice Verifies a BLS signature + * @param pubkeyG1 The G1 public key to verify against + * @param pubkeyG2 The G2 public key to verify against + * @param signature The signature to verify + * @param messageHash The message hash that was signed + * @return True if signature is valid, false otherwise + */ + function verify( + BN254.G1Point memory pubkeyG1, + BN254.G2Point memory pubkeyG2, + BN254.G1Point memory signature, + bytes32 messageHash + ) public view returns (bool) { + uint256 alpha = uint256( + keccak256(abi.encode(pubkeyG1.X, pubkeyG1.Y, pubkeyG2.X, pubkeyG2.Y, signature.X, signature.Y, messageHash)) + ); + + BN254.G1Point memory a1 = signature.plus(pubkeyG1.scalar_mul(alpha)); + BN254.G1Point memory b1 = BN254.hashToG1(messageHash).plus(BN254.generatorG1().scalar_mul(alpha)); + + return BN254.pairing(a1, BN254.negGeneratorG2(), b1, pubkeyG2); + } +} diff --git a/src/libraries/BN254.sol b/src/libraries/BN254.sol new file mode 100644 index 0000000..3f96a30 --- /dev/null +++ b/src/libraries/BN254.sol @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +library BN254 { + // modulus for the underlying field F_p of the elliptic curve + uint256 internal constant FP_MODULUS = + 21_888_242_871_839_275_222_246_405_745_257_275_088_696_311_157_297_823_662_689_037_894_645_226_208_583; + // modulus for the underlying field F_r of the elliptic curve + uint256 internal constant FR_MODULUS = + 21_888_242_871_839_275_222_246_405_745_257_275_088_548_364_400_416_034_343_698_204_186_575_808_495_617; + + 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 = + 11_559_732_032_986_387_107_991_004_021_392_285_783_925_812_861_821_192_530_917_403_151_452_391_805_634; + uint256 internal constant G2x0 = + 10_857_046_999_023_057_135_944_570_762_232_829_481_370_756_359_578_518_086_990_519_993_285_655_852_781; + uint256 internal constant G2y1 = + 4_082_367_875_863_433_681_332_203_403_145_435_568_316_851_327_593_401_208_105_741_076_214_120_093_531; + uint256 internal constant G2y0 = + 8_495_653_923_123_431_417_604_973_247_489_272_438_418_190_587_263_600_148_770_280_649_306_958_101_930; + + /// @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 = + 11_559_732_032_986_387_107_991_004_021_392_285_783_925_812_861_821_192_530_917_403_151_452_391_805_634; + uint256 internal constant nG2x0 = + 10_857_046_999_023_057_135_944_570_762_232_829_481_370_756_359_578_518_086_990_519_993_285_655_852_781; + uint256 internal constant nG2y1 = + 17_805_874_995_975_841_540_914_202_342_111_839_520_379_459_829_704_422_454_583_296_818_431_106_115_052; + uint256 internal constant nG2y0 = + 13_392_588_948_715_843_804_641_432_497_768_002_650_278_120_570_034_223_513_918_757_245_338_268_106_653; + + 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) + } + + 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 hashedG1 the keccak256 hash of the G1 Point + /// @dev used for BLS signatures + function hashG1Point( + BN254.G1Point memory pk + ) internal pure returns (bytes32 hashedG1) { + assembly { + mstore(0, mload(pk)) + mstore(0x20, mload(add(0x20, pk))) + hashedG1 := keccak256(0, 0x40) + } + } + + /// @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; + uint256[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/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index 6c0b423..bccf813 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -8,10 +8,7 @@ pragma solidity ^0.8.25; * Each value in a set has an associated status that tracks when it was enabled/disabled */ library PauseableEnumerableSet { - using PauseableEnumerableSet for Inner160; - using PauseableEnumerableSet for Uint160Set; - using PauseableEnumerableSet for InnerBytes32; - using PauseableEnumerableSet for InnerBytes; + using PauseableEnumerableSet for AddressSet; using PauseableEnumerableSet for Status; error AlreadyRegistered(); @@ -30,58 +27,26 @@ library PauseableEnumerableSet { } /** - * @dev Stores a uint160 value and its status + * @dev Stores an address value and its status */ - struct Inner160 { - uint160 value; + struct InnerAddress { + address value; Status status; } /** - * @dev Stores a bytes32 value and its status - */ - struct InnerBytes32 { - bytes32 value; - Status status; - } - - /** - * @dev Stores a bytes value and its status - */ - struct InnerBytes { - bytes value; - Status status; - } - - /** - * @dev Set of uint160 values with their statuses - */ - struct Uint160Set { - Inner160[] array; - mapping(uint160 => uint256) positions; - } - - /** - * @dev Set of address values, implemented using Uint160Set + * @dev Set of address values with their statuses */ struct AddressSet { - Uint160Set set; - } - - /** - * @dev Set of bytes32 values with their statuses - */ - struct Bytes32Set { - InnerBytes32[] array; - mapping(bytes32 => uint256) positions; + InnerAddress[] array; + mapping(address => uint256) positions; } /** - * @dev Set of bytes values with their statuses + * @dev Set of uint160 values, implemented using AddressSet */ - struct BytesSet { - InnerBytes[] array; - mapping(bytes => uint256) positions; + struct Uint160Set { + AddressSet set; } /** @@ -155,39 +120,6 @@ library PauseableEnumerableSet { return self.enabled < timestamp && (self.disabled == 0 || self.disabled >= timestamp); } - /** - * @notice Gets the value and status for an Inner160 - * @param self The Inner160 to get data from - * @return The value, enabled timestamp, and disabled timestamp - */ - function get( - Inner160 storage self - ) internal view returns (uint160, uint48, uint48) { - return (self.value, self.status.enabled, self.status.disabled); - } - - /** - * @notice Gets the value and status for an InnerBytes32 - * @param self The InnerBytes32 to get data from - * @return The value, enabled timestamp, and disabled timestamp - */ - function get( - InnerBytes32 storage self - ) internal view returns (bytes32, uint48, uint48) { - return (self.value, self.status.enabled, self.status.disabled); - } - - /** - * @notice Gets the value and status for an InnerBytes - * @param self The InnerBytes to get data from - * @return The value, enabled timestamp, and disabled timestamp - */ - function get( - InnerBytes storage self - ) internal view returns (bytes memory, uint48, uint48) { - return (self.value, self.status.enabled, self.status.disabled); - } - // AddressSet functions /** @@ -198,7 +130,7 @@ library PauseableEnumerableSet { function length( AddressSet storage self ) internal view returns (uint256) { - return self.set.length(); + return self.array.length; } /** @@ -208,8 +140,8 @@ library PauseableEnumerableSet { * @return The address, enabled timestamp, and disabled timestamp */ function at(AddressSet storage self, uint256 pos) internal view returns (address, uint48, uint48) { - (uint160 value, uint48 enabled, uint48 disabled) = self.set.at(pos); - return (address(value), enabled, disabled); + InnerAddress storage element = self.array[pos]; + return (element.value, element.status.enabled, element.status.disabled); } /** @@ -219,9 +151,17 @@ library PauseableEnumerableSet { * @return array Array of active addresses */ function getActive(AddressSet storage self, uint48 timestamp) internal view returns (address[] memory array) { - uint160[] memory uint160Array = self.set.getActive(timestamp); + uint256 arrayLen = self.array.length; + array = new address[](arrayLen); + uint256 len; + for (uint256 i; i < arrayLen; ++i) { + if (self.array[i].status.wasActiveAt(timestamp)) { + array[len++] = self.array[i].value; + } + } + assembly { - array := uint160Array + mstore(array, len) } return array; } @@ -230,31 +170,38 @@ library PauseableEnumerableSet { * @notice Checks if an address was active at a given timestamp * @param self The AddressSet to query * @param timestamp The timestamp to check - * @param addr The address to check + * @param value The address to check * @return bool Whether the address was active */ - function wasActiveAt(AddressSet storage self, uint48 timestamp, address addr) internal view returns (bool) { - return self.set.wasActiveAt(timestamp, uint160(addr)); + function wasActiveAt(AddressSet storage self, uint48 timestamp, address value) internal view returns (bool) { + uint256 pos = self.positions[value]; + return pos != 0 && self.array[pos - 1].status.wasActiveAt(timestamp); } /** * @notice Registers a new address * @param self The AddressSet to modify * @param timestamp The timestamp to set as enabled - * @param addr The address to register + * @param value The address to register */ - function register(AddressSet storage self, uint48 timestamp, address addr) internal { - self.set.register(timestamp, uint160(addr)); + function register(AddressSet storage self, uint48 timestamp, address value) internal { + if (self.positions[value] != 0) revert AlreadyRegistered(); + + InnerAddress storage element = self.array.push(); + element.value = value; + element.status.set(timestamp); + self.positions[value] = self.array.length; } /** * @notice Pauses an address * @param self The AddressSet to modify * @param timestamp The timestamp to set as disabled - * @param addr The address to pause + * @param value The address to pause */ - function pause(AddressSet storage self, uint48 timestamp, address addr) internal { - self.set.pause(timestamp, uint160(addr)); + function pause(AddressSet storage self, uint48 timestamp, address value) internal { + if (self.positions[value] == 0) revert NotRegistered(); + self.array[self.positions[value] - 1].status.disable(timestamp); } /** @@ -262,29 +209,11 @@ library PauseableEnumerableSet { * @param self The AddressSet to modify * @param timestamp The timestamp to set as enabled * @param immutablePeriod The required waiting period after disabling - * @param addr The address to unpause - */ - function unpause(AddressSet storage self, uint48 timestamp, uint48 immutablePeriod, address addr) internal { - self.set.unpause(timestamp, immutablePeriod, uint160(addr)); - } - - /** - * @notice Checks if an address can be unregistered - * @param self The AddressSet to query - * @param timestamp The current timestamp - * @param immutablePeriod The required waiting period after disabling - * @param value The address to check - * @return bool Whether the address can be unregistered + * @param value The address to unpause */ - function checkUnregister( - AddressSet storage self, - uint48 timestamp, - uint48 immutablePeriod, - address value - ) internal view returns (bool) { - uint256 pos = self.set.positions[uint160(value)]; - if (pos == 0) return false; - return self.set.array[pos - 1].status.checkUnregister(timestamp, immutablePeriod); + function unpause(AddressSet storage self, uint48 timestamp, uint48 immutablePeriod, address value) internal { + if (self.positions[value] == 0) revert NotRegistered(); + self.array[self.positions[value] - 1].status.enable(timestamp, immutablePeriod); } /** @@ -292,20 +221,36 @@ library PauseableEnumerableSet { * @param self The AddressSet to modify * @param timestamp The current timestamp * @param immutablePeriod The required waiting period after disabling - * @param addr The address to unregister + * @param value The address to unregister */ - function unregister(AddressSet storage self, uint48 timestamp, uint48 immutablePeriod, address addr) internal { - self.set.unregister(timestamp, immutablePeriod, uint160(addr)); + function unregister(AddressSet storage self, uint48 timestamp, uint48 immutablePeriod, address value) internal { + uint256 pos = self.positions[value]; + if (pos == 0) revert NotRegistered(); + pos--; + + self.array[pos].status.validateUnregister(timestamp, immutablePeriod); + + if (self.array.length <= pos + 1) { + delete self.positions[value]; + self.array.pop(); + return; + } + + self.array[pos] = self.array[self.array.length - 1]; + self.array.pop(); + + delete self.positions[value]; + self.positions[self.array[pos].value] = pos + 1; } /** * @notice Checks if an address is registered * @param self The AddressSet to query - * @param addr The address to check + * @param value The address to check * @return bool Whether the address is registered */ - function contains(AddressSet storage self, address addr) internal view returns (bool) { - return self.set.contains(uint160(addr)); + function contains(AddressSet storage self, address value) internal view returns (bool) { + return self.positions[value] != 0; } // Uint160Set functions @@ -318,7 +263,7 @@ library PauseableEnumerableSet { function length( Uint160Set storage self ) internal view returns (uint256) { - return self.array.length; + return self.set.length(); } /** @@ -328,7 +273,8 @@ library PauseableEnumerableSet { * @return The uint160, enabled timestamp, and disabled timestamp */ function at(Uint160Set storage self, uint256 pos) internal view returns (uint160, uint48, uint48) { - return self.array[pos].get(); + (address value, uint48 enabled, uint48 disabled) = self.set.at(pos); + return (uint160(value), enabled, disabled); } /** @@ -338,17 +284,9 @@ library PauseableEnumerableSet { * @return array Array of active uint160s */ function getActive(Uint160Set storage self, uint48 timestamp) internal view returns (uint160[] memory array) { - uint256 arrayLen = self.array.length; - array = new uint160[](arrayLen); - uint256 len; - for (uint256 i; i < arrayLen; ++i) { - if (self.array[i].status.wasActiveAt(timestamp)) { - array[len++] = self.array[i].value; - } - } - + address[] memory addressArray = self.set.getActive(timestamp); assembly { - mstore(array, len) + array := addressArray } return array; } @@ -361,8 +299,7 @@ library PauseableEnumerableSet { * @return bool Whether the uint160 was active */ function wasActiveAt(Uint160Set storage self, uint48 timestamp, uint160 value) internal view returns (bool) { - uint256 pos = self.positions[value]; - return pos != 0 && self.array[pos - 1].status.wasActiveAt(timestamp); + return self.set.wasActiveAt(timestamp, address(value)); } /** @@ -372,12 +309,7 @@ library PauseableEnumerableSet { * @param value The uint160 to register */ function register(Uint160Set storage self, uint48 timestamp, uint160 value) internal { - if (self.positions[value] != 0) revert AlreadyRegistered(); - - Inner160 storage element = self.array.push(); - element.value = value; - element.status.set(timestamp); - self.positions[value] = self.array.length; + self.set.register(timestamp, address(value)); } /** @@ -387,8 +319,7 @@ library PauseableEnumerableSet { * @param value The uint160 to pause */ function pause(Uint160Set storage self, uint48 timestamp, uint160 value) internal { - if (self.positions[value] == 0) revert NotRegistered(); - self.array[self.positions[value] - 1].status.disable(timestamp); + self.set.pause(timestamp, address(value)); } /** @@ -399,8 +330,7 @@ library PauseableEnumerableSet { * @param value The uint160 to unpause */ function unpause(Uint160Set storage self, uint48 timestamp, uint48 immutablePeriod, uint160 value) internal { - if (self.positions[value] == 0) revert NotRegistered(); - self.array[self.positions[value] - 1].status.enable(timestamp, immutablePeriod); + self.set.unpause(timestamp, immutablePeriod, address(value)); } /** @@ -411,23 +341,7 @@ library PauseableEnumerableSet { * @param value The uint160 to unregister */ function unregister(Uint160Set storage self, uint48 timestamp, uint48 immutablePeriod, uint160 value) internal { - uint256 pos = self.positions[value]; - if (pos == 0) revert NotRegistered(); - pos--; - - self.array[pos].status.validateUnregister(timestamp, immutablePeriod); - - if (self.array.length <= pos + 1) { - delete self.positions[value]; - self.array.pop(); - return; - } - - self.array[pos] = self.array[self.array.length - 1]; - self.array.pop(); - - delete self.positions[value]; - self.positions[self.array[pos].value] = pos + 1; + self.set.unregister(timestamp, immutablePeriod, address(value)); } /** @@ -437,310 +351,6 @@ library PauseableEnumerableSet { * @return bool Whether the uint160 is registered */ function contains(Uint160Set storage self, uint160 value) internal view returns (bool) { - return self.positions[value] != 0; - } - - // Bytes32Set functions - - /** - * @notice Gets the number of bytes32s in the set - * @param self The Bytes32Set to query - * @return uint256 The number of bytes32s - */ - function length( - Bytes32Set storage self - ) internal view returns (uint256) { - return self.array.length; - } - - /** - * @notice Gets the bytes32 and status at a given position - * @param self The Bytes32Set to query - * @param pos The position to query - * @return The bytes32, enabled timestamp, and disabled timestamp - */ - function at(Bytes32Set storage self, uint256 pos) internal view returns (bytes32, uint48, uint48) { - return self.array[pos].get(); - } - - /** - * @notice Gets all active bytes32s at a given timestamp - * @param self The Bytes32Set to query - * @param timestamp The timestamp to check - * @return array Array of active bytes32s - */ - function getActive(Bytes32Set storage self, uint48 timestamp) internal view returns (bytes32[] memory array) { - uint256 arrayLen = self.array.length; - array = new bytes32[](arrayLen); - uint256 len; - for (uint256 i; i < arrayLen; ++i) { - if (self.array[i].status.wasActiveAt(timestamp)) { - array[len++] = self.array[i].value; - } - } - - assembly { - mstore(array, len) - } - return array; - } - - /** - * @notice Checks if a bytes32 was active at a given timestamp - * @param self The Bytes32Set to query - * @param timestamp The timestamp to check - * @param value The bytes32 to check - * @return bool Whether the bytes32 was active - */ - function wasActiveAt(Bytes32Set storage self, uint48 timestamp, bytes32 value) internal view returns (bool) { - uint256 pos = self.positions[value]; - return pos != 0 && self.array[pos - 1].status.wasActiveAt(timestamp); - } - - /** - * @notice Registers a new bytes32 - * @param self The Bytes32Set to modify - * @param timestamp The timestamp to set as enabled - * @param value The bytes32 to register - */ - function register(Bytes32Set storage self, uint48 timestamp, bytes32 value) internal { - if (self.positions[value] != 0) revert AlreadyRegistered(); - - uint256 pos = self.array.length; - InnerBytes32 storage element = self.array.push(); - element.value = value; - element.status.set(timestamp); - self.positions[value] = pos + 1; - } - - /** - * @notice Pauses a bytes32 - * @param self The Bytes32Set to modify - * @param timestamp The timestamp to set as disabled - * @param value The bytes32 to pause - */ - function pause(Bytes32Set storage self, uint48 timestamp, bytes32 value) internal { - if (self.positions[value] == 0) revert NotRegistered(); - self.array[self.positions[value] - 1].status.disable(timestamp); - } - - /** - * @notice Unpauses a bytes32 - * @param self The Bytes32Set to modify - * @param timestamp The timestamp to set as enabled - * @param immutablePeriod The required waiting period after disabling - * @param value The bytes32 to unpause - */ - function unpause(Bytes32Set storage self, uint48 timestamp, uint48 immutablePeriod, bytes32 value) internal { - if (self.positions[value] == 0) revert NotRegistered(); - self.array[self.positions[value] - 1].status.enable(timestamp, immutablePeriod); - } - - /** - * @notice Checks if a bytes32 can be unregistered - * @param self The Bytes32Set to query - * @param timestamp The current timestamp - * @param immutablePeriod The required waiting period after disabling - * @param value The bytes32 to check - * @return bool Whether the bytes32 can be unregistered - */ - function checkUnregister( - Bytes32Set storage self, - uint48 timestamp, - uint48 immutablePeriod, - bytes32 value - ) internal view returns (bool) { - uint256 pos = self.positions[value]; - if (pos == 0) return false; - return self.array[pos - 1].status.checkUnregister(timestamp, immutablePeriod); - } - - /** - * @notice Unregisters a bytes32 - * @param self The Bytes32Set to modify - * @param timestamp The current timestamp - * @param immutablePeriod The required waiting period after disabling - * @param value The bytes32 to unregister - */ - function unregister(Bytes32Set storage self, uint48 timestamp, uint48 immutablePeriod, bytes32 value) internal { - uint256 pos = self.positions[value]; - if (pos == 0) revert NotRegistered(); - pos--; - - self.array[pos].status.validateUnregister(timestamp, immutablePeriod); - - if (self.array.length <= pos + 1) { - delete self.positions[value]; - self.array.pop(); - return; - } - - self.array[pos] = self.array[self.array.length - 1]; - self.array.pop(); - - delete self.positions[value]; - self.positions[self.array[pos].value] = pos + 1; - } - - /** - * @notice Checks if a bytes32 is registered - * @param self The Bytes32Set to query - * @param value The bytes32 to check - * @return bool Whether the bytes32 is registered - */ - function contains(Bytes32Set storage self, bytes32 value) internal view returns (bool) { - return self.positions[value] != 0; - } - - // BytesSet functions - - /** - * @notice Gets the number of bytes values in the set - * @param self The BytesSet to query - * @return uint256 The number of bytes values - */ - function length( - BytesSet storage self - ) internal view returns (uint256) { - return self.array.length; - } - - /** - * @notice Gets the bytes value and status at a given position - * @param self The BytesSet to query - * @param pos The position to query - * @return The bytes value, enabled timestamp, and disabled timestamp - */ - function at(BytesSet storage self, uint256 pos) internal view returns (bytes memory, uint48, uint48) { - return self.array[pos].get(); - } - - /** - * @notice Gets all active bytes values at a given timestamp - * @param self The BytesSet to query - * @param timestamp The timestamp to check - * @return array Array of active bytes values - */ - function getActive(BytesSet storage self, uint48 timestamp) internal view returns (bytes[] memory array) { - uint256 arrayLen = self.array.length; - array = new bytes[](arrayLen); - uint256 len; - for (uint256 i; i < arrayLen; ++i) { - if (self.array[i].status.wasActiveAt(timestamp)) { - array[len++] = self.array[i].value; - } - } - - assembly { - mstore(array, len) - } - return array; - } - - /** - * @notice Checks if a bytes value was active at a given timestamp - * @param self The BytesSet to query - * @param timestamp The timestamp to check - * @param value The bytes value to check - * @return bool Whether the bytes value was active - */ - function wasActiveAt(BytesSet storage self, uint48 timestamp, bytes memory value) internal view returns (bool) { - uint256 pos = self.positions[value]; - return pos != 0 && self.array[pos - 1].status.wasActiveAt(timestamp); - } - - /** - * @notice Registers a new bytes value - * @param self The BytesSet to modify - * @param timestamp The timestamp to set as enabled - * @param value The bytes value to register - */ - function register(BytesSet storage self, uint48 timestamp, bytes memory value) internal { - if (self.positions[value] != 0) revert AlreadyRegistered(); - - uint256 pos = self.array.length; - InnerBytes storage element = self.array.push(); - element.value = value; - element.status.set(timestamp); - self.positions[value] = pos + 1; - } - - /** - * @notice Pauses a bytes value - * @param self The BytesSet to modify - * @param timestamp The timestamp to set as disabled - * @param value The bytes value to pause - */ - function pause(BytesSet storage self, uint48 timestamp, bytes memory value) internal { - if (self.positions[value] == 0) revert NotRegistered(); - self.array[self.positions[value] - 1].status.disable(timestamp); - } - - /** - * @notice Unpauses a bytes value - * @param self The BytesSet to modify - * @param timestamp The timestamp to set as enabled - * @param immutablePeriod The required waiting period after disabling - * @param value The bytes value to unpause - */ - function unpause(BytesSet storage self, uint48 timestamp, uint48 immutablePeriod, bytes memory value) internal { - if (self.positions[value] == 0) revert NotRegistered(); - self.array[self.positions[value] - 1].status.enable(timestamp, immutablePeriod); - } - - /** - * @notice Checks if a bytes value can be unregistered - * @param self The BytesSet to query - * @param timestamp The current timestamp - * @param immutablePeriod The required waiting period after disabling - * @param value The bytes value to check - * @return bool Whether the bytes value can be unregistered - */ - function checkUnregister( - BytesSet storage self, - uint48 timestamp, - uint48 immutablePeriod, - bytes memory value - ) internal view returns (bool) { - uint256 pos = self.positions[value]; - if (pos == 0) return false; - return self.array[pos - 1].status.checkUnregister(timestamp, immutablePeriod); - } - - /** - * @notice Unregisters a bytes value - * @param self The BytesSet to modify - * @param timestamp The current timestamp - * @param immutablePeriod The required waiting period after disabling - * @param value The bytes value to unregister - */ - function unregister(BytesSet storage self, uint48 timestamp, uint48 immutablePeriod, bytes memory value) internal { - uint256 pos = self.positions[value]; - if (pos == 0) revert NotRegistered(); - pos--; - - self.array[pos].status.validateUnregister(timestamp, immutablePeriod); - - if (self.array.length <= pos + 1) { - delete self.positions[value]; - self.array.pop(); - return; - } - - self.array[pos] = self.array[self.array.length - 1]; - self.array.pop(); - - delete self.positions[value]; - self.positions[self.array[pos].value] = pos + 1; - } - - /** - * @notice Checks if a bytes value is registered - * @param self The BytesSet to query - * @param value The bytes value to check - * @return bool Whether the bytes value is registered - */ - function contains(BytesSet storage self, bytes memory value) internal view returns (bool) { - return self.positions[value] != 0; + return self.set.contains(address(value)); } } From 30bad7cd0e854126f5422e4296ebed2de299d5eb Mon Sep 17 00:00:00 2001 From: alrxy Date: Wed, 25 Dec 2024 12:26:48 +0400 Subject: [PATCH 04/21] chore: BN254 licence --- src/extensions/managers/keys/KeyManagerBLS.sol | 5 ++--- src/libraries/BN254.sol | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/extensions/managers/keys/KeyManagerBLS.sol b/src/extensions/managers/keys/KeyManagerBLS.sol index 857f00a..6c7c7b5 100644 --- a/src/extensions/managers/keys/KeyManagerBLS.sol +++ b/src/extensions/managers/keys/KeyManagerBLS.sol @@ -131,13 +131,12 @@ abstract contract KeyManagerBLS is KeyManager, BLSSig { revert PreviousKeySlashable(); } delete $._keyData[prevKey.X]; - $.aggregateKey = $.aggregateKey.plus(prevKey.negate()); } BN254.G1Point memory currentKey = $._key[operator]; if (currentKey.X != 0 || currentKey.Y != 0) { $._keyData[currentKey.X].status.disable(timestamp); - $.aggregateKey = $.aggregateKey.plus(currentKey.negate()); + $._aggregatedKey = $._aggregatedKey.plus(currentKey.negate()); } $._prevKey[operator] = currentKey; @@ -146,7 +145,7 @@ abstract contract KeyManagerBLS is KeyManager, BLSSig { if (key.X != 0 || key.Y != 0) { $._keyData[key.X].value = operator; $._keyData[key.X].status.set(timestamp); - $.aggregateKey = $.aggregateKey.plus(key); + $._aggregatedKey = $._aggregatedKey.plus(key); } } } diff --git a/src/libraries/BN254.sol b/src/libraries/BN254.sol index 3f96a30..ef49dc0 100644 --- a/src/libraries/BN254.sol +++ b/src/libraries/BN254.sol @@ -1,4 +1,6 @@ // SPDX-License-Identifier: MIT +// Original code: https://github.com/Layr-Labs/eigenlayer-middleware/blob/mainnet/src/libraries/BN254.sol +// Copyright (c) 2024 LayrLabs Inc. pragma solidity ^0.8.25; library BN254 { From 9e96fd8271f93e9979e5faa0bf93e7f49b0ba64b Mon Sep 17 00:00:00 2001 From: alrxy Date: Fri, 27 Dec 2024 08:29:28 +0400 Subject: [PATCH 05/21] feat: checkpointed aggregate keys and bls keys merkle --- .../managers/keys/KeyManagerBLS.sol | 43 +++- src/libraries/Merkle.sol | 212 ++++++++++++++++++ 2 files changed, 244 insertions(+), 11 deletions(-) create mode 100644 src/libraries/Merkle.sol diff --git a/src/extensions/managers/keys/KeyManagerBLS.sol b/src/extensions/managers/keys/KeyManagerBLS.sol index 6c7c7b5..0e8517f 100644 --- a/src/extensions/managers/keys/KeyManagerBLS.sol +++ b/src/extensions/managers/keys/KeyManagerBLS.sol @@ -5,6 +5,8 @@ import {KeyManager} from "../../../managers/extendable/KeyManager.sol"; import {PauseableEnumerableSet} from "../../../libraries/PauseableEnumerableSet.sol"; import {BN254} from "../../../libraries/BN254.sol"; import {BLSSig} from "../sigs/BLSSig.sol"; +import {MerkleLib} from "../../../libraries/Merkle.sol"; +import {Checkpoints} from "@symbiotic/contracts/libraries/Checkpoints.sol"; /** * @title KeyManagerBLS @@ -14,8 +16,12 @@ import {BLSSig} from "../sigs/BLSSig.sol"; abstract contract KeyManagerBLS is KeyManager, BLSSig { using BN254 for BN254.G1Point; using PauseableEnumerableSet for PauseableEnumerableSet.Status; + using MerkleLib for MerkleLib.Tree; + using Checkpoints for Checkpoints.Trace256; uint64 public constant KeyManagerBLS_VERSION = 1; + // must be same as TREE_DEPTH in MerkleLib.sol + uint256 private constant _TREE_DEPTH = 32; error DuplicateKey(); error PreviousKeySlashable(); @@ -23,9 +29,9 @@ abstract contract KeyManagerBLS is KeyManager, BLSSig { struct KeyManagerBLSStorage { mapping(address => BN254.G1Point) _key; mapping(address => BN254.G1Point) _prevKey; - mapping(uint256 => uint48) _aggregatedKeyTimestamp; mapping(uint256 => PauseableEnumerableSet.InnerAddress) _keyData; - BN254.G1Point _aggregatedKey; + Checkpoints.Trace256 _aggregatedKey; + MerkleLib.Tree _keyMerkle; } // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.KeyManagerBLS")) - 1)) & ~bytes32(uint256(0xff)) @@ -45,21 +51,26 @@ abstract contract KeyManagerBLS is KeyManager, BLSSig { BN254.G2Point memory aggregateG2Key, BN254.G1Point memory signature, bytes32 messageHash, - BN254.G1Point[] memory nonSigningKeys + BN254.G1Point[] memory nonSigningKeys, + uint256[] memory nonSigningKeyIndices, + bytes32[_TREE_DEPTH][] memory nonSigningKeyMerkleProofs, + bytes memory hint ) public view returns (bool) { KeyManagerBLSStorage storage $ = _getKeyManagerBLSStorage(); - if ($._aggregatedKeyTimestamp[aggregateG1Key.X] != timestamp) { + // verify that the aggregated key is the same as the one at the timestamp + uint256 x = $._aggregatedKey.upperLookupRecent(timestamp, hint); + if (aggregateG1Key.X != x) { return false; } + BN254.G1Point memory aggregatedNonSigningKey = BN254.G1Point(0, 0); for (uint256 i = 0; i < nonSigningKeys.length; i++) { - if (!$._keyData[nonSigningKeys[i].X].status.wasActiveAt(timestamp)) { + if (MerkleLib.branchRoot(bytes32(nonSigningKeys[i].X), nonSigningKeyMerkleProofs[i], nonSigningKeyIndices[i]) != $._keyMerkle.root()) { return false; } - if (true) { - aggregatedNonSigningKey = aggregatedNonSigningKey.plus(nonSigningKeys[i]); - } + aggregatedNonSigningKey = aggregatedNonSigningKey.plus(nonSigningKeys[i]); } + aggregateG1Key = aggregateG1Key.plus(aggregatedNonSigningKey.negate()); return BLSSig.verify(aggregateG1Key, aggregateG2Key, signature, messageHash); } @@ -130,22 +141,32 @@ abstract contract KeyManagerBLS is KeyManager, BLSSig { if (!$._keyData[prevKey.X].status.checkUnregister(timestamp, _SLASHING_WINDOW())) { revert PreviousKeySlashable(); } - delete $._keyData[prevKey.X]; + $._keyData[prevKey.X].value = address(0); } + uint256 x = $._aggregatedKey.latest(); + (, uint256 y) = BN254.findYFromX(x); + BN254.G1Point memory aggregatedKey = BN254.G1Point(x, y); BN254.G1Point memory currentKey = $._key[operator]; if (currentKey.X != 0 || currentKey.Y != 0) { $._keyData[currentKey.X].status.disable(timestamp); - $._aggregatedKey = $._aggregatedKey.plus(currentKey.negate()); + aggregatedKey = aggregatedKey.plus(currentKey.negate()); } $._prevKey[operator] = currentKey; $._key[operator] = key; if (key.X != 0 || key.Y != 0) { + if ($._keyData[key.X].status.enabled != 0 || $._keyData[key.X].status.disabled != 0) { + $._keyMerkle.insert(bytes32(key.X)); + } $._keyData[key.X].value = operator; $._keyData[key.X].status.set(timestamp); - $._aggregatedKey = $._aggregatedKey.plus(key); + aggregatedKey = aggregatedKey.plus(key); + } + + if (aggregatedKey.X != x) { + $._aggregatedKey.push(_now(), aggregatedKey.X); } } } diff --git a/src/libraries/Merkle.sol b/src/libraries/Merkle.sol new file mode 100644 index 0000000..a500cb6 --- /dev/null +++ b/src/libraries/Merkle.sol @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: MIT +// Original code: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/solidity/contracts/libs/Merkle.sol + +pragma solidity ^0.8.25; + +// work based on eth2 deposit contract, which is used under CC0-1.0 + +uint256 constant TREE_DEPTH = 32; +uint256 constant MAX_LEAVES = 2 ** TREE_DEPTH - 1; + +/** + * @title MerkleLib + * @author Celo Labs Inc. + * @notice An incremental merkle tree modeled on the eth2 deposit contract. + * + */ +library MerkleLib { + /** + * @notice Struct representing incremental merkle tree. Contains current + * branch and the number of inserted leaves in the tree. + * + */ + struct Tree { + bytes32[TREE_DEPTH] branch; + uint256 count; + } + + /** + * @notice Inserts `_node` into merkle tree + * @dev Reverts if tree is full + * @param _node Element to insert into tree + * + */ + function insert(Tree storage _tree, bytes32 _node) internal { + require(_tree.count < MAX_LEAVES, "merkle tree full"); + + _tree.count += 1; + uint256 size = _tree.count; + for (uint256 i = 0; i < TREE_DEPTH; i++) { + if ((size & 1) == 1) { + _tree.branch[i] = _node; + return; + } + _node = keccak256(abi.encodePacked(_tree.branch[i], _node)); + size /= 2; + } + // As the loop should always end prematurely with the `return` statement, + // this code should be unreachable. We assert `false` just to be safe. + assert(false); + } + + /** + * @notice Calculates and returns`_tree`'s current root given array of zero + * hashes + * @param _zeroes Array of zero hashes + * @return _current Calculated root of `_tree` + * + */ + function rootWithCtx( + Tree storage _tree, + bytes32[TREE_DEPTH] memory _zeroes + ) internal view returns (bytes32 _current) { + uint256 _index = _tree.count; + + for (uint256 i = 0; i < TREE_DEPTH; i++) { + uint256 _ithBit = (_index >> i) & 0x01; + bytes32 _next = _tree.branch[i]; + if (_ithBit == 1) { + _current = keccak256(abi.encodePacked(_next, _current)); + } else { + _current = keccak256(abi.encodePacked(_current, _zeroes[i])); + } + } + } + + /// @notice Calculates and returns`_tree`'s current root + function root( + Tree storage _tree + ) internal view returns (bytes32) { + return rootWithCtx(_tree, zeroHashes()); + } + + /// @notice Returns array of TREE_DEPTH zero hashes + /// @return _zeroes Array of TREE_DEPTH zero hashes + function zeroHashes() internal pure returns (bytes32[TREE_DEPTH] memory _zeroes) { + _zeroes[0] = Z_0; + _zeroes[1] = Z_1; + _zeroes[2] = Z_2; + _zeroes[3] = Z_3; + _zeroes[4] = Z_4; + _zeroes[5] = Z_5; + _zeroes[6] = Z_6; + _zeroes[7] = Z_7; + _zeroes[8] = Z_8; + _zeroes[9] = Z_9; + _zeroes[10] = Z_10; + _zeroes[11] = Z_11; + _zeroes[12] = Z_12; + _zeroes[13] = Z_13; + _zeroes[14] = Z_14; + _zeroes[15] = Z_15; + _zeroes[16] = Z_16; + _zeroes[17] = Z_17; + _zeroes[18] = Z_18; + _zeroes[19] = Z_19; + _zeroes[20] = Z_20; + _zeroes[21] = Z_21; + _zeroes[22] = Z_22; + _zeroes[23] = Z_23; + _zeroes[24] = Z_24; + _zeroes[25] = Z_25; + _zeroes[26] = Z_26; + _zeroes[27] = Z_27; + _zeroes[28] = Z_28; + _zeroes[29] = Z_29; + _zeroes[30] = Z_30; + _zeroes[31] = Z_31; + } + + /** + * @notice Calculates and returns the merkle root for the given leaf + * `_item`, a merkle branch, and the index of `_item` in the tree. + * @param _item Merkle leaf + * @param _branch Merkle proof + * @param _index Index of `_item` in tree + * @return _current Calculated merkle root + * + */ + function branchRoot( + bytes32 _item, + bytes32[TREE_DEPTH] memory _branch, // cheaper than calldata indexing + uint256 _index + ) internal pure returns (bytes32 _current) { + _current = _item; + + for (uint256 i = 0; i < TREE_DEPTH; i++) { + uint256 _ithBit = (_index >> i) & 0x01; + // cheaper than calldata indexing _branch[i*32:(i+1)*32]; + bytes32 _next = _branch[i]; + if (_ithBit == 1) { + _current = keccak256(abi.encodePacked(_next, _current)); + } else { + _current = keccak256(abi.encodePacked(_current, _next)); + } + } + } + + /** + * @notice Calculates and returns the merkle root as if the index is + * the topmost leaf in the tree. + * @param _item Merkle leaf + * @param _branch Merkle proof + * @param _index Index of `_item` in tree + * @dev Replaces siblings greater than the index (right subtrees) with zeroes. + * @return _current Calculated merkle root + * + */ + function reconstructRoot( + bytes32 _item, + bytes32[TREE_DEPTH] memory _branch, // cheaper than calldata indexing + uint256 _index + ) internal pure returns (bytes32 _current) { + _current = _item; + + bytes32[TREE_DEPTH] memory _zeroes = zeroHashes(); + + for (uint256 i = 0; i < TREE_DEPTH; i++) { + uint256 _ithBit = (_index >> i) & 0x01; + // cheaper than calldata indexing _branch[i*32:(i+1)*32]; + if (_ithBit == 1) { + _current = keccak256(abi.encodePacked(_branch[i], _current)); + } else { + // remove right subtree from proof + _current = keccak256(abi.encodePacked(_current, _zeroes[i])); + } + } + } + + // keccak256 zero hashes + bytes32 internal constant Z_0 = hex"0000000000000000000000000000000000000000000000000000000000000000"; + bytes32 internal constant Z_1 = hex"ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5"; + bytes32 internal constant Z_2 = hex"b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30"; + bytes32 internal constant Z_3 = hex"21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85"; + bytes32 internal constant Z_4 = hex"e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344"; + bytes32 internal constant Z_5 = hex"0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d"; + bytes32 internal constant Z_6 = hex"887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968"; + bytes32 internal constant Z_7 = hex"ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83"; + bytes32 internal constant Z_8 = hex"9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af"; + bytes32 internal constant Z_9 = hex"cefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0"; + bytes32 internal constant Z_10 = hex"f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5"; + bytes32 internal constant Z_11 = hex"f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892"; + bytes32 internal constant Z_12 = hex"3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c"; + bytes32 internal constant Z_13 = hex"c1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb"; + bytes32 internal constant Z_14 = hex"5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc"; + bytes32 internal constant Z_15 = hex"da7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2"; + bytes32 internal constant Z_16 = hex"2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f"; + bytes32 internal constant Z_17 = hex"e1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a"; + bytes32 internal constant Z_18 = hex"5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0"; + bytes32 internal constant Z_19 = hex"b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0"; + bytes32 internal constant Z_20 = hex"c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2"; + bytes32 internal constant Z_21 = hex"f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9"; + bytes32 internal constant Z_22 = hex"5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377"; + bytes32 internal constant Z_23 = hex"4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652"; + bytes32 internal constant Z_24 = hex"cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef"; + bytes32 internal constant Z_25 = hex"0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d"; + bytes32 internal constant Z_26 = hex"b8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0"; + bytes32 internal constant Z_27 = hex"838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e"; + bytes32 internal constant Z_28 = hex"662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e"; + bytes32 internal constant Z_29 = hex"388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322"; + bytes32 internal constant Z_30 = hex"93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735"; + bytes32 internal constant Z_31 = hex"8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9"; +} From 7256ce0e2f16f58a32e50844e51e247c459c0093 Mon Sep 17 00:00:00 2001 From: alrxy Date: Fri, 27 Dec 2024 08:55:59 +0400 Subject: [PATCH 06/21] fix: bls key insert to merkle --- src/examples/sqrt-task-network/SqrtTaskMiddleware.sol | 2 -- src/extensions/managers/keys/KeyManagerBLS.sol | 7 ++++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol index faeb767..05bcac6 100644 --- a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol +++ b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol @@ -11,7 +11,6 @@ import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/Signa import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; import {SharedVaults} from "../../extensions/SharedVaults.sol"; -import {Operators} from "../../extensions/operators/Operators.sol"; import {OwnableAccessManager} from "../../extensions/managers/access/OwnableAccessManager.sol"; import {NoKeyManager} from "../../extensions/managers/keys/NoKeyManager.sol"; @@ -20,7 +19,6 @@ import {EqualStakePower} from "../../extensions/managers/stake-powers/EqualStake contract SqrtTaskMiddleware is SharedVaults, - Operators, NoKeyManager, EIP712, OwnableAccessManager, diff --git a/src/extensions/managers/keys/KeyManagerBLS.sol b/src/extensions/managers/keys/KeyManagerBLS.sol index 0e8517f..95336e3 100644 --- a/src/extensions/managers/keys/KeyManagerBLS.sol +++ b/src/extensions/managers/keys/KeyManagerBLS.sol @@ -64,13 +64,14 @@ abstract contract KeyManagerBLS is KeyManager, BLSSig { } BN254.G1Point memory aggregatedNonSigningKey = BN254.G1Point(0, 0); + bytes32 root = $._keyMerkle.root(); for (uint256 i = 0; i < nonSigningKeys.length; i++) { - if (MerkleLib.branchRoot(bytes32(nonSigningKeys[i].X), nonSigningKeyMerkleProofs[i], nonSigningKeyIndices[i]) != $._keyMerkle.root()) { + if (MerkleLib.branchRoot(bytes32(nonSigningKeys[i].X), nonSigningKeyMerkleProofs[i], nonSigningKeyIndices[i]) != root) { return false; } aggregatedNonSigningKey = aggregatedNonSigningKey.plus(nonSigningKeys[i]); } - + aggregateG1Key = aggregateG1Key.plus(aggregatedNonSigningKey.negate()); return BLSSig.verify(aggregateG1Key, aggregateG2Key, signature, messageHash); } @@ -157,7 +158,7 @@ abstract contract KeyManagerBLS is KeyManager, BLSSig { $._key[operator] = key; if (key.X != 0 || key.Y != 0) { - if ($._keyData[key.X].status.enabled != 0 || $._keyData[key.X].status.disabled != 0) { + if ($._keyData[key.X].status.enabled == 0 && $._keyData[key.X].status.disabled == 0) { $._keyMerkle.insert(bytes32(key.X)); } $._keyData[key.X].value = operator; From f0387be5fbaeac30c8afc19af0f3d7e99bb101d2 Mon Sep 17 00:00:00 2001 From: alrxy Date: Fri, 27 Dec 2024 09:03:18 +0400 Subject: [PATCH 07/21] chore: bls keymanager verify optimization --- src/extensions/managers/keys/KeyManagerBLS.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/extensions/managers/keys/KeyManagerBLS.sol b/src/extensions/managers/keys/KeyManagerBLS.sol index 95336e3..fbf7f85 100644 --- a/src/extensions/managers/keys/KeyManagerBLS.sol +++ b/src/extensions/managers/keys/KeyManagerBLS.sol @@ -32,6 +32,7 @@ abstract contract KeyManagerBLS is KeyManager, BLSSig { mapping(uint256 => PauseableEnumerableSet.InnerAddress) _keyData; Checkpoints.Trace256 _aggregatedKey; MerkleLib.Tree _keyMerkle; + bytes32 _keyMerkleRoot; } // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.KeyManagerBLS")) - 1)) & ~bytes32(uint256(0xff)) @@ -64,7 +65,7 @@ abstract contract KeyManagerBLS is KeyManager, BLSSig { } BN254.G1Point memory aggregatedNonSigningKey = BN254.G1Point(0, 0); - bytes32 root = $._keyMerkle.root(); + bytes32 root = $._keyMerkleRoot; for (uint256 i = 0; i < nonSigningKeys.length; i++) { if (MerkleLib.branchRoot(bytes32(nonSigningKeys[i].X), nonSigningKeyMerkleProofs[i], nonSigningKeyIndices[i]) != root) { return false; @@ -168,6 +169,8 @@ abstract contract KeyManagerBLS is KeyManager, BLSSig { if (aggregatedKey.X != x) { $._aggregatedKey.push(_now(), aggregatedKey.X); + // all merkle tree slots should be hot + $._keyMerkleRoot = $._keyMerkle.root(); } } } From 57e16fb1d475c802fda81691d32801b659896fdd Mon Sep 17 00:00:00 2001 From: alrxy Date: Wed, 15 Jan 2025 16:12:25 +0400 Subject: [PATCH 08/21] WIP --- .../BLSSqrtTaskMiddleware.sol | 167 +++++++++++++++++ .../managers/keys/KeyManagerBLS.sol | 80 +++++--- src/extensions/managers/sigs/BLSSig.sol | 8 +- src/libraries/Merkle.sol | 134 ++++++-------- test/BLS.t.sol | 90 +++++++++ test/Merkle.t.sol | 113 ++++++++++++ test/helpers/FullMerkle.sol | 112 ++++++++++++ test/helpers/SimpleMerkle.sol | 30 +++ test/helpers/blsTestGenerator.py | 172 ++++++++++++++++++ test/helpers/blsTestVectors.json | 18 ++ 10 files changed, 814 insertions(+), 110 deletions(-) create mode 100644 src/examples/sqrt-task-network/BLSSqrtTaskMiddleware.sol create mode 100644 test/BLS.t.sol create mode 100644 test/Merkle.t.sol create mode 100644 test/helpers/FullMerkle.sol create mode 100644 test/helpers/SimpleMerkle.sol create mode 100644 test/helpers/blsTestGenerator.py create mode 100644 test/helpers/blsTestVectors.json diff --git a/src/examples/sqrt-task-network/BLSSqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/BLSSqrtTaskMiddleware.sol new file mode 100644 index 0000000..ca8deaf --- /dev/null +++ b/src/examples/sqrt-task-network/BLSSqrtTaskMiddleware.sol @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; +import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; +import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; + +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; + +import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; +import {SharedVaults} from "../../extensions/SharedVaults.sol"; +import {SelfRegisterOperators} from "../../extensions/operators/SelfRegisterOperators.sol"; + +import {OwnableAccessManager} from "../../extensions/managers/access/OwnableAccessManager.sol"; +import {KeyManagerBLS} from "../../extensions/managers/keys/KeyManagerBLS.sol"; +import {TimestampCapture} from "../../extensions/managers/capture-timestamps/TimestampCapture.sol"; +import {EqualStakePower} from "../../extensions/managers/stake-powers/EqualStakePower.sol"; + +contract BLSSqrtTaskMiddleware is + SharedVaults, + SelfRegisterOperators, + KeyManagerBLS, + OwnableAccessManager, + TimestampCapture, + EqualStakePower +{ + using Subnetwork for address; + using Math for uint256; + + error InvalidHints(); + error TaskCompleted(); + + event CreateTask(uint256 indexed taskIndex); + event CompleteTask(uint256 indexed taskIndex, bool isValidAnswer); + + struct Task { + uint48 captureTimestamp; + uint48 deadlineTimestamp; + uint256 value; + bool completed; + } + + uint48 public constant TASK_DURATION = 1 days; + Task[] public tasks; + + constructor( + address network, + uint48 slashingWindow, + address operatorRegistry, + address vaultRegistry, + address operatorNetOptin, + address reader, + address owner + ) { + initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, reader, owner); + } + + function initialize( + address network, + uint48 slashingWindow, + address vaultRegistry, + address operatorRegistry, + address operatorNetOptin, + address reader, + address owner + ) internal initializer { + __BaseMiddleware_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, reader); + __OwnableAccessManager_init(owner); + __SelfRegisterOperators_init("BLS Sqrt Task", 0); + } + + function createTask(uint256 value, address operator) external returns (uint256 taskIndex) { + taskIndex = tasks.length; + tasks.push( + Task({ + captureTimestamp: getCaptureTimestamp(), + deadlineTimestamp: getCaptureTimestamp() + TASK_DURATION, + value: value, + completed: false + }) + ); + + emit CreateTask(taskIndex); + } + + function completeTask( + uint256 taskIndex, + uint256 answer, + bytes calldata signature + ) external returns (bool isValidAnswer) { + if (!_verify(taskIndex, answer, signature)) { + // revert InvalidAnswer(); + } + + tasks[taskIndex].completed = true; + emit CompleteTask(taskIndex, true); + } + + function _verify(uint256 taskIndex, uint256 answer, bytes calldata signature) private view returns (bool) { + if (tasks[taskIndex].completed) { + revert TaskCompleted(); + } + // _verifySignature(taskIndex, answer, signature); + return _verifyAnswer(taskIndex, answer); + } + + function _verifyAnswer(uint256 taskIndex, uint256 answer) private view returns (bool) { + uint256 value = tasks[taskIndex].value; + uint256 square = answer ** 2; + if (square == value) { + return true; + } + + if (square < value) { + uint256 difference = value - square; + uint256 nextSquare = (answer + 1) ** 2; + uint256 nextDifference = nextSquare > value ? nextSquare - value : value - nextSquare; + if (difference <= nextDifference) { + return true; + } + } else { + uint256 difference = square - value; + uint256 prevSquare = (answer - 1) ** 2; + uint256 prevDifference = prevSquare > value ? prevSquare - value : value - prevSquare; + if (difference <= prevDifference) { + return true; + } + } + + return false; + } + + // function _slash(uint256 taskIndex, bytes[] calldata stakeHints, bytes[] calldata slashHints) private { + // Task storage task = tasks[taskIndex]; + // address[] memory vaults = _activeVaultsAt(task.captureTimestamp, task.operator); + // uint256 vaultsLength = vaults.length; + + // if (stakeHints.length != slashHints.length || stakeHints.length != vaultsLength) { + // revert InvalidHints(); + // } + + // bytes32 subnetwork = _NETWORK().subnetwork(0); + // for (uint256 i; i < vaultsLength; ++i) { + // address vault = vaults[i]; + // uint256 slashAmount = IBaseDelegator(IVault(vault).delegator()).stakeAt( + // subnetwork, task.operator, task.captureTimestamp, stakeHints[i] + // ); + + // if (slashAmount == 0) { + // continue; + // } + + // _slashVault(task.captureTimestamp, vault, subnetwork, task.operator, slashAmount, slashHints[i]); + // } + // } + + // function executeSlash( + // uint48 epochStart, + // address vault, + // bytes32 subnetwork, + // address operator, + // uint256 amount, + // bytes memory hints + // ) external checkAccess { + // _slashVault(epochStart, vault, subnetwork, operator, amount, hints); + // } +} diff --git a/src/extensions/managers/keys/KeyManagerBLS.sol b/src/extensions/managers/keys/KeyManagerBLS.sol index fbf7f85..c3bb1cb 100644 --- a/src/extensions/managers/keys/KeyManagerBLS.sol +++ b/src/extensions/managers/keys/KeyManagerBLS.sol @@ -21,7 +21,7 @@ abstract contract KeyManagerBLS is KeyManager, BLSSig { uint64 public constant KeyManagerBLS_VERSION = 1; // must be same as TREE_DEPTH in MerkleLib.sol - uint256 private constant _TREE_DEPTH = 32; + uint256 private constant _TREE_DEPTH = 16; error DuplicateKey(); error PreviousKeySlashable(); @@ -32,7 +32,7 @@ abstract contract KeyManagerBLS is KeyManager, BLSSig { mapping(uint256 => PauseableEnumerableSet.InnerAddress) _keyData; Checkpoints.Trace256 _aggregatedKey; MerkleLib.Tree _keyMerkle; - bytes32 _keyMerkleRoot; + Checkpoints.Trace256 _keyMerkleRoot; } // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.KeyManagerBLS")) - 1)) & ~bytes32(uint256(0xff)) @@ -55,19 +55,24 @@ abstract contract KeyManagerBLS is KeyManager, BLSSig { BN254.G1Point[] memory nonSigningKeys, uint256[] memory nonSigningKeyIndices, bytes32[_TREE_DEPTH][] memory nonSigningKeyMerkleProofs, - bytes memory hint + bytes memory aggregatedKeyHint, + bytes memory keyMerkleHint ) public view returns (bool) { KeyManagerBLSStorage storage $ = _getKeyManagerBLSStorage(); // verify that the aggregated key is the same as the one at the timestamp - uint256 x = $._aggregatedKey.upperLookupRecent(timestamp, hint); + uint256 x = $._aggregatedKey.upperLookupRecent(timestamp, aggregatedKeyHint); + bytes32 root = bytes32($._keyMerkleRoot.upperLookupRecent(timestamp, keyMerkleHint)); if (aggregateG1Key.X != x) { return false; } BN254.G1Point memory aggregatedNonSigningKey = BN254.G1Point(0, 0); - bytes32 root = $._keyMerkleRoot; for (uint256 i = 0; i < nonSigningKeys.length; i++) { - if (MerkleLib.branchRoot(bytes32(nonSigningKeys[i].X), nonSigningKeyMerkleProofs[i], nonSigningKeyIndices[i]) != root) { + if ( + MerkleLib.branchRoot( + bytes32(nonSigningKeys[i].X), nonSigningKeyMerkleProofs[i], nonSigningKeyIndices[i] + ) != root + ) { return false; } aggregatedNonSigningKey = aggregatedNonSigningKey.plus(nonSigningKeys[i]); @@ -131,46 +136,61 @@ abstract contract KeyManagerBLS is KeyManager, BLSSig { */ function _updateKey(address operator, bytes memory key_) internal override { KeyManagerBLSStorage storage $ = _getKeyManagerBLSStorage(); - BN254.G1Point memory key = abi.decode(key_, (BN254.G1Point)); uint48 timestamp = _now(); + uint256 x = $._aggregatedKey.latest(); + (, uint256 y) = BN254.findYFromX(x); + BN254.G1Point memory aggregatedKey = BN254.G1Point(x, y); + BN254.G1Point memory prevKey = $._prevKey[operator]; + BN254.G1Point memory currentKey = $._key[operator]; + BN254.G1Point memory key; + assembly { + key := key_ + } if ($._keyData[key.X].value != address(0)) { revert DuplicateKey(); } - BN254.G1Point memory prevKey = $._prevKey[operator]; - if (prevKey.X != 0 || prevKey.Y != 0) { - if (!$._keyData[prevKey.X].status.checkUnregister(timestamp, _SLASHING_WINDOW())) { - revert PreviousKeySlashable(); - } - $._keyData[prevKey.X].value = address(0); - } - - uint256 x = $._aggregatedKey.latest(); - (, uint256 y) = BN254.findYFromX(x); - BN254.G1Point memory aggregatedKey = BN254.G1Point(x, y); - BN254.G1Point memory currentKey = $._key[operator]; - if (currentKey.X != 0 || currentKey.Y != 0) { - $._keyData[currentKey.X].status.disable(timestamp); - aggregatedKey = aggregatedKey.plus(currentKey.negate()); + if ( + (prevKey.X != 0 || prevKey.Y != 0) + && !$._keyData[prevKey.X].status.checkUnregister(timestamp, _SLASHING_WINDOW()) + ) { + revert PreviousKeySlashable(); } + delete $._keyData[prevKey.X]; // nothing'll happen if prev key is zero $._prevKey[operator] = currentKey; $._key[operator] = key; - if (key.X != 0 || key.Y != 0) { - if ($._keyData[key.X].status.enabled == 0 && $._keyData[key.X].status.disabled == 0) { - $._keyMerkle.insert(bytes32(key.X)); - } $._keyData[key.X].value = operator; $._keyData[key.X].status.set(timestamp); + } + + if (currentKey.X == 0 && currentKey.Y == 0 && (key.X != 0 || key.Y != 0)) { aggregatedKey = aggregatedKey.plus(key); + $._keyMerkle.insert(bytes32(key.X)); + $._keyMerkleRoot.push(_now(), uint256($._keyMerkle.root())); + $._aggregatedKey.push(_now(), key.X); + return; + } + + bytes32[16] memory proof; + uint256 index; + assembly { + proof := add(key_, 64) + index := add(key_, 576) // 64 + 32 * 16 } - if (aggregatedKey.X != x) { - $._aggregatedKey.push(_now(), aggregatedKey.X); - // all merkle tree slots should be hot - $._keyMerkleRoot = $._keyMerkle.root(); + // remove current key from merkle tree and aggregated key when new key is zero else update + aggregatedKey = aggregatedKey.plus(currentKey.negate()); + if (key.X == 0 && key.Y == 0) { + $._keyMerkle.update(bytes32(0), bytes32(currentKey.X), proof, index); + } else { + aggregatedKey = aggregatedKey.plus(key); + $._keyMerkle.update(bytes32(key.X), bytes32(prevKey.X), proof, index); } + + $._aggregatedKey.push(_now(), aggregatedKey.X); + $._keyMerkleRoot.push(_now(), uint256($._keyMerkle.root())); } } diff --git a/src/extensions/managers/sigs/BLSSig.sol b/src/extensions/managers/sigs/BLSSig.sol index 2cfaebe..ba04cae 100644 --- a/src/extensions/managers/sigs/BLSSig.sol +++ b/src/extensions/managers/sigs/BLSSig.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.25; import {SigManager} from "../../../managers/extendable/SigManager.sol"; import {BN254} from "../../../libraries/BN254.sol"; +import "forge-std/console.sol"; /** * @title BLSSig @@ -29,8 +30,11 @@ contract BLSSig is SigManager { (BN254.G1Point memory pubkeyG1, BN254.G2Point memory pubkeyG2) = abi.decode(key_, (BN254.G1Point, BN254.G2Point)); BN254.G1Point memory sig = abi.decode(signature, (BN254.G1Point)); - bytes32 messageHash = - keccak256(abi.encodePacked(operator, BN254.hashG1Point(pubkeyG1), BN254.hashG2Point(pubkeyG2))); + bytes memory message = abi.encode(operator, pubkeyG1, pubkeyG2); + console.log("key ", pubkeyG1.X, pubkeyG1.Y); + console.logBytes(message); + bytes32 messageHash = keccak256(message); + console.logBytes32(messageHash); return verify(pubkeyG1, pubkeyG2, sig, messageHash); } diff --git a/src/libraries/Merkle.sol b/src/libraries/Merkle.sol index a500cb6..69c4ebc 100644 --- a/src/libraries/Merkle.sol +++ b/src/libraries/Merkle.sol @@ -5,8 +5,9 @@ pragma solidity ^0.8.25; // work based on eth2 deposit contract, which is used under CC0-1.0 -uint256 constant TREE_DEPTH = 32; +uint256 constant TREE_DEPTH = 16; uint256 constant MAX_LEAVES = 2 ** TREE_DEPTH - 1; +uint256 constant MASK = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe; /** * @title MerkleLib @@ -15,6 +16,14 @@ uint256 constant MAX_LEAVES = 2 ** TREE_DEPTH - 1; * */ library MerkleLib { + using MerkleLib for Tree; + + event UpdateLeaf(uint256 index, bytes32 node); + + error InvalidProof(); + error FullMerkleTree(); + error InvalidIndex(); + /** * @notice Struct representing incremental merkle tree. Contains current * branch and the number of inserted leaves in the tree. @@ -32,7 +41,11 @@ library MerkleLib { * */ function insert(Tree storage _tree, bytes32 _node) internal { - require(_tree.count < MAX_LEAVES, "merkle tree full"); + if (_tree.count >= MAX_LEAVES) { + revert FullMerkleTree(); + } + + emit UpdateLeaf(_tree.count, _node); _tree.count += 1; uint256 size = _tree.count; @@ -49,17 +62,52 @@ library MerkleLib { assert(false); } + function update( + Tree storage _tree, + bytes32 _node, + bytes32 _oldNode, + bytes32[TREE_DEPTH] memory _branch, + uint256 _index + ) internal { + if (_index >= _tree.count) { + revert InvalidIndex(); + } + + bytes32 _root = branchRoot(_oldNode, _branch, _index); + if (_root != _tree.root()) { + // should be cheap enough, if it's not filled fully, mb optimize by checking root externally + revert InvalidProof(); + } + + emit UpdateLeaf(_index, _node); + + uint256 lastIndex = _tree.count - 1; + for (uint256 i = 0; i < TREE_DEPTH; i++) { + if ((lastIndex / 2 * 2) == _index) { + _tree.branch[i] = _node; + return; + } + if (_index & 0x01 == 1) { + _node = keccak256(abi.encodePacked(_branch[i], _node)); + } else { + _node = keccak256(abi.encodePacked(_node, _branch[i])); + } + lastIndex /= 2; + _index /= 2; + } + + assert(false); + } + /** - * @notice Calculates and returns`_tree`'s current root given array of zero - * hashes - * @param _zeroes Array of zero hashes + * @notice Calculates and returns`_tree`'s current root * @return _current Calculated root of `_tree` * */ - function rootWithCtx( - Tree storage _tree, - bytes32[TREE_DEPTH] memory _zeroes + function root( + Tree storage _tree ) internal view returns (bytes32 _current) { + bytes32[TREE_DEPTH] memory _zeroes = zeroHashes(); uint256 _index = _tree.count; for (uint256 i = 0; i < TREE_DEPTH; i++) { @@ -73,13 +121,6 @@ library MerkleLib { } } - /// @notice Calculates and returns`_tree`'s current root - function root( - Tree storage _tree - ) internal view returns (bytes32) { - return rootWithCtx(_tree, zeroHashes()); - } - /// @notice Returns array of TREE_DEPTH zero hashes /// @return _zeroes Array of TREE_DEPTH zero hashes function zeroHashes() internal pure returns (bytes32[TREE_DEPTH] memory _zeroes) { @@ -99,22 +140,6 @@ library MerkleLib { _zeroes[13] = Z_13; _zeroes[14] = Z_14; _zeroes[15] = Z_15; - _zeroes[16] = Z_16; - _zeroes[17] = Z_17; - _zeroes[18] = Z_18; - _zeroes[19] = Z_19; - _zeroes[20] = Z_20; - _zeroes[21] = Z_21; - _zeroes[22] = Z_22; - _zeroes[23] = Z_23; - _zeroes[24] = Z_24; - _zeroes[25] = Z_25; - _zeroes[26] = Z_26; - _zeroes[27] = Z_27; - _zeroes[28] = Z_28; - _zeroes[29] = Z_29; - _zeroes[30] = Z_30; - _zeroes[31] = Z_31; } /** @@ -145,37 +170,6 @@ library MerkleLib { } } - /** - * @notice Calculates and returns the merkle root as if the index is - * the topmost leaf in the tree. - * @param _item Merkle leaf - * @param _branch Merkle proof - * @param _index Index of `_item` in tree - * @dev Replaces siblings greater than the index (right subtrees) with zeroes. - * @return _current Calculated merkle root - * - */ - function reconstructRoot( - bytes32 _item, - bytes32[TREE_DEPTH] memory _branch, // cheaper than calldata indexing - uint256 _index - ) internal pure returns (bytes32 _current) { - _current = _item; - - bytes32[TREE_DEPTH] memory _zeroes = zeroHashes(); - - for (uint256 i = 0; i < TREE_DEPTH; i++) { - uint256 _ithBit = (_index >> i) & 0x01; - // cheaper than calldata indexing _branch[i*32:(i+1)*32]; - if (_ithBit == 1) { - _current = keccak256(abi.encodePacked(_branch[i], _current)); - } else { - // remove right subtree from proof - _current = keccak256(abi.encodePacked(_current, _zeroes[i])); - } - } - } - // keccak256 zero hashes bytes32 internal constant Z_0 = hex"0000000000000000000000000000000000000000000000000000000000000000"; bytes32 internal constant Z_1 = hex"ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5"; @@ -193,20 +187,4 @@ library MerkleLib { bytes32 internal constant Z_13 = hex"c1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb"; bytes32 internal constant Z_14 = hex"5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc"; bytes32 internal constant Z_15 = hex"da7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2"; - bytes32 internal constant Z_16 = hex"2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f"; - bytes32 internal constant Z_17 = hex"e1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a"; - bytes32 internal constant Z_18 = hex"5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0"; - bytes32 internal constant Z_19 = hex"b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0"; - bytes32 internal constant Z_20 = hex"c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2"; - bytes32 internal constant Z_21 = hex"f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9"; - bytes32 internal constant Z_22 = hex"5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377"; - bytes32 internal constant Z_23 = hex"4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652"; - bytes32 internal constant Z_24 = hex"cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef"; - bytes32 internal constant Z_25 = hex"0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d"; - bytes32 internal constant Z_26 = hex"b8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0"; - bytes32 internal constant Z_27 = hex"838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e"; - bytes32 internal constant Z_28 = hex"662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e"; - bytes32 internal constant Z_29 = hex"388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322"; - bytes32 internal constant Z_30 = hex"93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735"; - bytes32 internal constant Z_31 = hex"8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9"; } diff --git a/test/BLS.t.sol b/test/BLS.t.sol new file mode 100644 index 0000000..c7d6736 --- /dev/null +++ b/test/BLS.t.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {POCBaseTest} from "@symbiotic-test/POCBase.t.sol"; + +import {BLSSqrtTaskMiddleware} from "../src/examples/sqrt-task-network/BLSSqrtTaskMiddleware.sol"; +import {IBaseMiddlewareReader} from "../src/interfaces/IBaseMiddlewareReader.sol"; + +//import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; +//import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; +// import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {BaseMiddlewareReader} from "../src/middleware/BaseMiddlewareReader.sol"; +import {BN254} from "../src/libraries/BN254.sol"; +import "forge-std/console.sol"; +//import {Slasher} from "@symbiotic/contracts/slasher/Slasher.sol"; +//import {VetoSlasher} from "@symbiotic/contracts/slasher/VetoSlasher.sol"; + +contract OperatorsRegistrationTest is POCBaseTest { + // using Subnetwork for bytes32; + // using Subnetwork for address; + using Math for uint256; + + address network = address(0x123); + + BLSSqrtTaskMiddleware internal middleware; + + uint48 internal slashingWindow = 1200; // 20 minutes + string internal constant BLS_TEST_DATA = "test/helpers/blsTestVectors.json"; + + function setUp() public override { + SYMBIOTIC_CORE_PROJECT_ROOT = "lib/core/"; + vm.warp(1_729_690_309); + + super.setUp(); + + _deposit(vault1, alice, 1000 ether); + _deposit(vault2, alice, 1000 ether); + _deposit(vault3, alice, 1000 ether); + + address readHelper = address(new BaseMiddlewareReader()); + + // Initialize middleware contract + middleware = new BLSSqrtTaskMiddleware( + address(network), + slashingWindow, + address(operatorRegistry), + address(vaultFactory), + address(operatorNetworkOptInService), + readHelper, + owner + ); + + _registerNetwork(network, address(middleware)); + + vm.warp(vm.getBlockTimestamp() + 1); + } + + function testBLSRegisterOperator() public { + string memory json = vm.readFile(BLS_TEST_DATA); + address operator = abi.decode(vm.parseJson(json, ".operator"), (address)); + uint256[] memory keyg1 = vm.parseJsonUintArray(json, ".publicKeyG1"); + uint256[] memory keyg2 = vm.parseJsonUintArray(json, ".publicKeyG2"); + BN254.G1Point memory keyG1 = BN254.G1Point(keyg1[0], keyg1[1]); + BN254.G2Point memory keyG2 = BN254.G2Point([keyg2[0], keyg2[1]], [keyg2[2], keyg2[3]]); + uint256[] memory sig = vm.parseJsonUintArray(json, ".signature"); + bytes memory signature = abi.encode(sig[0], sig[1]); + bytes memory key = abi.encode(keyG1, keyG2); + console.log("key: ", keyG1.X, keyG1.Y); + console.logBytes(key); + + // Register operator using Ed25519 signature + vm.prank(operator); + middleware.registerOperator(key, address(0), signature); + + // Verify operator is registered correctly + assertTrue(IBaseMiddlewareReader(address(middleware)).isOperatorRegistered(operator)); + + assertEq( + abi.decode(IBaseMiddlewareReader(address(middleware)).operatorKey(operator), (bytes32)), + bytes32(0) + ); + vm.warp(block.timestamp + 2); + assertEq( + abi.decode(IBaseMiddlewareReader(address(middleware)).operatorKey(operator), (BN254.G1Point)).X, keyG1.X + ); + } +} \ No newline at end of file diff --git a/test/Merkle.t.sol b/test/Merkle.t.sol new file mode 100644 index 0000000..399a7ae --- /dev/null +++ b/test/Merkle.t.sol @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import "forge-std/Test.sol"; +import "./helpers/SimpleMerkle.sol"; +import "./helpers/FullMerkle.sol"; + +contract MerkleTest is Test { + SimpleMerkle internal simpleMerkle; + FullMerkle internal fullMerkle; + + function setUp() public { + simpleMerkle = new SimpleMerkle(); + fullMerkle = new FullMerkle(); + } + + function testInsert() public { + bytes32 node = keccak256("test"); + + simpleMerkle.insert(node); + fullMerkle.insert(node); + + bytes32[16] memory proof = fullMerkle.getProof(0); + console.log("1"); + assertTrue(simpleMerkle.verify(node, proof, 0)); + console.log("2"); + assertTrue(fullMerkle.verify(node, proof, 0)); + console.log("3"); + assertEq(simpleMerkle.count(), fullMerkle.currentLeafIndex()); + console.log("4"); + assertEq(simpleMerkle.count(), 1); + assertEq(simpleMerkle.root(), fullMerkle.root()); + } + + function testMultipleInserts() public { + bytes32[] memory nodes = new bytes32[](3); + nodes[0] = keccak256("test1"); + nodes[1] = keccak256("test2"); + nodes[2] = keccak256("test3"); + + for(uint i = 0; i < nodes.length; i++) { + simpleMerkle.insert(nodes[i]); + fullMerkle.insert(nodes[i]); + + bytes32[16] memory proof = fullMerkle.getProof(i); + assertTrue(simpleMerkle.verify(nodes[i], proof, i)); + assertTrue(fullMerkle.verify(nodes[i], proof, i)); + assertEq(simpleMerkle.count(), fullMerkle.currentLeafIndex()); + assertEq(simpleMerkle.count(), i + 1); + assertEq(simpleMerkle.root(), fullMerkle.root()); + } + } + + function testVerify() public { + bytes32 node = keccak256("test"); + + simpleMerkle.insert(node); + fullMerkle.insert(node); + + bytes32[16] memory proof = fullMerkle.getProof(0); + + assertTrue(simpleMerkle.verify(node, proof, 0)); + assertTrue(fullMerkle.verify(node, proof, 0)); + assertEq(simpleMerkle.root(), fullMerkle.root()); + } + + function testUpdate() public { + bytes32 oldNode = keccak256("test"); + bytes32 newNode = keccak256("updated"); + + simpleMerkle.insert(oldNode); + fullMerkle.insert(oldNode); + + bytes32[16] memory proof = fullMerkle.getProof(0); + + simpleMerkle.update(newNode, oldNode, proof, 0); + fullMerkle.update(newNode, 0); + + assertEq(simpleMerkle.root(), fullMerkle.root()); + + // Verify new node + proof = fullMerkle.getProof(0); + assertTrue(simpleMerkle.verify(newNode, proof, 0)); + assertTrue(fullMerkle.verify(newNode, proof, 0)); + } + + function testFuzzInsert(bytes32 node) public { + simpleMerkle.insert(node); + fullMerkle.insert(node); + + bytes32[16] memory proof = fullMerkle.getProof(0); + assertTrue(simpleMerkle.verify(node, proof, 0)); + assertTrue(fullMerkle.verify(node, proof, 0)); + assertEq(simpleMerkle.count(), fullMerkle.currentLeafIndex()); + assertEq(simpleMerkle.root(), fullMerkle.root()); + } + + function testFuzzUpdate(bytes32 oldNode, bytes32 newNode) public { + simpleMerkle.insert(oldNode); + fullMerkle.insert(oldNode); + + bytes32[16] memory proof = fullMerkle.getProof(0); + + simpleMerkle.update(newNode, oldNode, proof, 0); + fullMerkle.update(newNode, 0); + + // Verify new node + proof = fullMerkle.getProof(0); + assertTrue(simpleMerkle.verify(newNode, proof, 0)); + assertTrue(fullMerkle.verify(newNode, proof, 0)); + assertEq(simpleMerkle.root(), fullMerkle.root()); + } +} diff --git a/test/helpers/FullMerkle.sol b/test/helpers/FullMerkle.sol new file mode 100644 index 0000000..16c353e --- /dev/null +++ b/test/helpers/FullMerkle.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +contract FullMerkle { + uint256 public constant DEPTH = 16; + bytes32[DEPTH] public zeroValues; + mapping(uint256 => mapping(uint256 => bytes32)) public nodes; + bytes32[] public leaves; + uint256 public currentLeafIndex; + + constructor() { + zeroValues[0] = 0x0000000000000000000000000000000000000000000000000000000000000000; + zeroValues[1] = 0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5; + zeroValues[2] = 0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30; + zeroValues[3] = 0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85; + zeroValues[4] = 0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344; + zeroValues[5] = 0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d; + zeroValues[6] = 0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968; + zeroValues[7] = 0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83; + zeroValues[8] = 0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af; + zeroValues[9] = 0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0; + zeroValues[10] = 0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5; + zeroValues[11] = 0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892; + zeroValues[12] = 0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c; + zeroValues[13] = 0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb; + zeroValues[14] = 0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc; + zeroValues[15] = 0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2; + } + + function insert(bytes32 _node) public { + require(currentLeafIndex < 2**DEPTH, "Tree is full"); + + uint256 leafPos = currentLeafIndex; + leaves.push(_node); + nodes[0][leafPos] = _node; + + _updatePath(leafPos); + currentLeafIndex++; + } + + function update(bytes32 _node, uint256 _index) public { + require(_index < leaves.length, "Leaf index out of bounds"); + + leaves[_index] = _node; + nodes[0][_index] = _node; + + _updatePath(_index); + } + + function root() public view returns (bytes32) { + return nodes[DEPTH][0]; + } + + function getProof(uint256 _index) public view returns (bytes32[16] memory) { + require(_index < leaves.length, "Leaf index out of bounds"); + + bytes32[16] memory proof; + uint256 currentIndex = _index; + + for(uint256 i = 0; i < DEPTH; i++) { + uint256 siblingIndex; + if(currentIndex % 2 == 0) { + siblingIndex = currentIndex + 1; + } else { + siblingIndex = currentIndex - 1; + } + + bytes32 sibling = nodes[i][siblingIndex]; + if(sibling == bytes32(0)) { + sibling = zeroValues[i]; + } + proof[i] = sibling; + + currentIndex = currentIndex / 2; + } + + return proof; + } + + function verify(bytes32 _node, bytes32[16] calldata _proof, uint256 _index) public view returns (bool) { + bytes32 computedHash = _node; + uint256 currentIndex = _index; + + for(uint256 i = 0; i < DEPTH; i++) { + bytes32 sibling = _proof[i]; + if(currentIndex % 2 == 0) { + computedHash = keccak256(abi.encodePacked(computedHash, sibling)); + } else { + computedHash = keccak256(abi.encodePacked(sibling, computedHash)); + } + currentIndex = currentIndex / 2; + } + + return computedHash == nodes[DEPTH][0]; + } + + function _updatePath(uint256 currentPos) private { + for(uint256 depth = 0; depth < DEPTH; depth++) { + uint256 leftPos = (currentPos / 2) * 2; + uint256 rightPos = leftPos + 1; + + bytes32 left = nodes[depth][leftPos]; + bytes32 right = nodes[depth][rightPos]; + if(left == bytes32(0)) left = zeroValues[depth]; + if(right == bytes32(0)) right = zeroValues[depth]; + + bytes32 parent = keccak256(abi.encodePacked(left, right)); + nodes[depth+1][currentPos/2] = parent; + currentPos = currentPos/2; + } + } +} diff --git a/test/helpers/SimpleMerkle.sol b/test/helpers/SimpleMerkle.sol new file mode 100644 index 0000000..63870cd --- /dev/null +++ b/test/helpers/SimpleMerkle.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import "../../src/libraries/Merkle.sol"; + +contract SimpleMerkle { + using MerkleLib for MerkleLib.Tree; + + MerkleLib.Tree private tree; + + function insert(bytes32 _node) external { + tree.insert(_node); + } + + function update(bytes32 _node, bytes32 _oldNode, bytes32[TREE_DEPTH] memory _proof, uint256 _index) external { + tree.update(_node, _oldNode, _proof, _index); + } + + function verify(bytes32 _node, bytes32[TREE_DEPTH] memory _proof, uint256 _index) external view returns (bool) { + return tree.root() == MerkleLib.branchRoot(_node, _proof, _index); + } + + function root() external view returns (bytes32) { + return tree.root(); + } + + function count() external view returns (uint256) { + return tree.count; + } +} diff --git a/test/helpers/blsTestGenerator.py b/test/helpers/blsTestGenerator.py new file mode 100644 index 0000000..c5a7402 --- /dev/null +++ b/test/helpers/blsTestGenerator.py @@ -0,0 +1,172 @@ +from py_ecc.optimized_bn128 import * +from eth_hash.auto import keccak +from typing import Tuple +import json +import os +from eth_account import Account +import eth_abi + +# used for helped aggregation +def get_public_key_G1(secret_key: int) -> Tuple[FQ, FQ, FQ]: + return multiply(G1, secret_key) + + +def get_public_key(secret_key: int) -> Tuple[FQ2, FQ2, FQ2]: + return multiply(G2, secret_key) + + +def sign(message: Tuple[FQ, FQ, FQ], secret_key: int): + return multiply(message, secret_key) + + +def aggregate_signatures(signatures: list[Tuple[FQ, FQ, FQ]]) -> Tuple[FQ, FQ, FQ]: + res = signatures[0] + for signature in signatures[1:]: + res = add(res, signature) + return res + + +def aggregate_public_keys(pubkeys: list[Tuple[FQ2, FQ2, FQ2]]) -> Tuple[FQ2, FQ2, FQ2]: + res = pubkeys[0] + for pubkey in pubkeys[1:]: + res = add(res, pubkey) + return res + + +# used for helped aggregation +def aggregate_public_keys_G1(pubkeys: list[Tuple[FQ, FQ, FQ]]) -> Tuple[FQ, FQ, FQ]: + res = pubkeys[0] + for pubkey in pubkeys[1:]: + res = add(res, pubkey) + return res + + +def hash_to_point(data: str): + return map_to_point(keccak(data)) + + +def map_to_point(x): + # Convert bytes to int and mod by field modulus + x_int = int.from_bytes(x, 'big') % curve_order + + while True: + # Calculate x^3 + 3 mod p + beta = (pow(x_int, 3, field_modulus) + 3) % field_modulus + + # Try to find y such tha t y^2 = beta + y, found = sqrt(beta) + + if found: + return (FQ(x_int), FQ(y), FQ(1)) + + x_int = (x_int + 1) % field_modulus + + +def sqrt(x_square: int) -> Tuple[int, bool]: + # Calculate y = x^((p+1)/4) mod p + # This is equivalent to finding square root modulo p + # where p ≡ 3 (mod 4) + exp = (field_modulus + 1) // 4 + y = pow(x_square, exp, field_modulus) + + # Verify y is actually a square root + if pow(y, 2, field_modulus) == x_square: + return y, True + return 0, False + + +def parse_solc_G1(solc_G1: Tuple[int, int]): + x, y = solc_G1 + return FQ(x), FQ(y), FQ(1) + + +def format_G1(g1_element: Tuple[FQ, FQ, FQ]) -> Tuple[FQ, FQ]: + x, y = normalize(g1_element) + return (str(x), str(y)) + + +def format_G2(g2_element: Tuple[FQ2, FQ2, FQ2]) -> Tuple[FQ2, FQ2]: + x, y = normalize(g2_element) + x1, x2 = x.coeffs + y1, y2 = y.coeffs + return x1, x2, y1, y2 + + +def verify(message: bytes, signature: Tuple[FQ, FQ, FQ], public_key: Tuple[FQ2, FQ2, FQ2]) -> bool: + # Map message to curve point + h = hash_to_point(message) + + # Check e(signature, G2) = e(h, public_key) + # Note: signature and h are in G1, while G2 and public_key are in G2 + pairing1 = pairing(G2, signature) + pairing2 = pairing(public_key, h) + + return pairing1 == pairing2 + + +def generate_operator_address() -> str: + # Generate random private key + private_key = os.urandom(32) + acc = Account.create(private_key) + # Pad address to 32 bytes + return acc.address + + +secret_key = 123 + +public_key = get_public_key(secret_key) +public_key_g1 = get_public_key_G1(secret_key) + +formatted_pubkey = format_G2(public_key) +formatted_pubkey_g1 = format_G1(public_key_g1) + +# Create message hash as done in the contract +operator = generate_operator_address() +message = eth_abi.encode( + ['address', 'uint256', 'uint256', 'uint256[2]', 'uint256[2]'], + [ + operator, *[int(formatted_pubkey_g1[0]), int(formatted_pubkey_g1[1])], + *[[int(formatted_pubkey[0]), int(formatted_pubkey[1])], + [int(formatted_pubkey[2]), int(formatted_pubkey[3])]] + ] +) + +print("key: ", int(public_key_g1[0]), int(public_key_g1[1])) +message_hash = keccak(message) +print(message_hash.hex()) +print(message.hex()) +data = message_hash + +message = hash_to_point(data) +# Generate signature +signature = sign(message, secret_key) +formatted_sig = format_G1(signature) + +# Format values for test output + +# Verify the signature +is_valid = verify(data, signature, public_key) +print(f"\nSignature valid: {is_valid}") + + +print("Test values:") +print(f"Public key: {formatted_pubkey}") +print(f"Message: {data.hex()}") +print(f"Signature: {formatted_sig}") + +# Test vectors +test_vectors = { + "operator": operator, + "publicKeyG1": [int(x) for x in formatted_pubkey_g1], + "publicKeyG2": [int(x) for x in formatted_pubkey], + "message": data.hex(), + "signature": [int(x) for x in formatted_sig], +} + +with open('test/helpers/blsTestVectors.json', 'w') as f: + json.dump(test_vectors, f, indent=4) + + +520925e2bc69aebedb68bde43f4fb41adf43e56cf056dbd49c93995b7df9c1a2 +000000000000000000000000f413e32f01fdaf718c8dc2fab8a925bd38a8038d1aa125a22bd902874034e67868aed40267e5575d5919677987e3bc6dd42a32fe1bacc186725464068956d9a191455c2d6f6db282d83645c610510d8d4efbaee02a9de38d14bef2cf9afc3c698a4211fa7ada7b4f036a2dfef0dc122b423259d01f1954b33144db2b5c90da089e8bde287ec7089d5d6433f3b6becaefdb678b +000000000000000000000000f413e32f01fdaf718c8dc2fab8a925bd38a8038d1aa125a22bd902874034e67868aed40267e5575d5919677987e3bc6dd42a32fe1bacc186725464068956d9a191455c2d6f6db282d83645c610510d8d4efbaee02a9de38d14bef2cf9afc3c698a4211fa7ada7b4f036a2dfef0dc122b423259d01f1954b33144db2b5c90da089e8bde287ec7089d5d6433f3b6becaefdb678b1b1bf37ecdba226629c20908c7f475c5b3a7628ce26d696436eab0b0148034dfcd1659dc18b57722ecf6a4beb4d04dfe780a660c4c3bb2b165ab8486114c464c62 \ No newline at end of file diff --git a/test/helpers/blsTestVectors.json b/test/helpers/blsTestVectors.json new file mode 100644 index 0000000..dddcf0d --- /dev/null +++ b/test/helpers/blsTestVectors.json @@ -0,0 +1,18 @@ +{ + "operator": "0xf413e32F01FdAF718C8dC2FaB8a925bD38A8038d", + "publicKeyG1": [ + 12044856177338138530874920409116381031299482413736640813205984105865239212798, + 12517680268608176148518636624900077770369569703938976095420084287124476243680 + ], + "publicKeyG2": [ + 19276105129625393659655050515259006463014579919681138299520812914148935621072, + 14066454060412929535985836631817650877381034334390275410072431082437297539867, + 12642665914920339463975152321804664028480770144655934937445922690262428344269, + 10109651107942685361120988628892759706059655669161016107907096760613704453218 + ], + "message": "520925e2bc69aebedb68bde43f4fb41adf43e56cf056dbd49c93995b7df9c1a2", + "signature": [ + 9382330587211034271669719278772268703456139133988114961761514585713866768613, + 21327385529985774298614944796969972975730579184860258367314101024719604202540 + ] +} \ No newline at end of file From c45722bd226a8e3e982007f5aa087b503e385087 Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 20 Jan 2025 15:42:59 +0400 Subject: [PATCH 09/21] WIP --- lib/core | 2 +- src/libraries/Merkle.sol | 25 ++++++++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/lib/core b/lib/core index 2a5f6f0..feb15ec 160000 --- a/lib/core +++ b/lib/core @@ -1 +1 @@ -Subproject commit 2a5f6f0fcee9a8d0ace03c38c77a352c5e5f95ae +Subproject commit feb15ec0b55e30b56b9595a8b9d8f179f173bb76 diff --git a/src/libraries/Merkle.sol b/src/libraries/Merkle.sol index 69c4ebc..65a330b 100644 --- a/src/libraries/Merkle.sol +++ b/src/libraries/Merkle.sol @@ -34,6 +34,14 @@ library MerkleLib { uint256 count; } + function upsert(Tree storage _tree, bytes32 _node, bytes memory _proof, uint256 _index) internal { + if (_proof.length != 0) { + update(_tree, _node, bytes32(0), _proof, _index); + } else { + insert(_tree, _node); + } + } + /** * @notice Inserts `_node` into merkle tree * @dev Reverts if tree is full @@ -66,7 +74,7 @@ library MerkleLib { Tree storage _tree, bytes32 _node, bytes32 _oldNode, - bytes32[TREE_DEPTH] memory _branch, + bytes memory _branch, uint256 _index ) internal { if (_index >= _tree.count) { @@ -87,10 +95,14 @@ library MerkleLib { _tree.branch[i] = _node; return; } + bytes32 _next; + assembly { + _next := mload(add(_branch, add(mul(i, 32), 32))) + } if (_index & 0x01 == 1) { - _node = keccak256(abi.encodePacked(_branch[i], _node)); + _node = keccak256(abi.encodePacked(_next, _node)); } else { - _node = keccak256(abi.encodePacked(_node, _branch[i])); + _node = keccak256(abi.encodePacked(_node, _next)); } lastIndex /= 2; _index /= 2; @@ -153,7 +165,7 @@ library MerkleLib { */ function branchRoot( bytes32 _item, - bytes32[TREE_DEPTH] memory _branch, // cheaper than calldata indexing + bytes memory _branch, // cheaper than calldata indexing uint256 _index ) internal pure returns (bytes32 _current) { _current = _item; @@ -161,7 +173,10 @@ library MerkleLib { for (uint256 i = 0; i < TREE_DEPTH; i++) { uint256 _ithBit = (_index >> i) & 0x01; // cheaper than calldata indexing _branch[i*32:(i+1)*32]; - bytes32 _next = _branch[i]; + bytes32 _next; + assembly { + _next := mload(add(_branch, add(mul(i, 32), 32))) + } if (_ithBit == 1) { _current = keccak256(abi.encodePacked(_next, _current)); } else { From ba1ea53d80580447ea860f9400f364daef1f21c7 Mon Sep 17 00:00:00 2001 From: Algys Ievlev Date: Tue, 21 Jan 2025 21:04:42 +0400 Subject: [PATCH 10/21] fix: bls verify & test --- src/extensions/managers/sigs/BLSSig.sol | 34 +++++++++++--- test/BLS.t.sol | 23 +++++++--- test/helpers/blsTestGenerator.py | 61 ++++++++++++++----------- test/helpers/blsTestVectors.json | 20 ++++---- 4 files changed, 89 insertions(+), 49 deletions(-) diff --git a/src/extensions/managers/sigs/BLSSig.sol b/src/extensions/managers/sigs/BLSSig.sol index ba04cae..e34f502 100644 --- a/src/extensions/managers/sigs/BLSSig.sol +++ b/src/extensions/managers/sigs/BLSSig.sol @@ -29,12 +29,21 @@ contract BLSSig is SigManager { ) internal view override returns (bool) { (BN254.G1Point memory pubkeyG1, BN254.G2Point memory pubkeyG2) = abi.decode(key_, (BN254.G1Point, BN254.G2Point)); + BN254.G1Point memory sig = abi.decode(signature, (BN254.G1Point)); + bytes memory message = abi.encode(operator, pubkeyG1, pubkeyG2); - console.log("key ", pubkeyG1.X, pubkeyG1.Y); + + console.log("pubkeyG1 ", pubkeyG1.X, pubkeyG1.Y); + console.log("message "); console.logBytes(message); + bytes32 messageHash = keccak256(message); console.logBytes32(messageHash); + console.log("sig ", sig.X, sig.Y); + console.log("pubkeyG2 X ", pubkeyG2.X[0], pubkeyG2.X[1]); + console.log("pubkeyG2 Y", pubkeyG2.Y[0], pubkeyG2.Y[1]); + return verify(pubkeyG1, pubkeyG2, sig, messageHash); } @@ -52,13 +61,26 @@ contract BLSSig is SigManager { BN254.G1Point memory signature, bytes32 messageHash ) public view returns (bool) { + BN254.G1Point memory messageG1 = BN254.hashToG1(messageHash); + console.log("messageG1 ", messageG1.X, messageG1.Y); uint256 alpha = uint256( - keccak256(abi.encode(pubkeyG1.X, pubkeyG1.Y, pubkeyG2.X, pubkeyG2.Y, signature.X, signature.Y, messageHash)) - ); - + keccak256(abi.encodePacked( + signature.X, + signature.Y, + pubkeyG1.X, + pubkeyG1.Y, + pubkeyG2.X, + pubkeyG2.Y, + messageG1.X, + messageG1.Y + )) + ) % BN254.FR_MODULUS; + BN254.G1Point memory a1 = signature.plus(pubkeyG1.scalar_mul(alpha)); - BN254.G1Point memory b1 = BN254.hashToG1(messageHash).plus(BN254.generatorG1().scalar_mul(alpha)); + BN254.G1Point memory b1 = messageG1.plus(BN254.generatorG1().scalar_mul(alpha)); - return BN254.pairing(a1, BN254.negGeneratorG2(), b1, pubkeyG2); + BN254.safePairing(a1, BN254.negGeneratorG2(), b1, pubkeyG2, 120000000); + return true; + //return BN254.pairing(a1, BN254.negGeneratorG2(), b1, pubkeyG2); } } diff --git a/test/BLS.t.sol b/test/BLS.t.sol index c7d6736..f457345 100644 --- a/test/BLS.t.sol +++ b/test/BLS.t.sol @@ -58,16 +58,28 @@ contract OperatorsRegistrationTest is POCBaseTest { vm.warp(vm.getBlockTimestamp() + 1); } - function testBLSRegisterOperator() public { + function testBLSRegisterOperator() public { string memory json = vm.readFile(BLS_TEST_DATA); address operator = abi.decode(vm.parseJson(json, ".operator"), (address)); uint256[] memory keyg1 = vm.parseJsonUintArray(json, ".publicKeyG1"); uint256[] memory keyg2 = vm.parseJsonUintArray(json, ".publicKeyG2"); + BN254.G1Point memory keyG1 = BN254.G1Point(keyg1[0], keyg1[1]); BN254.G2Point memory keyG2 = BN254.G2Point([keyg2[0], keyg2[1]], [keyg2[2], keyg2[3]]); + uint256[] memory sig = vm.parseJsonUintArray(json, ".signature"); - bytes memory signature = abi.encode(sig[0], sig[1]); + + // BN254.G1Point memory sigG1 = BN254.G1Point(sig[0], sig[1]); + + bytes memory message = abi.encode(operator, keyG1, keyG2); + + bytes32 messageHash = keccak256(message); + BN254.G1Point memory messageG1 = BN254.hashToG1(messageHash); + BN254.G1Point memory sigG1 = BN254.scalar_mul(messageG1, uint256(69)); + + bytes memory signature = abi.encode(sigG1); bytes memory key = abi.encode(keyG1, keyG2); + console.log("key: ", keyG1.X, keyG1.Y); console.logBytes(key); @@ -78,13 +90,10 @@ contract OperatorsRegistrationTest is POCBaseTest { // Verify operator is registered correctly assertTrue(IBaseMiddlewareReader(address(middleware)).isOperatorRegistered(operator)); - assertEq( - abi.decode(IBaseMiddlewareReader(address(middleware)).operatorKey(operator), (bytes32)), - bytes32(0) - ); + assertEq(abi.decode(IBaseMiddlewareReader(address(middleware)).operatorKey(operator), (bytes32)), bytes32(0)); vm.warp(block.timestamp + 2); assertEq( abi.decode(IBaseMiddlewareReader(address(middleware)).operatorKey(operator), (BN254.G1Point)).X, keyG1.X ); } -} \ No newline at end of file +} diff --git a/test/helpers/blsTestGenerator.py b/test/helpers/blsTestGenerator.py index c5a7402..7c51782 100644 --- a/test/helpers/blsTestGenerator.py +++ b/test/helpers/blsTestGenerator.py @@ -41,25 +41,34 @@ def aggregate_public_keys_G1(pubkeys: list[Tuple[FQ, FQ, FQ]]) -> Tuple[FQ, FQ, return res -def hash_to_point(data: str): - return map_to_point(keccak(data)) - - -def map_to_point(x): - # Convert bytes to int and mod by field modulus - x_int = int.from_bytes(x, 'big') % curve_order - +def hash_to_point(data: bytes): + x = int.from_bytes(data, byteorder='big') % field_modulus + while True: - # Calculate x^3 + 3 mod p - beta = (pow(x_int, 3, field_modulus) + 3) % field_modulus + beta, y = find_y_from_x(x) - # Try to find y such tha t y^2 = beta - y, found = sqrt(beta) - - if found: - return (FQ(x_int), FQ(y), FQ(1)) + # Check if y^2 == beta + if pow(y, 2, field_modulus) == beta: + return FQ(x), FQ(y), FQ(1) - x_int = (x_int + 1) % field_modulus + x = (x + 1) % field_modulus + + +def find_y_from_x(x: int) -> Tuple[int, int]: + """ + Given x coordinate, find y coordinate on BN254 curve + Returns (beta, y) where: + beta = x^3 + 3 (mod p) + y = sqrt(beta) if it exists + """ + # Calculate beta = x^3 + 3 mod p + beta = (pow(x, 3, field_modulus) + 3) % field_modulus + + # Calculate y = beta^((p+1)/4) mod p + # Using same exponent as in BN254.sol: 0xc19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52 + y = pow(beta, 0xc19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52, field_modulus) + + return beta, y def sqrt(x_square: int) -> Tuple[int, bool]: @@ -87,8 +96,8 @@ def format_G1(g1_element: Tuple[FQ, FQ, FQ]) -> Tuple[FQ, FQ]: def format_G2(g2_element: Tuple[FQ2, FQ2, FQ2]) -> Tuple[FQ2, FQ2]: x, y = normalize(g2_element) - x1, x2 = x.coeffs - y1, y2 = y.coeffs + x2, x1 = x.coeffs + y2, y1 = y.coeffs return x1, x2, y1, y2 @@ -112,7 +121,7 @@ def generate_operator_address() -> str: return acc.address -secret_key = 123 +secret_key = 69 public_key = get_public_key(secret_key) public_key_g1 = get_public_key_G1(secret_key) @@ -125,15 +134,15 @@ def generate_operator_address() -> str: message = eth_abi.encode( ['address', 'uint256', 'uint256', 'uint256[2]', 'uint256[2]'], [ - operator, *[int(formatted_pubkey_g1[0]), int(formatted_pubkey_g1[1])], - *[[int(formatted_pubkey[0]), int(formatted_pubkey[1])], - [int(formatted_pubkey[2]), int(formatted_pubkey[3])]] + operator, int(formatted_pubkey_g1[0]), int(formatted_pubkey_g1[1]), + [int(formatted_pubkey[0]), int(formatted_pubkey[1])], + [int(formatted_pubkey[2]), int(formatted_pubkey[3])] ] ) print("key: ", int(public_key_g1[0]), int(public_key_g1[1])) message_hash = keccak(message) -print(message_hash.hex()) +print("message_hash: ", message_hash.hex()) print(message.hex()) data = message_hash @@ -167,6 +176,6 @@ def generate_operator_address() -> str: json.dump(test_vectors, f, indent=4) -520925e2bc69aebedb68bde43f4fb41adf43e56cf056dbd49c93995b7df9c1a2 -000000000000000000000000f413e32f01fdaf718c8dc2fab8a925bd38a8038d1aa125a22bd902874034e67868aed40267e5575d5919677987e3bc6dd42a32fe1bacc186725464068956d9a191455c2d6f6db282d83645c610510d8d4efbaee02a9de38d14bef2cf9afc3c698a4211fa7ada7b4f036a2dfef0dc122b423259d01f1954b33144db2b5c90da089e8bde287ec7089d5d6433f3b6becaefdb678b -000000000000000000000000f413e32f01fdaf718c8dc2fab8a925bd38a8038d1aa125a22bd902874034e67868aed40267e5575d5919677987e3bc6dd42a32fe1bacc186725464068956d9a191455c2d6f6db282d83645c610510d8d4efbaee02a9de38d14bef2cf9afc3c698a4211fa7ada7b4f036a2dfef0dc122b423259d01f1954b33144db2b5c90da089e8bde287ec7089d5d6433f3b6becaefdb678b1b1bf37ecdba226629c20908c7f475c5b3a7628ce26d696436eab0b0148034dfcd1659dc18b57722ecf6a4beb4d04dfe780a660c4c3bb2b165ab8486114c464c62 \ No newline at end of file +# 520925e2bc69aebedb68bde43f4fb41adf43e56cf056dbd49c93995b7df9c1a2 +# 000000000000000000000000f413e32f01fdaf718c8dc2fab8a925bd38a8038d1aa125a22bd902874034e67868aed40267e5575d5919677987e3bc6dd42a32fe1bacc186725464068956d9a191455c2d6f6db282d83645c610510d8d4efbaee02a9de38d14bef2cf9afc3c698a4211fa7ada7b4f036a2dfef0dc122b423259d01f1954b33144db2b5c90da089e8bde287ec7089d5d6433f3b6becaefdb678b +# 000000000000000000000000f413e32f01fdaf718c8dc2fab8a925bd38a8038d1aa125a22bd902874034e67868aed40267e5575d5919677987e3bc6dd42a32fe1bacc186725464068956d9a191455c2d6f6db282d83645c610510d8d4efbaee02a9de38d14bef2cf9afc3c698a4211fa7ada7b4f036a2dfef0dc122b423259d01f1954b33144db2b5c90da089e8bde287ec7089d5d6433f3b6becaefdb678b1b1bf37ecdba226629c20908c7f475c5b3a7628ce26d696436eab0b0148034dfcd1659dc18b57722ecf6a4beb4d04dfe780a660c4c3bb2b165ab8486114c464c62 \ No newline at end of file diff --git a/test/helpers/blsTestVectors.json b/test/helpers/blsTestVectors.json index dddcf0d..1cb34d5 100644 --- a/test/helpers/blsTestVectors.json +++ b/test/helpers/blsTestVectors.json @@ -1,18 +1,18 @@ { - "operator": "0xf413e32F01FdAF718C8dC2FaB8a925bD38A8038d", + "operator": "0x3d0B06EF5Bc5d38C42A4E484fd309f34b5aAb5c5", "publicKeyG1": [ - 12044856177338138530874920409116381031299482413736640813205984105865239212798, - 12517680268608176148518636624900077770369569703938976095420084287124476243680 + 5138697240077803445514669414784254799933862402946278134326199877546184124353, + 12587011617949543324467535889916856826666519601316494966427400843934921824601 ], "publicKeyG2": [ - 19276105129625393659655050515259006463014579919681138299520812914148935621072, - 14066454060412929535985836631817650877381034334390275410072431082437297539867, - 12642665914920339463975152321804664028480770144655934937445922690262428344269, - 10109651107942685361120988628892759706059655669161016107907096760613704453218 + 5334410886741819556325359147377682006012228123419628681352847439302316235957, + 19101821850089705274637533855249918363070101489527618151493230256975900223847, + 4185483097059047421902184823581361466320657066600218863748375739772335928910, + 354176189041917478648604979334478067325821134838555150300539079146482658331 ], - "message": "520925e2bc69aebedb68bde43f4fb41adf43e56cf056dbd49c93995b7df9c1a2", + "message": "ef3d3174b0f4be482b0dcabb5867ba8c4ed11366a0b5bad60456bd460528a9fe", "signature": [ - 9382330587211034271669719278772268703456139133988114961761514585713866768613, - 21327385529985774298614944796969972975730579184860258367314101024719604202540 + 15234655148121944182904300878801953647185372752883511069010483540340494659232, + 17008597909425962529461463286833851648784022421671624523348375822261496690459 ] } \ No newline at end of file From 1d6a2e53cc8f52478e26d2f9c0a18e166d669ea0 Mon Sep 17 00:00:00 2001 From: Algys Ievlev Date: Tue, 21 Jan 2025 21:07:01 +0400 Subject: [PATCH 11/21] fix: bls switch to precalculated sig --- test/BLS.t.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/BLS.t.sol b/test/BLS.t.sol index f457345..47792d4 100644 --- a/test/BLS.t.sol +++ b/test/BLS.t.sol @@ -69,13 +69,13 @@ contract OperatorsRegistrationTest is POCBaseTest { uint256[] memory sig = vm.parseJsonUintArray(json, ".signature"); - // BN254.G1Point memory sigG1 = BN254.G1Point(sig[0], sig[1]); + BN254.G1Point memory sigG1 = BN254.G1Point(sig[0], sig[1]); - bytes memory message = abi.encode(operator, keyG1, keyG2); - - bytes32 messageHash = keccak256(message); - BN254.G1Point memory messageG1 = BN254.hashToG1(messageHash); - BN254.G1Point memory sigG1 = BN254.scalar_mul(messageG1, uint256(69)); + // to calc sig in solidity + // bytes memory message = abi.encode(operator, keyG1, keyG2); + // bytes32 messageHash = keccak256(message); + // BN254.G1Point memory messageG1 = BN254.hashToG1(messageHash); + // BN254.G1Point memory sigG1 = BN254.scalar_mul(messageG1, uint256(69)); bytes memory signature = abi.encode(sigG1); bytes memory key = abi.encode(keyG1, keyG2); From 03e0efba389664806ef572cb439ff21c493f9940 Mon Sep 17 00:00:00 2001 From: Algys Ievlev Date: Tue, 21 Jan 2025 21:50:15 +0400 Subject: [PATCH 12/21] fix: decoding & zero case in bls manager --- .../managers/keys/KeyManagerBLS.sol | 10 ++--- test/BLS.t.sol | 44 ++++++++++++------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/extensions/managers/keys/KeyManagerBLS.sol b/src/extensions/managers/keys/KeyManagerBLS.sol index c3bb1cb..4dc21c3 100644 --- a/src/extensions/managers/keys/KeyManagerBLS.sol +++ b/src/extensions/managers/keys/KeyManagerBLS.sol @@ -138,14 +138,14 @@ abstract contract KeyManagerBLS is KeyManager, BLSSig { KeyManagerBLSStorage storage $ = _getKeyManagerBLSStorage(); uint48 timestamp = _now(); uint256 x = $._aggregatedKey.latest(); - (, uint256 y) = BN254.findYFromX(x); + uint256 y = 0; + if (x != 0) { + (, y) = BN254.findYFromX(x); + } BN254.G1Point memory aggregatedKey = BN254.G1Point(x, y); BN254.G1Point memory prevKey = $._prevKey[operator]; BN254.G1Point memory currentKey = $._key[operator]; - BN254.G1Point memory key; - assembly { - key := key_ - } + BN254.G1Point memory key = abi.decode(key_, (BN254.G1Point)); if ($._keyData[key.X].value != address(0)) { revert DuplicateKey(); diff --git a/test/BLS.t.sol b/test/BLS.t.sol index 47792d4..e4ee897 100644 --- a/test/BLS.t.sol +++ b/test/BLS.t.sol @@ -14,14 +14,17 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {BaseMiddlewareReader} from "../src/middleware/BaseMiddlewareReader.sol"; import {BN254} from "../src/libraries/BN254.sol"; +import {BN254G2} from "./BN254G2.sol"; import "forge-std/console.sol"; //import {Slasher} from "@symbiotic/contracts/slasher/Slasher.sol"; //import {VetoSlasher} from "@symbiotic/contracts/slasher/VetoSlasher.sol"; contract OperatorsRegistrationTest is POCBaseTest { + // using Subnetwork for bytes32; // using Subnetwork for address; using Math for uint256; + using BN254 for BN254.G1Point; address network = address(0x123); @@ -58,38 +61,47 @@ contract OperatorsRegistrationTest is POCBaseTest { vm.warp(vm.getBlockTimestamp() + 1); } + function getG2Key(uint256 privateKey) public view returns (BN254.G2Point memory) { + BN254.G2Point memory G2 = BN254.generatorG2(); + (uint256 x1, uint256 x2, uint256 y1, uint256 y2) = BN254G2.ECTwistMul(privateKey, G2.X[1], G2.X[0], G2.Y[1], G2.Y[0]); + return BN254.G2Point([x2, x1], [y2, y1]); + } + function testBLSRegisterOperator() public { - string memory json = vm.readFile(BLS_TEST_DATA); - address operator = abi.decode(vm.parseJson(json, ".operator"), (address)); - uint256[] memory keyg1 = vm.parseJsonUintArray(json, ".publicKeyG1"); - uint256[] memory keyg2 = vm.parseJsonUintArray(json, ".publicKeyG2"); + address operator = address(0x123); + uint256 privateKey = 123; - BN254.G1Point memory keyG1 = BN254.G1Point(keyg1[0], keyg1[1]); - BN254.G2Point memory keyG2 = BN254.G2Point([keyg2[0], keyg2[1]], [keyg2[2], keyg2[3]]); + // get G1 public key + BN254.G1Point memory keyG1 = BN254.generatorG1().scalar_mul(privateKey); + // get G2 public key + BN254.G2Point memory keyG2 = getG2Key(privateKey); - uint256[] memory sig = vm.parseJsonUintArray(json, ".signature"); + // craft message [operator, keyG1, keyG2] + bytes memory message = abi.encode(operator, keyG1, keyG2); - BN254.G1Point memory sigG1 = BN254.G1Point(sig[0], sig[1]); + // map hash to G1 + BN254.G1Point memory messageG1 = BN254.hashToG1(keccak256(message)); - // to calc sig in solidity - // bytes memory message = abi.encode(operator, keyG1, keyG2); - // bytes32 messageHash = keccak256(message); - // BN254.G1Point memory messageG1 = BN254.hashToG1(messageHash); - // BN254.G1Point memory sigG1 = BN254.scalar_mul(messageG1, uint256(69)); + // sign message + BN254.G1Point memory sigG1 = messageG1.scalar_mul(privateKey); bytes memory signature = abi.encode(sigG1); bytes memory key = abi.encode(keyG1, keyG2); - console.log("key: ", keyG1.X, keyG1.Y); - console.logBytes(key); + // register operator in global registry + _registerOperator(operator); - // Register operator using Ed25519 signature + // opt-in operator to network + _optInOperatorNetwork(operator, network); + + // Register operator using BLS bn254 signature in middleware vm.prank(operator); middleware.registerOperator(key, address(0), signature); // Verify operator is registered correctly assertTrue(IBaseMiddlewareReader(address(middleware)).isOperatorRegistered(operator)); + // Verify operator key is registered correctly assertEq(abi.decode(IBaseMiddlewareReader(address(middleware)).operatorKey(operator), (bytes32)), bytes32(0)); vm.warp(block.timestamp + 2); assertEq( From c55067ca69b98426a88bddc409028efeaa99435c Mon Sep 17 00:00:00 2001 From: Algys Ievlev Date: Tue, 21 Jan 2025 21:54:30 +0400 Subject: [PATCH 13/21] fix: switch safeParing -> pairing & validate return --- src/extensions/managers/sigs/BLSSig.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/extensions/managers/sigs/BLSSig.sol b/src/extensions/managers/sigs/BLSSig.sol index e34f502..ca28c04 100644 --- a/src/extensions/managers/sigs/BLSSig.sol +++ b/src/extensions/managers/sigs/BLSSig.sol @@ -79,8 +79,6 @@ contract BLSSig is SigManager { BN254.G1Point memory a1 = signature.plus(pubkeyG1.scalar_mul(alpha)); BN254.G1Point memory b1 = messageG1.plus(BN254.generatorG1().scalar_mul(alpha)); - BN254.safePairing(a1, BN254.negGeneratorG2(), b1, pubkeyG2, 120000000); - return true; - //return BN254.pairing(a1, BN254.negGeneratorG2(), b1, pubkeyG2); + return BN254.pairing(a1, BN254.negGeneratorG2(), b1, pubkeyG2); } } From 542fc41815d083b719812595f1251323f0eff50f Mon Sep 17 00:00:00 2001 From: Algys Ievlev Date: Wed, 22 Jan 2025 11:37:01 +0400 Subject: [PATCH 14/21] feat: use pure solidity G2 operations for BLS tests --- test/BLS.t.sol | 4 +- test/libraries/BN254G2.sol | 394 +++++++++++++++++++++++++++++++++++++ 2 files changed, 396 insertions(+), 2 deletions(-) create mode 100644 test/libraries/BN254G2.sol diff --git a/test/BLS.t.sol b/test/BLS.t.sol index e4ee897..278c5f8 100644 --- a/test/BLS.t.sol +++ b/test/BLS.t.sol @@ -14,7 +14,7 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {BaseMiddlewareReader} from "../src/middleware/BaseMiddlewareReader.sol"; import {BN254} from "../src/libraries/BN254.sol"; -import {BN254G2} from "./BN254G2.sol"; +import {BN254G2} from "../test/libraries/BN254G2.sol"; import "forge-std/console.sol"; //import {Slasher} from "@symbiotic/contracts/slasher/Slasher.sol"; //import {VetoSlasher} from "@symbiotic/contracts/slasher/VetoSlasher.sol"; @@ -93,7 +93,7 @@ contract OperatorsRegistrationTest is POCBaseTest { // opt-in operator to network _optInOperatorNetwork(operator, network); - + // Register operator using BLS bn254 signature in middleware vm.prank(operator); middleware.registerOperator(key, address(0), signature); diff --git a/test/libraries/BN254G2.sol b/test/libraries/BN254G2.sol new file mode 100644 index 0000000..c8cfd1c --- /dev/null +++ b/test/libraries/BN254G2.sol @@ -0,0 +1,394 @@ +pragma solidity ^0.8.0; + +/** + * @title Elliptic curve operations on twist points for alt_bn128 + * @author Mustafa Al-Bassam (mus@musalbas.com) + * @dev Homepage: https://github.com/musalbas/solidity-BN256G2 + */ + +library BN254G2 { + uint256 internal constant FIELD_MODULUS = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; + uint256 internal constant TWISTBX = 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; + uint256 internal constant TWISTBY = 0x9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d2; + uint internal constant PTXX = 0; + uint internal constant PTXY = 1; + uint internal constant PTYX = 2; + uint internal constant PTYY = 3; + uint internal constant PTZX = 4; + uint internal constant PTZY = 5; + + /** + * @notice Add two twist points + * @param pt1xx Coefficient 1 of x on point 1 + * @param pt1xy Coefficient 2 of x on point 1 + * @param pt1yx Coefficient 1 of y on point 1 + * @param pt1yy Coefficient 2 of y on point 1 + * @param pt2xx Coefficient 1 of x on point 2 + * @param pt2xy Coefficient 2 of x on point 2 + * @param pt2yx Coefficient 1 of y on point 2 + * @param pt2yy Coefficient 2 of y on point 2 + * @return (pt3xx, pt3xy, pt3yx, pt3yy) + */ + function ECTwistAdd( + uint256 pt1xx, uint256 pt1xy, + uint256 pt1yx, uint256 pt1yy, + uint256 pt2xx, uint256 pt2xy, + uint256 pt2yx, uint256 pt2yy + ) public view returns ( + uint256, uint256, + uint256, uint256 + ) { + if ( + pt1xx == 0 && pt1xy == 0 && + pt1yx == 0 && pt1yy == 0 + ) { + if (!( + pt2xx == 0 && pt2xy == 0 && + pt2yx == 0 && pt2yy == 0 + )) { + assert(_isOnCurve( + pt2xx, pt2xy, + pt2yx, pt2yy + )); + } + return ( + pt2xx, pt2xy, + pt2yx, pt2yy + ); + } else if ( + pt2xx == 0 && pt2xy == 0 && + pt2yx == 0 && pt2yy == 0 + ) { + assert(_isOnCurve( + pt1xx, pt1xy, + pt1yx, pt1yy + )); + return ( + pt1xx, pt1xy, + pt1yx, pt1yy + ); + } + + assert(_isOnCurve( + pt1xx, pt1xy, + pt1yx, pt1yy + )); + assert(_isOnCurve( + pt2xx, pt2xy, + pt2yx, pt2yy + )); + + uint256[6] memory pt3 = _ECTwistAddJacobian( + pt1xx, pt1xy, + pt1yx, pt1yy, + 1, 0, + pt2xx, pt2xy, + pt2yx, pt2yy, + 1, 0 + ); + + return _fromJacobian( + pt3[PTXX], pt3[PTXY], + pt3[PTYX], pt3[PTYY], + pt3[PTZX], pt3[PTZY] + ); + } + + /** + * @notice Multiply a twist point by a scalar + * @param s Scalar to multiply by + * @param pt1xx Coefficient 1 of x + * @param pt1xy Coefficient 2 of x + * @param pt1yx Coefficient 1 of y + * @param pt1yy Coefficient 2 of y + * @return (pt2xx, pt2xy, pt2yx, pt2yy) + */ + function ECTwistMul( + uint256 s, + uint256 pt1xx, uint256 pt1xy, + uint256 pt1yx, uint256 pt1yy + ) public view returns ( + uint256, uint256, + uint256, uint256 + ) { + uint256 pt1zx = 1; + if ( + pt1xx == 0 && pt1xy == 0 && + pt1yx == 0 && pt1yy == 0 + ) { + pt1xx = 1; + pt1yx = 1; + pt1zx = 0; + } else { + assert(_isOnCurve( + pt1xx, pt1xy, + pt1yx, pt1yy + )); + } + + uint256[6] memory pt2 = _ECTwistMulJacobian( + s, + pt1xx, pt1xy, + pt1yx, pt1yy, + pt1zx, 0 + ); + + return _fromJacobian( + pt2[PTXX], pt2[PTXY], + pt2[PTYX], pt2[PTYY], + pt2[PTZX], pt2[PTZY] + ); + } + + /** + * @notice Get the field modulus + * @return The field modulus + */ + function GetFieldModulus() public pure returns (uint256) { + return FIELD_MODULUS; + } + + function submod(uint256 a, uint256 b, uint256 n) internal pure returns (uint256) { + return addmod(a, n - b, n); + } + + function _FQ2Mul( + uint256 xx, uint256 xy, + uint256 yx, uint256 yy + ) internal pure returns (uint256, uint256) { + return ( + submod(mulmod(xx, yx, FIELD_MODULUS), mulmod(xy, yy, FIELD_MODULUS), FIELD_MODULUS), + addmod(mulmod(xx, yy, FIELD_MODULUS), mulmod(xy, yx, FIELD_MODULUS), FIELD_MODULUS) + ); + } + + function _FQ2Muc( + uint256 xx, uint256 xy, + uint256 c + ) internal pure returns (uint256, uint256) { + return ( + mulmod(xx, c, FIELD_MODULUS), + mulmod(xy, c, FIELD_MODULUS) + ); + } + + function _FQ2Add( + uint256 xx, uint256 xy, + uint256 yx, uint256 yy + ) internal pure returns (uint256, uint256) { + return ( + addmod(xx, yx, FIELD_MODULUS), + addmod(xy, yy, FIELD_MODULUS) + ); + } + + function _FQ2Sub( + uint256 xx, uint256 xy, + uint256 yx, uint256 yy + ) internal pure returns (uint256 rx, uint256 ry) { + return ( + submod(xx, yx, FIELD_MODULUS), + submod(xy, yy, FIELD_MODULUS) + ); + } + + function _FQ2Div( + uint256 xx, uint256 xy, + uint256 yx, uint256 yy + ) internal view returns (uint256, uint256) { + (yx, yy) = _FQ2Inv(yx, yy); + return _FQ2Mul(xx, xy, yx, yy); + } + + function _FQ2Inv(uint256 x, uint256 y) internal view returns (uint256, uint256) { + uint256 inv = _modInv(addmod(mulmod(y, y, FIELD_MODULUS), mulmod(x, x, FIELD_MODULUS), FIELD_MODULUS), FIELD_MODULUS); + return ( + mulmod(x, inv, FIELD_MODULUS), + FIELD_MODULUS - mulmod(y, inv, FIELD_MODULUS) + ); + } + + function _isOnCurve( + uint256 xx, uint256 xy, + uint256 yx, uint256 yy + ) internal pure returns (bool) { + uint256 yyx; + uint256 yyy; + uint256 xxxx; + uint256 xxxy; + (yyx, yyy) = _FQ2Mul(yx, yy, yx, yy); + (xxxx, xxxy) = _FQ2Mul(xx, xy, xx, xy); + (xxxx, xxxy) = _FQ2Mul(xxxx, xxxy, xx, xy); + (yyx, yyy) = _FQ2Sub(yyx, yyy, xxxx, xxxy); + (yyx, yyy) = _FQ2Sub(yyx, yyy, TWISTBX, TWISTBY); + return yyx == 0 && yyy == 0; + } + + function _modInv(uint256 a, uint256 n) internal view returns (uint256 result) { + bool success; + assembly { + let freemem := mload(0x40) + mstore(freemem, 0x20) + mstore(add(freemem,0x20), 0x20) + mstore(add(freemem,0x40), 0x20) + mstore(add(freemem,0x60), a) + mstore(add(freemem,0x80), sub(n, 2)) + mstore(add(freemem,0xA0), n) + success := staticcall(sub(gas(), 2000), 5, freemem, 0xC0, freemem, 0x20) + result := mload(freemem) + } + require(success); + } + + function _fromJacobian( + uint256 pt1xx, uint256 pt1xy, + uint256 pt1yx, uint256 pt1yy, + uint256 pt1zx, uint256 pt1zy + ) internal view returns ( + uint256 pt2xx, uint256 pt2xy, + uint256 pt2yx, uint256 pt2yy + ) { + uint256 invzx; + uint256 invzy; + (invzx, invzy) = _FQ2Inv(pt1zx, pt1zy); + (pt2xx, pt2xy) = _FQ2Mul(pt1xx, pt1xy, invzx, invzy); + (pt2yx, pt2yy) = _FQ2Mul(pt1yx, pt1yy, invzx, invzy); + } + + function _ECTwistAddJacobian( + uint256 pt1xx, uint256 pt1xy, + uint256 pt1yx, uint256 pt1yy, + uint256 pt1zx, uint256 pt1zy, + uint256 pt2xx, uint256 pt2xy, + uint256 pt2yx, uint256 pt2yy, + uint256 pt2zx, uint256 pt2zy) internal pure returns (uint256[6] memory pt3) { + if (pt1zx == 0 && pt1zy == 0) { + ( + pt3[PTXX], pt3[PTXY], + pt3[PTYX], pt3[PTYY], + pt3[PTZX], pt3[PTZY] + ) = ( + pt2xx, pt2xy, + pt2yx, pt2yy, + pt2zx, pt2zy + ); + return pt3; + } else if (pt2zx == 0 && pt2zy == 0) { + ( + pt3[PTXX], pt3[PTXY], + pt3[PTYX], pt3[PTYY], + pt3[PTZX], pt3[PTZY] + ) = ( + pt1xx, pt1xy, + pt1yx, pt1yy, + pt1zx, pt1zy + ); + return pt3; + } + + (pt2yx, pt2yy) = _FQ2Mul(pt2yx, pt2yy, pt1zx, pt1zy); // U1 = y2 * z1 + (pt3[PTYX], pt3[PTYY]) = _FQ2Mul(pt1yx, pt1yy, pt2zx, pt2zy); // U2 = y1 * z2 + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt1zx, pt1zy); // V1 = x2 * z1 + (pt3[PTZX], pt3[PTZY]) = _FQ2Mul(pt1xx, pt1xy, pt2zx, pt2zy); // V2 = x1 * z2 + + if (pt2xx == pt3[PTZX] && pt2xy == pt3[PTZY]) { + if (pt2yx == pt3[PTYX] && pt2yy == pt3[PTYY]) { + ( + pt3[PTXX], pt3[PTXY], + pt3[PTYX], pt3[PTYY], + pt3[PTZX], pt3[PTZY] + ) = _ECTwistDoubleJacobian(pt1xx, pt1xy, pt1yx, pt1yy, pt1zx, pt1zy); + return pt3; + } + ( + pt3[PTXX], pt3[PTXY], + pt3[PTYX], pt3[PTYY], + pt3[PTZX], pt3[PTZY] + ) = ( + 1, 0, + 1, 0, + 0, 0 + ); + return pt3; + } + + (pt2zx, pt2zy) = _FQ2Mul(pt1zx, pt1zy, pt2zx, pt2zy); // W = z1 * z2 + (pt1xx, pt1xy) = _FQ2Sub(pt2yx, pt2yy, pt3[PTYX], pt3[PTYY]); // U = U1 - U2 + (pt1yx, pt1yy) = _FQ2Sub(pt2xx, pt2xy, pt3[PTZX], pt3[PTZY]); // V = V1 - V2 + (pt1zx, pt1zy) = _FQ2Mul(pt1yx, pt1yy, pt1yx, pt1yy); // V_squared = V * V + (pt2yx, pt2yy) = _FQ2Mul(pt1zx, pt1zy, pt3[PTZX], pt3[PTZY]); // V_squared_times_V2 = V_squared * V2 + (pt1zx, pt1zy) = _FQ2Mul(pt1zx, pt1zy, pt1yx, pt1yy); // V_cubed = V * V_squared + (pt3[PTZX], pt3[PTZY]) = _FQ2Mul(pt1zx, pt1zy, pt2zx, pt2zy); // newz = V_cubed * W + (pt2xx, pt2xy) = _FQ2Mul(pt1xx, pt1xy, pt1xx, pt1xy); // U * U + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt2zx, pt2zy); // U * U * W + (pt2xx, pt2xy) = _FQ2Sub(pt2xx, pt2xy, pt1zx, pt1zy); // U * U * W - V_cubed + (pt2zx, pt2zy) = _FQ2Muc(pt2yx, pt2yy, 2); // 2 * V_squared_times_V2 + (pt2xx, pt2xy) = _FQ2Sub(pt2xx, pt2xy, pt2zx, pt2zy); // A = U * U * W - V_cubed - 2 * V_squared_times_V2 + (pt3[PTXX], pt3[PTXY]) = _FQ2Mul(pt1yx, pt1yy, pt2xx, pt2xy); // newx = V * A + (pt1yx, pt1yy) = _FQ2Sub(pt2yx, pt2yy, pt2xx, pt2xy); // V_squared_times_V2 - A + (pt1yx, pt1yy) = _FQ2Mul(pt1xx, pt1xy, pt1yx, pt1yy); // U * (V_squared_times_V2 - A) + (pt1xx, pt1xy) = _FQ2Mul(pt1zx, pt1zy, pt3[PTYX], pt3[PTYY]); // V_cubed * U2 + (pt3[PTYX], pt3[PTYY]) = _FQ2Sub(pt1yx, pt1yy, pt1xx, pt1xy); // newy = U * (V_squared_times_V2 - A) - V_cubed * U2 + } + + function _ECTwistDoubleJacobian( + uint256 pt1xx, uint256 pt1xy, + uint256 pt1yx, uint256 pt1yy, + uint256 pt1zx, uint256 pt1zy + ) internal pure returns ( + uint256 pt2xx, uint256 pt2xy, + uint256 pt2yx, uint256 pt2yy, + uint256 pt2zx, uint256 pt2zy + ) { + (pt2xx, pt2xy) = _FQ2Muc(pt1xx, pt1xy, 3); // 3 * x + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt1xx, pt1xy); // W = 3 * x * x + (pt1zx, pt1zy) = _FQ2Mul(pt1yx, pt1yy, pt1zx, pt1zy); // S = y * z + (pt2yx, pt2yy) = _FQ2Mul(pt1xx, pt1xy, pt1yx, pt1yy); // x * y + (pt2yx, pt2yy) = _FQ2Mul(pt2yx, pt2yy, pt1zx, pt1zy); // B = x * y * S + (pt1xx, pt1xy) = _FQ2Mul(pt2xx, pt2xy, pt2xx, pt2xy); // W * W + (pt2zx, pt2zy) = _FQ2Muc(pt2yx, pt2yy, 8); // 8 * B + (pt1xx, pt1xy) = _FQ2Sub(pt1xx, pt1xy, pt2zx, pt2zy); // H = W * W - 8 * B + (pt2zx, pt2zy) = _FQ2Mul(pt1zx, pt1zy, pt1zx, pt1zy); // S_squared = S * S + (pt2yx, pt2yy) = _FQ2Muc(pt2yx, pt2yy, 4); // 4 * B + (pt2yx, pt2yy) = _FQ2Sub(pt2yx, pt2yy, pt1xx, pt1xy); // 4 * B - H + (pt2yx, pt2yy) = _FQ2Mul(pt2yx, pt2yy, pt2xx, pt2xy); // W * (4 * B - H) + (pt2xx, pt2xy) = _FQ2Muc(pt1yx, pt1yy, 8); // 8 * y + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt1yx, pt1yy); // 8 * y * y + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt2zx, pt2zy); // 8 * y * y * S_squared + (pt2yx, pt2yy) = _FQ2Sub(pt2yx, pt2yy, pt2xx, pt2xy); // newy = W * (4 * B - H) - 8 * y * y * S_squared + (pt2xx, pt2xy) = _FQ2Muc(pt1xx, pt1xy, 2); // 2 * H + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt1zx, pt1zy); // newx = 2 * H * S + (pt2zx, pt2zy) = _FQ2Mul(pt1zx, pt1zy, pt2zx, pt2zy); // S * S_squared + (pt2zx, pt2zy) = _FQ2Muc(pt2zx, pt2zy, 8); // newz = 8 * S * S_squared + } + + function _ECTwistMulJacobian( + uint256 d, + uint256 pt1xx, uint256 pt1xy, + uint256 pt1yx, uint256 pt1yy, + uint256 pt1zx, uint256 pt1zy + ) internal pure returns (uint256[6] memory pt2) { + while (d != 0) { + if ((d & 1) != 0) { + pt2 = _ECTwistAddJacobian( + pt2[PTXX], pt2[PTXY], + pt2[PTYX], pt2[PTYY], + pt2[PTZX], pt2[PTZY], + pt1xx, pt1xy, + pt1yx, pt1yy, + pt1zx, pt1zy); + } + ( + pt1xx, pt1xy, + pt1yx, pt1yy, + pt1zx, pt1zy + ) = _ECTwistDoubleJacobian( + pt1xx, pt1xy, + pt1yx, pt1yy, + pt1zx, pt1zy + ); + + d = d / 2; + } + } +} \ No newline at end of file From 997cbde24231e1a72d4c686c1f99f836b8c16d13 Mon Sep 17 00:00:00 2001 From: Algys Ievlev Date: Wed, 22 Jan 2025 11:40:46 +0400 Subject: [PATCH 15/21] chore: add bn254g2 usage warning --- test/libraries/BN254G2.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/libraries/BN254G2.sol b/test/libraries/BN254G2.sol index c8cfd1c..f6e745c 100644 --- a/test/libraries/BN254G2.sol +++ b/test/libraries/BN254G2.sol @@ -6,6 +6,8 @@ pragma solidity ^0.8.0; * @dev Homepage: https://github.com/musalbas/solidity-BN256G2 */ + // WARNING: this code is used ONLY for testing purposes, DO NOT USE IN PRODUCTION + library BN254G2 { uint256 internal constant FIELD_MODULUS = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; uint256 internal constant TWISTBX = 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; From 5e56bfda733014167374e30e96ec178b60cddec1 Mon Sep 17 00:00:00 2001 From: alrxy Date: Wed, 22 Jan 2025 15:40:18 +0400 Subject: [PATCH 16/21] WIP --- foundry.toml | 4 +- src/libraries/Merkle.sol | 149 ++++++++++++++++++++++++++-------- test/BLS.t.sol | 9 +- test/Merkle.t.sol | 78 +++++++++++++----- test/helpers/FullMerkle.sol | 62 +++++++++----- test/helpers/SimpleMerkle.sol | 18 +++- 6 files changed, 238 insertions(+), 82 deletions(-) diff --git a/foundry.toml b/foundry.toml index 2d58b0d..afb1497 100644 --- a/foundry.toml +++ b/foundry.toml @@ -15,7 +15,7 @@ quote_style = "double" tab_width = 4 [fuzz] -runs = 4096 -max_test_rejects = 262144 +runs = 64 +max_test_rejects = 1262144 # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/src/libraries/Merkle.sol b/src/libraries/Merkle.sol index 65a330b..b9137ef 100644 --- a/src/libraries/Merkle.sol +++ b/src/libraries/Merkle.sol @@ -7,7 +7,7 @@ pragma solidity ^0.8.25; uint256 constant TREE_DEPTH = 16; uint256 constant MAX_LEAVES = 2 ** TREE_DEPTH - 1; -uint256 constant MASK = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe; +bytes32 constant ZERO_ELEMENT = bytes32(0); /** * @title MerkleLib @@ -19,10 +19,14 @@ library MerkleLib { using MerkleLib for Tree; event UpdateLeaf(uint256 index, bytes32 node); + event PopLeaf(); error InvalidProof(); error FullMerkleTree(); error InvalidIndex(); + error SameNodeUpdate(); + error ZeroElementStoring(); + error EmptyTree(); /** * @notice Struct representing incremental merkle tree. Contains current @@ -32,14 +36,7 @@ library MerkleLib { struct Tree { bytes32[TREE_DEPTH] branch; uint256 count; - } - - function upsert(Tree storage _tree, bytes32 _node, bytes memory _proof, uint256 _index) internal { - if (_proof.length != 0) { - update(_tree, _node, bytes32(0), _proof, _index); - } else { - insert(_tree, _node); - } + bytes32 lastNode; } /** @@ -52,18 +49,21 @@ library MerkleLib { if (_tree.count >= MAX_LEAVES) { revert FullMerkleTree(); } + if (_node == ZERO_ELEMENT) { + revert ZeroElementStoring(); + } - emit UpdateLeaf(_tree.count, _node); + uint256 _index = _tree.count++; + emit UpdateLeaf(_index, _node); - _tree.count += 1; - uint256 size = _tree.count; + _tree.lastNode = _node; for (uint256 i = 0; i < TREE_DEPTH; i++) { - if ((size & 1) == 1) { + if ((_index & 1) == 0) { _tree.branch[i] = _node; return; } _node = keccak256(abi.encodePacked(_tree.branch[i], _node)); - size /= 2; + _index >>= 1; } // As the loop should always end prematurely with the `return` statement, // this code should be unreachable. We assert `false` just to be safe. @@ -74,11 +74,11 @@ library MerkleLib { Tree storage _tree, bytes32 _node, bytes32 _oldNode, - bytes memory _branch, + bytes32[TREE_DEPTH] memory _branch, uint256 _index ) internal { - if (_index >= _tree.count) { - revert InvalidIndex(); + if (_node == _oldNode) { + revert SameNodeUpdate(); } bytes32 _root = branchRoot(_oldNode, _branch, _index); @@ -87,30 +87,118 @@ library MerkleLib { revert InvalidProof(); } + unsafeUpdate(_tree, _node, _branch, _index); + } + + // without proof checking + function unsafeUpdate( + Tree storage _tree, + bytes32 _node, + bytes32[TREE_DEPTH] memory _branch, + uint256 _index + ) internal { + if (_index >= _tree.count) { + revert InvalidIndex(); + } + + if (_node == bytes32(0)) { + revert ZeroElementStoring(); + } + emit UpdateLeaf(_index, _node); - uint256 lastIndex = _tree.count - 1; + uint256 lastIndex = _tree.count; for (uint256 i = 0; i < TREE_DEPTH; i++) { if ((lastIndex / 2 * 2) == _index) { _tree.branch[i] = _node; return; } - bytes32 _next; - assembly { - _next := mload(add(_branch, add(mul(i, 32), 32))) - } - if (_index & 0x01 == 1) { - _node = keccak256(abi.encodePacked(_next, _node)); + if ((_index & 1) == 1) { + _node = keccak256(abi.encodePacked(_branch[i], _node)); } else { - _node = keccak256(abi.encodePacked(_node, _next)); + _node = keccak256(abi.encodePacked(_node, _branch[i])); } - lastIndex /= 2; - _index /= 2; + lastIndex >>= 1; + _index >>= 1; } assert(false); } + function pop(Tree storage _tree, bytes32 _secondLastNode, bytes32[TREE_DEPTH] memory _secondLastBranch) internal { + if (_tree.count > 1) { + bytes32 _root = branchRoot(_secondLastNode, _secondLastBranch, _tree.count - 2); + if (_root != _tree.root()) { + revert InvalidProof(); + } + } + + unsafePop(_tree, _secondLastNode, _secondLastBranch); + } + + function unsafePop( + Tree storage _tree, + bytes32 _secondLastNode, + bytes32[TREE_DEPTH] memory _secondLastBranch + ) internal { + if (_tree.count == 0) { + revert EmptyTree(); + } + + // edge-case for single node tree, in this case _secondLastNode is bytes32(0) and _secondLastBranch is full of zero hashes + if (_tree.count == 1) { + emit PopLeaf(); + _tree.count = 0; + _tree.lastNode = bytes32(0); + _tree.branch[0] = bytes32(0); + return; + } + + uint256 _lastIndex = --_tree.count; // tree.count - 2 + uint256 _index = _lastIndex - 1; + + emit PopLeaf(); + + _tree.lastNode = _secondLastNode; + for (uint256 i = 0; i < TREE_DEPTH; i++) { + if ((_lastIndex / 2 * 2) == (_index / 2 * 2)) { + _tree.branch[i] = _secondLastNode; + return; + } + if ((_index & 1) == 1) { + _secondLastNode = keccak256(abi.encodePacked(_secondLastBranch[i], _secondLastNode)); + } else { + _secondLastNode = keccak256(abi.encodePacked(_secondLastNode, _secondLastBranch[i])); + } + _lastIndex >>= 1; + _index >>= 1; + } + } + + function remove( + Tree storage _tree, + bytes32 _node, + bytes32[TREE_DEPTH] memory _branch, + uint256 _index, + bytes32 _secondLastNode, + bytes32[TREE_DEPTH] memory _secondLastBranch + ) internal { + bytes32 _root = _tree.root(); + bytes32 _updateRoot = branchRoot(_node, _branch, _index); + if (_updateRoot != _root) { + revert InvalidProof(); + } + if (_tree.count > 1) { + bytes32 _popRoot = branchRoot(_secondLastNode, _secondLastBranch, _tree.count - 2); + if (_popRoot != _root) { + revert InvalidProof(); + } + } + + unsafeUpdate(_tree, _tree.lastNode, _branch, _index); + unsafePop(_tree, _secondLastNode, _secondLastBranch); + } + /** * @notice Calculates and returns`_tree`'s current root * @return _current Calculated root of `_tree` @@ -165,7 +253,7 @@ library MerkleLib { */ function branchRoot( bytes32 _item, - bytes memory _branch, // cheaper than calldata indexing + bytes32[TREE_DEPTH] memory _branch, // cheaper than calldata indexing uint256 _index ) internal pure returns (bytes32 _current) { _current = _item; @@ -173,10 +261,7 @@ library MerkleLib { for (uint256 i = 0; i < TREE_DEPTH; i++) { uint256 _ithBit = (_index >> i) & 0x01; // cheaper than calldata indexing _branch[i*32:(i+1)*32]; - bytes32 _next; - assembly { - _next := mload(add(_branch, add(mul(i, 32), 32))) - } + bytes32 _next = _branch[i]; if (_ithBit == 1) { _current = keccak256(abi.encodePacked(_next, _current)); } else { diff --git a/test/BLS.t.sol b/test/BLS.t.sol index c7d6736..5ce9c4b 100644 --- a/test/BLS.t.sol +++ b/test/BLS.t.sol @@ -58,7 +58,7 @@ contract OperatorsRegistrationTest is POCBaseTest { vm.warp(vm.getBlockTimestamp() + 1); } - function testBLSRegisterOperator() public { + function testBLSRegisterOperator() public { string memory json = vm.readFile(BLS_TEST_DATA); address operator = abi.decode(vm.parseJson(json, ".operator"), (address)); uint256[] memory keyg1 = vm.parseJsonUintArray(json, ".publicKeyG1"); @@ -78,13 +78,10 @@ contract OperatorsRegistrationTest is POCBaseTest { // Verify operator is registered correctly assertTrue(IBaseMiddlewareReader(address(middleware)).isOperatorRegistered(operator)); - assertEq( - abi.decode(IBaseMiddlewareReader(address(middleware)).operatorKey(operator), (bytes32)), - bytes32(0) - ); + assertEq(abi.decode(IBaseMiddlewareReader(address(middleware)).operatorKey(operator), (bytes32)), bytes32(0)); vm.warp(block.timestamp + 2); assertEq( abi.decode(IBaseMiddlewareReader(address(middleware)).operatorKey(operator), (BN254.G1Point)).X, keyG1.X ); } -} \ No newline at end of file +} diff --git a/test/Merkle.t.sol b/test/Merkle.t.sol index 399a7ae..415207b 100644 --- a/test/Merkle.t.sol +++ b/test/Merkle.t.sol @@ -16,7 +16,7 @@ contract MerkleTest is Test { function testInsert() public { bytes32 node = keccak256("test"); - + simpleMerkle.insert(node); fullMerkle.insert(node); @@ -35,10 +35,10 @@ contract MerkleTest is Test { function testMultipleInserts() public { bytes32[] memory nodes = new bytes32[](3); nodes[0] = keccak256("test1"); - nodes[1] = keccak256("test2"); + nodes[1] = keccak256("test2"); nodes[2] = keccak256("test3"); - for(uint i = 0; i < nodes.length; i++) { + for (uint256 i = 0; i < nodes.length; i++) { simpleMerkle.insert(nodes[i]); fullMerkle.insert(nodes[i]); @@ -53,12 +53,12 @@ contract MerkleTest is Test { function testVerify() public { bytes32 node = keccak256("test"); - + simpleMerkle.insert(node); fullMerkle.insert(node); bytes32[16] memory proof = fullMerkle.getProof(0); - + assertTrue(simpleMerkle.verify(node, proof, 0)); assertTrue(fullMerkle.verify(node, proof, 0)); assertEq(simpleMerkle.root(), fullMerkle.root()); @@ -67,24 +67,28 @@ contract MerkleTest is Test { function testUpdate() public { bytes32 oldNode = keccak256("test"); bytes32 newNode = keccak256("updated"); - + simpleMerkle.insert(oldNode); fullMerkle.insert(oldNode); bytes32[16] memory proof = fullMerkle.getProof(0); - + simpleMerkle.update(newNode, oldNode, proof, 0); fullMerkle.update(newNode, 0); assertEq(simpleMerkle.root(), fullMerkle.root()); - + // Verify new node proof = fullMerkle.getProof(0); assertTrue(simpleMerkle.verify(newNode, proof, 0)); assertTrue(fullMerkle.verify(newNode, proof, 0)); } - function testFuzzInsert(bytes32 node) public { + function testFuzzInsert( + bytes32 node + ) public { + vm.assume(node != bytes32(0)); + simpleMerkle.insert(node); fullMerkle.insert(node); @@ -95,19 +99,53 @@ contract MerkleTest is Test { assertEq(simpleMerkle.root(), fullMerkle.root()); } - function testFuzzUpdate(bytes32 oldNode, bytes32 newNode) public { - simpleMerkle.insert(oldNode); - fullMerkle.insert(oldNode); + function testFuzzUpdate(bytes32[8] memory _nodes, uint256 _index, bytes32 newNode) public { + vm.assume(_index < _nodes.length); + vm.assume(_nodes[_index] != bytes32(0)); + vm.assume(newNode != _nodes[_index]); + + for (uint256 i = 0; i < _nodes.length; i++) { + simpleMerkle.insert(_nodes[i]); + fullMerkle.insert(_nodes[i]); + } + + bytes32[16] memory proof = fullMerkle.getProof(_index); + + fullMerkle.update(newNode, _index); + simpleMerkle.update(newNode, _nodes[_index], proof, _index); - bytes32[16] memory proof = fullMerkle.getProof(0); - - simpleMerkle.update(newNode, oldNode, proof, 0); - fullMerkle.update(newNode, 0); - // Verify new node - proof = fullMerkle.getProof(0); - assertTrue(simpleMerkle.verify(newNode, proof, 0)); - assertTrue(fullMerkle.verify(newNode, proof, 0)); + // proof = fullMerkle.getProof(_index); + assertTrue(fullMerkle.verify(newNode, proof, _index)); + assertTrue(simpleMerkle.verify(newNode, proof, _index)); assertEq(simpleMerkle.root(), fullMerkle.root()); } + + function testFuzzRemove(bytes32[8] memory _nodes, uint256 _index) public { + vm.assume(_index < _nodes.length); + for (uint256 i = 0; i < _nodes.length; i++) { + vm.assume(_nodes[i] != bytes32(0)); + } + + for (uint256 i = 0; i < _nodes.length; i++) { + // simpleMerkle.insert(_nodes[i]); + fullMerkle.insert(_nodes[i]); + } + + bytes32[16] memory proof = fullMerkle.getProof(_index); + bytes32[16] memory secondLastBranch; + bytes32 secondLastNode; + if (_nodes.length > 1) { + secondLastNode = _nodes[_nodes.length - 2]; + secondLastBranch = fullMerkle.getProof(_nodes.length - 2); + } + // simpleMerkle.remove(_nodes[_index], proof, _index, secondLastNode, secondLastBranch); + fullMerkle.remove(_index); + + for (uint256 i = 0; i < _nodes.length - 1; i++) { + proof = fullMerkle.getProof(i); + assertTrue(fullMerkle.verify(_nodes[i], proof, i)); + // assertTrue(simpleMerkle.verify(_nodes[i], proof, i)); + } + } } diff --git a/test/helpers/FullMerkle.sol b/test/helpers/FullMerkle.sol index 16c353e..0f9db4b 100644 --- a/test/helpers/FullMerkle.sol +++ b/test/helpers/FullMerkle.sol @@ -5,7 +5,6 @@ contract FullMerkle { uint256 public constant DEPTH = 16; bytes32[DEPTH] public zeroValues; mapping(uint256 => mapping(uint256 => bytes32)) public nodes; - bytes32[] public leaves; uint256 public currentLeafIndex; constructor() { @@ -27,11 +26,12 @@ contract FullMerkle { zeroValues[15] = 0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2; } - function insert(bytes32 _node) public { - require(currentLeafIndex < 2**DEPTH, "Tree is full"); + function insert( + bytes32 _node + ) public { + require(currentLeafIndex < 2 ** DEPTH, "Tree is full"); uint256 leafPos = currentLeafIndex; - leaves.push(_node); nodes[0][leafPos] = _node; _updatePath(leafPos); @@ -39,34 +39,52 @@ contract FullMerkle { } function update(bytes32 _node, uint256 _index) public { - require(_index < leaves.length, "Leaf index out of bounds"); + require(_index < currentLeafIndex, "Leaf index out of bounds"); - leaves[_index] = _node; nodes[0][_index] = _node; _updatePath(_index); } + function pop() public { + require(currentLeafIndex > 0, "Tree is empty"); + + uint256 leafPos = currentLeafIndex - 1; + nodes[0][leafPos] = bytes32(0); + + _updatePath(leafPos); + currentLeafIndex--; + } + + function remove( + uint256 _index + ) public { + require(_index < currentLeafIndex, "Leaf index out of bounds"); + + update(nodes[0][currentLeafIndex - 1], _index); + pop(); + } + function root() public view returns (bytes32) { return nodes[DEPTH][0]; } - function getProof(uint256 _index) public view returns (bytes32[16] memory) { - require(_index < leaves.length, "Leaf index out of bounds"); - - bytes32[16] memory proof; + function getProof( + uint256 _index + ) public view returns (bytes32[16] memory proof) { + require(_index < currentLeafIndex, "Leaf index out of bounds"); uint256 currentIndex = _index; - for(uint256 i = 0; i < DEPTH; i++) { + for (uint256 i = 0; i < DEPTH; i++) { uint256 siblingIndex; - if(currentIndex % 2 == 0) { + if (currentIndex % 2 == 0) { siblingIndex = currentIndex + 1; } else { siblingIndex = currentIndex - 1; } bytes32 sibling = nodes[i][siblingIndex]; - if(sibling == bytes32(0)) { + if (sibling == bytes32(0)) { sibling = zeroValues[i]; } proof[i] = sibling; @@ -81,9 +99,9 @@ contract FullMerkle { bytes32 computedHash = _node; uint256 currentIndex = _index; - for(uint256 i = 0; i < DEPTH; i++) { + for (uint256 i = 0; i < DEPTH; i++) { bytes32 sibling = _proof[i]; - if(currentIndex % 2 == 0) { + if (currentIndex % 2 == 0) { computedHash = keccak256(abi.encodePacked(computedHash, sibling)); } else { computedHash = keccak256(abi.encodePacked(sibling, computedHash)); @@ -94,19 +112,21 @@ contract FullMerkle { return computedHash == nodes[DEPTH][0]; } - function _updatePath(uint256 currentPos) private { - for(uint256 depth = 0; depth < DEPTH; depth++) { + function _updatePath( + uint256 currentPos + ) private { + for (uint256 depth = 0; depth < DEPTH; depth++) { uint256 leftPos = (currentPos / 2) * 2; uint256 rightPos = leftPos + 1; bytes32 left = nodes[depth][leftPos]; bytes32 right = nodes[depth][rightPos]; - if(left == bytes32(0)) left = zeroValues[depth]; - if(right == bytes32(0)) right = zeroValues[depth]; + if (left == bytes32(0)) left = zeroValues[depth]; + if (right == bytes32(0)) right = zeroValues[depth]; bytes32 parent = keccak256(abi.encodePacked(left, right)); - nodes[depth+1][currentPos/2] = parent; - currentPos = currentPos/2; + nodes[depth + 1][currentPos / 2] = parent; + currentPos = currentPos / 2; } } } diff --git a/test/helpers/SimpleMerkle.sol b/test/helpers/SimpleMerkle.sol index 63870cd..c6d691d 100644 --- a/test/helpers/SimpleMerkle.sol +++ b/test/helpers/SimpleMerkle.sol @@ -8,7 +8,9 @@ contract SimpleMerkle { MerkleLib.Tree private tree; - function insert(bytes32 _node) external { + function insert( + bytes32 _node + ) external { tree.insert(_node); } @@ -20,6 +22,20 @@ contract SimpleMerkle { return tree.root() == MerkleLib.branchRoot(_node, _proof, _index); } + function pop(bytes32 _secondLastNode, bytes32[TREE_DEPTH] memory _secondLastBranch) external { + tree.pop(_secondLastNode, _secondLastBranch); + } + + function remove( + bytes32 _node, + bytes32[TREE_DEPTH] memory _proof, + uint256 _index, + bytes32 _secondLastNode, + bytes32[TREE_DEPTH] memory _secondLastBranch + ) external { + tree.remove(_node, _proof, _index, _secondLastNode, _secondLastBranch); + } + function root() external view returns (bytes32) { return tree.root(); } From ab1ca539df47fddb0dea6cb9767856ff0c12cdb4 Mon Sep 17 00:00:00 2001 From: alrxy Date: Wed, 22 Jan 2025 15:48:52 +0400 Subject: [PATCH 17/21] chore: lint --- src/extensions/managers/sigs/BLSSig.sol | 21 +- test/BLS.t.sol | 10 +- test/Merkle.t.sol | 24 +- test/helpers/FullMerkle.sol | 34 +- test/helpers/SimpleMerkle.sol | 4 +- test/helpers/blsTestGenerator.py | 10 +- test/helpers/blsTestVectors.json | 18 -- test/libraries/BN254G2.sol | 392 +++++++++--------------- 8 files changed, 200 insertions(+), 313 deletions(-) delete mode 100644 test/helpers/blsTestVectors.json diff --git a/src/extensions/managers/sigs/BLSSig.sol b/src/extensions/managers/sigs/BLSSig.sol index ca28c04..0301a9d 100644 --- a/src/extensions/managers/sigs/BLSSig.sol +++ b/src/extensions/managers/sigs/BLSSig.sol @@ -29,7 +29,7 @@ contract BLSSig is SigManager { ) internal view override returns (bool) { (BN254.G1Point memory pubkeyG1, BN254.G2Point memory pubkeyG2) = abi.decode(key_, (BN254.G1Point, BN254.G2Point)); - + BN254.G1Point memory sig = abi.decode(signature, (BN254.G1Point)); bytes memory message = abi.encode(operator, pubkeyG1, pubkeyG2); @@ -43,7 +43,7 @@ contract BLSSig is SigManager { console.log("sig ", sig.X, sig.Y); console.log("pubkeyG2 X ", pubkeyG2.X[0], pubkeyG2.X[1]); console.log("pubkeyG2 Y", pubkeyG2.Y[0], pubkeyG2.Y[1]); - + return verify(pubkeyG1, pubkeyG2, sig, messageHash); } @@ -64,18 +64,13 @@ contract BLSSig is SigManager { BN254.G1Point memory messageG1 = BN254.hashToG1(messageHash); console.log("messageG1 ", messageG1.X, messageG1.Y); uint256 alpha = uint256( - keccak256(abi.encodePacked( - signature.X, - signature.Y, - pubkeyG1.X, - pubkeyG1.Y, - pubkeyG2.X, - pubkeyG2.Y, - messageG1.X, - messageG1.Y - )) + keccak256( + abi.encodePacked( + signature.X, signature.Y, pubkeyG1.X, pubkeyG1.Y, pubkeyG2.X, pubkeyG2.Y, messageG1.X, messageG1.Y + ) + ) ) % BN254.FR_MODULUS; - + BN254.G1Point memory a1 = signature.plus(pubkeyG1.scalar_mul(alpha)); BN254.G1Point memory b1 = messageG1.plus(BN254.generatorG1().scalar_mul(alpha)); diff --git a/test/BLS.t.sol b/test/BLS.t.sol index 278c5f8..ca31481 100644 --- a/test/BLS.t.sol +++ b/test/BLS.t.sol @@ -20,7 +20,6 @@ import "forge-std/console.sol"; //import {VetoSlasher} from "@symbiotic/contracts/slasher/VetoSlasher.sol"; contract OperatorsRegistrationTest is POCBaseTest { - // using Subnetwork for bytes32; // using Subnetwork for address; using Math for uint256; @@ -61,9 +60,12 @@ contract OperatorsRegistrationTest is POCBaseTest { vm.warp(vm.getBlockTimestamp() + 1); } - function getG2Key(uint256 privateKey) public view returns (BN254.G2Point memory) { + function getG2Key( + uint256 privateKey + ) public view returns (BN254.G2Point memory) { BN254.G2Point memory G2 = BN254.generatorG2(); - (uint256 x1, uint256 x2, uint256 y1, uint256 y2) = BN254G2.ECTwistMul(privateKey, G2.X[1], G2.X[0], G2.Y[1], G2.Y[0]); + (uint256 x1, uint256 x2, uint256 y1, uint256 y2) = + BN254G2.ECTwistMul(privateKey, G2.X[1], G2.X[0], G2.Y[1], G2.Y[0]); return BN254.G2Point([x2, x1], [y2, y1]); } @@ -73,7 +75,7 @@ contract OperatorsRegistrationTest is POCBaseTest { // get G1 public key BN254.G1Point memory keyG1 = BN254.generatorG1().scalar_mul(privateKey); - // get G2 public key + // get G2 public key BN254.G2Point memory keyG2 = getG2Key(privateKey); // craft message [operator, keyG1, keyG2] diff --git a/test/Merkle.t.sol b/test/Merkle.t.sol index 399a7ae..0ef82ae 100644 --- a/test/Merkle.t.sol +++ b/test/Merkle.t.sol @@ -16,7 +16,7 @@ contract MerkleTest is Test { function testInsert() public { bytes32 node = keccak256("test"); - + simpleMerkle.insert(node); fullMerkle.insert(node); @@ -35,10 +35,10 @@ contract MerkleTest is Test { function testMultipleInserts() public { bytes32[] memory nodes = new bytes32[](3); nodes[0] = keccak256("test1"); - nodes[1] = keccak256("test2"); + nodes[1] = keccak256("test2"); nodes[2] = keccak256("test3"); - for(uint i = 0; i < nodes.length; i++) { + for (uint256 i = 0; i < nodes.length; i++) { simpleMerkle.insert(nodes[i]); fullMerkle.insert(nodes[i]); @@ -53,12 +53,12 @@ contract MerkleTest is Test { function testVerify() public { bytes32 node = keccak256("test"); - + simpleMerkle.insert(node); fullMerkle.insert(node); bytes32[16] memory proof = fullMerkle.getProof(0); - + assertTrue(simpleMerkle.verify(node, proof, 0)); assertTrue(fullMerkle.verify(node, proof, 0)); assertEq(simpleMerkle.root(), fullMerkle.root()); @@ -67,24 +67,26 @@ contract MerkleTest is Test { function testUpdate() public { bytes32 oldNode = keccak256("test"); bytes32 newNode = keccak256("updated"); - + simpleMerkle.insert(oldNode); fullMerkle.insert(oldNode); bytes32[16] memory proof = fullMerkle.getProof(0); - + simpleMerkle.update(newNode, oldNode, proof, 0); fullMerkle.update(newNode, 0); assertEq(simpleMerkle.root(), fullMerkle.root()); - + // Verify new node proof = fullMerkle.getProof(0); assertTrue(simpleMerkle.verify(newNode, proof, 0)); assertTrue(fullMerkle.verify(newNode, proof, 0)); } - function testFuzzInsert(bytes32 node) public { + function testFuzzInsert( + bytes32 node + ) public { simpleMerkle.insert(node); fullMerkle.insert(node); @@ -100,10 +102,10 @@ contract MerkleTest is Test { fullMerkle.insert(oldNode); bytes32[16] memory proof = fullMerkle.getProof(0); - + simpleMerkle.update(newNode, oldNode, proof, 0); fullMerkle.update(newNode, 0); - + // Verify new node proof = fullMerkle.getProof(0); assertTrue(simpleMerkle.verify(newNode, proof, 0)); diff --git a/test/helpers/FullMerkle.sol b/test/helpers/FullMerkle.sol index 16c353e..51d0864 100644 --- a/test/helpers/FullMerkle.sol +++ b/test/helpers/FullMerkle.sol @@ -27,8 +27,10 @@ contract FullMerkle { zeroValues[15] = 0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2; } - function insert(bytes32 _node) public { - require(currentLeafIndex < 2**DEPTH, "Tree is full"); + function insert( + bytes32 _node + ) public { + require(currentLeafIndex < 2 ** DEPTH, "Tree is full"); uint256 leafPos = currentLeafIndex; leaves.push(_node); @@ -51,22 +53,24 @@ contract FullMerkle { return nodes[DEPTH][0]; } - function getProof(uint256 _index) public view returns (bytes32[16] memory) { + function getProof( + uint256 _index + ) public view returns (bytes32[16] memory) { require(_index < leaves.length, "Leaf index out of bounds"); bytes32[16] memory proof; uint256 currentIndex = _index; - for(uint256 i = 0; i < DEPTH; i++) { + for (uint256 i = 0; i < DEPTH; i++) { uint256 siblingIndex; - if(currentIndex % 2 == 0) { + if (currentIndex % 2 == 0) { siblingIndex = currentIndex + 1; } else { siblingIndex = currentIndex - 1; } bytes32 sibling = nodes[i][siblingIndex]; - if(sibling == bytes32(0)) { + if (sibling == bytes32(0)) { sibling = zeroValues[i]; } proof[i] = sibling; @@ -81,9 +85,9 @@ contract FullMerkle { bytes32 computedHash = _node; uint256 currentIndex = _index; - for(uint256 i = 0; i < DEPTH; i++) { + for (uint256 i = 0; i < DEPTH; i++) { bytes32 sibling = _proof[i]; - if(currentIndex % 2 == 0) { + if (currentIndex % 2 == 0) { computedHash = keccak256(abi.encodePacked(computedHash, sibling)); } else { computedHash = keccak256(abi.encodePacked(sibling, computedHash)); @@ -94,19 +98,21 @@ contract FullMerkle { return computedHash == nodes[DEPTH][0]; } - function _updatePath(uint256 currentPos) private { - for(uint256 depth = 0; depth < DEPTH; depth++) { + function _updatePath( + uint256 currentPos + ) private { + for (uint256 depth = 0; depth < DEPTH; depth++) { uint256 leftPos = (currentPos / 2) * 2; uint256 rightPos = leftPos + 1; bytes32 left = nodes[depth][leftPos]; bytes32 right = nodes[depth][rightPos]; - if(left == bytes32(0)) left = zeroValues[depth]; - if(right == bytes32(0)) right = zeroValues[depth]; + if (left == bytes32(0)) left = zeroValues[depth]; + if (right == bytes32(0)) right = zeroValues[depth]; bytes32 parent = keccak256(abi.encodePacked(left, right)); - nodes[depth+1][currentPos/2] = parent; - currentPos = currentPos/2; + nodes[depth + 1][currentPos / 2] = parent; + currentPos = currentPos / 2; } } } diff --git a/test/helpers/SimpleMerkle.sol b/test/helpers/SimpleMerkle.sol index 63870cd..6da119c 100644 --- a/test/helpers/SimpleMerkle.sol +++ b/test/helpers/SimpleMerkle.sol @@ -8,7 +8,9 @@ contract SimpleMerkle { MerkleLib.Tree private tree; - function insert(bytes32 _node) external { + function insert( + bytes32 _node + ) external { tree.insert(_node); } diff --git a/test/helpers/blsTestGenerator.py b/test/helpers/blsTestGenerator.py index 7c51782..0d63f69 100644 --- a/test/helpers/blsTestGenerator.py +++ b/test/helpers/blsTestGenerator.py @@ -140,10 +140,8 @@ def generate_operator_address() -> str: ] ) -print("key: ", int(public_key_g1[0]), int(public_key_g1[1])) message_hash = keccak(message) print("message_hash: ", message_hash.hex()) -print(message.hex()) data = message_hash message = hash_to_point(data) @@ -157,7 +155,6 @@ def generate_operator_address() -> str: is_valid = verify(data, signature, public_key) print(f"\nSignature valid: {is_valid}") - print("Test values:") print(f"Public key: {formatted_pubkey}") print(f"Message: {data.hex()}") @@ -173,9 +170,4 @@ def generate_operator_address() -> str: } with open('test/helpers/blsTestVectors.json', 'w') as f: - json.dump(test_vectors, f, indent=4) - - -# 520925e2bc69aebedb68bde43f4fb41adf43e56cf056dbd49c93995b7df9c1a2 -# 000000000000000000000000f413e32f01fdaf718c8dc2fab8a925bd38a8038d1aa125a22bd902874034e67868aed40267e5575d5919677987e3bc6dd42a32fe1bacc186725464068956d9a191455c2d6f6db282d83645c610510d8d4efbaee02a9de38d14bef2cf9afc3c698a4211fa7ada7b4f036a2dfef0dc122b423259d01f1954b33144db2b5c90da089e8bde287ec7089d5d6433f3b6becaefdb678b -# 000000000000000000000000f413e32f01fdaf718c8dc2fab8a925bd38a8038d1aa125a22bd902874034e67868aed40267e5575d5919677987e3bc6dd42a32fe1bacc186725464068956d9a191455c2d6f6db282d83645c610510d8d4efbaee02a9de38d14bef2cf9afc3c698a4211fa7ada7b4f036a2dfef0dc122b423259d01f1954b33144db2b5c90da089e8bde287ec7089d5d6433f3b6becaefdb678b1b1bf37ecdba226629c20908c7f475c5b3a7628ce26d696436eab0b0148034dfcd1659dc18b57722ecf6a4beb4d04dfe780a660c4c3bb2b165ab8486114c464c62 \ No newline at end of file + json.dump(test_vectors, f, indent=4) \ No newline at end of file diff --git a/test/helpers/blsTestVectors.json b/test/helpers/blsTestVectors.json deleted file mode 100644 index 1cb34d5..0000000 --- a/test/helpers/blsTestVectors.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "operator": "0x3d0B06EF5Bc5d38C42A4E484fd309f34b5aAb5c5", - "publicKeyG1": [ - 5138697240077803445514669414784254799933862402946278134326199877546184124353, - 12587011617949543324467535889916856826666519601316494966427400843934921824601 - ], - "publicKeyG2": [ - 5334410886741819556325359147377682006012228123419628681352847439302316235957, - 19101821850089705274637533855249918363070101489527618151493230256975900223847, - 4185483097059047421902184823581361466320657066600218863748375739772335928910, - 354176189041917478648604979334478067325821134838555150300539079146482658331 - ], - "message": "ef3d3174b0f4be482b0dcabb5867ba8c4ed11366a0b5bad60456bd460528a9fe", - "signature": [ - 15234655148121944182904300878801953647185372752883511069010483540340494659232, - 17008597909425962529461463286833851648784022421671624523348375822261496690459 - ] -} \ No newline at end of file diff --git a/test/libraries/BN254G2.sol b/test/libraries/BN254G2.sol index f6e745c..229ae18 100644 --- a/test/libraries/BN254G2.sol +++ b/test/libraries/BN254G2.sol @@ -6,18 +6,18 @@ pragma solidity ^0.8.0; * @dev Homepage: https://github.com/musalbas/solidity-BN256G2 */ - // WARNING: this code is used ONLY for testing purposes, DO NOT USE IN PRODUCTION +// WARNING: this code is used ONLY for testing purposes, DO NOT USE IN PRODUCTION library BN254G2 { uint256 internal constant FIELD_MODULUS = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; uint256 internal constant TWISTBX = 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; uint256 internal constant TWISTBY = 0x9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d2; - uint internal constant PTXX = 0; - uint internal constant PTXY = 1; - uint internal constant PTYX = 2; - uint internal constant PTYY = 3; - uint internal constant PTZX = 4; - uint internal constant PTZY = 5; + uint256 internal constant PTXX = 0; + uint256 internal constant PTXY = 1; + uint256 internal constant PTYX = 2; + uint256 internal constant PTYY = 3; + uint256 internal constant PTZX = 4; + uint256 internal constant PTZY = 5; /** * @notice Add two twist points @@ -32,68 +32,31 @@ library BN254G2 { * @return (pt3xx, pt3xy, pt3yx, pt3yy) */ function ECTwistAdd( - uint256 pt1xx, uint256 pt1xy, - uint256 pt1yx, uint256 pt1yy, - uint256 pt2xx, uint256 pt2xy, - uint256 pt2yx, uint256 pt2yy - ) public view returns ( - uint256, uint256, - uint256, uint256 - ) { - if ( - pt1xx == 0 && pt1xy == 0 && - pt1yx == 0 && pt1yy == 0 - ) { - if (!( - pt2xx == 0 && pt2xy == 0 && - pt2yx == 0 && pt2yy == 0 - )) { - assert(_isOnCurve( - pt2xx, pt2xy, - pt2yx, pt2yy - )); + uint256 pt1xx, + uint256 pt1xy, + uint256 pt1yx, + uint256 pt1yy, + uint256 pt2xx, + uint256 pt2xy, + uint256 pt2yx, + uint256 pt2yy + ) public view returns (uint256, uint256, uint256, uint256) { + if (pt1xx == 0 && pt1xy == 0 && pt1yx == 0 && pt1yy == 0) { + if (!(pt2xx == 0 && pt2xy == 0 && pt2yx == 0 && pt2yy == 0)) { + assert(_isOnCurve(pt2xx, pt2xy, pt2yx, pt2yy)); } - return ( - pt2xx, pt2xy, - pt2yx, pt2yy - ); - } else if ( - pt2xx == 0 && pt2xy == 0 && - pt2yx == 0 && pt2yy == 0 - ) { - assert(_isOnCurve( - pt1xx, pt1xy, - pt1yx, pt1yy - )); - return ( - pt1xx, pt1xy, - pt1yx, pt1yy - ); + return (pt2xx, pt2xy, pt2yx, pt2yy); + } else if (pt2xx == 0 && pt2xy == 0 && pt2yx == 0 && pt2yy == 0) { + assert(_isOnCurve(pt1xx, pt1xy, pt1yx, pt1yy)); + return (pt1xx, pt1xy, pt1yx, pt1yy); } - assert(_isOnCurve( - pt1xx, pt1xy, - pt1yx, pt1yy - )); - assert(_isOnCurve( - pt2xx, pt2xy, - pt2yx, pt2yy - )); + assert(_isOnCurve(pt1xx, pt1xy, pt1yx, pt1yy)); + assert(_isOnCurve(pt2xx, pt2xy, pt2yx, pt2yy)); - uint256[6] memory pt3 = _ECTwistAddJacobian( - pt1xx, pt1xy, - pt1yx, pt1yy, - 1, 0, - pt2xx, pt2xy, - pt2yx, pt2yy, - 1, 0 - ); + uint256[6] memory pt3 = _ECTwistAddJacobian(pt1xx, pt1xy, pt1yx, pt1yy, 1, 0, pt2xx, pt2xy, pt2yx, pt2yy, 1, 0); - return _fromJacobian( - pt3[PTXX], pt3[PTXY], - pt3[PTYX], pt3[PTYY], - pt3[PTZX], pt3[PTZY] - ); + return _fromJacobian(pt3[PTXX], pt3[PTXY], pt3[PTYX], pt3[PTYY], pt3[PTZX], pt3[PTZY]); } /** @@ -107,39 +70,23 @@ library BN254G2 { */ function ECTwistMul( uint256 s, - uint256 pt1xx, uint256 pt1xy, - uint256 pt1yx, uint256 pt1yy - ) public view returns ( - uint256, uint256, - uint256, uint256 - ) { + uint256 pt1xx, + uint256 pt1xy, + uint256 pt1yx, + uint256 pt1yy + ) public view returns (uint256, uint256, uint256, uint256) { uint256 pt1zx = 1; - if ( - pt1xx == 0 && pt1xy == 0 && - pt1yx == 0 && pt1yy == 0 - ) { + if (pt1xx == 0 && pt1xy == 0 && pt1yx == 0 && pt1yy == 0) { pt1xx = 1; pt1yx = 1; pt1zx = 0; } else { - assert(_isOnCurve( - pt1xx, pt1xy, - pt1yx, pt1yy - )); + assert(_isOnCurve(pt1xx, pt1xy, pt1yx, pt1yy)); } - uint256[6] memory pt2 = _ECTwistMulJacobian( - s, - pt1xx, pt1xy, - pt1yx, pt1yy, - pt1zx, 0 - ); + uint256[6] memory pt2 = _ECTwistMulJacobian(s, pt1xx, pt1xy, pt1yx, pt1yy, pt1zx, 0); - return _fromJacobian( - pt2[PTXX], pt2[PTXY], - pt2[PTYX], pt2[PTYY], - pt2[PTZX], pt2[PTZY] - ); + return _fromJacobian(pt2[PTXX], pt2[PTXY], pt2[PTYX], pt2[PTYY], pt2[PTZX], pt2[PTZY]); } /** @@ -154,66 +101,37 @@ library BN254G2 { return addmod(a, n - b, n); } - function _FQ2Mul( - uint256 xx, uint256 xy, - uint256 yx, uint256 yy - ) internal pure returns (uint256, uint256) { + function _FQ2Mul(uint256 xx, uint256 xy, uint256 yx, uint256 yy) internal pure returns (uint256, uint256) { return ( submod(mulmod(xx, yx, FIELD_MODULUS), mulmod(xy, yy, FIELD_MODULUS), FIELD_MODULUS), addmod(mulmod(xx, yy, FIELD_MODULUS), mulmod(xy, yx, FIELD_MODULUS), FIELD_MODULUS) ); } - function _FQ2Muc( - uint256 xx, uint256 xy, - uint256 c - ) internal pure returns (uint256, uint256) { - return ( - mulmod(xx, c, FIELD_MODULUS), - mulmod(xy, c, FIELD_MODULUS) - ); + function _FQ2Muc(uint256 xx, uint256 xy, uint256 c) internal pure returns (uint256, uint256) { + return (mulmod(xx, c, FIELD_MODULUS), mulmod(xy, c, FIELD_MODULUS)); } - function _FQ2Add( - uint256 xx, uint256 xy, - uint256 yx, uint256 yy - ) internal pure returns (uint256, uint256) { - return ( - addmod(xx, yx, FIELD_MODULUS), - addmod(xy, yy, FIELD_MODULUS) - ); + function _FQ2Add(uint256 xx, uint256 xy, uint256 yx, uint256 yy) internal pure returns (uint256, uint256) { + return (addmod(xx, yx, FIELD_MODULUS), addmod(xy, yy, FIELD_MODULUS)); } - function _FQ2Sub( - uint256 xx, uint256 xy, - uint256 yx, uint256 yy - ) internal pure returns (uint256 rx, uint256 ry) { - return ( - submod(xx, yx, FIELD_MODULUS), - submod(xy, yy, FIELD_MODULUS) - ); + function _FQ2Sub(uint256 xx, uint256 xy, uint256 yx, uint256 yy) internal pure returns (uint256 rx, uint256 ry) { + return (submod(xx, yx, FIELD_MODULUS), submod(xy, yy, FIELD_MODULUS)); } - function _FQ2Div( - uint256 xx, uint256 xy, - uint256 yx, uint256 yy - ) internal view returns (uint256, uint256) { + function _FQ2Div(uint256 xx, uint256 xy, uint256 yx, uint256 yy) internal view returns (uint256, uint256) { (yx, yy) = _FQ2Inv(yx, yy); return _FQ2Mul(xx, xy, yx, yy); } function _FQ2Inv(uint256 x, uint256 y) internal view returns (uint256, uint256) { - uint256 inv = _modInv(addmod(mulmod(y, y, FIELD_MODULUS), mulmod(x, x, FIELD_MODULUS), FIELD_MODULUS), FIELD_MODULUS); - return ( - mulmod(x, inv, FIELD_MODULUS), - FIELD_MODULUS - mulmod(y, inv, FIELD_MODULUS) - ); + uint256 inv = + _modInv(addmod(mulmod(y, y, FIELD_MODULUS), mulmod(x, x, FIELD_MODULUS), FIELD_MODULUS), FIELD_MODULUS); + return (mulmod(x, inv, FIELD_MODULUS), FIELD_MODULUS - mulmod(y, inv, FIELD_MODULUS)); } - function _isOnCurve( - uint256 xx, uint256 xy, - uint256 yx, uint256 yy - ) internal pure returns (bool) { + function _isOnCurve(uint256 xx, uint256 xy, uint256 yx, uint256 yy) internal pure returns (bool) { uint256 yyx; uint256 yyy; uint256 xxxx; @@ -231,11 +149,11 @@ library BN254G2 { assembly { let freemem := mload(0x40) mstore(freemem, 0x20) - mstore(add(freemem,0x20), 0x20) - mstore(add(freemem,0x40), 0x20) - mstore(add(freemem,0x60), a) - mstore(add(freemem,0x80), sub(n, 2)) - mstore(add(freemem,0xA0), n) + mstore(add(freemem, 0x20), 0x20) + mstore(add(freemem, 0x40), 0x20) + mstore(add(freemem, 0x60), a) + mstore(add(freemem, 0x80), sub(n, 2)) + mstore(add(freemem, 0xA0), n) success := staticcall(sub(gas(), 2000), 5, freemem, 0xC0, freemem, 0x20) result := mload(freemem) } @@ -243,13 +161,13 @@ library BN254G2 { } function _fromJacobian( - uint256 pt1xx, uint256 pt1xy, - uint256 pt1yx, uint256 pt1yy, - uint256 pt1zx, uint256 pt1zy - ) internal view returns ( - uint256 pt2xx, uint256 pt2xy, - uint256 pt2yx, uint256 pt2yy - ) { + uint256 pt1xx, + uint256 pt1xy, + uint256 pt1yx, + uint256 pt1yy, + uint256 pt1zx, + uint256 pt1zy + ) internal view returns (uint256 pt2xx, uint256 pt2xy, uint256 pt2yx, uint256 pt2yy) { uint256 invzx; uint256 invzy; (invzx, invzy) = _FQ2Inv(pt1zx, pt1zy); @@ -258,139 +176,127 @@ library BN254G2 { } function _ECTwistAddJacobian( - uint256 pt1xx, uint256 pt1xy, - uint256 pt1yx, uint256 pt1yy, - uint256 pt1zx, uint256 pt1zy, - uint256 pt2xx, uint256 pt2xy, - uint256 pt2yx, uint256 pt2yy, - uint256 pt2zx, uint256 pt2zy) internal pure returns (uint256[6] memory pt3) { - if (pt1zx == 0 && pt1zy == 0) { - ( - pt3[PTXX], pt3[PTXY], - pt3[PTYX], pt3[PTYY], - pt3[PTZX], pt3[PTZY] - ) = ( - pt2xx, pt2xy, - pt2yx, pt2yy, - pt2zx, pt2zy - ); - return pt3; - } else if (pt2zx == 0 && pt2zy == 0) { - ( - pt3[PTXX], pt3[PTXY], - pt3[PTYX], pt3[PTYY], - pt3[PTZX], pt3[PTZY] - ) = ( - pt1xx, pt1xy, - pt1yx, pt1yy, - pt1zx, pt1zy - ); - return pt3; - } + uint256 pt1xx, + uint256 pt1xy, + uint256 pt1yx, + uint256 pt1yy, + uint256 pt1zx, + uint256 pt1zy, + uint256 pt2xx, + uint256 pt2xy, + uint256 pt2yx, + uint256 pt2yy, + uint256 pt2zx, + uint256 pt2zy + ) internal pure returns (uint256[6] memory pt3) { + if (pt1zx == 0 && pt1zy == 0) { + (pt3[PTXX], pt3[PTXY], pt3[PTYX], pt3[PTYY], pt3[PTZX], pt3[PTZY]) = + (pt2xx, pt2xy, pt2yx, pt2yy, pt2zx, pt2zy); + return pt3; + } else if (pt2zx == 0 && pt2zy == 0) { + (pt3[PTXX], pt3[PTXY], pt3[PTYX], pt3[PTYY], pt3[PTZX], pt3[PTZY]) = + (pt1xx, pt1xy, pt1yx, pt1yy, pt1zx, pt1zy); + return pt3; + } - (pt2yx, pt2yy) = _FQ2Mul(pt2yx, pt2yy, pt1zx, pt1zy); // U1 = y2 * z1 - (pt3[PTYX], pt3[PTYY]) = _FQ2Mul(pt1yx, pt1yy, pt2zx, pt2zy); // U2 = y1 * z2 - (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt1zx, pt1zy); // V1 = x2 * z1 - (pt3[PTZX], pt3[PTZY]) = _FQ2Mul(pt1xx, pt1xy, pt2zx, pt2zy); // V2 = x1 * z2 + (pt2yx, pt2yy) = _FQ2Mul(pt2yx, pt2yy, pt1zx, pt1zy); // U1 = y2 * z1 + (pt3[PTYX], pt3[PTYY]) = _FQ2Mul(pt1yx, pt1yy, pt2zx, pt2zy); // U2 = y1 * z2 + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt1zx, pt1zy); // V1 = x2 * z1 + (pt3[PTZX], pt3[PTZY]) = _FQ2Mul(pt1xx, pt1xy, pt2zx, pt2zy); // V2 = x1 * z2 - if (pt2xx == pt3[PTZX] && pt2xy == pt3[PTZY]) { - if (pt2yx == pt3[PTYX] && pt2yy == pt3[PTYY]) { - ( - pt3[PTXX], pt3[PTXY], - pt3[PTYX], pt3[PTYY], - pt3[PTZX], pt3[PTZY] - ) = _ECTwistDoubleJacobian(pt1xx, pt1xy, pt1yx, pt1yy, pt1zx, pt1zy); - return pt3; - } - ( - pt3[PTXX], pt3[PTXY], - pt3[PTYX], pt3[PTYY], - pt3[PTZX], pt3[PTZY] - ) = ( - 1, 0, - 1, 0, - 0, 0 - ); + if (pt2xx == pt3[PTZX] && pt2xy == pt3[PTZY]) { + if (pt2yx == pt3[PTYX] && pt2yy == pt3[PTYY]) { + (pt3[PTXX], pt3[PTXY], pt3[PTYX], pt3[PTYY], pt3[PTZX], pt3[PTZY]) = + _ECTwistDoubleJacobian(pt1xx, pt1xy, pt1yx, pt1yy, pt1zx, pt1zy); return pt3; } + (pt3[PTXX], pt3[PTXY], pt3[PTYX], pt3[PTYY], pt3[PTZX], pt3[PTZY]) = (1, 0, 1, 0, 0, 0); + return pt3; + } - (pt2zx, pt2zy) = _FQ2Mul(pt1zx, pt1zy, pt2zx, pt2zy); // W = z1 * z2 - (pt1xx, pt1xy) = _FQ2Sub(pt2yx, pt2yy, pt3[PTYX], pt3[PTYY]); // U = U1 - U2 - (pt1yx, pt1yy) = _FQ2Sub(pt2xx, pt2xy, pt3[PTZX], pt3[PTZY]); // V = V1 - V2 - (pt1zx, pt1zy) = _FQ2Mul(pt1yx, pt1yy, pt1yx, pt1yy); // V_squared = V * V - (pt2yx, pt2yy) = _FQ2Mul(pt1zx, pt1zy, pt3[PTZX], pt3[PTZY]); // V_squared_times_V2 = V_squared * V2 - (pt1zx, pt1zy) = _FQ2Mul(pt1zx, pt1zy, pt1yx, pt1yy); // V_cubed = V * V_squared - (pt3[PTZX], pt3[PTZY]) = _FQ2Mul(pt1zx, pt1zy, pt2zx, pt2zy); // newz = V_cubed * W - (pt2xx, pt2xy) = _FQ2Mul(pt1xx, pt1xy, pt1xx, pt1xy); // U * U - (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt2zx, pt2zy); // U * U * W - (pt2xx, pt2xy) = _FQ2Sub(pt2xx, pt2xy, pt1zx, pt1zy); // U * U * W - V_cubed - (pt2zx, pt2zy) = _FQ2Muc(pt2yx, pt2yy, 2); // 2 * V_squared_times_V2 - (pt2xx, pt2xy) = _FQ2Sub(pt2xx, pt2xy, pt2zx, pt2zy); // A = U * U * W - V_cubed - 2 * V_squared_times_V2 - (pt3[PTXX], pt3[PTXY]) = _FQ2Mul(pt1yx, pt1yy, pt2xx, pt2xy); // newx = V * A - (pt1yx, pt1yy) = _FQ2Sub(pt2yx, pt2yy, pt2xx, pt2xy); // V_squared_times_V2 - A - (pt1yx, pt1yy) = _FQ2Mul(pt1xx, pt1xy, pt1yx, pt1yy); // U * (V_squared_times_V2 - A) - (pt1xx, pt1xy) = _FQ2Mul(pt1zx, pt1zy, pt3[PTYX], pt3[PTYY]); // V_cubed * U2 - (pt3[PTYX], pt3[PTYY]) = _FQ2Sub(pt1yx, pt1yy, pt1xx, pt1xy); // newy = U * (V_squared_times_V2 - A) - V_cubed * U2 + (pt2zx, pt2zy) = _FQ2Mul(pt1zx, pt1zy, pt2zx, pt2zy); // W = z1 * z2 + (pt1xx, pt1xy) = _FQ2Sub(pt2yx, pt2yy, pt3[PTYX], pt3[PTYY]); // U = U1 - U2 + (pt1yx, pt1yy) = _FQ2Sub(pt2xx, pt2xy, pt3[PTZX], pt3[PTZY]); // V = V1 - V2 + (pt1zx, pt1zy) = _FQ2Mul(pt1yx, pt1yy, pt1yx, pt1yy); // V_squared = V * V + (pt2yx, pt2yy) = _FQ2Mul(pt1zx, pt1zy, pt3[PTZX], pt3[PTZY]); // V_squared_times_V2 = V_squared * V2 + (pt1zx, pt1zy) = _FQ2Mul(pt1zx, pt1zy, pt1yx, pt1yy); // V_cubed = V * V_squared + (pt3[PTZX], pt3[PTZY]) = _FQ2Mul(pt1zx, pt1zy, pt2zx, pt2zy); // newz = V_cubed * W + (pt2xx, pt2xy) = _FQ2Mul(pt1xx, pt1xy, pt1xx, pt1xy); // U * U + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt2zx, pt2zy); // U * U * W + (pt2xx, pt2xy) = _FQ2Sub(pt2xx, pt2xy, pt1zx, pt1zy); // U * U * W - V_cubed + (pt2zx, pt2zy) = _FQ2Muc(pt2yx, pt2yy, 2); // 2 * V_squared_times_V2 + (pt2xx, pt2xy) = _FQ2Sub(pt2xx, pt2xy, pt2zx, pt2zy); // A = U * U * W - V_cubed - 2 * V_squared_times_V2 + (pt3[PTXX], pt3[PTXY]) = _FQ2Mul(pt1yx, pt1yy, pt2xx, pt2xy); // newx = V * A + (pt1yx, pt1yy) = _FQ2Sub(pt2yx, pt2yy, pt2xx, pt2xy); // V_squared_times_V2 - A + (pt1yx, pt1yy) = _FQ2Mul(pt1xx, pt1xy, pt1yx, pt1yy); // U * (V_squared_times_V2 - A) + (pt1xx, pt1xy) = _FQ2Mul(pt1zx, pt1zy, pt3[PTYX], pt3[PTYY]); // V_cubed * U2 + (pt3[PTYX], pt3[PTYY]) = _FQ2Sub(pt1yx, pt1yy, pt1xx, pt1xy); // newy = U * (V_squared_times_V2 - A) - V_cubed * U2 } function _ECTwistDoubleJacobian( - uint256 pt1xx, uint256 pt1xy, - uint256 pt1yx, uint256 pt1yy, - uint256 pt1zx, uint256 pt1zy - ) internal pure returns ( - uint256 pt2xx, uint256 pt2xy, - uint256 pt2yx, uint256 pt2yy, - uint256 pt2zx, uint256 pt2zy - ) { - (pt2xx, pt2xy) = _FQ2Muc(pt1xx, pt1xy, 3); // 3 * x + uint256 pt1xx, + uint256 pt1xy, + uint256 pt1yx, + uint256 pt1yy, + uint256 pt1zx, + uint256 pt1zy + ) + internal + pure + returns (uint256 pt2xx, uint256 pt2xy, uint256 pt2yx, uint256 pt2yy, uint256 pt2zx, uint256 pt2zy) + { + (pt2xx, pt2xy) = _FQ2Muc(pt1xx, pt1xy, 3); // 3 * x (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt1xx, pt1xy); // W = 3 * x * x (pt1zx, pt1zy) = _FQ2Mul(pt1yx, pt1yy, pt1zx, pt1zy); // S = y * z (pt2yx, pt2yy) = _FQ2Mul(pt1xx, pt1xy, pt1yx, pt1yy); // x * y (pt2yx, pt2yy) = _FQ2Mul(pt2yx, pt2yy, pt1zx, pt1zy); // B = x * y * S (pt1xx, pt1xy) = _FQ2Mul(pt2xx, pt2xy, pt2xx, pt2xy); // W * W - (pt2zx, pt2zy) = _FQ2Muc(pt2yx, pt2yy, 8); // 8 * B + (pt2zx, pt2zy) = _FQ2Muc(pt2yx, pt2yy, 8); // 8 * B (pt1xx, pt1xy) = _FQ2Sub(pt1xx, pt1xy, pt2zx, pt2zy); // H = W * W - 8 * B (pt2zx, pt2zy) = _FQ2Mul(pt1zx, pt1zy, pt1zx, pt1zy); // S_squared = S * S - (pt2yx, pt2yy) = _FQ2Muc(pt2yx, pt2yy, 4); // 4 * B + (pt2yx, pt2yy) = _FQ2Muc(pt2yx, pt2yy, 4); // 4 * B (pt2yx, pt2yy) = _FQ2Sub(pt2yx, pt2yy, pt1xx, pt1xy); // 4 * B - H (pt2yx, pt2yy) = _FQ2Mul(pt2yx, pt2yy, pt2xx, pt2xy); // W * (4 * B - H) - (pt2xx, pt2xy) = _FQ2Muc(pt1yx, pt1yy, 8); // 8 * y + (pt2xx, pt2xy) = _FQ2Muc(pt1yx, pt1yy, 8); // 8 * y (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt1yx, pt1yy); // 8 * y * y (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt2zx, pt2zy); // 8 * y * y * S_squared (pt2yx, pt2yy) = _FQ2Sub(pt2yx, pt2yy, pt2xx, pt2xy); // newy = W * (4 * B - H) - 8 * y * y * S_squared - (pt2xx, pt2xy) = _FQ2Muc(pt1xx, pt1xy, 2); // 2 * H + (pt2xx, pt2xy) = _FQ2Muc(pt1xx, pt1xy, 2); // 2 * H (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt1zx, pt1zy); // newx = 2 * H * S (pt2zx, pt2zy) = _FQ2Mul(pt1zx, pt1zy, pt2zx, pt2zy); // S * S_squared - (pt2zx, pt2zy) = _FQ2Muc(pt2zx, pt2zy, 8); // newz = 8 * S * S_squared + (pt2zx, pt2zy) = _FQ2Muc(pt2zx, pt2zy, 8); // newz = 8 * S * S_squared } function _ECTwistMulJacobian( uint256 d, - uint256 pt1xx, uint256 pt1xy, - uint256 pt1yx, uint256 pt1yy, - uint256 pt1zx, uint256 pt1zy + uint256 pt1xx, + uint256 pt1xy, + uint256 pt1yx, + uint256 pt1yy, + uint256 pt1zx, + uint256 pt1zy ) internal pure returns (uint256[6] memory pt2) { while (d != 0) { if ((d & 1) != 0) { pt2 = _ECTwistAddJacobian( - pt2[PTXX], pt2[PTXY], - pt2[PTYX], pt2[PTYY], - pt2[PTZX], pt2[PTZY], - pt1xx, pt1xy, - pt1yx, pt1yy, - pt1zx, pt1zy); + pt2[PTXX], + pt2[PTXY], + pt2[PTYX], + pt2[PTYY], + pt2[PTZX], + pt2[PTZY], + pt1xx, + pt1xy, + pt1yx, + pt1yy, + pt1zx, + pt1zy + ); } - ( - pt1xx, pt1xy, - pt1yx, pt1yy, - pt1zx, pt1zy - ) = _ECTwistDoubleJacobian( - pt1xx, pt1xy, - pt1yx, pt1yy, - pt1zx, pt1zy - ); + (pt1xx, pt1xy, pt1yx, pt1yy, pt1zx, pt1zy) = + _ECTwistDoubleJacobian(pt1xx, pt1xy, pt1yx, pt1yy, pt1zx, pt1zy); d = d / 2; } } -} \ No newline at end of file +} From c2218d922b0beab8e8e0306c75f39e67779904a8 Mon Sep 17 00:00:00 2001 From: alrxy Date: Wed, 22 Jan 2025 16:04:17 +0400 Subject: [PATCH 18/21] chore: rm console log and fix bls storage location --- src/extensions/managers/keys/KeyManagerBLS.sol | 2 +- src/extensions/managers/sigs/BLSSig.sol | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/extensions/managers/keys/KeyManagerBLS.sol b/src/extensions/managers/keys/KeyManagerBLS.sol index 4dc21c3..a4786db 100644 --- a/src/extensions/managers/keys/KeyManagerBLS.sol +++ b/src/extensions/managers/keys/KeyManagerBLS.sol @@ -37,7 +37,7 @@ abstract contract KeyManagerBLS is KeyManager, BLSSig { // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.KeyManagerBLS")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant KeyManagerBLSStorageLocation = - 0x4da47716e6090d5a5545e03387f4dac112d37cd069a5573bb81de8579bd9dc00; + 0xd7c6d1e3027b949fd4edf42b481934f7c4e193928cd161b15a475e3400c5ed00; function _getKeyManagerBLSStorage() internal pure returns (KeyManagerBLSStorage storage s) { bytes32 location = KeyManagerBLSStorageLocation; diff --git a/src/extensions/managers/sigs/BLSSig.sol b/src/extensions/managers/sigs/BLSSig.sol index 0301a9d..3a6669f 100644 --- a/src/extensions/managers/sigs/BLSSig.sol +++ b/src/extensions/managers/sigs/BLSSig.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.25; import {SigManager} from "../../../managers/extendable/SigManager.sol"; import {BN254} from "../../../libraries/BN254.sol"; -import "forge-std/console.sol"; /** * @title BLSSig @@ -31,18 +30,8 @@ contract BLSSig is SigManager { abi.decode(key_, (BN254.G1Point, BN254.G2Point)); BN254.G1Point memory sig = abi.decode(signature, (BN254.G1Point)); - bytes memory message = abi.encode(operator, pubkeyG1, pubkeyG2); - - console.log("pubkeyG1 ", pubkeyG1.X, pubkeyG1.Y); - console.log("message "); - console.logBytes(message); - bytes32 messageHash = keccak256(message); - console.logBytes32(messageHash); - console.log("sig ", sig.X, sig.Y); - console.log("pubkeyG2 X ", pubkeyG2.X[0], pubkeyG2.X[1]); - console.log("pubkeyG2 Y", pubkeyG2.Y[0], pubkeyG2.Y[1]); return verify(pubkeyG1, pubkeyG2, sig, messageHash); } @@ -62,7 +51,6 @@ contract BLSSig is SigManager { bytes32 messageHash ) public view returns (bool) { BN254.G1Point memory messageG1 = BN254.hashToG1(messageHash); - console.log("messageG1 ", messageG1.X, messageG1.Y); uint256 alpha = uint256( keccak256( abi.encodePacked( From b618a84892d1930636c097ddfa30aa372ccaac41 Mon Sep 17 00:00:00 2001 From: alrxy Date: Wed, 22 Jan 2025 17:01:18 +0400 Subject: [PATCH 19/21] feat: IMT with remove --- .../managers/keys/KeyManagerBLS.sol | 4 +- src/libraries/Merkle.sol | 130 +++++------------- test/Merkle.t.sol | 16 +-- test/helpers/FullMerkle.sol | 5 +- test/helpers/SimpleMerkle.sol | 18 +-- 5 files changed, 51 insertions(+), 122 deletions(-) diff --git a/src/extensions/managers/keys/KeyManagerBLS.sol b/src/extensions/managers/keys/KeyManagerBLS.sol index c3bb1cb..1e36746 100644 --- a/src/extensions/managers/keys/KeyManagerBLS.sol +++ b/src/extensions/managers/keys/KeyManagerBLS.sol @@ -184,10 +184,10 @@ abstract contract KeyManagerBLS is KeyManager, BLSSig { // remove current key from merkle tree and aggregated key when new key is zero else update aggregatedKey = aggregatedKey.plus(currentKey.negate()); if (key.X == 0 && key.Y == 0) { - $._keyMerkle.update(bytes32(0), bytes32(currentKey.X), proof, index); + $._keyMerkle.remove(bytes32(currentKey.X), proof, index); } else { aggregatedKey = aggregatedKey.plus(key); - $._keyMerkle.update(bytes32(key.X), bytes32(prevKey.X), proof, index); + $._keyMerkle.update(bytes32(key.X), bytes32(prevKey.X), proof, index, false); } $._aggregatedKey.push(_now(), aggregatedKey.X); diff --git a/src/libraries/Merkle.sol b/src/libraries/Merkle.sol index b9137ef..837351c 100644 --- a/src/libraries/Merkle.sol +++ b/src/libraries/Merkle.sol @@ -7,7 +7,6 @@ pragma solidity ^0.8.25; uint256 constant TREE_DEPTH = 16; uint256 constant MAX_LEAVES = 2 ** TREE_DEPTH - 1; -bytes32 constant ZERO_ELEMENT = bytes32(0); /** * @title MerkleLib @@ -25,7 +24,6 @@ library MerkleLib { error FullMerkleTree(); error InvalidIndex(); error SameNodeUpdate(); - error ZeroElementStoring(); error EmptyTree(); /** @@ -35,8 +33,7 @@ library MerkleLib { */ struct Tree { bytes32[TREE_DEPTH] branch; - uint256 count; - bytes32 lastNode; + bytes32[] leaves; } /** @@ -46,24 +43,22 @@ library MerkleLib { * */ function insert(Tree storage _tree, bytes32 _node) internal { - if (_tree.count >= MAX_LEAVES) { + uint256 _size = _tree.leaves.length; + + if (_size >= MAX_LEAVES) { revert FullMerkleTree(); } - if (_node == ZERO_ELEMENT) { - revert ZeroElementStoring(); - } - uint256 _index = _tree.count++; - emit UpdateLeaf(_index, _node); + emit UpdateLeaf(_size, _node); + _tree.leaves.push(_node); - _tree.lastNode = _node; for (uint256 i = 0; i < TREE_DEPTH; i++) { - if ((_index & 1) == 0) { + if ((_size & 1) == 0) { _tree.branch[i] = _node; return; } _node = keccak256(abi.encodePacked(_tree.branch[i], _node)); - _index >>= 1; + _size >>= 1; } // As the loop should always end prematurely with the `return` statement, // this code should be unreachable. We assert `false` just to be safe. @@ -73,9 +68,10 @@ library MerkleLib { function update( Tree storage _tree, bytes32 _node, - bytes32 _oldNode, + bytes32 _oldNode, // we could read from storage, but we already have to check the old node proof validity bytes32[TREE_DEPTH] memory _branch, - uint256 _index + uint256 _index, + bool isRemove ) internal { if (_node == _oldNode) { revert SameNodeUpdate(); @@ -87,29 +83,20 @@ library MerkleLib { revert InvalidProof(); } - unsafeUpdate(_tree, _node, _branch, _index); - } - - // without proof checking - function unsafeUpdate( - Tree storage _tree, - bytes32 _node, - bytes32[TREE_DEPTH] memory _branch, - uint256 _index - ) internal { - if (_index >= _tree.count) { + uint256 size = _tree.leaves.length; + if (_index >= size) { revert InvalidIndex(); } - if (_node == bytes32(0)) { - revert ZeroElementStoring(); + if (isRemove) { + size--; } + _tree.leaves[_index] = _node; emit UpdateLeaf(_index, _node); - uint256 lastIndex = _tree.count; for (uint256 i = 0; i < TREE_DEPTH; i++) { - if ((lastIndex / 2 * 2) == _index) { + if ((size / 2 * 2) == _index) { _tree.branch[i] = _node; return; } @@ -118,85 +105,38 @@ library MerkleLib { } else { _node = keccak256(abi.encodePacked(_node, _branch[i])); } - lastIndex >>= 1; + size >>= 1; _index >>= 1; } assert(false); } - function pop(Tree storage _tree, bytes32 _secondLastNode, bytes32[TREE_DEPTH] memory _secondLastBranch) internal { - if (_tree.count > 1) { - bytes32 _root = branchRoot(_secondLastNode, _secondLastBranch, _tree.count - 2); - if (_root != _tree.root()) { - revert InvalidProof(); - } - } - - unsafePop(_tree, _secondLastNode, _secondLastBranch); - } - - function unsafePop( - Tree storage _tree, - bytes32 _secondLastNode, - bytes32[TREE_DEPTH] memory _secondLastBranch + function pop( + Tree storage _tree ) internal { - if (_tree.count == 0) { - revert EmptyTree(); - } - - // edge-case for single node tree, in this case _secondLastNode is bytes32(0) and _secondLastBranch is full of zero hashes - if (_tree.count == 1) { - emit PopLeaf(); - _tree.count = 0; - _tree.lastNode = bytes32(0); - _tree.branch[0] = bytes32(0); - return; - } - - uint256 _lastIndex = --_tree.count; // tree.count - 2 - uint256 _index = _lastIndex - 1; - + _tree.leaves.pop(); + uint256 size = _tree.leaves.length; + bytes32 _node = bytes32(0); emit PopLeaf(); - _tree.lastNode = _secondLastNode; for (uint256 i = 0; i < TREE_DEPTH; i++) { - if ((_lastIndex / 2 * 2) == (_index / 2 * 2)) { - _tree.branch[i] = _secondLastNode; + if ((size & 1) == 0) { + _tree.branch[i] = _node; return; } - if ((_index & 1) == 1) { - _secondLastNode = keccak256(abi.encodePacked(_secondLastBranch[i], _secondLastNode)); - } else { - _secondLastNode = keccak256(abi.encodePacked(_secondLastNode, _secondLastBranch[i])); - } - _lastIndex >>= 1; - _index >>= 1; + _node = keccak256(abi.encodePacked(_tree.branch[i], _node)); + size >>= 1; } + + assert(false); } - function remove( - Tree storage _tree, - bytes32 _node, - bytes32[TREE_DEPTH] memory _branch, - uint256 _index, - bytes32 _secondLastNode, - bytes32[TREE_DEPTH] memory _secondLastBranch - ) internal { - bytes32 _root = _tree.root(); - bytes32 _updateRoot = branchRoot(_node, _branch, _index); - if (_updateRoot != _root) { - revert InvalidProof(); - } - if (_tree.count > 1) { - bytes32 _popRoot = branchRoot(_secondLastNode, _secondLastBranch, _tree.count - 2); - if (_popRoot != _root) { - revert InvalidProof(); - } + function remove(Tree storage _tree, bytes32 _node, bytes32[TREE_DEPTH] memory _branch, uint256 _index) internal { + if (_index != _tree.leaves.length - 1) { + update(_tree, _tree.leaves[_tree.leaves.length - 1], _node, _branch, _index, true); } - - unsafeUpdate(_tree, _tree.lastNode, _branch, _index); - unsafePop(_tree, _secondLastNode, _secondLastBranch); + pop(_tree); } /** @@ -208,10 +148,10 @@ library MerkleLib { Tree storage _tree ) internal view returns (bytes32 _current) { bytes32[TREE_DEPTH] memory _zeroes = zeroHashes(); - uint256 _index = _tree.count; + uint256 _size = _tree.leaves.length; for (uint256 i = 0; i < TREE_DEPTH; i++) { - uint256 _ithBit = (_index >> i) & 0x01; + uint256 _ithBit = (_size >> i) & 0x01; bytes32 _next = _tree.branch[i]; if (_ithBit == 1) { _current = keccak256(abi.encodePacked(_next, _current)); diff --git a/test/Merkle.t.sol b/test/Merkle.t.sol index 415207b..ea9780d 100644 --- a/test/Merkle.t.sol +++ b/test/Merkle.t.sol @@ -128,24 +128,22 @@ contract MerkleTest is Test { } for (uint256 i = 0; i < _nodes.length; i++) { - // simpleMerkle.insert(_nodes[i]); + simpleMerkle.insert(_nodes[i]); fullMerkle.insert(_nodes[i]); + assertEq(simpleMerkle.root(), fullMerkle.root()); } bytes32[16] memory proof = fullMerkle.getProof(_index); - bytes32[16] memory secondLastBranch; - bytes32 secondLastNode; - if (_nodes.length > 1) { - secondLastNode = _nodes[_nodes.length - 2]; - secondLastBranch = fullMerkle.getProof(_nodes.length - 2); - } - // simpleMerkle.remove(_nodes[_index], proof, _index, secondLastNode, secondLastBranch); + simpleMerkle.remove(_nodes[_index], proof, _index); fullMerkle.remove(_index); + assertEq(simpleMerkle.root(), fullMerkle.root()); + + _nodes[_index] = _nodes[_nodes.length - 1]; for (uint256 i = 0; i < _nodes.length - 1; i++) { proof = fullMerkle.getProof(i); assertTrue(fullMerkle.verify(_nodes[i], proof, i)); - // assertTrue(simpleMerkle.verify(_nodes[i], proof, i)); + assertTrue(simpleMerkle.verify(_nodes[i], proof, i)); } } } diff --git a/test/helpers/FullMerkle.sol b/test/helpers/FullMerkle.sol index 0f9db4b..cfd6518 100644 --- a/test/helpers/FullMerkle.sol +++ b/test/helpers/FullMerkle.sol @@ -49,10 +49,7 @@ contract FullMerkle { function pop() public { require(currentLeafIndex > 0, "Tree is empty"); - uint256 leafPos = currentLeafIndex - 1; - nodes[0][leafPos] = bytes32(0); - - _updatePath(leafPos); + update(bytes32(0), currentLeafIndex - 1); currentLeafIndex--; } diff --git a/test/helpers/SimpleMerkle.sol b/test/helpers/SimpleMerkle.sol index c6d691d..4d634df 100644 --- a/test/helpers/SimpleMerkle.sol +++ b/test/helpers/SimpleMerkle.sol @@ -15,25 +15,19 @@ contract SimpleMerkle { } function update(bytes32 _node, bytes32 _oldNode, bytes32[TREE_DEPTH] memory _proof, uint256 _index) external { - tree.update(_node, _oldNode, _proof, _index); + tree.update(_node, _oldNode, _proof, _index, false); } function verify(bytes32 _node, bytes32[TREE_DEPTH] memory _proof, uint256 _index) external view returns (bool) { return tree.root() == MerkleLib.branchRoot(_node, _proof, _index); } - function pop(bytes32 _secondLastNode, bytes32[TREE_DEPTH] memory _secondLastBranch) external { - tree.pop(_secondLastNode, _secondLastBranch); + function pop() external { + tree.pop(); } - function remove( - bytes32 _node, - bytes32[TREE_DEPTH] memory _proof, - uint256 _index, - bytes32 _secondLastNode, - bytes32[TREE_DEPTH] memory _secondLastBranch - ) external { - tree.remove(_node, _proof, _index, _secondLastNode, _secondLastBranch); + function remove(bytes32 _node, bytes32[TREE_DEPTH] memory _proof, uint256 _index) external { + tree.remove(_node, _proof, _index); } function root() external view returns (bytes32) { @@ -41,6 +35,6 @@ contract SimpleMerkle { } function count() external view returns (uint256) { - return tree.count; + return tree.leaves.length; } } From 52588f91dbc7cca4d9c957f5b10f857fefd643a5 Mon Sep 17 00:00:00 2001 From: alrxy Date: Thu, 23 Jan 2025 17:50:17 +0400 Subject: [PATCH 20/21] feat: merkle full tree given all nodes root calc --- src/libraries/Merkle.sol | 21 +++++++++++++++++++++ test/Merkle.t.sol | 20 ++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/libraries/Merkle.sol b/src/libraries/Merkle.sol index 837351c..b4d6e1b 100644 --- a/src/libraries/Merkle.sol +++ b/src/libraries/Merkle.sol @@ -210,6 +210,27 @@ library MerkleLib { } } + function treeRoot( + bytes32[] memory _leaves + ) internal view returns (bytes32) { + uint256 _size = _leaves.length; + bytes32[TREE_DEPTH] memory _zeroes = zeroHashes(); + + for (uint256 depth = 0; depth < TREE_DEPTH; depth++) { + for (uint256 i = 0; i < _size - 1; i += 2) { + _leaves[i / 2] = keccak256(abi.encodePacked(_leaves[i], _leaves[i + 1])); + } + + if (_size % 2 == 1) { + _leaves[(_size - 1) / 2] = keccak256(abi.encodePacked(_leaves[_size - 1], _zeroes[depth])); + } + + _size = (_size + 1) / 2; + } + + return _leaves[0]; + } + // keccak256 zero hashes bytes32 internal constant Z_0 = hex"0000000000000000000000000000000000000000000000000000000000000000"; bytes32 internal constant Z_1 = hex"ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5"; diff --git a/test/Merkle.t.sol b/test/Merkle.t.sol index ea9780d..8335dfc 100644 --- a/test/Merkle.t.sol +++ b/test/Merkle.t.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.25; import "forge-std/Test.sol"; import "./helpers/SimpleMerkle.sol"; import "./helpers/FullMerkle.sol"; +import "../src/libraries/Merkle.sol"; contract MerkleTest is Test { SimpleMerkle internal simpleMerkle; @@ -146,4 +147,23 @@ contract MerkleTest is Test { assertTrue(simpleMerkle.verify(_nodes[i], proof, i)); } } + + function testTreeRoot( + bytes32[8] memory _leaves + ) public { + for (uint256 i = 0; i < _leaves.length; i++) { + vm.assume(_leaves[i] != bytes32(0)); + } + + for (uint256 i = 0; i < _leaves.length; i++) { + simpleMerkle.insert(_leaves[i]); + } + + bytes32[] memory leaves = new bytes32[](_leaves.length); + for (uint256 i = 0; i < _leaves.length; i++) { + leaves[i] = _leaves[i]; + } + + assertEq(MerkleLib.treeRoot(leaves), simpleMerkle.root()); + } } From 5396c3901e3674579a2a0c23eaf59ed97b1bb2df Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 27 Jan 2025 14:46:13 +0400 Subject: [PATCH 21/21] fix: lint --- src/libraries/PauseableEnumerableSet.sol | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index 4724599..e1a6564 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -261,7 +261,12 @@ library PauseableEnumerableSet { * @param value The address to check * @return bool Whether the address can be unregistered */ - function checkUnregister(AddressSet storage self, uint48 timestamp, uint48 immutablePeriod, address value) internal view returns (bool) { + function checkUnregister( + AddressSet storage self, + uint48 timestamp, + uint48 immutablePeriod, + address value + ) internal view returns (bool) { uint256 pos = self.positions[value]; if (pos == 0) revert NotRegistered(); pos--;