From 5703b25c314521ec882f818424bd2457e5571190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crist=C3=B3v=C3=A3o=20Honorato?= Date: Thu, 6 Jun 2024 20:20:57 +0200 Subject: [PATCH 1/7] Still broken wip, first crack at committee design --- packages/evm/contracts/Enclave.sol | 8 +- .../interfaces/ICommitteeCoordinator.sol | 32 ++++ .../interfaces/ICyphernodeRegistry.sol | 38 ++--- .../evm/contracts/interfaces/IEnclave.sol | 8 +- .../evm/contracts/interfaces/INodePool.sol | 7 - .../registry/CyphernodeRegistryOwnable.sol | 78 ++++------ .../registry/NaiveCommitteeCoordinator.sol | 142 ++++++++++++++++++ .../contracts/test/MockCyphernodeRegistry.sol | 26 ++-- 8 files changed, 241 insertions(+), 98 deletions(-) create mode 100644 packages/evm/contracts/interfaces/ICommitteeCoordinator.sol delete mode 100644 packages/evm/contracts/interfaces/INodePool.sol create mode 100644 packages/evm/contracts/registry/NaiveCommitteeCoordinator.sol diff --git a/packages/evm/contracts/Enclave.sol b/packages/evm/contracts/Enclave.sol index 644cc14b..74d0b20f 100644 --- a/packages/evm/contracts/Enclave.sol +++ b/packages/evm/contracts/Enclave.sol @@ -106,7 +106,7 @@ contract Enclave is IEnclave, OwnableUpgradeable { //////////////////////////////////////////////////////////// function request( - address[] memory pools, // TODO: should we allow for multiple pools? + address filter, uint32[2] calldata threshold, // TODO: do we also need a start block/time? Would it be possible to have computations where inputs are //published before the request is made? This kind of assumes the cypher nodes have already been selected @@ -169,7 +169,7 @@ contract Enclave is IEnclave, OwnableUpgradeable { e3s[e3Id] = e3; require( - cyphernodeRegistry.selectCommittee(e3Id, pools, threshold), + cyphernodeRegistry.requestCommittee(e3Id, filter, threshold), CommitteeSelectionFailed() ); // TODO: validate that the selected pool accepts both the computation and execution modules. @@ -177,7 +177,7 @@ contract Enclave is IEnclave, OwnableUpgradeable { emit E3Requested( e3Id, e3s[e3Id], - pools, + filter, computationModule, executionModule ); @@ -190,7 +190,7 @@ contract Enclave is IEnclave, OwnableUpgradeable { require(e3.expiration == 0, E3AlreadyActivated(e3Id)); bytes memory committeePublicKey = cyphernodeRegistry - .getCommitteePublicKey(e3Id); + .committeePublicKey(e3Id); // Note: This check feels weird require(committeePublicKey.length > 0, CommitteeSelectionFailed()); diff --git a/packages/evm/contracts/interfaces/ICommitteeCoordinator.sol b/packages/evm/contracts/interfaces/ICommitteeCoordinator.sol new file mode 100644 index 00000000..8177745f --- /dev/null +++ b/packages/evm/contracts/interfaces/ICommitteeCoordinator.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.26; + +interface ICommitteeCoordinator { + /// @notice This event MUST be emitted when a committee is selected for an E3. + /// @param e3Id ID of the E3 for which the committee was selected. + /// @param threshold The M/N threshold for the committee. + event CommitteeRequested(uint256 indexed e3Id, uint32[2] threshold); + + /// @notice This event MUST be emitted when a committee is selected for an E3. + /// @param e3Id ID of the E3 for which the committee was selected. + /// @param publicKey Public key of the committee. + event CommitteeAssembled(uint256 indexed e3Id, bytes publicKey); + + /// @notice This function should be called by the Enclave contract to select a node committee. + /// @param e3Id ID of the E3 for which to select the committee. + /// @param threshold The M/N threshold for the committee. + /// @return success True if committee selection was successfully initiated. + function requestCommittee( + uint256 e3Id, + uint32[2] calldata threshold + ) external returns (bool success); + + /// @notice This function should be called by the Enclave contract to get the public key of a committee. + /// @dev This function MUST revert if no committee has been requested for the given E3. + /// @dev This function MUST revert if the committee has not yet published a public key. + /// @param e3Id ID of the E3 for which to get the committee public key. + /// @return publicKey The public key of the committee. + function committeePublicKey( + uint256 e3Id + ) external view returns (bytes memory); +} diff --git a/packages/evm/contracts/interfaces/ICyphernodeRegistry.sol b/packages/evm/contracts/interfaces/ICyphernodeRegistry.sol index f69a1d28..3d6da67e 100644 --- a/packages/evm/contracts/interfaces/ICyphernodeRegistry.sol +++ b/packages/evm/contracts/interfaces/ICyphernodeRegistry.sol @@ -4,48 +4,40 @@ pragma solidity >=0.8.26; interface ICyphernodeRegistry { /// @notice This event MUST be emitted when a committee is selected for an E3. /// @param e3Id ID of the E3 for which the committee was selected. - /// @param pools Addresses of the pools of nodes from which the committee was selected. + /// @param filter Address of the contract that will coordinate committee selection. /// @param threshold The M/N threshold for the committee. event CommitteeRequested( uint256 indexed e3Id, - address[] pools, + address filter, uint32[2] threshold ); /// @notice This event MUST be emitted when a committee is selected for an E3. /// @param e3Id ID of the E3 for which the committee was selected. - /// @param nodes Addresses of the nodes in the committee. - /// @param merkleRoots Merkle roots of the nodes in the committee. /// @param publicKey Public key of the committee. - event CommitteeSelected( - uint256 indexed e3Id, - address[] nodes, - bytes32[] merkleRoots, - bytes publicKey - ); + event CommitteeSelected(uint256 indexed e3Id, bytes publicKey); - /// @notice This event MUST be emitted when a node is added to the registry. - /// @param nodeId ID of the node. - /// @param node Address of the node. - event NodeAdded(uint256 indexed nodeId, address indexed node); + /// @notice This event MUST be emitted when a filter is added to the registry. + event FilterAdded(address indexed filter); - /// @notice This event MUST be emitted when a node is removed from the registry. - /// @param nodeId ID of the node. - /// @param node Address of the node. - event NodeRemoved(uint256 indexed nodeId, address indexed node); + /// @notice This event MUST be emitted when a filter is removed from the registry. + event FilterRemoved(address indexed filter); /// @notice This event MUST be emitted when `encalve` is set. /// @param enclave Address of the enclave contract. event EnclaveSet(address indexed enclave); + function addFilter(address filter) external; + + function removeFilter(address filter) external; + /// @notice This function should be called by the Enclave contract to select a node committee. /// @param e3Id ID of the E3 for which to select the committee. - /// @param pools IDs of the pool of nodes from which to select the committee. /// @param threshold The M/N threshold for the committee. /// @return success True if committee selection was successfully initiated. - function selectCommittee( + function requestCommittee( uint256 e3Id, - address[] memory pools, + address filter, uint32[2] calldata threshold ) external returns (bool success); @@ -54,7 +46,7 @@ interface ICyphernodeRegistry { /// @dev This function MUST revert if the committee has not yet published a public key. /// @param e3Id ID of the E3 for which to get the committee public key. /// @return publicKey The public key of the committee. - function getCommitteePublicKey( + function committeePublicKey( uint256 e3Id - ) external view returns (bytes memory publicKey); + ) external view returns (bytes memory); } diff --git a/packages/evm/contracts/interfaces/IEnclave.sol b/packages/evm/contracts/interfaces/IEnclave.sol index bf7e4f5e..d1b48177 100644 --- a/packages/evm/contracts/interfaces/IEnclave.sol +++ b/packages/evm/contracts/interfaces/IEnclave.sol @@ -13,13 +13,13 @@ interface IEnclave { /// @notice This event MUST be emitted when an Encrypted Execution Environment (E3) is successfully requested. /// @param e3Id ID of the E3. /// @param e3 Details of the E3. - /// @param pool Address of the pool of nodes from which the Cypher Node committee was selected. + /// @param filter Address of the pool of nodes from which the Cypher Node committee was selected. /// @param computationModule Address of the Computation module selected. /// @param executionModule Address of the execution module selected. event E3Requested( uint256 e3Id, E3 e3, - address[] pool, + address filter, IComputationModule indexed computationModule, IExecutionModule indexed executionModule ); @@ -87,7 +87,7 @@ interface IEnclave { /// @notice This function should be called to request a computation within an Encrypted Execution Environment (E3). /// @dev This function MUST emit the E3Requested event. - /// @param pools IDs of the pool of nodes from which to select the committee. + /// @param filter IDs of the pool of nodes from which to select the committee. /// @param threshold The M/N threshold for the committee. /// @param duration The duration of the computation in seconds. /// @param computationModule Address of the computation module. @@ -97,7 +97,7 @@ interface IEnclave { /// @return e3Id ID of the E3. /// @return e3 The E3 struct. function request( - address[] memory pools, + address filter, uint32[2] calldata threshold, uint256 duration, IComputationModule computationModule, diff --git a/packages/evm/contracts/interfaces/INodePool.sol b/packages/evm/contracts/interfaces/INodePool.sol deleted file mode 100644 index bf1a9100..00000000 --- a/packages/evm/contracts/interfaces/INodePool.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity >=0.8.26; - -interface INodePool { - /// @notice This function MUST return the Merkle root of this contract's curated pool of nodes. - function merkleRoot() external returns (bytes32 merkleRoot); -} diff --git a/packages/evm/contracts/registry/CyphernodeRegistryOwnable.sol b/packages/evm/contracts/registry/CyphernodeRegistryOwnable.sol index 69a6ec24..b84e215d 100644 --- a/packages/evm/contracts/registry/CyphernodeRegistryOwnable.sol +++ b/packages/evm/contracts/registry/CyphernodeRegistryOwnable.sol @@ -2,26 +2,12 @@ pragma solidity >=0.8.26; import { ICyphernodeRegistry } from "../interfaces/ICyphernodeRegistry.sol"; +import { ICommitteeCoordinator } from "../interfaces/ICommitteeCoordinator.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; contract CyphernodeRegistryOwnable is ICyphernodeRegistry, OwnableUpgradeable { - struct Committee { - address[] nodes; - uint32[2] threshold; - address[] pools; - bytes32[] merkleRoots; - bytes publicKey; - } - - struct Node { - bool eligible; - // Number of duties the node has not yet completed. - // Incremented each time a duty is added, decremented each time a duty is completed. - uint256 outstandingDuties; - } - //////////////////////////////////////////////////////////// // // // Storage Variables // @@ -30,8 +16,9 @@ contract CyphernodeRegistryOwnable is ICyphernodeRegistry, OwnableUpgradeable { address public enclave; - mapping(uint256 e3 => Committee committee) public committees; - mapping(address nodeId => Node node) public nodes; + mapping(address filter => bool enabled) public filters; + + mapping(uint256 e3 => ICommitteeCoordinator coordinator) public requests; //////////////////////////////////////////////////////////// // // @@ -39,6 +26,7 @@ contract CyphernodeRegistryOwnable is ICyphernodeRegistry, OwnableUpgradeable { // // //////////////////////////////////////////////////////////// + error CommitteeAlreadyRequested(); error CommitteeAlreadyExists(); error CommitteeAlreadyPublished(); error CommitteeDoesNotExist(); @@ -78,36 +66,21 @@ contract CyphernodeRegistryOwnable is ICyphernodeRegistry, OwnableUpgradeable { // // //////////////////////////////////////////////////////////// - function selectCommittee( + function requestCommittee( uint256 e3Id, - address[] memory pools, + address filter, uint32[2] calldata threshold ) external onlyEnclave returns (bool success) { - Committee storage committee = committees[e3Id]; - require(committee.threshold.length == 0, CommitteeAlreadyExists()); - committee.threshold = threshold; - committee.pools = pools; - success = true; - - emit CommitteeRequested(e3Id, pools, threshold); - } - - function publishCommittee( - uint256 e3Id, - address[] memory _nodes, - bytes32[] memory merkleRoots, - bytes memory publicKey - ) external onlyOwner { - Committee storage committee = committees[e3Id]; require( - keccak256(committee.publicKey) == keccak256(hex""), - CommitteeAlreadyPublished() + requests[e3Id] == ICommitteeCoordinator(address(0)), + CommitteeAlreadyRequested() ); - committee.nodes = _nodes; - committee.merkleRoots = merkleRoots; - committee.publicKey = publicKey; + requests[e3Id] = ICommitteeCoordinator(filter); - emit CommitteeSelected(e3Id, _nodes, merkleRoots, publicKey); + ICommitteeCoordinator(filter).requestCommittee(e3Id, threshold); + + emit CommitteeRequested(e3Id, filter, threshold); + success = true; } //////////////////////////////////////////////////////////// @@ -121,23 +94,28 @@ contract CyphernodeRegistryOwnable is ICyphernodeRegistry, OwnableUpgradeable { emit EnclaveSet(_enclave); } + function addFilter(address filter) external { + filters[filter] = true; + emit FilterAdded(filter); + } + + function removeFilter(address filter) external onlyOwner { + filters[filter] = false; + emit FilterRemoved(filter); + } + //////////////////////////////////////////////////////////// // // // Get Functions // // // //////////////////////////////////////////////////////////// - function getCommitteePublicKey( + function committeePublicKey( uint256 e3Id ) external view returns (bytes memory publicKey) { - publicKey = committees[e3Id].publicKey; - require(publicKey.length > 0, NoPublicKeyPublished()); - } + require(requests[e3Id] != ICommitteeCoordinator(address(0))); - function getCommittee( - uint256 e3Id - ) external view returns (Committee memory committee) { - committee = committees[e3Id]; - require(committees[e3Id].threshold.length > 0, CommitteeDoesNotExist()); + publicKey = requests[e3Id].committeePublicKey(e3Id); + require(publicKey.length > 0, NoPublicKeyPublished()); } } diff --git a/packages/evm/contracts/registry/NaiveCommitteeCoordinator.sol b/packages/evm/contracts/registry/NaiveCommitteeCoordinator.sol new file mode 100644 index 00000000..1a6e2031 --- /dev/null +++ b/packages/evm/contracts/registry/NaiveCommitteeCoordinator.sol @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.26; + +import { ICommitteeCoordinator } from "../interfaces/ICommitteeCoordinator.sol"; +import { + OwnableUpgradeable +} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract NaiveCommitteeCoordinator is + ICommitteeCoordinator, + OwnableUpgradeable +{ + struct Committee { + address[] nodes; + uint32[2] threshold; + bytes publicKey; + } + + struct Node { + bool eligible; + // Number of duties the node has not yet completed. + // Incremented each time a duty is added, decremented each time a duty is completed. + uint256 outstandingDuties; + } + + //////////////////////////////////////////////////////////// + // // + // Storage Variables // + // // + //////////////////////////////////////////////////////////// + + address public registry; + + mapping(uint256 e3 => Committee committee) public committees; + + //////////////////////////////////////////////////////////// + // // + // Errors // + // // + //////////////////////////////////////////////////////////// + + error CommitteeAlreadyExists(); + error CommitteeAlreadyPublished(); + error CommitteeDoesNotExist(); + error NoPublicKeyPublished(); + error OnlyRegistry(); + + //////////////////////////////////////////////////////////// + // // + // Modifiers // + // // + //////////////////////////////////////////////////////////// + + modifier onlyRegistry() { + require(msg.sender == registry, OnlyRegistry()); + _; + } + + //////////////////////////////////////////////////////////// + // // + // Initialization // + // // + //////////////////////////////////////////////////////////// + + constructor(address _owner, address _enclave) { + initialize(_owner, _enclave); + } + + function initialize(address _owner, address _registry) public initializer { + __Ownable_init(msg.sender); + setRegistry(_registry); + transferOwnership(_owner); + } + + //////////////////////////////////////////////////////////// + // // + // Core Entrypoints // + // // + //////////////////////////////////////////////////////////// + + function requestCommittee( + uint256 e3Id, + uint32[2] calldata threshold + ) external onlyRegistry returns (bool success) { + Committee storage committee = committees[e3Id]; + require(committee.threshold.length == 0, CommitteeAlreadyExists()); + committee.threshold = threshold; + success = true; + } + + function publishCommittee( + uint256 e3Id, + address[] memory _nodes, + bytes memory publicKey + ) external onlyOwner { + Committee storage committee = committees[e3Id]; + require( + keccak256(committee.publicKey) == keccak256(hex""), + CommitteeAlreadyPublished() + ); + committee.nodes = _nodes; + committee.publicKey = publicKey; + } + + //////////////////////////////////////////////////////////// + // // + // Set Functions // + // // + //////////////////////////////////////////////////////////// + + function setRegistry(address _registry) public onlyOwner { + registry = _registry; + } + + //////////////////////////////////////////////////////////// + // // + // Get Functions // + // // + //////////////////////////////////////////////////////////// + + function committeePublicKey( + uint256 e3Id + ) external view returns (bytes memory publicKey) { + publicKey = committees[e3Id].publicKey; + require(publicKey.length > 0, NoPublicKeyPublished()); + } + + /* + * This naive registry does make store and make nodes available on chain + * other more sophisticated registry may keep these on an off-chain tree + * and store the root + * + * The idea is to not include this speficics on the main interfaces for + * enclave for now + */ + function getCommittee( + uint256 e3Id + ) external view returns (Committee memory committee) { + committee = committees[e3Id]; + require(committees[e3Id].threshold.length > 0, CommitteeDoesNotExist()); + } +} diff --git a/packages/evm/contracts/test/MockCyphernodeRegistry.sol b/packages/evm/contracts/test/MockCyphernodeRegistry.sol index 484eae7e..8da724d8 100644 --- a/packages/evm/contracts/test/MockCyphernodeRegistry.sol +++ b/packages/evm/contracts/test/MockCyphernodeRegistry.sol @@ -4,19 +4,19 @@ pragma solidity >=0.8.26; import { ICyphernodeRegistry } from "../interfaces/ICyphernodeRegistry.sol"; contract MockCyphernodeRegistry is ICyphernodeRegistry { - function selectCommittee( + function requestCommittee( uint256, - address[] memory pools, + address filter, uint32[2] calldata ) external pure returns (bool success) { - if (pools[0] == address(2)) { + if (filter == address(2)) { success = false; } else { success = true; } } - function getCommitteePublicKey( + function committeePublicKey( uint256 e3Id ) external pure returns (bytes memory) { if (e3Id == type(uint256).max) { @@ -25,24 +25,30 @@ contract MockCyphernodeRegistry is ICyphernodeRegistry { return abi.encodePacked(keccak256(abi.encode(e3Id))); } } + + function addFilter(address filter) external {} + + function removeFilter(address filter) external {} } contract MockCyphernodeRegistryEmptyKey is ICyphernodeRegistry { - function selectCommittee( + function requestCommittee( uint256, - address[] memory pools, + address filter, uint32[2] calldata ) external pure returns (bool success) { - if (pools[0] == address(2)) { + if (filter == address(2)) { success = false; } else { success = true; } } - function getCommitteePublicKey( - uint256 - ) external pure returns (bytes memory) { + function committeePublicKey(uint256) external pure returns (bytes memory) { return hex""; } + + function addFilter(address filter) external {} + + function removeFilter(address filter) external {} } From bfe05f785d18b723fe3a932986919e65851a90db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crist=C3=B3v=C3=A3o=20Honorato?= Date: Sat, 8 Jun 2024 01:00:02 +0200 Subject: [PATCH 2/7] Rename Coordinator to Filter. Reintroduce Cyphernode status tracking at registry level --- packages/evm/contracts/Enclave.sol | 7 +-- .../interfaces/ICommitteeCoordinator.sol | 32 ---------- .../interfaces/ICyphernodeRegistry.sol | 24 ++++---- .../contracts/interfaces/IRegistryFilter.sol | 28 +++++++++ .../registry/CyphernodeRegistryOwnable.sol | 59 +++++++++++++------ ...oordinator.sol => NaiveRegistryFilter.sol} | 50 ++++++++-------- .../contracts/test/MockCyphernodeRegistry.sol | 12 ++-- 7 files changed, 115 insertions(+), 97 deletions(-) delete mode 100644 packages/evm/contracts/interfaces/ICommitteeCoordinator.sol create mode 100644 packages/evm/contracts/interfaces/IRegistryFilter.sol rename packages/evm/contracts/registry/{NaiveCommitteeCoordinator.sol => NaiveRegistryFilter.sol} (82%) diff --git a/packages/evm/contracts/Enclave.sol b/packages/evm/contracts/Enclave.sol index 74d0b20f..a4f9a475 100644 --- a/packages/evm/contracts/Enclave.sol +++ b/packages/evm/contracts/Enclave.sol @@ -189,14 +189,13 @@ contract Enclave is IEnclave, OwnableUpgradeable { E3 memory e3 = getE3(e3Id); require(e3.expiration == 0, E3AlreadyActivated(e3Id)); - bytes memory committeePublicKey = cyphernodeRegistry - .committeePublicKey(e3Id); + bytes memory publicKey = cyphernodeRegistry.committeePublicKey(e3Id); // Note: This check feels weird - require(committeePublicKey.length > 0, CommitteeSelectionFailed()); + require(publicKey.length > 0, CommitteeSelectionFailed()); // TODO: this should be based on the duration requested, not the current max duration. e3s[e3Id].expiration = block.timestamp + maxDuration; - e3s[e3Id].committeePublicKey = committeePublicKey; + e3s[e3Id].committeePublicKey = publicKey; emit E3Activated(e3Id, e3.expiration, e3.committeePublicKey); diff --git a/packages/evm/contracts/interfaces/ICommitteeCoordinator.sol b/packages/evm/contracts/interfaces/ICommitteeCoordinator.sol deleted file mode 100644 index 8177745f..00000000 --- a/packages/evm/contracts/interfaces/ICommitteeCoordinator.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity >=0.8.26; - -interface ICommitteeCoordinator { - /// @notice This event MUST be emitted when a committee is selected for an E3. - /// @param e3Id ID of the E3 for which the committee was selected. - /// @param threshold The M/N threshold for the committee. - event CommitteeRequested(uint256 indexed e3Id, uint32[2] threshold); - - /// @notice This event MUST be emitted when a committee is selected for an E3. - /// @param e3Id ID of the E3 for which the committee was selected. - /// @param publicKey Public key of the committee. - event CommitteeAssembled(uint256 indexed e3Id, bytes publicKey); - - /// @notice This function should be called by the Enclave contract to select a node committee. - /// @param e3Id ID of the E3 for which to select the committee. - /// @param threshold The M/N threshold for the committee. - /// @return success True if committee selection was successfully initiated. - function requestCommittee( - uint256 e3Id, - uint32[2] calldata threshold - ) external returns (bool success); - - /// @notice This function should be called by the Enclave contract to get the public key of a committee. - /// @dev This function MUST revert if no committee has been requested for the given E3. - /// @dev This function MUST revert if the committee has not yet published a public key. - /// @param e3Id ID of the E3 for which to get the committee public key. - /// @return publicKey The public key of the committee. - function committeePublicKey( - uint256 e3Id - ) external view returns (bytes memory); -} diff --git a/packages/evm/contracts/interfaces/ICyphernodeRegistry.sol b/packages/evm/contracts/interfaces/ICyphernodeRegistry.sol index 3d6da67e..23d0618e 100644 --- a/packages/evm/contracts/interfaces/ICyphernodeRegistry.sol +++ b/packages/evm/contracts/interfaces/ICyphernodeRegistry.sol @@ -12,24 +12,22 @@ interface ICyphernodeRegistry { uint32[2] threshold ); - /// @notice This event MUST be emitted when a committee is selected for an E3. - /// @param e3Id ID of the E3 for which the committee was selected. - /// @param publicKey Public key of the committee. - event CommitteeSelected(uint256 indexed e3Id, bytes publicKey); - - /// @notice This event MUST be emitted when a filter is added to the registry. - event FilterAdded(address indexed filter); - - /// @notice This event MUST be emitted when a filter is removed from the registry. - event FilterRemoved(address indexed filter); - /// @notice This event MUST be emitted when `encalve` is set. /// @param enclave Address of the enclave contract. event EnclaveSet(address indexed enclave); - function addFilter(address filter) external; + /// @notice This event MUST be emitted when a cyphernode is added to the registry. + event CyphernodeAdded(address indexed node); + + /// @notice This event MUST be emitted when a cyphernode is removed from the registry. + event CyphernodeRemoved(address indexed node); + + /// @notice This event MUST be emitted when a committee is selected for an E3. + /// @param e3Id ID of the E3 for which the committee was selected. + /// @param publicKey Public key of the committee. + event CommitteeSelected(uint256 indexed e3Id, bytes publicKey); - function removeFilter(address filter) external; + function isCyphernodeEnabled(address ciphernode) external returns (bool); /// @notice This function should be called by the Enclave contract to select a node committee. /// @param e3Id ID of the E3 for which to select the committee. diff --git a/packages/evm/contracts/interfaces/IRegistryFilter.sol b/packages/evm/contracts/interfaces/IRegistryFilter.sol new file mode 100644 index 00000000..a8b70318 --- /dev/null +++ b/packages/evm/contracts/interfaces/IRegistryFilter.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.26; + +interface IRegistryFilter { + event CommitteeRequested(uint256 indexed e3Id, uint32[2] threshold); + + event CommitteeCreated( + uint256 indexed e3Id, + bytes publicKey, + address[] cyphernodes + ); + + function requestCommittee( + uint256 e3Id, + uint32[2] calldata threshold + ) external returns (bool success); + + function retrieveCommittee( + uint256 e3Id + ) + external + view + returns ( + uint32[2] memory threshold, + bytes memory publicKey, + address[] memory cyphernodes + ); +} diff --git a/packages/evm/contracts/registry/CyphernodeRegistryOwnable.sol b/packages/evm/contracts/registry/CyphernodeRegistryOwnable.sol index b84e215d..e56fd57a 100644 --- a/packages/evm/contracts/registry/CyphernodeRegistryOwnable.sol +++ b/packages/evm/contracts/registry/CyphernodeRegistryOwnable.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.26; import { ICyphernodeRegistry } from "../interfaces/ICyphernodeRegistry.sol"; -import { ICommitteeCoordinator } from "../interfaces/ICommitteeCoordinator.sol"; +import { IRegistryFilter } from "../interfaces/IRegistryFilter.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; @@ -16,9 +16,9 @@ contract CyphernodeRegistryOwnable is ICyphernodeRegistry, OwnableUpgradeable { address public enclave; - mapping(address filter => bool enabled) public filters; + mapping(address cyphernode => bool isEnabled) public isEnabled; - mapping(uint256 e3 => ICommitteeCoordinator coordinator) public requests; + mapping(uint256 e3Id => IRegistryFilter filter) public requests; //////////////////////////////////////////////////////////// // // @@ -30,7 +30,8 @@ contract CyphernodeRegistryOwnable is ICyphernodeRegistry, OwnableUpgradeable { error CommitteeAlreadyExists(); error CommitteeAlreadyPublished(); error CommitteeDoesNotExist(); - error NoPublicKeyPublished(); + error CommitteeInvalid(); + error CyphernodeNotEnabled(address node); error OnlyEnclave(); //////////////////////////////////////////////////////////// @@ -72,13 +73,12 @@ contract CyphernodeRegistryOwnable is ICyphernodeRegistry, OwnableUpgradeable { uint32[2] calldata threshold ) external onlyEnclave returns (bool success) { require( - requests[e3Id] == ICommitteeCoordinator(address(0)), + requests[e3Id] == IRegistryFilter(address(0)), CommitteeAlreadyRequested() ); - requests[e3Id] = ICommitteeCoordinator(filter); - - ICommitteeCoordinator(filter).requestCommittee(e3Id, threshold); + requests[e3Id] = IRegistryFilter(filter); + IRegistryFilter(filter).requestCommittee(e3Id, threshold); emit CommitteeRequested(e3Id, filter, threshold); success = true; } @@ -94,14 +94,18 @@ contract CyphernodeRegistryOwnable is ICyphernodeRegistry, OwnableUpgradeable { emit EnclaveSet(_enclave); } - function addFilter(address filter) external { - filters[filter] = true; - emit FilterAdded(filter); + function addCyphernode(address node) external onlyOwner { + isEnabled[node] = true; + emit CyphernodeAdded(node); + } + + function removeCyphernode(address node) external onlyOwner { + isEnabled[node] = false; + emit CyphernodeRemoved(node); } - function removeFilter(address filter) external onlyOwner { - filters[filter] = false; - emit FilterRemoved(filter); + function isCyphernodeEnabled(address node) external view returns (bool) { + return isEnabled[node]; } //////////////////////////////////////////////////////////// @@ -112,10 +116,29 @@ contract CyphernodeRegistryOwnable is ICyphernodeRegistry, OwnableUpgradeable { function committeePublicKey( uint256 e3Id - ) external view returns (bytes memory publicKey) { - require(requests[e3Id] != ICommitteeCoordinator(address(0))); + ) external view returns (bytes memory) { + require( + requests[e3Id] != IRegistryFilter(address(0)), + CommitteeDoesNotExist() + ); + + ( + uint32[2] memory threshold, + bytes memory publicKey, + address[] memory cyphernodes + ) = IRegistryFilter(requests[e3Id]).retrieveCommittee(e3Id); + + require( + threshold[0] > 0 && threshold[0] < threshold[1], + CommitteeInvalid() + ); + + require(threshold[1] <= cyphernodes.length, CommitteeInvalid()); + + for (uint256 i = 0; i < cyphernodes.length; i++) { + require(isEnabled[cyphernodes[i]] == true, CommitteeInvalid()); + } - publicKey = requests[e3Id].committeePublicKey(e3Id); - require(publicKey.length > 0, NoPublicKeyPublished()); + return publicKey; } } diff --git a/packages/evm/contracts/registry/NaiveCommitteeCoordinator.sol b/packages/evm/contracts/registry/NaiveRegistryFilter.sol similarity index 82% rename from packages/evm/contracts/registry/NaiveCommitteeCoordinator.sol rename to packages/evm/contracts/registry/NaiveRegistryFilter.sol index 1a6e2031..bf51509c 100644 --- a/packages/evm/contracts/registry/NaiveCommitteeCoordinator.sol +++ b/packages/evm/contracts/registry/NaiveRegistryFilter.sol @@ -1,15 +1,12 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.26; -import { ICommitteeCoordinator } from "../interfaces/ICommitteeCoordinator.sol"; +import { IRegistryFilter } from "../interfaces/IRegistryFilter.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -contract NaiveCommitteeCoordinator is - ICommitteeCoordinator, - OwnableUpgradeable -{ +contract NaiveRegistryFilter is IRegistryFilter, OwnableUpgradeable { struct Committee { address[] nodes; uint32[2] threshold; @@ -42,7 +39,7 @@ contract NaiveCommitteeCoordinator is error CommitteeAlreadyExists(); error CommitteeAlreadyPublished(); error CommitteeDoesNotExist(); - error NoPublicKeyPublished(); + error CommitteeNotPublished(); error OnlyRegistry(); //////////////////////////////////////////////////////////// @@ -88,9 +85,29 @@ contract NaiveCommitteeCoordinator is success = true; } + function retrieveCommittee( + uint256 e3Id + ) + external + view + returns ( + uint32[2] memory threshold, + bytes memory publicKey, + address[] memory ciphernodes + ) + { + Committee storage committee = committees[e3Id]; + require(committee.threshold.length > 0, CommitteeDoesNotExist()); + threshold = committee.threshold; + require(committee.publicKey.length > 0, CommitteeNotPublished()); + publicKey = committee.publicKey; + require(committee.nodes.length > 0, CommitteeNotPublished()); + ciphernodes = committee.nodes; + } + function publishCommittee( uint256 e3Id, - address[] memory _nodes, + address[] memory nodes, bytes memory publicKey ) external onlyOwner { Committee storage committee = committees[e3Id]; @@ -98,7 +115,7 @@ contract NaiveCommitteeCoordinator is keccak256(committee.publicKey) == keccak256(hex""), CommitteeAlreadyPublished() ); - committee.nodes = _nodes; + committee.nodes = nodes; committee.publicKey = publicKey; } @@ -122,21 +139,6 @@ contract NaiveCommitteeCoordinator is uint256 e3Id ) external view returns (bytes memory publicKey) { publicKey = committees[e3Id].publicKey; - require(publicKey.length > 0, NoPublicKeyPublished()); - } - - /* - * This naive registry does make store and make nodes available on chain - * other more sophisticated registry may keep these on an off-chain tree - * and store the root - * - * The idea is to not include this speficics on the main interfaces for - * enclave for now - */ - function getCommittee( - uint256 e3Id - ) external view returns (Committee memory committee) { - committee = committees[e3Id]; - require(committees[e3Id].threshold.length > 0, CommitteeDoesNotExist()); + require(publicKey.length > 0, CommitteeNotPublished()); } } diff --git a/packages/evm/contracts/test/MockCyphernodeRegistry.sol b/packages/evm/contracts/test/MockCyphernodeRegistry.sol index 8da724d8..3e85d9d6 100644 --- a/packages/evm/contracts/test/MockCyphernodeRegistry.sol +++ b/packages/evm/contracts/test/MockCyphernodeRegistry.sol @@ -26,9 +26,9 @@ contract MockCyphernodeRegistry is ICyphernodeRegistry { } } - function addFilter(address filter) external {} - - function removeFilter(address filter) external {} + function isCyphernodeEnabled(address) external pure returns (bool) { + return false; + } } contract MockCyphernodeRegistryEmptyKey is ICyphernodeRegistry { @@ -48,7 +48,7 @@ contract MockCyphernodeRegistryEmptyKey is ICyphernodeRegistry { return hex""; } - function addFilter(address filter) external {} - - function removeFilter(address filter) external {} + function isCyphernodeEnabled(address) external pure returns (bool) { + return false; + } } From c6b5ac33af11a94aa9195de3b728daeee5858bf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crist=C3=B3v=C3=A3o=20Honorato?= Date: Tue, 11 Jun 2024 14:14:46 +0200 Subject: [PATCH 3/7] Push the committee publishing onus, to the filter. Since it is an async process, we require the filter to post/publish the resulting key --- .../interfaces/ICyphernodeRegistry.sol | 15 ++++-- .../contracts/interfaces/IRegistryFilter.sol | 19 ------- .../registry/CyphernodeRegistryOwnable.sol | 50 +++++++++---------- 3 files changed, 34 insertions(+), 50 deletions(-) diff --git a/packages/evm/contracts/interfaces/ICyphernodeRegistry.sol b/packages/evm/contracts/interfaces/ICyphernodeRegistry.sol index 23d0618e..dfea6e19 100644 --- a/packages/evm/contracts/interfaces/ICyphernodeRegistry.sol +++ b/packages/evm/contracts/interfaces/ICyphernodeRegistry.sol @@ -12,6 +12,16 @@ interface ICyphernodeRegistry { uint32[2] threshold ); + /// @notice This event MUST be emitted when a committee is selected for an E3. + /// @param e3Id ID of the E3 for which the committee was selected. + /// @param cyphernodes Addresses of the selected Cyphernodes. + /// @param publicKey Public key of the committee. + event CommitteePublished( + uint256 indexed e3Id, + address[] cyphernodes, + bytes publicKey + ); + /// @notice This event MUST be emitted when `encalve` is set. /// @param enclave Address of the enclave contract. event EnclaveSet(address indexed enclave); @@ -22,11 +32,6 @@ interface ICyphernodeRegistry { /// @notice This event MUST be emitted when a cyphernode is removed from the registry. event CyphernodeRemoved(address indexed node); - /// @notice This event MUST be emitted when a committee is selected for an E3. - /// @param e3Id ID of the E3 for which the committee was selected. - /// @param publicKey Public key of the committee. - event CommitteeSelected(uint256 indexed e3Id, bytes publicKey); - function isCyphernodeEnabled(address ciphernode) external returns (bool); /// @notice This function should be called by the Enclave contract to select a node committee. diff --git a/packages/evm/contracts/interfaces/IRegistryFilter.sol b/packages/evm/contracts/interfaces/IRegistryFilter.sol index a8b70318..c861d734 100644 --- a/packages/evm/contracts/interfaces/IRegistryFilter.sol +++ b/packages/evm/contracts/interfaces/IRegistryFilter.sol @@ -2,27 +2,8 @@ pragma solidity >=0.8.26; interface IRegistryFilter { - event CommitteeRequested(uint256 indexed e3Id, uint32[2] threshold); - - event CommitteeCreated( - uint256 indexed e3Id, - bytes publicKey, - address[] cyphernodes - ); - function requestCommittee( uint256 e3Id, uint32[2] calldata threshold ) external returns (bool success); - - function retrieveCommittee( - uint256 e3Id - ) - external - view - returns ( - uint32[2] memory threshold, - bytes memory publicKey, - address[] memory cyphernodes - ); } diff --git a/packages/evm/contracts/registry/CyphernodeRegistryOwnable.sol b/packages/evm/contracts/registry/CyphernodeRegistryOwnable.sol index e56fd57a..e665eec4 100644 --- a/packages/evm/contracts/registry/CyphernodeRegistryOwnable.sol +++ b/packages/evm/contracts/registry/CyphernodeRegistryOwnable.sol @@ -19,6 +19,7 @@ contract CyphernodeRegistryOwnable is ICyphernodeRegistry, OwnableUpgradeable { mapping(address cyphernode => bool isEnabled) public isEnabled; mapping(uint256 e3Id => IRegistryFilter filter) public requests; + mapping(uint256 e3Id => bytes publicKey) public publicKeys; //////////////////////////////////////////////////////////// // // @@ -27,10 +28,9 @@ contract CyphernodeRegistryOwnable is ICyphernodeRegistry, OwnableUpgradeable { //////////////////////////////////////////////////////////// error CommitteeAlreadyRequested(); - error CommitteeAlreadyExists(); error CommitteeAlreadyPublished(); error CommitteeDoesNotExist(); - error CommitteeInvalid(); + error CommitteeNotPublished(); error CyphernodeNotEnabled(address node); error OnlyEnclave(); @@ -83,6 +83,25 @@ contract CyphernodeRegistryOwnable is ICyphernodeRegistry, OwnableUpgradeable { success = true; } + function publishCommittee( + uint256 e3Id, + address[] calldata cyphernodes, + bytes calldata publicKey + ) external { + // only to be published by the filter + require(address(requests[e3Id]) == msg.sender, CommitteeDoesNotExist()); + + for (uint256 i = 0; i < cyphernodes.length; i++) { + require( + isEnabled[cyphernodes[i]] == true, + CyphernodeNotEnabled(cyphernodes[i]) + ); + } + + publicKeys[e3Id] = publicKey; + emit CommitteePublished(e3Id, cyphernodes, publicKey); + } + //////////////////////////////////////////////////////////// // // // Set Functions // @@ -116,29 +135,8 @@ contract CyphernodeRegistryOwnable is ICyphernodeRegistry, OwnableUpgradeable { function committeePublicKey( uint256 e3Id - ) external view returns (bytes memory) { - require( - requests[e3Id] != IRegistryFilter(address(0)), - CommitteeDoesNotExist() - ); - - ( - uint32[2] memory threshold, - bytes memory publicKey, - address[] memory cyphernodes - ) = IRegistryFilter(requests[e3Id]).retrieveCommittee(e3Id); - - require( - threshold[0] > 0 && threshold[0] < threshold[1], - CommitteeInvalid() - ); - - require(threshold[1] <= cyphernodes.length, CommitteeInvalid()); - - for (uint256 i = 0; i < cyphernodes.length; i++) { - require(isEnabled[cyphernodes[i]] == true, CommitteeInvalid()); - } - - return publicKey; + ) external view returns (bytes memory publicKey) { + publicKey = publicKeys[e3Id]; + require(publicKey.length > 0, CommitteeNotPublished()); } } From 596d01a063254bf29b5c596f391c8dae7bffdc45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crist=C3=B3v=C3=A3o=20Honorato?= Date: Tue, 11 Jun 2024 14:43:53 +0200 Subject: [PATCH 4/7] Adjust tests --- packages/evm/contracts/Enclave.sol | 1 - .../registry/NaiveRegistryFilter.sol | 49 ++++------------- packages/evm/test/Enclave.spec.ts | 54 ++++++++++--------- 3 files changed, 38 insertions(+), 66 deletions(-) diff --git a/packages/evm/contracts/Enclave.sol b/packages/evm/contracts/Enclave.sol index a4f9a475..9fac99d5 100644 --- a/packages/evm/contracts/Enclave.sol +++ b/packages/evm/contracts/Enclave.sol @@ -172,7 +172,6 @@ contract Enclave is IEnclave, OwnableUpgradeable { cyphernodeRegistry.requestCommittee(e3Id, filter, threshold), CommitteeSelectionFailed() ); - // TODO: validate that the selected pool accepts both the computation and execution modules. emit E3Requested( e3Id, diff --git a/packages/evm/contracts/registry/NaiveRegistryFilter.sol b/packages/evm/contracts/registry/NaiveRegistryFilter.sol index bf51509c..d48bba7e 100644 --- a/packages/evm/contracts/registry/NaiveRegistryFilter.sol +++ b/packages/evm/contracts/registry/NaiveRegistryFilter.sol @@ -6,6 +6,14 @@ import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +interface IRegistry { + function publishCommittee( + uint256 e3Id, + address[] calldata cyphernodes, + bytes calldata publicKey + ) external; +} + contract NaiveRegistryFilter is IRegistryFilter, OwnableUpgradeable { struct Committee { address[] nodes; @@ -13,13 +21,6 @@ contract NaiveRegistryFilter is IRegistryFilter, OwnableUpgradeable { bytes publicKey; } - struct Node { - bool eligible; - // Number of duties the node has not yet completed. - // Incremented each time a duty is added, decremented each time a duty is completed. - uint256 outstandingDuties; - } - //////////////////////////////////////////////////////////// // // // Storage Variables // @@ -85,26 +86,6 @@ contract NaiveRegistryFilter is IRegistryFilter, OwnableUpgradeable { success = true; } - function retrieveCommittee( - uint256 e3Id - ) - external - view - returns ( - uint32[2] memory threshold, - bytes memory publicKey, - address[] memory ciphernodes - ) - { - Committee storage committee = committees[e3Id]; - require(committee.threshold.length > 0, CommitteeDoesNotExist()); - threshold = committee.threshold; - require(committee.publicKey.length > 0, CommitteeNotPublished()); - publicKey = committee.publicKey; - require(committee.nodes.length > 0, CommitteeNotPublished()); - ciphernodes = committee.nodes; - } - function publishCommittee( uint256 e3Id, address[] memory nodes, @@ -117,6 +98,7 @@ contract NaiveRegistryFilter is IRegistryFilter, OwnableUpgradeable { ); committee.nodes = nodes; committee.publicKey = publicKey; + IRegistry(registry).publishCommittee(e3Id, nodes, publicKey); } //////////////////////////////////////////////////////////// @@ -128,17 +110,4 @@ contract NaiveRegistryFilter is IRegistryFilter, OwnableUpgradeable { function setRegistry(address _registry) public onlyOwner { registry = _registry; } - - //////////////////////////////////////////////////////////// - // // - // Get Functions // - // // - //////////////////////////////////////////////////////////// - - function committeePublicKey( - uint256 e3Id - ) external view returns (bytes memory publicKey) { - publicKey = committees[e3Id].publicKey; - require(publicKey.length > 0, CommitteeNotPublished()); - } } diff --git a/packages/evm/test/Enclave.spec.ts b/packages/evm/test/Enclave.spec.ts index a98838c9..d5653999 100644 --- a/packages/evm/test/Enclave.spec.ts +++ b/packages/evm/test/Enclave.spec.ts @@ -16,6 +16,10 @@ import { deployOutputVerifierFixture } from "./fixtures/MockOutputVerifier.fixtu const abiCoder = ethers.AbiCoder.defaultAbiCoder(); const AddressTwo = "0x0000000000000000000000000000000000000002"; +const AddressSix = "0x0000000000000000000000000000000000000006"; + +const FilterFail = AddressTwo; +const FilterOkay = AddressSix; describe("Enclave", function () { async function setup() { @@ -47,7 +51,7 @@ describe("Enclave", function () { registry, }, request: { - pool: [ethers.ZeroAddress], + filter: FilterOkay, threshold: [2, 2] as [number, number], duration: time.duration.days(30), computationModule: await computationModule.getAddress(), @@ -177,7 +181,7 @@ describe("Enclave", function () { it("returns correct E3 details", async function () { const { enclave, request } = await loadFixture(setup); await enclave.request( - request.pool, + request.filter, request.threshold, request.duration, request.computationModule, @@ -399,7 +403,7 @@ describe("Enclave", function () { const { enclave, request } = await loadFixture(setup); await expect( enclave.request( - request.pool, + request.filter, request.threshold, request.duration, request.computationModule, @@ -413,7 +417,7 @@ describe("Enclave", function () { const { enclave, request } = await loadFixture(setup); await expect( enclave.request( - request.pool, + request.filter, [0, 2], request.duration, request.computationModule, @@ -428,7 +432,7 @@ describe("Enclave", function () { const { enclave, request } = await loadFixture(setup); await expect( enclave.request( - request.pool, + request.filter, [3, 2], request.duration, request.computationModule, @@ -443,7 +447,7 @@ describe("Enclave", function () { const { enclave, request } = await loadFixture(setup); await expect( enclave.request( - request.pool, + request.filter, request.threshold, 0, request.computationModule, @@ -458,7 +462,7 @@ describe("Enclave", function () { const { enclave, request } = await loadFixture(setup); await expect( enclave.request( - request.pool, + request.filter, request.threshold, time.duration.days(31), request.computationModule, @@ -473,7 +477,7 @@ describe("Enclave", function () { const { enclave, request } = await loadFixture(setup); await expect( enclave.request( - request.pool, + request.filter, request.threshold, request.duration, ethers.ZeroAddress, @@ -490,7 +494,7 @@ describe("Enclave", function () { const { enclave, request } = await loadFixture(setup); await expect( enclave.request( - request.pool, + request.filter, request.threshold, request.duration, request.computationModule, @@ -508,7 +512,7 @@ describe("Enclave", function () { await expect( enclave.request( - request.pool, + request.filter, request.threshold, request.duration, request.computationModule, @@ -523,7 +527,7 @@ describe("Enclave", function () { const { enclave, request } = await loadFixture(setup); await expect( enclave.request( - request.pool, + request.filter, request.threshold, request.duration, request.computationModule, @@ -538,7 +542,7 @@ describe("Enclave", function () { const { enclave, request } = await loadFixture(setup); await expect( enclave.request( - [AddressTwo], + FilterFail, request.threshold, request.duration, request.computationModule, @@ -552,7 +556,7 @@ describe("Enclave", function () { it("instantiates a new E3", async function () { const { enclave, request } = await loadFixture(setup); await enclave.request( - request.pool, + request.filter, request.threshold, request.duration, request.computationModule, @@ -580,7 +584,7 @@ describe("Enclave", function () { it("emits E3Requested event", async function () { const { enclave, request } = await loadFixture(setup); const tx = await enclave.request( - request.pool, + request.filter, request.threshold, request.duration, request.computationModule, @@ -596,7 +600,7 @@ describe("Enclave", function () { .withArgs( 0, e3, - request.pool, + request.filter, request.computationModule, request.executionModule, ); @@ -615,7 +619,7 @@ describe("Enclave", function () { const { enclave, request } = await loadFixture(setup); await enclave.request( - request.pool, + request.filter, request.threshold, request.duration, request.computationModule, @@ -635,7 +639,7 @@ describe("Enclave", function () { const { enclave, request } = await loadFixture(setup); await enclave.request( - request.pool, + request.filter, request.threshold, request.duration, request.computationModule, @@ -667,7 +671,7 @@ describe("Enclave", function () { } = await loadFixture(setup); await enclave.request( - request.pool, + request.filter, request.threshold, request.duration, request.computationModule, @@ -678,7 +682,7 @@ describe("Enclave", function () { ); const e3Id = 0; - const publicKey = await registry.getCommitteePublicKey(e3Id); + const publicKey = await registry.committeePublicKey(e3Id); let e3 = await enclave.getE3(e3Id); expect(e3.committeePublicKey).to.not.equal(publicKey); @@ -692,7 +696,7 @@ describe("Enclave", function () { const { enclave, request } = await loadFixture(setup); await enclave.request( - request.pool, + request.filter, request.threshold, request.duration, request.computationModule, @@ -710,7 +714,7 @@ describe("Enclave", function () { const { enclave, request } = await loadFixture(setup); await enclave.request( - request.pool, + request.filter, request.threshold, request.duration, request.computationModule, @@ -742,7 +746,7 @@ describe("Enclave", function () { const { enclave, request } = await loadFixture(setup); await enclave.request( - request.pool, + request.filter, request.threshold, request.duration, request.computationModule, @@ -768,7 +772,7 @@ describe("Enclave", function () { const { enclave, request } = await loadFixture(setup); await enclave.request( - request.pool, + request.filter, request.threshold, request.duration, request.computationModule, @@ -790,7 +794,7 @@ describe("Enclave", function () { const { enclave, request } = await loadFixture(setup); await enclave.request( - request.pool, + request.filter, request.threshold, request.duration, request.computationModule, @@ -817,7 +821,7 @@ describe("Enclave", function () { const { enclave, request } = await loadFixture(setup); await enclave.request( - request.pool, + request.filter, request.threshold, request.duration, request.computationModule, From b2e7196cf72a476d795e7cf0194c3503f99e4c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crist=C3=B3v=C3=A3o=20Honorato?= Date: Fri, 21 Jun 2024 15:53:20 +0200 Subject: [PATCH 5/7] Simplify the two step CommitteeSelection process. For now remove proof and rely on filter --- .../interfaces/ICyphernodeRegistry.sol | 21 ++-- .../registry/CyphernodeRegistryOwnable.sol | 20 ++-- .../registry/NaiveRegistryFilter.sol | 11 +- .../contracts/test/MockCyphernodeRegistry.sol | 4 + .../evm/contracts/test/MockRegistryFilter.sol | 113 ++++++++++++++++++ 5 files changed, 141 insertions(+), 28 deletions(-) create mode 100644 packages/evm/contracts/test/MockRegistryFilter.sol diff --git a/packages/evm/contracts/interfaces/ICyphernodeRegistry.sol b/packages/evm/contracts/interfaces/ICyphernodeRegistry.sol index dfea6e19..4f69764e 100644 --- a/packages/evm/contracts/interfaces/ICyphernodeRegistry.sol +++ b/packages/evm/contracts/interfaces/ICyphernodeRegistry.sol @@ -14,13 +14,8 @@ interface ICyphernodeRegistry { /// @notice This event MUST be emitted when a committee is selected for an E3. /// @param e3Id ID of the E3 for which the committee was selected. - /// @param cyphernodes Addresses of the selected Cyphernodes. /// @param publicKey Public key of the committee. - event CommitteePublished( - uint256 indexed e3Id, - address[] cyphernodes, - bytes publicKey - ); + event CommitteePublished(uint256 indexed e3Id, bytes publicKey); /// @notice This event MUST be emitted when `encalve` is set. /// @param enclave Address of the enclave contract. @@ -34,8 +29,10 @@ interface ICyphernodeRegistry { function isCyphernodeEnabled(address ciphernode) external returns (bool); - /// @notice This function should be called by the Enclave contract to select a node committee. + /// @notice Initiates the committee selection process for a specified E3. + /// @dev This function MUST revert when not called by the Enclave contract. /// @param e3Id ID of the E3 for which to select the committee. + /// @param filter The address of the filter responsible for the committee selection process. /// @param threshold The M/N threshold for the committee. /// @return success True if committee selection was successfully initiated. function requestCommittee( @@ -44,6 +41,16 @@ interface ICyphernodeRegistry { uint32[2] calldata threshold ) external returns (bool success); + /// @notice Publishes the public key resulting from the committee selection process. + /// @dev This function MUST revert if not called by the previously selected filter. + /// @param e3Id ID of the E3 for which to select the committee. + /// @param publicKey The public key generated by the selected committee. + function publishCommittee( + uint256 e3Id, + // NOTE: Consider including a bytes proof parameter for verification purposes ? + bytes calldata publicKey + ) external; + /// @notice This function should be called by the Enclave contract to get the public key of a committee. /// @dev This function MUST revert if no committee has been requested for the given E3. /// @dev This function MUST revert if the committee has not yet published a public key. diff --git a/packages/evm/contracts/registry/CyphernodeRegistryOwnable.sol b/packages/evm/contracts/registry/CyphernodeRegistryOwnable.sol index e665eec4..537d9958 100644 --- a/packages/evm/contracts/registry/CyphernodeRegistryOwnable.sol +++ b/packages/evm/contracts/registry/CyphernodeRegistryOwnable.sol @@ -83,23 +83,19 @@ contract CyphernodeRegistryOwnable is ICyphernodeRegistry, OwnableUpgradeable { success = true; } - function publishCommittee( - uint256 e3Id, - address[] calldata cyphernodes, - bytes calldata publicKey - ) external { + function publishCommittee(uint256 e3Id, bytes calldata publicKey) external { // only to be published by the filter require(address(requests[e3Id]) == msg.sender, CommitteeDoesNotExist()); - for (uint256 i = 0; i < cyphernodes.length; i++) { - require( - isEnabled[cyphernodes[i]] == true, - CyphernodeNotEnabled(cyphernodes[i]) - ); - } + // for (uint256 i = 0; i < cyphernodes.length; i++) { + // require( + // isEnabled[cyphernodes[i]] == true, + // CyphernodeNotEnabled(cyphernodes[i]) + // ); + // } publicKeys[e3Id] = publicKey; - emit CommitteePublished(e3Id, cyphernodes, publicKey); + emit CommitteePublished(e3Id, publicKey); } //////////////////////////////////////////////////////////// diff --git a/packages/evm/contracts/registry/NaiveRegistryFilter.sol b/packages/evm/contracts/registry/NaiveRegistryFilter.sol index d48bba7e..e247de53 100644 --- a/packages/evm/contracts/registry/NaiveRegistryFilter.sol +++ b/packages/evm/contracts/registry/NaiveRegistryFilter.sol @@ -1,19 +1,12 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.26; +import { ICyphernodeRegistry } from "../interfaces/ICyphernodeRegistry.sol"; import { IRegistryFilter } from "../interfaces/IRegistryFilter.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -interface IRegistry { - function publishCommittee( - uint256 e3Id, - address[] calldata cyphernodes, - bytes calldata publicKey - ) external; -} - contract NaiveRegistryFilter is IRegistryFilter, OwnableUpgradeable { struct Committee { address[] nodes; @@ -98,7 +91,7 @@ contract NaiveRegistryFilter is IRegistryFilter, OwnableUpgradeable { ); committee.nodes = nodes; committee.publicKey = publicKey; - IRegistry(registry).publishCommittee(e3Id, nodes, publicKey); + ICyphernodeRegistry(registry).publishCommittee(e3Id, publicKey); } //////////////////////////////////////////////////////////// diff --git a/packages/evm/contracts/test/MockCyphernodeRegistry.sol b/packages/evm/contracts/test/MockCyphernodeRegistry.sol index 3e85d9d6..7cf83f09 100644 --- a/packages/evm/contracts/test/MockCyphernodeRegistry.sol +++ b/packages/evm/contracts/test/MockCyphernodeRegistry.sol @@ -16,6 +16,8 @@ contract MockCyphernodeRegistry is ICyphernodeRegistry { } } + function publishCommittee(uint256, bytes calldata) external {} + function committeePublicKey( uint256 e3Id ) external pure returns (bytes memory) { @@ -44,6 +46,8 @@ contract MockCyphernodeRegistryEmptyKey is ICyphernodeRegistry { } } + function publishCommittee(uint256, bytes calldata) external {} + function committeePublicKey(uint256) external pure returns (bytes memory) { return hex""; } diff --git a/packages/evm/contracts/test/MockRegistryFilter.sol b/packages/evm/contracts/test/MockRegistryFilter.sol new file mode 100644 index 00000000..d48bba7e --- /dev/null +++ b/packages/evm/contracts/test/MockRegistryFilter.sol @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.26; + +import { IRegistryFilter } from "../interfaces/IRegistryFilter.sol"; +import { + OwnableUpgradeable +} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +interface IRegistry { + function publishCommittee( + uint256 e3Id, + address[] calldata cyphernodes, + bytes calldata publicKey + ) external; +} + +contract NaiveRegistryFilter is IRegistryFilter, OwnableUpgradeable { + struct Committee { + address[] nodes; + uint32[2] threshold; + bytes publicKey; + } + + //////////////////////////////////////////////////////////// + // // + // Storage Variables // + // // + //////////////////////////////////////////////////////////// + + address public registry; + + mapping(uint256 e3 => Committee committee) public committees; + + //////////////////////////////////////////////////////////// + // // + // Errors // + // // + //////////////////////////////////////////////////////////// + + error CommitteeAlreadyExists(); + error CommitteeAlreadyPublished(); + error CommitteeDoesNotExist(); + error CommitteeNotPublished(); + error OnlyRegistry(); + + //////////////////////////////////////////////////////////// + // // + // Modifiers // + // // + //////////////////////////////////////////////////////////// + + modifier onlyRegistry() { + require(msg.sender == registry, OnlyRegistry()); + _; + } + + //////////////////////////////////////////////////////////// + // // + // Initialization // + // // + //////////////////////////////////////////////////////////// + + constructor(address _owner, address _enclave) { + initialize(_owner, _enclave); + } + + function initialize(address _owner, address _registry) public initializer { + __Ownable_init(msg.sender); + setRegistry(_registry); + transferOwnership(_owner); + } + + //////////////////////////////////////////////////////////// + // // + // Core Entrypoints // + // // + //////////////////////////////////////////////////////////// + + function requestCommittee( + uint256 e3Id, + uint32[2] calldata threshold + ) external onlyRegistry returns (bool success) { + Committee storage committee = committees[e3Id]; + require(committee.threshold.length == 0, CommitteeAlreadyExists()); + committee.threshold = threshold; + success = true; + } + + function publishCommittee( + uint256 e3Id, + address[] memory nodes, + bytes memory publicKey + ) external onlyOwner { + Committee storage committee = committees[e3Id]; + require( + keccak256(committee.publicKey) == keccak256(hex""), + CommitteeAlreadyPublished() + ); + committee.nodes = nodes; + committee.publicKey = publicKey; + IRegistry(registry).publishCommittee(e3Id, nodes, publicKey); + } + + //////////////////////////////////////////////////////////// + // // + // Set Functions // + // // + //////////////////////////////////////////////////////////// + + function setRegistry(address _registry) public onlyOwner { + registry = _registry; + } +} From 7482c89f17396f77255c9e02da63ac80229de698 Mon Sep 17 00:00:00 2001 From: Auryn Macmillan Date: Sat, 22 Jun 2024 11:26:43 +0100 Subject: [PATCH 6/7] finish publish input tests --- packages/evm/contracts/Enclave.sol | 4 +- packages/evm/contracts/interfaces/IE3.sol | 1 + .../evm/contracts/test/MockInputValidator.sol | 2 +- packages/evm/test/Enclave.spec.ts | 59 +++++++++++++++---- 4 files changed, 54 insertions(+), 12 deletions(-) diff --git a/packages/evm/contracts/Enclave.sol b/packages/evm/contracts/Enclave.sol index 9fac99d5..be590a49 100644 --- a/packages/evm/contracts/Enclave.sol +++ b/packages/evm/contracts/Enclave.sol @@ -163,6 +163,7 @@ contract Enclave is IEnclave, OwnableUpgradeable { inputValidator: inputValidator, outputVerifier: outputVerifier, committeePublicKey: hex"", + inputs: new bytes[](0), ciphertextOutput: hex"", plaintextOutput: hex"" }); @@ -217,7 +218,8 @@ contract Enclave is IEnclave, OwnableUpgradeable { bytes memory input; (input, success) = e3.inputValidator.validate(msg.sender, data); require(success, InvalidInput()); - // TODO: do we need to store or accumulate the inputs? Probably yes. + // TODO: probably better to accumulate inputs, rather than just dumping them in storage. + e3s[e3Id].inputs.push(input); emit InputPublished(e3Id, input); } diff --git a/packages/evm/contracts/interfaces/IE3.sol b/packages/evm/contracts/interfaces/IE3.sol index 4d2b18a6..7ef7ab3e 100644 --- a/packages/evm/contracts/interfaces/IE3.sol +++ b/packages/evm/contracts/interfaces/IE3.sol @@ -25,6 +25,7 @@ struct E3 { IInputValidator inputValidator; IOutputVerifier outputVerifier; bytes committeePublicKey; + bytes[] inputs; bytes ciphertextOutput; bytes plaintextOutput; } diff --git a/packages/evm/contracts/test/MockInputValidator.sol b/packages/evm/contracts/test/MockInputValidator.sol index 1763472c..2dcb0c89 100644 --- a/packages/evm/contracts/test/MockInputValidator.sol +++ b/packages/evm/contracts/test/MockInputValidator.sol @@ -8,7 +8,7 @@ contract MockInputValidator is IInputValidator { address, bytes memory params ) external pure returns (bytes memory input, bool success) { - input = abi.decode(params, (bytes)); + input = params; if (input.length == 3) { success = false; diff --git a/packages/evm/test/Enclave.spec.ts b/packages/evm/test/Enclave.spec.ts index d5653999..bacd8279 100644 --- a/packages/evm/test/Enclave.spec.ts +++ b/packages/evm/test/Enclave.spec.ts @@ -782,11 +782,9 @@ describe("Enclave", function () { { value: 10 }, ); - const inputData = abiCoder.encode(["bytes"], ["0xaabbcc"]); - await enclave.activate(0); await expect( - enclave.publishInput(0, inputData), + enclave.publishInput(0, "0xaabbcc"), ).to.be.revertedWithCustomError(enclave, "InvalidInput"); }); @@ -804,19 +802,60 @@ describe("Enclave", function () { { value: 10 }, ); - const inputData = abiCoder.encode(["bytes32"], [ZeroHash]); - await enclave.activate(0); - await expect(enclave.publishInput(0, inputData)).to.not.be.reverted; + await expect(enclave.publishInput(0, ZeroHash)).to.not.be.reverted; await mine(2, { interval: request.duration }); await expect( - enclave.publishInput(0, inputData), + enclave.publishInput(0, ZeroHash), ).to.be.revertedWithCustomError(enclave, "InputDeadlinePassed"); }); - it("sets ciphertextInput correctly"); - it("returns true if input is published successfully"); + it("sets ciphertextInput correctly", async function () { + const { enclave, request } = await loadFixture(setup); + const inputData = "0x12345678"; + + await enclave.request( + request.filter, + request.threshold, + request.duration, + request.computationModule, + request.cMParams, + request.executionModule, + request.eMParams, + { value: 10 }, + ); + + await enclave.activate(0); + + expect(await enclave.publishInput(0, inputData)).to.not.be.reverted; + let e3 = await enclave.getE3(0); + expect(e3.inputs[0]).to.equal(inputData); + expect(await enclave.publishInput(0, inputData)).to.not.be.reverted; + e3 = await enclave.getE3(0); + expect(e3.inputs[1]).to.equal(inputData); + }); + it("returns true if input is published successfully", async function () { + const { enclave, request } = await loadFixture(setup); + const inputData = "0x12345678"; + + await enclave.request( + request.filter, + request.threshold, + request.duration, + request.computationModule, + request.cMParams, + request.executionModule, + request.eMParams, + { value: 10 }, + ); + + await enclave.activate(0); + + expect(await enclave.publishInput.staticCall(0, inputData)).to.equal( + true, + ); + }); it("emits InputPublished event", async function () { const { enclave, request } = await loadFixture(setup); @@ -838,7 +877,7 @@ describe("Enclave", function () { await expect(enclave.publishInput(e3Id, inputData)) .to.emit(enclave, "InputPublished") - .withArgs(e3Id, "0xaabbccddeeff"); + .withArgs(e3Id, inputData); }); }); From ca901de90804f0d8ff644b95c777a7a20c02ef9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crist=C3=B3v=C3=A3o=20Honorato?= Date: Wed, 26 Jun 2024 14:52:48 +0200 Subject: [PATCH 7/7] Reintroduce abi.encoded proof that is to be provided when publishing a committee --- .../contracts/interfaces/ICyphernodeRegistry.sol | 4 ++-- .../registry/CyphernodeRegistryOwnable.sol | 8 ++++++-- .../contracts/registry/NaiveRegistryFilter.sol | 6 +++++- .../contracts/test/MockCyphernodeRegistry.sol | 16 ++++++++++++---- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/packages/evm/contracts/interfaces/ICyphernodeRegistry.sol b/packages/evm/contracts/interfaces/ICyphernodeRegistry.sol index 4f69764e..f06adcfa 100644 --- a/packages/evm/contracts/interfaces/ICyphernodeRegistry.sol +++ b/packages/evm/contracts/interfaces/ICyphernodeRegistry.sol @@ -27,7 +27,7 @@ interface ICyphernodeRegistry { /// @notice This event MUST be emitted when a cyphernode is removed from the registry. event CyphernodeRemoved(address indexed node); - function isCyphernodeEnabled(address ciphernode) external returns (bool); + function isCyphernodeEligible(address cyphernode) external returns (bool); /// @notice Initiates the committee selection process for a specified E3. /// @dev This function MUST revert when not called by the Enclave contract. @@ -47,7 +47,7 @@ interface ICyphernodeRegistry { /// @param publicKey The public key generated by the selected committee. function publishCommittee( uint256 e3Id, - // NOTE: Consider including a bytes proof parameter for verification purposes ? + bytes calldata proof, bytes calldata publicKey ) external; diff --git a/packages/evm/contracts/registry/CyphernodeRegistryOwnable.sol b/packages/evm/contracts/registry/CyphernodeRegistryOwnable.sol index 537d9958..c9c17ca5 100644 --- a/packages/evm/contracts/registry/CyphernodeRegistryOwnable.sol +++ b/packages/evm/contracts/registry/CyphernodeRegistryOwnable.sol @@ -83,7 +83,11 @@ contract CyphernodeRegistryOwnable is ICyphernodeRegistry, OwnableUpgradeable { success = true; } - function publishCommittee(uint256 e3Id, bytes calldata publicKey) external { + function publishCommittee( + uint256 e3Id, + bytes calldata, + bytes calldata publicKey + ) external { // only to be published by the filter require(address(requests[e3Id]) == msg.sender, CommitteeDoesNotExist()); @@ -119,7 +123,7 @@ contract CyphernodeRegistryOwnable is ICyphernodeRegistry, OwnableUpgradeable { emit CyphernodeRemoved(node); } - function isCyphernodeEnabled(address node) external view returns (bool) { + function isCyphernodeEligible(address node) external view returns (bool) { return isEnabled[node]; } diff --git a/packages/evm/contracts/registry/NaiveRegistryFilter.sol b/packages/evm/contracts/registry/NaiveRegistryFilter.sol index e247de53..5966c8ea 100644 --- a/packages/evm/contracts/registry/NaiveRegistryFilter.sol +++ b/packages/evm/contracts/registry/NaiveRegistryFilter.sol @@ -91,7 +91,11 @@ contract NaiveRegistryFilter is IRegistryFilter, OwnableUpgradeable { ); committee.nodes = nodes; committee.publicKey = publicKey; - ICyphernodeRegistry(registry).publishCommittee(e3Id, publicKey); + ICyphernodeRegistry(registry).publishCommittee( + e3Id, + abi.encode(nodes), + publicKey + ); } //////////////////////////////////////////////////////////// diff --git a/packages/evm/contracts/test/MockCyphernodeRegistry.sol b/packages/evm/contracts/test/MockCyphernodeRegistry.sol index 7cf83f09..de4e95f3 100644 --- a/packages/evm/contracts/test/MockCyphernodeRegistry.sol +++ b/packages/evm/contracts/test/MockCyphernodeRegistry.sol @@ -16,7 +16,11 @@ contract MockCyphernodeRegistry is ICyphernodeRegistry { } } - function publishCommittee(uint256, bytes calldata) external {} + function publishCommittee( + uint256, + bytes calldata, + bytes calldata + ) external {} function committeePublicKey( uint256 e3Id @@ -28,7 +32,7 @@ contract MockCyphernodeRegistry is ICyphernodeRegistry { } } - function isCyphernodeEnabled(address) external pure returns (bool) { + function isCyphernodeEligible(address) external pure returns (bool) { return false; } } @@ -46,13 +50,17 @@ contract MockCyphernodeRegistryEmptyKey is ICyphernodeRegistry { } } - function publishCommittee(uint256, bytes calldata) external {} + function publishCommittee( + uint256, + bytes calldata, + bytes calldata + ) external {} function committeePublicKey(uint256) external pure returns (bytes memory) { return hex""; } - function isCyphernodeEnabled(address) external pure returns (bool) { + function isCyphernodeEligible(address) external pure returns (bool) { return false; } }