diff --git a/.gitignore b/.gitignore index deea2d0..36a800a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,6 @@ out/ docs/ # Dotenv file -.env \ No newline at end of file +.env + +node_modules/ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..2fe0ea9 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,241 @@ +{ + "name": "symbiotic-tests", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "symbiotic-tests", + "version": "1.0.0", + "dependencies": { + "@noble/curves": "^1.3.0", + "ethereum-cryptography": "^2.1.3", + "ethers": "^6.11.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==" + }, + "node_modules/@noble/curves": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.6.0.tgz", + "integrity": "sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==", + "dependencies": { + "@noble/hashes": "1.5.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", + "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/base": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", + "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", + "dependencies": { + "@noble/curves": "~1.4.0", + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", + "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", + "dependencies": { + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@types/node": { + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/aes-js": { + "version": "4.0.0-beta.5", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==" + }, + "node_modules/ethereum-cryptography": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", + "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", + "dependencies": { + "@noble/curves": "1.4.2", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0" + } + }, + "node_modules/ethereum-cryptography/node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethereum-cryptography/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethers": { + "version": "6.13.4", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.4.tgz", + "integrity": "sha512-21YtnZVg4/zKkCQPjrDj38B1r4nQvTZLopUGMLQ1ePU2zV/joCfDC3t3iKQjWRzjjjbzR+mdAIoikeBRNkdllA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/ethers-io/" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@adraffy/ens-normalize": "1.10.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@types/node": "22.7.5", + "aes-js": "4.0.0-beta.5", + "tslib": "2.7.0", + "ws": "8.17.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ethers/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethers/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ed497d0 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "symbiotic-tests", + "version": "1.0.0", + "description": "Test helpers for Symbiotic protocol", + "private": true, + "scripts": { + "testgen": "node test/helpers/ed25519TestGenerator.js" + }, + "dependencies": { + "@noble/curves": "^1.3.0", + "ethereum-cryptography": "^2.1.3", + "ethers": "^6.11.1" + }, + "engines": { + "node": ">=18.0.0" + } +} \ No newline at end of file diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index d8deb60..d123f79 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -67,7 +67,9 @@ contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256 { * @param epoch The epoch number. * @return The start timestamp. */ - function getEpochStart(uint48 epoch) public view returns (uint48) { + function getEpochStart( + uint48 epoch + ) public view returns (uint48) { return START_TIMESTAMP + epoch * EPOCH_DURATION; } @@ -142,10 +144,13 @@ contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256 { * @param stakeHints Hints for determining stakes. * @param slashHints Hints for the slashing process. */ - function slash(uint48 epoch, bytes32 key, uint256 amount, bytes[][] memory stakeHints, bytes[] memory slashHints) - public - onlyOwner - { + function slash( + uint48 epoch, + bytes32 key, + uint256 amount, + bytes[][] memory stakeHints, + bytes[] memory slashHints + ) public onlyOwner { SlashParams memory params; params.epochStart = getEpochStart(epoch); params.operator = operatorByKey(abi.encode(key)); diff --git a/src/key-storage/KeyStorage256.sol b/src/key-storage/KeyStorage256.sol index 3ab62eb..6c98483 100644 --- a/src/key-storage/KeyStorage256.sol +++ b/src/key-storage/KeyStorage256.sol @@ -24,7 +24,9 @@ abstract contract KeyStorage256 is BaseMiddleware { * @param key The key for which to find the associated operator * @return The address of the operator linked to the specified key */ - function operatorByKey(bytes memory key) public view override returns (address) { + function operatorByKey( + bytes memory key + ) public view override returns (address) { return keyToOperator[abi.decode(key, (bytes32))]; } @@ -34,8 +36,14 @@ abstract contract KeyStorage256 is BaseMiddleware { * @param operator The address of the operator * @return The key associated with the specified operator */ - function operatorKey(address operator) public view override returns (bytes memory) { - return abi.encode(keys[operator].getActive(getCaptureTimestamp())[0]); + function operatorKey( + address operator + ) public view override returns (bytes memory) { + bytes32[] memory active = keys[operator].getActive(getCaptureTimestamp()); + if (active.length == 0) { + return abi.encode(ZERO_BYTES32); + } + return abi.encode(active[0]); } /** diff --git a/src/key-storage/KeyStorageBytes.sol b/src/key-storage/KeyStorageBytes.sol index 1d709ed..addac96 100644 --- a/src/key-storage/KeyStorageBytes.sol +++ b/src/key-storage/KeyStorageBytes.sol @@ -22,7 +22,9 @@ abstract contract KeyStorageBytes is BaseManager { * @param key The BLS key for which to find the associated operator * @return The address of the operator linked to the specified BLS key */ - function operatorByKey(bytes memory key) public view returns (address) { + function operatorByKey( + bytes memory key + ) public view returns (address) { return _keyData[key].getAddress(); } @@ -32,7 +34,9 @@ abstract contract KeyStorageBytes is BaseManager { * @param operator The address of the operator * @return The BLS key associated with the specified operator */ - function operatorKey(address operator) public view returns (bytes memory) { + function operatorKey( + address operator + ) public view returns (bytes memory) { if (keyUpdateTimestamp[operator] == getCaptureTimestamp()) { return prevKeys[operator]; } diff --git a/src/libraries/Ed25519.sol b/src/libraries/Ed25519.sol index 580b419..084b82d 100644 --- a/src/libraries/Ed25519.sol +++ b/src/libraries/Ed25519.sol @@ -5,7 +5,9 @@ pragma solidity ^0.8; library Ed25519 { // Computes (v^(2^250-1), v^11) mod p - function pow22501(uint256 v) private pure returns (uint256 p22501, uint256 p11) { + function pow22501( + uint256 v + ) private pure returns (uint256 p22501, uint256 p11) { p11 = mulmod(v, v, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); p22501 = mulmod(p11, p11, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); p22501 = mulmod( diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index 3946510..c95790f 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -44,7 +44,9 @@ library PauseableEnumerableSet { * @param self The AddressSet storage. * @return The number of elements in the set. */ - function length(AddressSet storage self) internal view returns (uint256) { + function length( + AddressSet storage self + ) internal view returns (uint256) { return self.set.length(); } @@ -142,7 +144,9 @@ library PauseableEnumerableSet { * @param self The Uint160Set storage. * @return The number of elements. */ - function length(Uint160Set storage self) internal view returns (uint256) { + function length( + Uint160Set storage self + ) internal view returns (uint256) { return self.array.length; } @@ -283,7 +287,9 @@ library PauseableEnumerableSet { * @param self The Inner struct * @return The stored Uint160 as address */ - function getAddress(Inner storage self) internal view returns (address) { + function getAddress( + Inner storage self + ) internal view returns (address) { return address(self.value); } @@ -292,7 +298,9 @@ library PauseableEnumerableSet { * @param self The Inner struct. * @return The value, enabled timestamp and disabled timestamp. */ - function get(Inner storage self) internal view returns (uint160, uint48, uint48) { + function get( + Inner storage self + ) internal view returns (uint160, uint48, uint48) { return (self.value, self.enabledTimestamp, self.disabledTimestamp); } @@ -418,7 +426,9 @@ library PauseableEnumerableSet { * @param self The AddressSet storage. * @return The number of elements in the set. */ - function length(Bytes32Set storage self) internal view returns (uint256) { + function length( + Bytes32Set storage self + ) internal view returns (uint256) { return self.set.length(); } @@ -526,7 +536,9 @@ library PauseableEnumerableSet { * @param self The Uint160Set storage. * @return The number of elements. */ - function length(Uint256Set storage self) internal view returns (uint256) { + function length( + Uint256Set storage self + ) internal view returns (uint256) { return self.array.length; } @@ -688,7 +700,9 @@ library PauseableEnumerableSet { * @param self The Inner struct * @return The stored Uint160 as address */ - function getBytes32(Inner256 storage self) internal view returns (bytes32) { + function getBytes32( + Inner256 storage self + ) internal view returns (bytes32) { return bytes32(self.value); } @@ -697,7 +711,9 @@ library PauseableEnumerableSet { * @param self The Inner struct. * @return The value, enabled timestamp and disabled timestamp. */ - function get(Inner256 storage self) internal view returns (uint256, uint48, uint48) { + function get( + Inner256 storage self + ) internal view returns (uint256, uint48, uint48) { return (self.value, self.enabledTimestamp, self.disabledTimestamp); } diff --git a/src/managers/OperatorManager.sol b/src/managers/OperatorManager.sol index 04b51f7..aede709 100644 --- a/src/managers/OperatorManager.sol +++ b/src/managers/OperatorManager.sol @@ -34,7 +34,9 @@ abstract contract OperatorManager is BaseManager { * @param pos The index position in the operators array. * @return The address, enabled epoch, disabled epoch and enabled before disabled epoch of the operator. */ - function operatorWithTimesAt(uint256 pos) public view returns (address, uint48, uint48) { + function operatorWithTimesAt( + uint256 pos + ) public view returns (address, uint48, uint48) { return _operators.at(pos); } @@ -51,7 +53,9 @@ abstract contract OperatorManager is BaseManager { * @param timestamp The timestamp to check. * @return An array of addresses representing the active operators. */ - function activeOperatorsAt(uint48 timestamp) public view returns (address[] memory) { + function activeOperatorsAt( + uint48 timestamp + ) public view returns (address[] memory) { return _operators.getActive(timestamp); } @@ -70,7 +74,9 @@ abstract contract OperatorManager is BaseManager { * @param operator The address of the operator to check. * @return A boolean indicating whether the operator is registered. */ - function isOperatorRegistered(address operator) public view returns (bool) { + function isOperatorRegistered( + address operator + ) public view returns (bool) { return _operators.contains(operator); } @@ -78,7 +84,9 @@ abstract contract OperatorManager is BaseManager { * @notice Registers a new operator. * @param operator The address of the operator to register. */ - function _registerOperator(address operator) internal { + function _registerOperator( + address operator + ) internal { if (!IRegistry(OPERATOR_REGISTRY).isEntity(operator)) { revert NotOperator(); } @@ -94,7 +102,9 @@ abstract contract OperatorManager is BaseManager { * @notice Pauses a registered operator. * @param operator The address of the operator to pause. */ - function _pauseOperator(address operator) internal { + function _pauseOperator( + address operator + ) internal { _operators.pause(Time.timestamp(), operator); } @@ -102,7 +112,9 @@ abstract contract OperatorManager is BaseManager { * @notice Unpauses a paused operator. * @param operator The address of the operator to unpause. */ - function _unpauseOperator(address operator) internal { + function _unpauseOperator( + address operator + ) internal { _operators.unpause(Time.timestamp(), SLASHING_WINDOW, operator); } @@ -110,7 +122,9 @@ abstract contract OperatorManager is BaseManager { * @notice Unregisters an operator. * @param operator The address of the operator to unregister. */ - function _unregisterOperator(address operator) internal { + function _unregisterOperator( + address operator + ) internal { _operators.unregister(Time.timestamp(), SLASHING_WINDOW, operator); } } diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol index d988a44..c9aef49 100644 --- a/src/managers/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -61,7 +61,9 @@ abstract contract VaultManager is BaseManager { * @return enableTime The time when the subnetwork was enabled * @return disableTime The time when the subnetwork was disabled */ - function subnetworkWithTimesAt(uint256 pos) public view returns (uint160, uint48, uint48) { + function subnetworkWithTimesAt( + uint256 pos + ) public view returns (uint160, uint48, uint48) { return _subnetworks.at(pos); } @@ -78,7 +80,9 @@ abstract contract VaultManager is BaseManager { * @param timestamp The timestamp to check activity at * @return An array of active subnetwork addresses */ - function activeSubnetworksAt(uint48 timestamp) public view returns (uint160[] memory) { + function activeSubnetworksAt( + uint48 timestamp + ) public view returns (uint160[] memory) { return _subnetworks.getActive(timestamp); } @@ -107,7 +111,9 @@ abstract contract VaultManager is BaseManager { * @return enableTime The time when the vault was enabled * @return disableTime The time when the vault was disabled */ - function sharedVaultWithTimesAt(uint256 pos) public view returns (address, uint48, uint48) { + function sharedVaultWithTimesAt( + uint256 pos + ) public view returns (address, uint48, uint48) { return _sharedVaults.at(pos); } @@ -124,7 +130,9 @@ abstract contract VaultManager is BaseManager { * @param operator The address of the operator * @return The count of vaults for the operator */ - function operatorVaultsLength(address operator) public view returns (uint256) { + function operatorVaultsLength( + address operator + ) public view returns (uint256) { return _operatorVaults[operator].length(); } @@ -145,7 +153,9 @@ abstract contract VaultManager is BaseManager { * @param operator The address of the operator * @return An array of active vault addresses */ - function activeOperatorVaults(address operator) public view returns (address[] memory) { + function activeOperatorVaults( + address operator + ) public view returns (address[] memory) { return _operatorVaults[operator].getActive(getCaptureTimestamp()); } @@ -183,7 +193,9 @@ abstract contract VaultManager is BaseManager { * @param timestamp The timestamp to check activity at * @return An array of active vault addresses */ - function activeVaultsAt(uint48 timestamp) public view virtual returns (address[] memory) { + function activeVaultsAt( + uint48 timestamp + ) public view virtual returns (address[] memory) { address[] memory activeSharedVaults_ = _sharedVaults.getActive(timestamp); uint256 len = activeSharedVaults_.length; address[] memory vaults = new address[](len + _vaultOperator.length()); @@ -212,7 +224,9 @@ abstract contract VaultManager is BaseManager { * @param operator The address of the operator * @return An array of active vault addresses */ - function activeVaults(address operator) public view virtual returns (address[] memory) { + function activeVaults( + address operator + ) public view virtual returns (address[] memory) { uint48 timestamp = getCaptureTimestamp(); address[] memory activeSharedVaults_ = _sharedVaults.getActive(timestamp); address[] memory activeOperatorVaults_ = _operatorVaults[operator].getActive(timestamp); @@ -349,7 +363,9 @@ abstract contract VaultManager is BaseManager { * @param operator The address of the operator * @return stake The total stake amount */ - function getOperatorStake(address operator) public view virtual returns (uint256 stake) { + function getOperatorStake( + address operator + ) public view virtual returns (uint256 stake) { address[] memory vaults = activeVaults(operator); uint160[] memory subnetworks = activeSubnetworks(); @@ -388,7 +404,9 @@ abstract contract VaultManager is BaseManager { * @param operator The address of the operator * @return power The total power amount */ - function getOperatorPower(address operator) public view virtual returns (uint256 power) { + function getOperatorPower( + address operator + ) public view virtual returns (uint256 power) { address[] memory vaults = activeVaults(operator); uint160[] memory subnetworks = activeSubnetworks(); @@ -427,7 +445,9 @@ abstract contract VaultManager is BaseManager { * @param operators Array of operator addresses * @return stake The total stake amount */ - function _totalStake(address[] memory operators) internal view returns (uint256 stake) { + function _totalStake( + address[] memory operators + ) internal view returns (uint256 stake) { for (uint256 i; i < operators.length; ++i) { uint256 operatorStake = getOperatorStake(operators[i]); stake += operatorStake; @@ -441,7 +461,9 @@ abstract contract VaultManager is BaseManager { * @param operators Array of operator addresses * @return power The total power amount */ - function _totalPower(address[] memory operators) internal view returns (uint256 power) { + function _totalPower( + address[] memory operators + ) internal view returns (uint256 power) { for (uint256 i; i < operators.length; ++i) { uint256 operatorStake = getOperatorPower(operators[i]); power += operatorStake; @@ -454,7 +476,9 @@ abstract contract VaultManager is BaseManager { * @notice Registers a new subnetwork * @param subnetwork The identifier of the subnetwork to register */ - function _registerSubnetwork(uint96 subnetwork) internal { + function _registerSubnetwork( + uint96 subnetwork + ) internal { _subnetworks.register(Time.timestamp(), uint160(subnetwork)); } @@ -462,7 +486,9 @@ abstract contract VaultManager is BaseManager { * @notice Pauses a subnetwork * @param subnetwork The identifier of the subnetwork to pause */ - function _pauseSubnetwork(uint96 subnetwork) internal { + function _pauseSubnetwork( + uint96 subnetwork + ) internal { _subnetworks.pause(Time.timestamp(), uint160(subnetwork)); } @@ -470,7 +496,9 @@ abstract contract VaultManager is BaseManager { * @notice Unpauses a subnetwork * @param subnetwork The identifier of the subnetwork to unpause */ - function _unpauseSubnetwork(uint96 subnetwork) internal { + function _unpauseSubnetwork( + uint96 subnetwork + ) internal { _subnetworks.unpause(Time.timestamp(), SLASHING_WINDOW, uint160(subnetwork)); } @@ -478,7 +506,9 @@ abstract contract VaultManager is BaseManager { * @notice Unregisters a subnetwork * @param subnetwork The identifier of the subnetwork to unregister */ - function _unregisterSubnetwork(uint96 subnetwork) internal { + function _unregisterSubnetwork( + uint96 subnetwork + ) internal { _subnetworks.unregister(Time.timestamp(), SLASHING_WINDOW, uint160(subnetwork)); } @@ -486,7 +516,9 @@ abstract contract VaultManager is BaseManager { * @notice Registers a new shared vault * @param vault The address of the vault to register */ - function _registerSharedVault(address vault) internal { + function _registerSharedVault( + address vault + ) internal { _validateVault(vault); _sharedVaults.register(Time.timestamp(), vault); } @@ -509,7 +541,9 @@ abstract contract VaultManager is BaseManager { * @notice Pauses a shared vault * @param vault The address of the vault to pause */ - function _pauseSharedVault(address vault) internal { + function _pauseSharedVault( + address vault + ) internal { _sharedVaults.pause(Time.timestamp(), vault); } @@ -517,7 +551,9 @@ abstract contract VaultManager is BaseManager { * @notice Unpauses a shared vault * @param vault The address of the vault to unpause */ - function _unpauseSharedVault(address vault) internal { + function _unpauseSharedVault( + address vault + ) internal { _sharedVaults.unpause(Time.timestamp(), SLASHING_WINDOW, vault); } @@ -543,7 +579,9 @@ abstract contract VaultManager is BaseManager { * @notice Unregisters a shared vault * @param vault The address of the vault to unregister */ - function _unregisterSharedVault(address vault) internal { + function _unregisterSharedVault( + address vault + ) internal { _sharedVaults.unregister(Time.timestamp(), SLASHING_WINDOW, vault); } @@ -626,7 +664,9 @@ abstract contract VaultManager is BaseManager { * @notice Validates if the vault is properly initialized and registered * @param vault The address of the vault to validate */ - function _validateVault(address vault) private view { + function _validateVault( + address vault + ) private view { if (!IRegistry(VAULT_REGISTRY).isEntity(vault)) { revert NotVault(); } diff --git a/src/middleware/BaseMiddleware.sol b/src/middleware/BaseMiddleware.sol index 3a76478..a6ab904 100644 --- a/src/middleware/BaseMiddleware.sol +++ b/src/middleware/BaseMiddleware.sol @@ -48,7 +48,9 @@ abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager * @param key The key for which to find the associated operator * @return The address of the operator linked to the specified key */ - function operatorByKey(bytes memory key) public view virtual returns (address); + function operatorByKey( + bytes memory key + ) public view virtual returns (address); /** * @notice Returns the current or previous key for a given operator @@ -56,5 +58,7 @@ abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager * @param operator The address of the operator * @return The key associated with the specified operator */ - function operatorKey(address operator) public view virtual returns (bytes memory); + function operatorKey( + address operator + ) public view virtual returns (bytes memory); } diff --git a/src/middleware/extensions/Operators.sol b/src/middleware/extensions/Operators.sol index 7ba0d76..7e0ed3a 100644 --- a/src/middleware/extensions/Operators.sol +++ b/src/middleware/extensions/Operators.sol @@ -19,24 +19,36 @@ abstract contract Operators is BaseMiddleware { } } - function unregisterOperator(address operator) public checkAccess { + function unregisterOperator( + address operator + ) public checkAccess { _beforeUnregisterOperator(operator); _unregisterOperator(operator); } - function pauseOperator(address operator) public checkAccess { + function pauseOperator( + address operator + ) public checkAccess { _beforePauseOperator(operator); _pauseOperator(operator); } - function unpauseOperator(address operator) public checkAccess { + function unpauseOperator( + address operator + ) public checkAccess { _beforeUnpauseOperator(operator); _unpauseOperator(operator); } function _beforeRegisterOperator(address operator, bytes memory key, address vault) internal virtual {} function _beforeRegisterOperatorVault(address operator, address vault) internal virtual {} - function _beforeUnregisterOperator(address operator) internal virtual {} - function _beforePauseOperator(address operator) internal virtual {} - function _beforeUnpauseOperator(address operator) internal virtual {} + function _beforeUnregisterOperator( + address operator + ) internal virtual {} + function _beforePauseOperator( + address operator + ) internal virtual {} + function _beforeUnpauseOperator( + address operator + ) internal virtual {} } diff --git a/src/middleware/extensions/OzAccessManaged.sol b/src/middleware/extensions/OzAccessManaged.sol index 9d7fd9f..992f913 100644 --- a/src/middleware/extensions/OzAccessManaged.sol +++ b/src/middleware/extensions/OzAccessManaged.sol @@ -6,7 +6,9 @@ import {AccessManagedUpgradeable} from "@openzeppelin/contracts-upgradeable/acce import {BaseMiddleware} from "../BaseMiddleware.sol"; abstract contract OzAccessManaged is BaseMiddleware, AccessManagedUpgradeable { - constructor(address authority) { + constructor( + address authority + ) { __AccessManaged_init(authority); } diff --git a/src/middleware/extensions/SelfRegisterOperators.sol b/src/middleware/extensions/SelfRegisterOperators.sol index 9946526..17ef236 100644 --- a/src/middleware/extensions/SelfRegisterOperators.sol +++ b/src/middleware/extensions/SelfRegisterOperators.sol @@ -45,7 +45,13 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig { function _beforeUpdateOperatorKey(address operator, bytes memory key) internal virtual {} function _beforeRegisterOperator(address operator, bytes memory key, address vault) internal virtual {} - function _beforeUnregisterOperator(address operator) internal virtual {} - function _beforePauseOperator(address operator) internal virtual {} - function _beforeUnpauseOperator(address operator) internal virtual {} + function _beforeUnregisterOperator( + address operator + ) internal virtual {} + function _beforePauseOperator( + address operator + ) internal virtual {} + function _beforeUnpauseOperator( + address operator + ) internal virtual {} } diff --git a/src/middleware/extensions/SharedVaults.sol b/src/middleware/extensions/SharedVaults.sol index 77347ff..601e072 100644 --- a/src/middleware/extensions/SharedVaults.sol +++ b/src/middleware/extensions/SharedVaults.sol @@ -4,28 +4,44 @@ pragma solidity ^0.8.25; import {BaseMiddleware} from "../BaseMiddleware.sol"; abstract contract SharedVaults is BaseMiddleware { - function registerSharedVault(address sharedVault) public checkAccess { + function registerSharedVault( + address sharedVault + ) public checkAccess { _beforeRegisterSharedVault(sharedVault); _registerSharedVault(sharedVault); } - function pauseSharedVault(address sharedVault) public checkAccess { + function pauseSharedVault( + address sharedVault + ) public checkAccess { _beforePauseSharedVault(sharedVault); _pauseSharedVault(sharedVault); } - function unpauseSharedVault(address sharedVault) public checkAccess { + function unpauseSharedVault( + address sharedVault + ) public checkAccess { _beforeUnpauseSharedVault(sharedVault); _unpauseSharedVault(sharedVault); } - function unregisterSharedVault(address sharedVault) public checkAccess { + function unregisterSharedVault( + address sharedVault + ) public checkAccess { _beforeUnregisterSharedVault(sharedVault); _unregisterSharedVault(sharedVault); } - function _beforeRegisterSharedVault(address sharedVault) internal virtual {} - function _beforePauseSharedVault(address sharedVault) internal virtual {} - function _beforeUnpauseSharedVault(address sharedVault) internal virtual {} - function _beforeUnregisterSharedVault(address sharedVault) internal virtual {} + function _beforeRegisterSharedVault( + address sharedVault + ) internal virtual {} + function _beforePauseSharedVault( + address sharedVault + ) internal virtual {} + function _beforeUnpauseSharedVault( + address sharedVault + ) internal virtual {} + function _beforeUnregisterSharedVault( + address sharedVault + ) internal virtual {} } diff --git a/src/middleware/extensions/Subnetworks.sol b/src/middleware/extensions/Subnetworks.sol index 4250bf1..362b942 100644 --- a/src/middleware/extensions/Subnetworks.sol +++ b/src/middleware/extensions/Subnetworks.sol @@ -6,28 +6,44 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {BaseMiddleware} from "../BaseMiddleware.sol"; abstract contract Subnetworks is BaseMiddleware { - function registerSubnetwork(uint96 subnetwork) public checkAccess { + function registerSubnetwork( + uint96 subnetwork + ) public checkAccess { _beforeRegisterSubnetwork(subnetwork); _registerSubnetwork(subnetwork); } - function pauseSubnetwork(uint96 subnetwork) public checkAccess { + function pauseSubnetwork( + uint96 subnetwork + ) public checkAccess { _beforePauseSubnetwork(subnetwork); _pauseSubnetwork(subnetwork); } - function unpauseSubnetwork(uint96 subnetwork) public checkAccess { + function unpauseSubnetwork( + uint96 subnetwork + ) public checkAccess { _beforeUnpauseSubnetwork(subnetwork); _unpauseSubnetwork(subnetwork); } - function unregisterSubnetwork(uint96 subnetwork) public checkAccess { + function unregisterSubnetwork( + uint96 subnetwork + ) public checkAccess { _beforeUnregisterSubnetwork(subnetwork); _unregisterSubnetwork(subnetwork); } - function _beforeRegisterSubnetwork(uint96 subnetwork) internal virtual {} - function _beforePauseSubnetwork(uint96 subnetwork) internal virtual {} - function _beforeUnpauseSubnetwork(uint96 subnetwork) internal virtual {} - function _beforeUnregisterSubnetwork(uint96 subnetwork) internal virtual {} + function _beforeRegisterSubnetwork( + uint96 subnetwork + ) internal virtual {} + function _beforePauseSubnetwork( + uint96 subnetwork + ) internal virtual {} + function _beforeUnpauseSubnetwork( + uint96 subnetwork + ) internal virtual {} + function _beforeUnregisterSubnetwork( + uint96 subnetwork + ) internal virtual {} } diff --git a/src/middleware/extensions/sigs/ECDSASig.sol b/src/middleware/extensions/sigs/ECDSASig.sol index 1e6d930..b86b0be 100644 --- a/src/middleware/extensions/sigs/ECDSASig.sol +++ b/src/middleware/extensions/sigs/ECDSASig.sol @@ -1,17 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {BaseSig} from "./BaseSig.sol"; abstract contract ECDSASig is BaseSig { - using ECDSA for bytes32; - function _verifyKeySignature(bytes memory key_, bytes memory signature) internal view override returns (bool) { bytes32 key = abi.decode(key_, (bytes32)); bytes32 hash = keccak256(abi.encodePacked(msg.sender, key)); - address signer = hash.recover(signature); + (uint8 v, bytes32 r, bytes32 s) = abi.decode(signature, (uint8, bytes32, bytes32)); + address signer = ecrecover(hash, v, r, s); address keyAddress = address(uint160(uint256(key))); - return signer == keyAddress; + return signer == keyAddress && signer != address(0); } } diff --git a/test/SigTests.t.sol b/test/SigTests.t.sol new file mode 100644 index 0000000..dce7992 --- /dev/null +++ b/test/SigTests.t.sol @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Test, Vm, console} from "forge-std/Test.sol"; +import {POCBaseTest} from "@symbiotic-test/POCBase.t.sol"; +import {SelfRegisterMiddleware} from "../src/examples/self-register-network/SelfRegisterMiddleware.sol"; +import {SelfRegisterEd25519Middleware} from "../src/examples/self-register-network/SelfRegisterEd25519Middleware.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; + +contract SigTests is POCBaseTest { + using ECDSA for bytes32; + using MessageHashUtils for bytes32; + + SelfRegisterMiddleware internal middleware; + SelfRegisterEd25519Middleware internal ed25519Middleware; + uint256 internal operatorPrivateKey; + address internal operator; + bytes32 internal operatorPublicKey; + string internal constant ED25519_TEST_DATA = "test/helpers/ed25519TestData.json"; + address internal ed25519Operator; + + function setUp() public override { + SYMBIOTIC_CORE_PROJECT_ROOT = "lib/core/"; + vm.warp(1_729_690_309); + super.setUp(); + + (operator, operatorPrivateKey) = makeAddrAndKey("operator"); + operatorPublicKey = bytes32(uint256(uint160(operator))); + + string memory json = vm.readFile(ED25519_TEST_DATA); + ed25519Operator = abi.decode(vm.parseJson(json, ".operator"), (address)); + + // Initialize both middlewares + middleware = new SelfRegisterMiddleware( + address(0x123), + address(operatorRegistry), + address(vaultFactory), + address(operatorNetworkOptInService), + owner, + 1200 // slashing window + ); + + ed25519Middleware = new SelfRegisterEd25519Middleware( + address(0x456), + address(operatorRegistry), + address(vaultFactory), + address(operatorNetworkOptInService), + owner, + 1200 // slashing window + ); + + _registerNetwork(address(0x123), address(middleware)); + _registerNetwork(address(0x456), address(ed25519Middleware)); + _registerOperator(operator); + _registerOperator(ed25519Operator); + _optInOperatorVault(vault1, operator); + _optInOperatorVault(vault1, ed25519Operator); + _optInOperatorNetwork(operator, address(0x123)); + _optInOperatorNetwork(ed25519Operator, address(0x456)); + } + + function testEd25519RegisterOperator() public { + string memory json = vm.readFile(ED25519_TEST_DATA); + bytes32 key = abi.decode(vm.parseJson(json, ".key"), (bytes32)); + bytes memory signature = abi.decode(vm.parseJson(json, ".signature"), (bytes)); + + // Register operator using Ed25519 signature + vm.prank(ed25519Operator); + ed25519Middleware.registerOperator(abi.encode(key), address(vault1), signature); + + // Verify operator is registered correctly + assertTrue(ed25519Middleware.isOperatorRegistered(ed25519Operator)); + + assertEq(abi.decode(ed25519Middleware.operatorKey(ed25519Operator), (bytes32)), bytes32(0)); + vm.warp(block.timestamp + 2); + assertEq(abi.decode(ed25519Middleware.operatorKey(ed25519Operator), (bytes32)), key); + } + + function testEd25519RegisterOperatorInvalidSignature() public { + string memory json = vm.readFile(ED25519_TEST_DATA); + bytes32 key = abi.decode(vm.parseJson(json, ".key"), (bytes32)); + + // Create invalid signature + bytes32 r = bytes32(uint256(1)); + bytes32 s = bytes32(uint256(2)); + bytes memory signature = abi.encodePacked(r, s); + + // Attempt to register with invalid signature should fail + vm.prank(ed25519Operator); + vm.expectRevert(); + ed25519Middleware.registerOperator(abi.encode(key), address(vault1), signature); + } + + function testEd25519RegisterOperatorWrongSender() public { + string memory json = vm.readFile(ED25519_TEST_DATA); + bytes32 key = abi.decode(vm.parseJson(json, ".key"), (bytes32)); + bytes memory signature = abi.decode(vm.parseJson(json, ".signature"), (bytes)); + bytes32 r; + bytes32 s; + assembly { + r := mload(add(signature, 32)) + s := mload(add(signature, 64)) + } + + // Attempt to register from different address should fail + vm.prank(alice); + vm.expectRevert(); + ed25519Middleware.registerOperator(abi.encode(key), address(vault1), abi.encodePacked(r, s)); + } + + function testSelfRegisterOperator() public { + // Create registration message + bytes32 messageHash = keccak256(abi.encodePacked(operator, operatorPublicKey)); + // Sign message with operator's private key + (uint8 v, bytes32 r, bytes32 s) = vm.sign(operatorPrivateKey, messageHash); + bytes memory signature = abi.encode(v, r, s); + // Register operator using their own signature + vm.prank(operator); + middleware.registerOperator(abi.encode(operatorPublicKey), address(vault1), signature); + + // Verify operator is registered correctly + assertTrue(middleware.isOperatorRegistered(operator)); + + assertEq(abi.decode(middleware.operatorKey(operator), (bytes32)), bytes32(0)); + vm.warp(block.timestamp + 2); + assertEq(abi.decode(middleware.operatorKey(operator), (bytes32)), operatorPublicKey); + } + + function testSelfRegisterOperatorInvalidSignature() public { + // Create registration message with wrong key + bytes32 wrongKey = bytes32(uint256(1)); + bytes32 messageHash = keccak256(abi.encodePacked(operator, wrongKey)); + // Sign message + (uint8 v, bytes32 r, bytes32 s) = vm.sign(operatorPrivateKey, messageHash); + bytes memory signature = abi.encode(v, r, s); + + // Attempt to register with mismatched key should fail + vm.prank(operator); + vm.expectRevert(); + middleware.registerOperator(abi.encode(operatorPublicKey), address(vault1), signature); + } + + function testSelfRegisterOperatorWrongSender() public { + // Create valid registration message + bytes32 messageHash = keccak256(abi.encodePacked(operator, operatorPublicKey)); + // Sign message with operator's key + (uint8 v, bytes32 r, bytes32 s) = vm.sign(operatorPrivateKey, messageHash); + bytes memory signature = abi.encode(v, r, s); + + // Attempt to register from different address should fail + vm.prank(alice); + vm.expectRevert(); + middleware.registerOperator(abi.encode(operatorPublicKey), address(vault1), signature); + } + + function testSelfRegisterOperatorAlreadyRegistered() public { + // Create registration message + bytes32 messageHash = keccak256(abi.encodePacked(operator, operatorPublicKey)); + // Sign message + (uint8 v, bytes32 r, bytes32 s) = vm.sign(operatorPrivateKey, messageHash); + bytes memory signature = abi.encode(v, r, s); + // Register operator first time + vm.prank(operator); + middleware.registerOperator(abi.encode(operatorPublicKey), address(vault1), signature); + + // Attempt to register again should fail + vm.prank(operator); + vm.expectRevert(); + middleware.registerOperator(abi.encode(operatorPublicKey), address(vault1), signature); + } + + function testEd25519RegisterOperatorMismatchedKeyAndSignature() public { + string memory json = vm.readFile(ED25519_TEST_DATA); + bytes32 key = abi.decode(vm.parseJson(json, ".invalidKey"), (bytes32)); + bytes memory signature = abi.decode(vm.parseJson(json, ".invalidSignature"), (bytes)); + + // Attempt to register with mismatched key and signature should fail + vm.prank(ed25519Operator); + vm.expectRevert(); + ed25519Middleware.registerOperator(abi.encode(key), address(vault1), signature); + } +} diff --git a/test/helpers/ed25519TestData.json b/test/helpers/ed25519TestData.json new file mode 100644 index 0000000..f09a97a --- /dev/null +++ b/test/helpers/ed25519TestData.json @@ -0,0 +1,7 @@ +{ + "operator": "0xc2c1697Fe88772f844D1b622F4fc0E6E0b16Cb77", + "key": "0x121516a0d84c94a28d9f9cd2263d3fe8eceee3cef43aa043e388cce914f85205", + "signature": "0x5d590ecf3f7019846aba3c363ddb8f640109f9fa35a5d8ce027407e8d4507045b651075cfbecbb86ede90a4f20ac99fa969419c81ee4d7066868bfcd5e47d50d", + "invalidKey": "0x98c6edf296f06a1b23e56d56020c0ea8289d07a8b0a23e953b24239697cf5a96", + "invalidSignature": "0x8f3d84ae06d3d912328d6c5c97622d880c2de9255d50c697314fc5f61ce0d211b8603ac2c3287ed607364d8cd1e89675a1f3e0892c11ddc7116cf92221b63600" +} \ No newline at end of file diff --git a/test/helpers/ed25519TestGenerator.js b/test/helpers/ed25519TestGenerator.js new file mode 100644 index 0000000..7096f75 --- /dev/null +++ b/test/helpers/ed25519TestGenerator.js @@ -0,0 +1,107 @@ +const fs = require('fs'); +const { ed25519 } = require('@noble/curves/ed25519'); +const { bytesToHex, hexToBytes } = require('@noble/curves/abstract/utils'); +const { keccak256 } = require('ethereum-cryptography/keccak'); +const { ethers } = require('ethers'); + +// Generate random operator address +function generateOperatorAddress() { + const wallet = ethers.Wallet.createRandom(); + return wallet.address; +} + +// Generate Ed25519 keypair and signature +function generateTestData(operatorAddress) { + // Generate keypair + const privateKey = ed25519.utils.randomPrivateKey(); + const publicKey = ed25519.getPublicKey(privateKey); + + // Create message hash as done in the contract + let message = keccak256( + Buffer.concat([ + Buffer.from(operatorAddress.replace('0x', ''), 'hex'), + publicKey, + ]) + ); + + // Add 9 bytes of zeros to the message + message = Buffer.concat([ + message, + Buffer.alloc(9) + ]); + + // Sign the message + const signature = "0x" + bytesToHex(ed25519.sign(message, privateKey)); + + // Verify the signature + const isValid = ed25519.verify( + hexToBytes(signature.slice(2)), // Remove 0x prefix + message, + publicKey + ); + + if (!isValid) { + throw new Error('Generated signature failed verification'); + } + + // Return ABI encoded data + const abiCoder = new ethers.AbiCoder(); + const key = abiCoder.encode(['bytes32'], ['0x' + bytesToHex(publicKey)]); + + return { + operatorAddress, + key, + signature: signature + }; +} + +// Generate invalid test data with mismatched key and signature +function generateInvalidTestData(operatorAddress) { + // Generate two different keypairs + const privateKey1 = ed25519.utils.randomPrivateKey(); + const publicKey1 = ed25519.getPublicKey(privateKey1); + const privateKey2 = ed25519.utils.randomPrivateKey(); + const publicKey2 = ed25519.getPublicKey(privateKey2); + + // Create message hash with publicKey1 + let message = keccak256( + Buffer.concat([ + Buffer.from(operatorAddress.replace('0x', ''), 'hex'), + publicKey1, + ]) + ); + + message = Buffer.concat([ + message, + Buffer.alloc(9) + ]); + + // Sign with privateKey2 (mismatch) + const signature = "0x" + bytesToHex(ed25519.sign(message, privateKey2)); + + // ABI encode publicKey1 + const abiCoder = new ethers.AbiCoder(); + const key = abiCoder.encode(['bytes32'], ['0x' + bytesToHex(publicKey1)]); + + return { + operatorAddress, + key, + signature: signature + }; +} + +// Generate both valid and invalid test data +const operatorAddress = generateOperatorAddress(); +const validTestData = generateTestData(operatorAddress); +const invalidTestData = generateInvalidTestData(operatorAddress); + +// Write data to file +fs.writeFileSync('test/helpers/ed25519TestData.json', + JSON.stringify({ + operator: validTestData.operatorAddress, + key: validTestData.key, + signature: validTestData.signature, + invalidKey: invalidTestData.key, + invalidSignature: invalidTestData.signature + }, null, 2) +); \ No newline at end of file