Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encalve Spec #1

Merged
merged 62 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
1f371a0
started drafting the spec
auryn-macmillan May 23, 2024
67716cc
started drafting Enclave.sol
auryn-macmillan May 24, 2024
3db9135
delete Lock.sol
auryn-macmillan May 24, 2024
a0d97eb
fix solidlity version in hardhat config
auryn-macmillan May 24, 2024
7479ba2
inheritance refactor
auryn-macmillan May 24, 2024
ee4087e
add publish and decrypt
auryn-macmillan May 24, 2024
89bb9d0
move Enclave events to IEnclave
auryn-macmillan May 27, 2024
ee64908
fix typo in IE3.sol
auryn-macmillan May 27, 2024
647d8cd
format IE3 natspec
auryn-macmillan May 27, 2024
922b89b
re-order IEnclave events
auryn-macmillan May 27, 2024
de7557d
add getE3()
auryn-macmillan May 27, 2024
24f23e8
add a TODO for allowlists
auryn-macmillan May 28, 2024
7ac26a4
add e3id to node selection
auryn-macmillan May 28, 2024
522a91b
add dev tags to IEnclave
auryn-macmillan May 28, 2024
729275a
add activate()
auryn-macmillan May 28, 2024
f4a42fd
add test scaffolding
auryn-macmillan May 28, 2024
000dc33
fix: lowercase folder names
auryn-macmillan May 28, 2024
469265a
fix path
auryn-macmillan May 28, 2024
655cc8a
add mock contracts
auryn-macmillan May 28, 2024
14cc970
todo: do we need a start timestamp for requests?
auryn-macmillan May 30, 2024
fa0605d
select committee after instantiating E3 object in storage
auryn-macmillan May 30, 2024
cda415b
add "yarn clean" to root package
auryn-macmillan May 30, 2024
805bf94
add ownableUpgradable and start scaffolding tests
auryn-macmillan May 30, 2024
15104ec
change initialization flow to allow _owner to differ from msg.sender
auryn-macmillan May 31, 2024
e999807
add tests for setters
auryn-macmillan May 31, 2024
6097f28
add mock fixtures
auryn-macmillan May 31, 2024
9b7d075
use mocks instead of otherAccount
auryn-macmillan May 31, 2024
bb5342f
await promise on getAddress
auryn-macmillan May 31, 2024
8c8eaab
add unit tests for Enclave.request()
auryn-macmillan Jun 3, 2024
27c1b58
add unit test for getE3()
auryn-macmillan Jun 3, 2024
9bc9340
add unit test stubs for remaining core functions
auryn-macmillan Jun 3, 2024
2bdadb6
rename some functions
auryn-macmillan Jun 3, 2024
7139263
Fix typo in folder name
cristovaoth Jun 3, 2024
6fc29b1
Bump dependencies
cristovaoth Jun 3, 2024
ebe4904
Adjust prettier solidity version
cristovaoth Jun 3, 2024
71a1824
Small gas optimization
cristovaoth Jun 3, 2024
860de67
Remove unused variables
cristovaoth Jun 3, 2024
e1c3109
Include yarn.lock
cristovaoth Jun 3, 2024
bb628ea
Simplify Enclave.fixture setup
cristovaoth Jun 3, 2024
be79dce
Reorganize test folder structure
cristovaoth Jun 3, 2024
50e9c2e
Remove unnecessary test hook
cristovaoth Jun 3, 2024
c4de25a
Delete unused file
cristovaoth Jun 3, 2024
8e16a39
Simplify computationModuleFixture
cristovaoth Jun 3, 2024
ecd83c1
Simplify Fixture deployment
cristovaoth Jun 3, 2024
14ae4f1
Simplify main Enclave.fixture
cristovaoth Jun 3, 2024
9910f39
Move away from setup that uses multiple fixtures in a before each. Di…
cristovaoth Jun 3, 2024
d54a1ce
Add test for activate: reversts when e3 does not exist
cristovaoth Jun 3, 2024
90ccbba
add test fix bug: set expiration in storage when activating e3
cristovaoth Jun 3, 2024
39e88eb
rename publishDecryptedOutput to publishPlaintextOutput
auryn-macmillan Jun 3, 2024
bbcc841
change pool to address, rather than uint256
auryn-macmillan Jun 4, 2024
a6a39b3
add TODO
auryn-macmillan Jun 4, 2024
c4a7623
delete registry directory
auryn-macmillan Jun 4, 2024
404a4ef
CypherNode --> Cyphernode
auryn-macmillan Jun 4, 2024
868de3d
CypherNode --> Cyphernode
auryn-macmillan Jun 4, 2024
6dba323
Active test: invalid committee public key
cristovaoth Jun 4, 2024
47883f1
Add committeePublicKey test
cristovaoth Jun 4, 2024
31e11eb
Bring prettier width configuration down, and closer to the default. 1…
cristovaoth Jun 4, 2024
6076376
Test e3 action event emission and return value
cristovaoth Jun 4, 2024
de17029
start drafting ownable cyphernodeRegistry
auryn-macmillan Jun 4, 2024
50a9383
First crack at implementing CyphernodeRegistryOwnable
auryn-macmillan Jun 5, 2024
72f187d
squash linter errors
auryn-macmillan Jun 5, 2024
2164536
fill missing natspec comments on ICyphernodeRegistry
auryn-macmillan Jun 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@
"url": "https://github.com/gnosisguild"
},
"scripts": {
"clean": "cd packages/evm && yarn clean",
"compile": "yarn evm:compile",
"lint": "yarn evm:lint",
"typechain": "yarn evm:typechain",
"test": "yarn evm:test",
"coverage": "yarn evm:coverage",
"evm:install": "cd packages/evm && yarn install",
"evm:compile": "cd packages/evm && yarn compile",
"evm:lint": "cd packages/evm && yarn lint",
"evm:typechain": "cd packages/evm && yarn typechain",
"evm:test": "cd packages/evm && yarn test",
"evm:coverage": "cd packages/evm && yarn coverage",
"preinstall": "yarn evm:install"
}
Expand Down
57 changes: 57 additions & 0 deletions packages/docs/contracts.spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Enclave Specification

This document is a specification of the smart contract components to Enclave, an open source protocol for Encrypted Execution Environments (E3).

## Actors

There are five groups of actors in Enclave:

1. **Requesters:** Anyone can request an E3 from the Enclave protocol by calling the corresponding smart contract entrypoint and depositing a bond proportional to the number, threshold, and duration of Cypher Nodes that they request.
2. **Data Providers:** Individuals and systems providing inputs to a requested E3. Data Providers contribute data encrypted to the public threshold key that is created, and published on chain, by the Cypher Nodes selected for a requested E3.
3. **Execution Modules:** Enclave is a modular framework, allowing the choice of many different Execution Modules in which to run encrypted computations. Broadly, Execution Modules fall into two categories: (1) Provable (like RISC Zero’s virtual machine[^1], Arbitrum’s WAVM[^2], or Succinct's SP1[^3]) and (2) Oracle-based. The former provides cryptographic guarantees of correct execution, while the latter provides economic guarantees of correct execution.
4. **Cypher Nodes:** Cypher Nodes are responsible for creating threshold public keys and decrypting the cyphertext output for each requested computation. Cypher Nodes can be registered by anyone staking Enclave tokens.
5. **Token Holders:** As the top-level governance body, Enclave token holders are responsible for setting protocol parameters, overseeing protocol upgrades, and facilitating dispute resolution.

Enclave is a smart contract protocol for coordinating the interactions between these various actors.

## Components

Enclave is a modular architecture, this section describes each of the various smart contract components that constitute the Enclave protocol.

### Core

Contains the main entrypoints for requesting and publishing inputs to E3s.

**`requestE3(uint256 computationId, bytes memory data)`**

**`publishInput(bytes32 e3Id, bytes memory data)`**

**`publishOutput(bytes32 e3Id, bytes memory data)`**

**`registerNode()`**

### CyphernodeRegistry

Registry of staked Cyphernodes that are eligible to be selected for E3 duties.

### ComputationRegistry

Registry of computations which can be requested via the protocol.

### IComputationModule

Computation module contracts implement any specific

### ExecutionModuleRegistry

Registry of execution modules on which a requested computation can be run.

### IExecutionModule

Interface defining interactions with any given execution module.

---

[^1]: RISC Zero is a general-purpose, zero-knowledge virtual machine. More information can be found on their website at https://risczero.com
[^2]: WAVM is Arbitrum’s execution environment, provable via optimistic fraud proofs. More information can be found on their website at https://arbitrum.io
[^3]: SP1 is a performant, 100% open-source, contributor-friendly zero-knowledge virtual machine (zkVM) that can prove the execution of arbitrary Rust (or any LLVM-compiled language) programs. More information can be found on Succinct's github at https://github.com/succinctlabs/sp1
2 changes: 1 addition & 1 deletion packages/evm/.eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ extends:
- "prettier"
parser: "@typescript-eslint/parser"
parserOptions:
project: "tsconfig.json"
project: "packages/evm/tsconfig.json"
plugins:
- "@typescript-eslint"
root: true
Expand Down
3 changes: 1 addition & 2 deletions packages/evm/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,4 @@ deployments
.pnp.*
coverage.json
package-lock.json
pnpm-lock.yaml
yarn.lock
pnpm-lock.yaml
2 changes: 1 addition & 1 deletion packages/evm/.prettierrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ trailingComma: "all"
overrides:
- files: "*.sol"
options:
compiler: "0.8.17"
compiler: "0.8.26"
parser: "solidity-parse"
tabWidth: 4
- files: "*.ts"
Expand Down
246 changes: 246 additions & 0 deletions packages/evm/contracts/Enclave.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.8.26;

import { IEnclave, E3, IComputationModule, IExecutionModule } from "./interfaces/IEnclave.sol";
import { ICypherNodeRegistry } from "./interfaces/ICypherNodeRegistry.sol";
import { IInputValidator } from "./interfaces/IInputValidator.sol";
import { IOutputVerifier } from "./interfaces/IOutputVerifier.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

contract Enclave is IEnclave, OwnableUpgradeable {
////////////////////////////////////////////////////////////
// //
// Storage Variables //
// //
////////////////////////////////////////////////////////////

ICypherNodeRegistry public cypherNodeRegistry; // TODO: add a setter function.
uint256 public maxDuration; // TODO: add a setter function.
uint256 public nexte3Id; // ID of the next E3.
uint256 public requests; // total number of requests made to Enclave.

// TODO: should computation and execution modules be explicitly allowed?
// My intuition is that an allowlist is required since they impose slashing conditions.
// But perhaps this is one place where node pools might be utilized, allowing nodes to
// opt in to being selected for specific computations, along with the corresponding slashing conditions.
// This would reduce the governance overhead for Enclave.
// TODO: add setter function
mapping(IComputationModule => bool allowed) public computationModules; // Mapping of allowed computation modules.
// TODO: add setter function
mapping(IExecutionModule => bool allowed) public executionModules; // Mapping of allowed execution modules.

mapping(uint256 id => E3) public e3s; // Mapping of E3s.

////////////////////////////////////////////////////////////
// //
// Errors //
// //
////////////////////////////////////////////////////////////

error CommitteeSelectionFailed();
error ComputationModuleNotAllowed(IComputationModule computationModule);
error E3AlreadyActivated(uint256 e3Id);
error E3DoesNotExist(uint256 e3Id);
error ModuleAlreadyEnabled(address module);
error ModuleNotEnabled(address module);
error InputDeadlinePassed(uint256 e3Id, uint256 expiration);
error InputDeadlineNotPassed(uint256 e3Id, uint256 expiration);
error InvalidComputation();
error InvalidExecutionModuleSetup();
error InvalidCypherNodeRegistry(ICypherNodeRegistry cypherNodeRegistry);
error InvalidInput();
error InvalidDuration(uint256 duration);
error InvalidOutput();
error InvalidThreshold(uint32[2] threshold);
error CiphertextOutputAlreadyPublished(uint256 e3Id);
error CiphertextOutputNotPublished(uint256 e3Id);
error PaymentRequired(uint256 value);
error PlaintextOutputAlreadyPublished(uint256 e3Id);

////////////////////////////////////////////////////////////
// //
// Initialization //
// //
////////////////////////////////////////////////////////////

/// @param _owner The owner of this contract
/// @param _maxDuration The maximum duration of a computation in seconds
constructor(address _owner, ICypherNodeRegistry _cypherNodeRegistry, uint256 _maxDuration) {
initialize(_owner, _cypherNodeRegistry, _maxDuration);
}

/// @param _owner The owner of this contract
/// @param _maxDuration The maximum duration of a computation in seconds
function initialize(
address _owner,
ICypherNodeRegistry _cypherNodeRegistry,
uint256 _maxDuration
) public initializer {
__Ownable_init(msg.sender);
setMaxDuration(_maxDuration);
setCypherNodeRegistry(_cypherNodeRegistry);
if (_owner != owner()) transferOwnership(_owner);
}

////////////////////////////////////////////////////////////
// //
// Core Entrypoints //
// //
////////////////////////////////////////////////////////////

function request(
uint256 poolId,
uint32[2] calldata threshold,
uint256 duration, // 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 and generated a shared secret.
IComputationModule computationModule,
bytes memory computationParams,
IExecutionModule executionModule,
bytes memory emParams
) external payable returns (uint256 e3Id, E3 memory e3) {
// TODO: allow for other payment methods or only native tokens?
// TODO: should payment checks be done somewhere else? Perhaps in the computation module or cypher node registry?
require(msg.value > 0, PaymentRequired(msg.value));

require(threshold[1] >= threshold[0] && threshold[0] > 0, InvalidThreshold(threshold));
require(duration > 0 && duration <= maxDuration, InvalidDuration(duration)); // TODO: should 0 be a magic number for infinite duration?
require(computationModules[computationModule], ComputationModuleNotAllowed(computationModule));
require(executionModules[executionModule], ModuleNotEnabled(address(executionModule)));

// TODO: should IDs be incremental or produced deterministic?
e3Id = nexte3Id;
nexte3Id++;

IInputValidator inputValidator = computationModule.validate(computationParams);
require(address(inputValidator) != address(0), InvalidComputation());

// TODO: validate that the requested computation can be performed by the given execution module.
IOutputVerifier outputVerifier = executionModule.validate(emParams);
require(address(outputVerifier) != address(0), InvalidExecutionModuleSetup());

e3 = E3({
threshold: threshold,
expiration: 0,
computationModule: computationModule,
executionModule: executionModule,
inputValidator: inputValidator,
outputVerifier: outputVerifier,
committeePublicKey: hex"",
ciphertextOutput: hex"",
plaintextOutput: hex""
});
e3s[e3Id] = e3;

require(cypherNodeRegistry.selectCommittee(e3Id, poolId, threshold), CommitteeSelectionFailed());
// TODO: validate that the selected pool accepts both the computation and execution modules.

emit E3Requested(e3Id, e3s[e3Id], poolId, computationModule, executionModule);
}

function activate(uint256 e3Id) external returns (bool success) {
E3 memory e3 = getE3(e3Id);
require(e3.expiration == 0, E3AlreadyActivated(e3Id));
e3.expiration = block.timestamp + maxDuration; // TODO: this should be based on the duration requested, not the current max duration.

bytes memory committeePublicKey = cypherNodeRegistry.getCommitteePublicKey(e3Id);
success = committeePublicKey.length > 0;
require(success, CommitteeSelectionFailed());
e3s[e3Id].committeePublicKey = committeePublicKey;

emit E3Activated(e3Id, e3.expiration, e3.committeePublicKey);
}

function publishInput(uint256 e3Id, bytes memory data) external returns (bool success) {
E3 memory e3 = getE3(e3Id);
require(e3.expiration > block.timestamp, InputDeadlinePassed(e3Id, e3.expiration)); // TODO: should we have an input window, including both a start and end timestamp?
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.
emit InputPublished(e3Id, input);
}

function publishCiphertextOutput(uint256 e3Id, bytes memory data) external returns (bool success) {
E3 memory e3 = getE3(e3Id);
require(e3.expiration <= block.timestamp, InputDeadlineNotPassed(e3Id, e3.expiration));
require(e3.ciphertextOutput.length == 0, CiphertextOutputAlreadyPublished(e3Id)); // TODO: should the output verifier be able to change its mind? i.e. should we be able to call this multiple times?
bytes memory output;
(output, success) = e3.outputVerifier.verify(e3Id, data);
require(success, InvalidOutput());
e3s[e3Id].ciphertextOutput = output;

emit CiphertextOutputPublished(e3Id, output);
}

function publishDecryptedOutput(uint256 e3Id, bytes memory data) external returns (bool success) {
E3 memory e3 = getE3(e3Id);
require(e3.ciphertextOutput.length > 0, CiphertextOutputNotPublished(e3Id));
require(e3.plaintextOutput.length == 0, PlaintextOutputAlreadyPublished(e3Id));
bytes memory output;
(output, success) = e3.computationModule.verify(e3Id, data);
require(success, InvalidOutput());
e3s[e3Id].plaintextOutput = output;

emit PlaintextOutputPublished(e3Id, output);
}

////////////////////////////////////////////////////////////
// //
// Set Functions //
// //
////////////////////////////////////////////////////////////

function setMaxDuration(uint256 _maxDuration) public onlyOwner returns (bool success) {
maxDuration = _maxDuration;
success = true;
emit MaxDurationSet(_maxDuration);
}

function setCypherNodeRegistry(ICypherNodeRegistry _cypherNodeRegistry) public onlyOwner returns (bool success) {
require(
address(_cypherNodeRegistry) != address(0) && _cypherNodeRegistry != cypherNodeRegistry,
InvalidCypherNodeRegistry(_cypherNodeRegistry)
);
cypherNodeRegistry = _cypherNodeRegistry;
success = true;
emit CypherNodeRegistrySet(address(_cypherNodeRegistry));
}

function enableComputationModule(IComputationModule computationModule) public onlyOwner returns (bool success) {
require(!computationModules[computationModule], ModuleAlreadyEnabled(address(computationModule)));
computationModules[computationModule] = true;
success = true;
emit ComputationModuleEnabled(computationModule);
}

function enableExecutionModule(IExecutionModule executionModule) public onlyOwner returns (bool success) {
require(!executionModules[executionModule], ModuleAlreadyEnabled(address(executionModule)));
executionModules[executionModule] = true;
success = true;
emit ExecutionModuleEnabled(executionModule);
}

function disableComputationModule(IComputationModule computationModule) public onlyOwner returns (bool success) {
require(computationModules[computationModule], ModuleNotEnabled(address(computationModule)));
delete computationModules[computationModule];
success = true;
emit ComputationModuleDisabled(computationModule);
}

function disableExecutionModule(IExecutionModule executionModule) public onlyOwner returns (bool success) {
require(executionModules[executionModule], ModuleNotEnabled(address(executionModule)));
delete executionModules[executionModule];
success = true;
emit ExecutionModuleDisabled(executionModule);
}

////////////////////////////////////////////////////////////
// //
// Get Functions //
// //
////////////////////////////////////////////////////////////

function getE3(uint256 e3Id) public view returns (E3 memory e3) {
e3 = e3s[e3Id];
require(e3.computationModule != IComputationModule(address(0)), E3DoesNotExist(e3Id));
}
}
36 changes: 0 additions & 36 deletions packages/evm/contracts/Lock.sol

This file was deleted.

Loading
Loading