pragma solidity ^0.4.21; import "./ERC725.sol"; import "./ERC735.sol"; import "../common/MessageSigned.sol"; /** * @title Self sovereign Identity * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) */ contract Identity is ERC725, ERC735, MessageSigned { uint256 public nonce; address public recoveryContract; uint256 salt; mapping (bytes32 => uint256) indexes; mapping (bytes32 => Key) keys; mapping (bytes32 => bool) isKeyPurpose; mapping (bytes32 => bytes32[]) keysByPurpose; mapping (uint256 => Transaction) pendingTx; mapping (uint256 => uint256) purposeThreshold; mapping (bytes32 => Claim) claims; mapping (uint256 => bytes32[]) claimsByType; struct Transaction { uint256 approverCount; address to; uint256 value; bytes data; mapping(bytes32 => bool) approvals; } /** * @notice requires called by identity itself, otherwise forward to execute process */ modifier managementOnly { if(msg.sender == address(this)) { _; } else { _execute(keccak256(msg.sender), address(this), 0, msg.data); } } /** * @notice requires called by recovery address */ modifier recoveryOnly { require( recoveryContract != address(0) && msg.sender == recoveryContract ); _; } /** * @notice requires `_signature` from a `_key` with `_messageHash` * @param _key key expected out from `_signature` of `_messageHash` * @param _messageHash message signed in `_signature` by `_key` * @param _signature `_messageHash` signed by `_key` */ modifier keyMessageSigned ( bytes32 _key, bytes32 _messageHash, bytes _signature ) { require( _key == keccak256( recoverAddress( getSignHash(_messageHash), _signature ), salt ) ); _; } /** * @notice constructor builds identity with provided `_keys` * or uses `msg.sender` as first MANAGEMENT + ACTION key * @param _keys Keys to add * @param _purposes `_keys` corresponding purposes * @param _types `_keys` corresponding types * @param _managerThreshold how much keys needs to sign management calls * @param _actorThreshold how much keys need to sign action management calls * @param _recoveryContract Option to initialize with recovery defined */ constructor( bytes32[] _keys, uint256[] _purposes, uint256[] _types, uint256 _managerThreshold, uint256 _actorThreshold, address _recoveryContract ) public { bytes32[] memory initKeys = _keys; uint256[] memory initPurposes = _purposes; uint256[] memory initTypes = _types; uint256 managerThreshold = _managerThreshold; if (_keys.length == 0) { initKeys = new bytes32[](2); initPurposes = new uint256[](2); initTypes = new uint256[](2); initKeys[0] = keccak256(msg.sender); initKeys[1] = initKeys[0]; initPurposes[0] = MANAGEMENT_KEY; initPurposes[1] = ACTION_KEY; initTypes[0] = 0; initTypes[1] = 0; managerThreshold = 1; } _constructIdentity( initKeys, initPurposes, initTypes, managerThreshold, _actorThreshold, _recoveryContract ); } /** * @notice default function allows deposit of ETH */ function () public payable { } //////////////// // Execute calls and multisig approval //////////////// /** * @notice execute (or request) call * @param _to destination of call * @param _value amount of ETH in call * @param _data data */ function execute( address _to, uint256 _value, bytes _data ) public returns (uint256 txId) { txId = _execute(keccak256(msg.sender), _to, _value, _data); } /** * @notice approve a multisigned execution * @param _txId unique id multisig transaction * @param _approval approve (true) or reject (false) */ function approve(uint256 _txId, bool _approval) public returns (bool success) { return _approveRequest(keccak256(msg.sender), _txId, _approval); } //////////////// // Message Signed functions //////////////// /** * @notice execute (or request) call using ethereum signed message as authorization * @param _to destination of call * @param _value amount of ETH in call * @param _data data * @param _nonce current nonce * @param _key key authorizing the call * @param _signature signature of key */ function executeMessageSigned( address _to, uint256 _value, bytes _data, uint256 _nonce, bytes32 _key, bytes _signature ) public keyMessageSigned( _key, keccak256( address(this), bytes4(keccak256("execute(address,uint256,bytes)")), _to, _value, _data, _nonce ), _signature ) returns (uint256 txId) { require(_nonce == nonce); txId = _execute(_key, _to, _value, _data); } /** * @notice approve a multisigned execution using ethereum signed message as authorization * @param _txId unique id multisig transaction * @param _approval approve (true) or reject (false) * @param _key key authorizing the call * @param _signature signature of key */ function approveMessageSigned( uint256 _txId, bool _approval, bytes32 _key, bytes _signature ) public keyMessageSigned( _key, keccak256( address(this), bytes4(keccak256("approve(uint256,bool)")), _txId, _approval ), _signature ) returns (bool success) { return _approveRequest(_key, _txId, _approval); } //////////////// // Management functions //////////////// /** * @notice Adds a _key to the identity. The `_purpose` * @param _key key hash being added * @param _purpose specifies the purpose of key. * @param _type inform type of key */ function addKey( bytes32 _key, uint256 _purpose, uint256 _type ) public managementOnly returns (bool success) { _addKey(_key, _purpose, _type, salt); return true; } /** * @notice Replaces one `_oldKey` with other `_newKey` * @param _oldKey key to remove * @param _newKey key to add * @param _newType inform type of `_newKey` */ function replaceKey( bytes32 _oldKey, bytes32 _newKey, uint256 _newType ) public managementOnly returns (bool success) { return _replaceKey(_oldKey, _newKey, _newType, salt); } /** * @notice Removes `_purpose` of `_key` * @param _key key to remove * @param _purpose purpose to remove */ function removeKey( bytes32 _key, uint256 _purpose ) public managementOnly returns (bool success) { _removeKey(_key, _purpose, salt); return true; } /** * @notice Defines minimum approval required by key type * @param _purpose select purpose * @param _minimumApprovals select how much signatures needed */ function setMinimumApprovalsByKeyType( uint256 _purpose, uint256 _minimumApprovals ) public managementOnly { require(_minimumApprovals > 0); require(_minimumApprovals <= keysByPurpose[keccak256(_purpose, salt)].length); purposeThreshold[_purpose] = _minimumApprovals; } /** * @notice Defines recovery address. This is one time only action. * @param _recoveryContract address of recovery contract */ function setupRecovery(address _recoveryContract) public managementOnly { require(recoveryContract == address(0)); recoveryContract = _recoveryContract; } //////////////// // Claim related //////////////// function addClaim( uint256 _topic, uint256 _scheme, address _issuer, bytes _signature, bytes _data, string _uri ) public returns (bytes32 claimHash) { claimHash = keccak256(_issuer, _topic); if (msg.sender == address(this)) { if (claims[claimHash].topic > 0) { _modifyClaim(claimHash, _topic, _scheme, _issuer, _signature, _data, _uri); } else { _includeClaim(claimHash, _topic, _scheme, _issuer, _signature, _data, _uri); } } else { require(keyHasPurpose(keccak256(msg.sender), CLAIM_SIGNER_KEY)); _requestApproval(0, address(this), 0, msg.data); emit ClaimRequested( claimHash, _topic, _scheme, _issuer, _signature, _data, _uri ); } } function removeClaim(bytes32 _claimId) public returns (bool success) { Claim memory c = claims[_claimId]; require( msg.sender == c.issuer || msg.sender == address(this) ); // MUST only be done by the issuer of the claim, or KEYS OF PURPOSE 1, or the identity itself. // TODO If its the identity itself, the approval process will determine its approval. uint256 claimIdTopicPos = indexes[_claimId]; delete indexes[_claimId]; bytes32[] storage claimsTopicArr = claimsByType[c.topic]; bytes32 replacer = claimsTopicArr[claimsTopicArr.length - 1]; claimsTopicArr[claimIdTopicPos] = replacer; indexes[replacer] = claimIdTopicPos; delete claims[_claimId]; claimsTopicArr.length--; emit ClaimRemoved(_claimId, c.topic, c.scheme, c.issuer, c.signature, c.data, c.uri); return true; } //////////////// // Recovery methods //////////////// /** * @notice Increase salt for hashing storage pointer of keys and add `_recoveryNewKey` * @param _recoveryNewKey new key being defined */ function recoveryReset(bytes32 _recoveryNewKey) public recoveryOnly { salt++; _addKey(_recoveryNewKey, MANAGEMENT_KEY, 0, salt); _addKey(_recoveryNewKey, ACTION_KEY, 0, salt); purposeThreshold[MANAGEMENT_KEY] = 1; } //////////////// // Public Views //////////////// function getKey( bytes32 _key ) public view returns(uint256[] purposes, uint256 keyType, bytes32 key) { Key storage myKey = keys[keccak256(_key, salt)]; return (myKey.purposes, myKey.keyType, myKey.key); } function keyHasPurpose(bytes32 _key, uint256 _purpose) public view returns (bool exists) { return isKeyPurpose[keccak256(keccak256(_key, salt), _purpose)]; } function getKeyPurpose(bytes32 _key) public view returns(uint256[] purpose) { return keys[keccak256(_key, salt)].purposes; } function getKeysByPurpose(uint256 _purpose) public view returns(bytes32[]) { return keysByPurpose[keccak256(_purpose, salt)]; } function getClaim(bytes32 _claimId) public view returns( uint256 topic, uint256 scheme, address issuer, bytes signature, bytes data, string uri ) { Claim memory _claim = claims[_claimId]; return (_claim.topic, _claim.scheme, _claim.issuer, _claim.signature, _claim.data, _claim.uri); } function getClaimIdsByTopic(uint256 _topic) public view returns(bytes32[] claimIds) { return claimsByType[_topic]; } //////////////// // Internal methods //////////////// function _constructIdentity( bytes32[] _keys, uint256[] _purposes, uint256[] _types, uint256 _managerThreshold, uint256 _actorThreshold, address _recoveryContract ) internal { uint256 _salt = salt; uint len = _keys.length; require(len > 0); require(purposeThreshold[MANAGEMENT_KEY] == 0, "Already Initialized (1)"); require(keysByPurpose[keccak256(MANAGEMENT_KEY, _salt)].length == 0, "Already Initialized (2)"); require(len == _purposes.length, "Wrong _purposes lenght"); uint managersAdded = 0; for(uint i = 0; i < len; i++) { uint256 _purpose = _purposes[i]; _addKey(_keys[i], _purpose, _types[i], _salt); if(_purpose == MANAGEMENT_KEY) { managersAdded++; } } require(_managerThreshold <= managersAdded, "managers added is less then required"); purposeThreshold[MANAGEMENT_KEY] = _managerThreshold; purposeThreshold[ACTION_KEY] = _actorThreshold; recoveryContract = _recoveryContract; } function _execute( bytes32 _key, address _to, uint256 _value, bytes _data ) internal returns (uint256 txId) { uint256 requiredPurpose = _to == address(this) ? MANAGEMENT_KEY : ACTION_KEY; require(keyHasPurpose(_key, requiredPurpose)); if (purposeThreshold[requiredPurpose] == 1) { txId = nonce++; _commitCall(txId, _to, _value, _data); } else { txId = _requestApproval(_key, _to, _value, _data); } } function _commitCall( uint256 _txId, address _to, uint256 _value, bytes _data ) internal returns(bool success) { success = _to.call.value(_value)(_data); if (success) { emit Executed(_txId, _to, _value, _data); } else { emit ExecutionFailed(_txId, _to, _value, _data); } } function _requestApproval( bytes32 _key, address _to, uint256 _value, bytes _data ) internal returns (uint256 txId) { txId = nonce++; pendingTx[txId] = Transaction({ approverCount: _key == 0 ? 0 : 1, to: _to, value: _value, data: _data }); if (_key != 0) { pendingTx[txId].approvals[_key] = true; } emit ExecutionRequested(txId, _to, _value, _data); } //////////////// // Private methods //////////////// function _approveRequest( bytes32 _key, uint256 _txId, bool _approval ) private returns(bool success) //(?) should return approved instead of success? { Transaction memory approvedTx = pendingTx[_txId]; require(approvedTx.approverCount > 0); uint256 requiredKeyPurpose = approvedTx.to == address(this) ? MANAGEMENT_KEY : ACTION_KEY; require(keyHasPurpose(_key, requiredKeyPurpose)); require(pendingTx[_txId].approvals[_key] != _approval); if (_approval) { if (approvedTx.approverCount + 1 == purposeThreshold[requiredKeyPurpose]) { delete pendingTx[_txId]; emit Approved(_txId, _approval); return _commitCall(_txId, approvedTx.to, approvedTx.value, approvedTx.data); } else { pendingTx[_txId].approvals[_key] = true; pendingTx[_txId].approverCount++; } } else { delete pendingTx[_txId].approvals[_key]; if (pendingTx[_txId].approverCount == 1) { delete pendingTx[_txId]; emit Approved(_txId, _approval); } else { pendingTx[_txId].approverCount--; } } } function _addKey( bytes32 _key, uint256 _purpose, uint256 _type, uint256 _salt ) private { require(_key != 0); require(_purpose != 0); bytes32 keySaltedHash = keccak256(_key, _salt); //key storage pointer bytes32 saltedKeyPurposeHash = keccak256(keySaltedHash, _purpose); // accounts by purpose hash element index pointer require(!isKeyPurpose[saltedKeyPurposeHash]); //cannot add a key already added isKeyPurpose[saltedKeyPurposeHash] = true; //set authorization uint256 keyElementIndex = keysByPurpose[saltedKeyPurposeHash].push(_key) - 1; //add key to list by purpose indexes[saltedKeyPurposeHash] = keyElementIndex; //save index of key in list by purpose if (keys[keySaltedHash].key == 0) { //is a new key uint256[] memory purposes = new uint256[](1); //create new array with first purpose purposes[0] = _purpose; keys[keySaltedHash] = Key(purposes,_type,_key); //add new key } else { uint256 addedPurposeElementIndex = keys[keySaltedHash].purposes.push(_purpose) - 1; //add purpose to key bytes32 keyPurposeSaltedHash = keccak256(_key, _purpose, _salt); //index of purpose in key pointer indexes[keyPurposeSaltedHash] = addedPurposeElementIndex; //save index } emit KeyAdded(_key, _purpose, _type); } function _removeKey( bytes32 _key, uint256 _purpose, uint256 _salt ) private { bytes32 keySaltedHash = keccak256(_key, _salt); // key storage pointer _removeKeyFromPurposes(keySaltedHash, _purpose, _salt); //remove key purposes array purpose element Key storage myKey = keys[keySaltedHash]; //load Key storage pointer uint256 _type = myKey.keyType; //save type for case key deleted uint256 replacerPurposeIndex = myKey.purposes.length; //load amount of purposes bytes32 keyPurposeSaltedHash = keccak256(_key, _purpose, _salt); //account purpose array element index uint256 removedPurposeIndex = indexes[keyPurposeSaltedHash]; //read old index delete indexes[keyPurposeSaltedHash]; //delete key's purpose index if (replacerPurposeIndex > 1) { //is not the last key replacerPurposeIndex--; //move to last element pos if(removedPurposeIndex != replacerPurposeIndex) { //removed element is not last element uint256 replacerPurpose = myKey.purposes[replacerPurposeIndex]; //take last element myKey.purposes[removedPurposeIndex] = replacerPurpose; //replace removed element with replacer element indexes[keccak256(_key, replacerPurpose, _salt)] = removedPurposeIndex; //update index } myKey.purposes.length--; //remove last element } else { //is the last purpose delete keys[keySaltedHash]; //drop this Key } emit KeyRemoved(_key, _purpose, _type); } function _removeKeyFromPurposes( bytes32 keySaltedHash, uint256 _purpose, uint256 _salt ) private { bytes32 purposeSaltedHash = keccak256(_purpose, _salt); // salted accounts by purpose array index pointer // forbidden to remove last management key if (_purpose == MANAGEMENT_KEY) { require(purposeThreshold[MANAGEMENT_KEY] <= keysByPurpose[purposeSaltedHash].length-1); } bytes32 saltedKeyPurposeHash = keccak256(keySaltedHash, _purpose); // accounts by purpose hash element index pointer require(isKeyPurpose[saltedKeyPurposeHash]); //not possible to remove what not exists delete isKeyPurpose[saltedKeyPurposeHash]; //remove authorization // remove keys by purpose array key element uint256 removedKeyIndex = indexes[saltedKeyPurposeHash]; // read old key element index delete indexes[saltedKeyPurposeHash]; // delete key index uint256 replacerKeyIndex = keysByPurpose[purposeSaltedHash].length - 1; // replacer is last element if (removedKeyIndex != replacerKeyIndex) { // deleted not the last element, replace deleted by last element bytes32 replacerKey = keysByPurpose[purposeSaltedHash][replacerKeyIndex]; // get replacer key keysByPurpose[purposeSaltedHash][removedKeyIndex] = replacerKey; // overwrite removed index by replacer indexes[keccak256(keccak256(replacerKey, _salt), _purpose)] = removedKeyIndex; // update saltedKeyPurposeHash index of replacer } keysByPurpose[purposeSaltedHash].length--; // remove last element } /** * @notice Replaces one `_oldKey` with other `_newKey` * @param _oldKey key to remove * @param _newKey key to add * @param _newType inform type of `_newKey` * @param _salt current salt */ function _replaceKey( bytes32 _oldKey, bytes32 _newKey, uint256 _newType, uint256 _salt ) private returns (bool success) { bytes32 newKeySaltedHash = keccak256(_newKey, _salt); // key storage pointer if (_oldKey == _newKey) { //not replacing key, just keyType keys[newKeySaltedHash].keyType == _newType; return true; } bytes32 oldKeySaltedHash = keccak256(_oldKey, _salt); // key storage pointer Key memory oldKey = keys[oldKeySaltedHash]; delete keys[oldKeySaltedHash]; uint256 len = oldKey.purposes.length; for (uint i = 0; i < len; i++) { _replaceKeyPurpose(oldKeySaltedHash, oldKeySaltedHash, _oldKey, _newKey, oldKey.purposes[i], _salt); } keys[newKeySaltedHash] = Key(oldKey.purposes, _newType, _newKey); //add new key return true; } function _replaceKeyPurpose( bytes32 newKeySaltedHash, bytes32 oldKeySaltedHash, bytes32 _oldKey, bytes32 _newKey, uint256 _purpose, uint256 _salt ) internal { bytes32 purposeSaltedHash = keccak256(_purpose, _salt); // salted accounts by purpose array index pointer bytes32 saltedOldKeyPurposeHash = keccak256(oldKeySaltedHash, _purpose); // accounts by purpose hash element index pointer bytes32 saltedNewKeyPurposeHash = keccak256(newKeySaltedHash, _purpose); // accounts by purpose hash element index pointer bytes32 oldKeyPurposeSaltedHash = keccak256(_oldKey, _purpose, _salt); //account purpose array element index bytes32 newKeyPurposeSaltedHash = keccak256(_newKey, _purpose, _salt); //account purpose array element index delete isKeyPurpose[saltedOldKeyPurposeHash]; //clear oldKey auth isKeyPurpose[saltedNewKeyPurposeHash] = true; //set newKey auth uint256 replacedKeyElementIndex = indexes[saltedOldKeyPurposeHash]; delete indexes[saltedOldKeyPurposeHash]; keysByPurpose[purposeSaltedHash][replacedKeyElementIndex] = _newKey; //replace key at list by purpose indexes[saltedNewKeyPurposeHash] = replacedKeyElementIndex; // save index indexes[newKeyPurposeSaltedHash] = indexes[oldKeyPurposeSaltedHash]; //transfer key purposes list index delete indexes[oldKeyPurposeSaltedHash]; } function _includeClaim( bytes32 _claimHash, uint256 _topic, uint256 _scheme, address _issuer, bytes _signature, bytes _data, string _uri ) private { claims[_claimHash] = Claim( { topic: _topic, scheme: _scheme, issuer: _issuer, signature: _signature, data: _data, uri: _uri } ); indexes[_claimHash] = claimsByType[_topic].length; claimsByType[_topic].push(_claimHash); emit ClaimAdded( _claimHash, _topic, _scheme, _issuer, _signature, _data, _uri ); } function _modifyClaim( bytes32 _claimHash, uint256 _topic, uint256 _scheme, address _issuer, bytes _signature, bytes _data, string _uri ) private { require(msg.sender == _issuer); claims[_claimHash] = Claim({ topic: _topic, scheme: _scheme, issuer: _issuer, signature: _signature, data: _data, uri: _uri }); emit ClaimChanged( _claimHash, _topic, _scheme, _issuer, _signature, _data, _uri ); } }