diff --git a/examples/common/python/connectors/ethereum/contracts/proxy-model/WorkOrderRegistry.sol b/examples/common/python/connectors/ethereum/contracts/proxy-model/WorkOrderRegistry.sol new file mode 100644 index 000000000..fa98fd573 --- /dev/null +++ b/examples/common/python/connectors/ethereum/contracts/proxy-model/WorkOrderRegistry.sol @@ -0,0 +1,171 @@ +/* Copyright 2019 iExec Blockchain Tech +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +pragma solidity ^0.5.0; + +import "./libs/SignatureVerifier.sol"; + +contract WorkOrderRegistry is SignatureVerifier +{ + uint256 constant public TIMEOUT = 1 days; + + enum WorkOrderStatus + { + NULL, + ACTIVE, + COMPLETED, + FAILED + } + + struct WorkOrder + { + uint256 status; + uint256 timestamp; + bytes32 workerID; + bytes32 requesterID; + bytes request; + address verifier; + uint256 returnCode; + bytes response; + } + + // workOrderID → workOrder + mapping(bytes32 => WorkOrder) m_workorders; + + event workOrderSubmitted( + bytes32 indexed workOrderID, + bytes32 indexed workerID, + bytes32 indexed requesterID); + + event workOrderCompleted( + bytes32 indexed workOrderID, + uint256 indexed workOrderReturnCode); + + event workOrderTimedout( + bytes32 indexed workOrderID); + + constructor() + public + {} + + function workOrderSubmit( + bytes32 _workerID, + bytes32 _requesterID, + bytes memory _workOrderRequest, + address _verifier, + bytes32 _salt) + public returns ( + uint256 errorCode + ) { + bytes32 workOrderID = keccak256(abi.encode( + _workerID, + _requesterID, + _workOrderRequest, + _verifier, + _salt + )); + + WorkOrder storage wo = m_workorders[workOrderID]; + require(wo.status == uint256(WorkOrderStatus.NULL), "wo-already-submitted"); + wo.status = uint256(WorkOrderStatus.ACTIVE); + wo.timestamp = now; + wo.workerID = _workerID; + wo.requesterID = _requesterID; + wo.request = _workOrderRequest; + wo.verifier = _verifier; + + emit workOrderSubmitted( + workOrderID, + _workerID, + _requesterID); + + return 0; + } + + function workOrderComplete( + bytes32 _workOrderID, + uint256 _workOrderReturnCode, + bytes memory _workOrderResponse, + bytes memory _workOrderSignature + ) public returns ( + uint256 errorCode + ) { + WorkOrder storage wo = m_workorders[_workOrderID]; + require(wo.status == uint256(WorkOrderStatus.ACTIVE), "wo-already-submitted"); + + require(checkSignature( + wo.verifier, + toEthSignedMessageHash( + keccak256(abi.encodePacked( + _workOrderID, + _workOrderReturnCode, + _workOrderResponse + )) + ), + _workOrderSignature + )); + + wo.status = uint256(WorkOrderStatus.COMPLETED); + wo.returnCode = _workOrderReturnCode; + wo.response = _workOrderResponse; + + emit workOrderCompleted( + _workOrderID, + _workOrderReturnCode); + + return 0; + } + + function workOrderTimeout( + bytes32 _workOrderID + ) public returns ( + uint256 errorCode + ) { + WorkOrder storage wo = m_workorders[_workOrderID]; + require(wo.status == uint256(WorkOrderStatus.ACTIVE)); + require(wo.timestamp + TIMEOUT < now); + + wo.status = uint256(WorkOrderStatus.FAILED); + + emit workOrderTimedout(_workOrderID); + + return 0; + } + + function workOrderGet( + bytes32 _workOrderID + ) public view returns ( + uint256 status, + uint256 timestamp, + bytes32 workerID, + bytes32 requesterID, + bytes memory request, + address verifier, + uint256 returnCode, + bytes memory response + ) { + WorkOrder storage wo = m_workorders[_workOrderID]; + return ( + wo.status, + wo.timestamp, + wo.workerID, + wo.requesterID, + wo.request, + wo.verifier, + wo.returnCode, + wo.response); + } + +} diff --git a/examples/common/python/connectors/ethereum/contracts/proxy-model/WorkerRegistry.sol b/examples/common/python/connectors/ethereum/contracts/proxy-model/WorkerRegistry.sol new file mode 100644 index 000000000..3b06331c2 --- /dev/null +++ b/examples/common/python/connectors/ethereum/contracts/proxy-model/WorkerRegistry.sol @@ -0,0 +1,142 @@ +/* Copyright 2019 iExec Blockchain Tech +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +pragma solidity ^0.5.0; + +contract WorkerRegistry +{ + enum WorkerStatus + { + NULL, + ACTIVE, + OFFLINE, + DECOMMISSIONED, + COMPROMISED + } + + struct Worker + { + uint256 status; + uint256 workerType; + bytes32 organizationId; + bytes32[] appTypeIds; + string details; + } + + // WorkerID → Worker + mapping(bytes32 => Worker) private m_workers; + + // workerType → organizationId → appTypeId → WorkerID[] + mapping(uint256 => mapping(bytes32 => mapping(bytes32 => bytes32[]))) m_workersDB; + + constructor() + public + {} + + function workerRegister( + bytes32 workerID, + uint256 workerType, + bytes32 organizationId, + bytes32[] memory appTypeIds, + string memory details + ) public { + Worker storage worker = m_workers[workerID]; + + require(worker.status == uint256(WorkerStatus.NULL)); + worker.status = uint256(WorkerStatus.ACTIVE); + worker.workerType = workerType; + worker.organizationId = organizationId; + worker.appTypeIds = appTypeIds; + worker.details = details; + + require(workerType != uint256(0)); + require(organizationId != bytes32(0)); + m_workersDB[uint256(0)][bytes32(0) ][bytes32(0)].push(workerID); + m_workersDB[workerType][bytes32(0) ][bytes32(0)].push(workerID); + m_workersDB[uint256(0)][organizationId][bytes32(0)].push(workerID); + m_workersDB[workerType][organizationId][bytes32(0)].push(workerID); + + for (uint256 p = 0; p < appTypeIds.length; ++p) + { + require(appTypeIds[p] != bytes32(0)); + m_workersDB[uint256(0)][bytes32(0) ][appTypeIds[p]].push(workerID); + m_workersDB[workerType][bytes32(0) ][appTypeIds[p]].push(workerID); + m_workersDB[uint256(0)][organizationId][appTypeIds[p]].push(workerID); + m_workersDB[workerType][organizationId][appTypeIds[p]].push(workerID); + } + } + + function workerUpdate( + bytes32 workerID, + string memory details + ) public { + require(m_workers[workerID].status != uint256(WorkerStatus.NULL)); + m_workers[workerID].details = details; + } + + function workerSetStatus( + bytes32 workerID, + uint256 status + ) public { + require(m_workers[workerID].status != uint256(WorkerStatus.NULL)); + require(status != uint256(WorkerStatus.NULL)); + m_workers[workerID].status = status; + } + + function workerLookUp( + uint256 workerType, + bytes32 organizationId, + bytes32 appTypeId + ) public view returns( + uint256 totalCount, + uint256 lookupTag, + bytes32[] memory ids + ) { + return workerLookUpNext(workerType, organizationId, appTypeId, 0); + } + + function workerLookUpNext( + uint256 workerType, + bytes32 organizationId, + bytes32 appTypeId, + uint256 /*lookUpTag*/ + ) public view returns( + uint256 totalCount, + uint256 newLookupTag, + bytes32[] memory ids + ) { + bytes32[] storage matchs = m_workersDB[workerType][organizationId][appTypeId]; + return (matchs.length, 0, matchs); + } + + function workerRetrieve( + bytes32 workerID + ) public view returns ( + uint256 status, + uint256 workerType, + bytes32 organizationId, + bytes32[] memory appTypeIds, + string memory details + ) { + Worker storage worker = m_workers[workerID]; + return ( + worker.status, + worker.workerType, + worker.organizationId, + worker.appTypeIds, + worker.details + ); + } +} diff --git a/examples/common/python/connectors/ethereum/contracts/proxy-model/interfaces/IERC1271.sol b/examples/common/python/connectors/ethereum/contracts/proxy-model/interfaces/IERC1271.sol new file mode 100644 index 000000000..174ccac17 --- /dev/null +++ b/examples/common/python/connectors/ethereum/contracts/proxy-model/interfaces/IERC1271.sol @@ -0,0 +1,47 @@ +/* Copyright 2019 iExec Blockchain Tech +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +pragma solidity ^0.5.0; + +contract IERC1271 +{ + // bytes4(keccak256("isValidSignature(bytes,bytes)") + bytes4 constant internal MAGICVALUE = 0x20c13b0b; + + /** + * @dev Should return whether the signature provided is valid for the provided data + * @param _data Arbitrary length data signed on the behalf of address(this) + * @param _signature Signature byte array associated with _data + * + * MUST return the bytes4 magic value 0x20c13b0b when function passes. + * MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5) + * MUST allow external calls + */ + // function isValidSignature( + // bytes memory _data, + // bytes memory _signature) + // public + // view + // returns (bytes4 magicValue); + + // Newer version ? From 0x V2 + function isValidSignature( + bytes32 _data, + bytes memory _signature + ) + public + view + returns (bool isValid); +} diff --git a/examples/common/python/connectors/ethereum/contracts/proxy-model/interfaces/IERC734.sol b/examples/common/python/connectors/ethereum/contracts/proxy-model/interfaces/IERC734.sol new file mode 100644 index 000000000..d6491beaf --- /dev/null +++ b/examples/common/python/connectors/ethereum/contracts/proxy-model/interfaces/IERC734.sol @@ -0,0 +1,50 @@ +/* Copyright 2019 iExec Blockchain Tech +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +pragma solidity ^0.5.0; + +contract IERC734 +{ + // 1: MANAGEMENT keys, which can manage the identity + uint256 public constant MANAGEMENT_KEY = 1; + // 2: ACTION keys, which perform actions in this identities name (signing, logins, transactions, etc.) + uint256 public constant ACTION_KEY = 2; + // 3: CLAIM signer keys, used to sign claims on other identities which need to be revokable. + uint256 public constant CLAIM_SIGNER_KEY = 3; + // 4: ENCRYPTION keys, used to encrypt data e.g. hold in claims. + uint256 public constant ENCRYPTION_KEY = 4; + + // KeyType + uint256 public constant ECDSA_TYPE = 1; + // https://medium.com/@alexberegszaszi/lets-bring-the-70s-to-ethereum-48daa16a4b51 + uint256 public constant RSA_TYPE = 2; + + // Events + event KeyAdded (bytes32 indexed key, uint256 indexed purpose, uint256 indexed keyType); + event KeyRemoved (bytes32 indexed key, uint256 indexed purpose, uint256 indexed keyType); + event ExecutionRequested(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data); + event Executed (uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data); + event ExecutionFailed (uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data); + event Approved (uint256 indexed executionId, bool approved); + + // Functions + function getKey (bytes32 _key ) external view returns (uint256[] memory purposes, uint256 keyType, bytes32 key); + function keyHasPurpose (bytes32 _key, uint256 purpose ) external view returns (bool exists); + function getKeysByPurpose(uint256 _purpose ) external view returns (bytes32[] memory keys); + function addKey (bytes32 _key, uint256 _purpose, uint256 _keyType ) external returns (bool success); + function removeKey (bytes32 _key, uint256 _purpose ) external returns (bool success); + function execute (address _to, uint256 _value, bytes calldata _data) external returns (uint256 executionId); + function approve (uint256 _id, bool _approve ) external returns (bool success); +} diff --git a/examples/common/python/connectors/ethereum/contracts/proxy-model/libs/Set.sol b/examples/common/python/connectors/ethereum/contracts/proxy-model/libs/Set.sol new file mode 100644 index 000000000..1f56672cd --- /dev/null +++ b/examples/common/python/connectors/ethereum/contracts/proxy-model/libs/Set.sol @@ -0,0 +1,91 @@ +/* Copyright 2019 iExec Blockchain Tech +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +pragma solidity ^0.5.0; + +library Set +{ + struct set + { + bytes32[] members; + mapping(bytes32 => uint256) indexes; + } + + function length(set storage _list) + internal view returns (uint256) + { + return _list.members.length; + } + + function at(set storage _list, uint256 _index) + internal view returns (bytes32) + { + return _list.members[_index - 1]; + } + + function indexOf(set storage _list, bytes32 _value) + internal view returns (uint256) + { + return _list.indexes[_value]; + } + + function contains(set storage _list, bytes32 _value) + internal view returns (bool) + { + return indexOf(_list, _value) != 0; + } + + function content(set storage _list) + internal view returns (bytes32[] memory) + { + return _list.members; + } + + function add(set storage _list, bytes32 _value) + internal returns (bool) + { + if (contains(_list, _value)) + { + return false; + } + _list.indexes[_value] = _list.members.push(_value); + return true; + } + + function remove(set storage _list, bytes32 _value) + internal returns (bool) + { + if (!contains(_list, _value)) + { + return false; + } + + uint256 i = indexOf(_list, _value); + uint256 last = length(_list); + + if (i != last) + { + bytes32 swapValue = _list.members[last - 1]; + _list.members[i - 1] = swapValue; + _list.indexes[swapValue] = i; + } + + delete _list.indexes[_value]; + --_list.members.length; + + return true; + } + +} diff --git a/examples/common/python/connectors/ethereum/contracts/proxy-model/libs/SignatureVerifier.sol b/examples/common/python/connectors/ethereum/contracts/proxy-model/libs/SignatureVerifier.sol new file mode 100644 index 000000000..83e05dd3b --- /dev/null +++ b/examples/common/python/connectors/ethereum/contracts/proxy-model/libs/SignatureVerifier.sol @@ -0,0 +1,74 @@ +/* Copyright 2019 iExec Blockchain Tech +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +pragma solidity ^0.5.0; + +import "../interfaces/IERC734.sol"; +import "../interfaces/IERC1271.sol"; + +contract SignatureVerifier +{ + function addrToKey(address _addr) + internal pure returns (bytes32) + { + return bytes32(uint256(_addr)); + } + + function checkIdentity(address _identity, address _candidate, uint256 _purpose) + internal view returns (bool valid) + { + return _identity == _candidate || IERC734(_identity).keyHasPurpose(addrToKey(_candidate), _purpose); // Simple address || ERC 734 identity contract + } + + function checkSignature( + address _identity, + bytes32 _hash, + bytes memory _signature) + internal view returns (bool) + { + return recoverCheck(_identity, _hash, _signature) || IERC1271(_identity).isValidSignature(_hash, _signature); + } + + // recoverCheck does not revert if signature has invalid format + function recoverCheck(address candidate, bytes32 hash, bytes memory sign) + internal pure returns (bool) + { + bytes32 r; + bytes32 s; + uint8 v; + if (sign.length != 65) return false; + assembly + { + r := mload(add(sign, 0x20)) + s := mload(add(sign, 0x40)) + v := byte(0, mload(add(sign, 0x60))) + } + if (v < 27) v += 27; + if (v != 27 && v != 28) return false; + return candidate == ecrecover(hash, v, r, s); + } + + function toEthSignedMessageHash(bytes32 msg_hash) + internal pure returns (bytes32) + { + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", msg_hash)); + } + + function toEthTypedStructHash(bytes32 struct_hash, bytes32 domain_separator) + internal pure returns (bytes32) + { + return keccak256(abi.encodePacked("\x19\x01", domain_separator, struct_hash)); + } +}