// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.17; import "./interface/IIdentity.sol"; import "./interface/IClaimIssuer.sol"; import "./version/Version.sol"; import "./storage/Storage.sol"; /** * @dev Implementation of the `IERC734` "KeyHolder" and the `IERC735` "ClaimHolder" interfaces * into a common Identity Contract. * This implementation has a separate contract were it declares all storage, * allowing for it to be used as an upgradable logic contract. */ contract Identity is Storage, IIdentity, Version { /** * @notice Prevent any direct calls to the implementation contract (marked by _canInteract = false). */ modifier delegatedOnly() { require(_canInteract == true, "Interacting with the library contract is forbidden."); _; } /** * @notice requires management key to call this function, or internal call */ modifier onlyManager() { require(msg.sender == address(this) || keyHasPurpose(keccak256(abi.encode(msg.sender)), 1) , "Permissions: Sender does not have management key"); _; } /** * @notice requires claim key to call this function, or internal call */ modifier onlyClaimKey() { require(msg.sender == address(this) || keyHasPurpose(keccak256(abi.encode(msg.sender)), 3) , "Permissions: Sender does not have claim signer key"); _; } /** * @notice constructor of the Identity contract * @param initialManagementKey the address of the management key at deployment * @param _isLibrary boolean value stating if the contract is library or not * calls __Identity_init if contract is not library */ constructor(address initialManagementKey, bool _isLibrary) { require(initialManagementKey != address(0), "invalid argument - zero address"); if (!_isLibrary) { __Identity_init(initialManagementKey); } else { _initialized = true; } } /** * @notice When using this contract as an implementation for a proxy, call this initializer with a delegatecall. * * @param initialManagementKey The ethereum address to be set as the management key of the ONCHAINID. */ function initialize(address initialManagementKey) external { require(initialManagementKey != address(0), "invalid argument - zero address"); __Identity_init(initialManagementKey); } /** * @dev See {IERC734-execute}. * @notice Passes an execution instruction to the keymanager. * If the sender is an ACTION key and the destination address is not the identity contract itself, then the * execution is immediately approved and performed. * If the destination address is the identity itself, then the execution would be performed immediately only if * the sender is a MANAGEMENT key. * Otherwise the execution request must be approved via the `approve` method. * @return executionId to use in the approve function, to approve or reject this execution. */ function execute(address _to, uint256 _value, bytes memory _data) external delegatedOnly override payable returns (uint256 executionId) { uint256 _executionId = _executionNonce; _executions[_executionId].to = _to; _executions[_executionId].value = _value; _executions[_executionId].data = _data; _executionNonce++; emit ExecutionRequested(_executionId, _to, _value, _data); if (keyHasPurpose(keccak256(abi.encode(msg.sender)), 1)) { approve(_executionId, true); } else if (_to != address(this) && keyHasPurpose(keccak256(abi.encode(msg.sender)), 2)){ approve(_executionId, true); } return _executionId; } /** * @dev See {IERC734-getKey}. * @notice Implementation of the getKey function from the ERC-734 standard * @param _key The public key. for non-hex and long keys, its the Keccak256 hash of the key * @return purposes Returns the full key data, if present in the identity. * @return keyType Returns the full key data, if present in the identity. * @return key Returns the full key data, if present in the identity. */ function getKey(bytes32 _key) external override view returns(uint256[] memory purposes, uint256 keyType, bytes32 key) { return (_keys[_key].purposes, _keys[_key].keyType, _keys[_key].key); } /** * @dev See {IERC734-getKeyPurposes}. * @notice gets the purposes of a key * @param _key The public key. for non-hex and long keys, its the Keccak256 hash of the key * @return _purposes Returns the purposes of the specified key */ function getKeyPurposes(bytes32 _key) external override view returns(uint256[] memory _purposes) { return (_keys[_key].purposes); } /** * @dev See {IERC734-getKeysByPurpose}. * @notice gets all the keys with a specific purpose from an identity * @param _purpose a uint256[] Array of the key types, like 1 = MANAGEMENT, 2 = ACTION, 3 = CLAIM, 4 = ENCRYPTION * @return keys Returns an array of public key bytes32 hold by this identity and having the specified purpose */ function getKeysByPurpose(uint256 _purpose) external override view returns(bytes32[] memory keys) { return _keysByPurpose[_purpose]; } /** * @dev See {IERC735-getClaimIdsByTopic}. * @notice Implementation of the getClaimIdsByTopic function from the ERC-735 standard. * used to get all the claims from the specified topic * @param _topic The identity of the claim i.e. keccak256(abi.encode(_issuer, _topic)) * @return claimIds Returns an array of claim IDs by topic. */ function getClaimIdsByTopic(uint256 _topic) external override view returns(bytes32[] memory claimIds) { return _claimsByTopic[_topic]; } /** * @notice implementation of the addKey function of the ERC-734 standard * Adds a _key to the identity. The _purpose specifies the purpose of key. Initially we propose four purposes: * 1: MANAGEMENT keys, which can manage the identity * 2: ACTION keys, which perform actions in this identities name (signing, logins, transactions, etc.) * 3: CLAIM signer keys, used to sign claims on other identities which need to be revokable. * 4: ENCRYPTION keys, used to encrypt data e.g. hold in claims. * MUST only be done by keys of purpose 1, or the identity itself. * If its the identity itself, the approval process will determine its approval. * @param _key keccak256 representation of an ethereum address * @param _type type of key used, which would be a uint256 for different key types. e.g. 1 = ECDSA, 2 = RSA, etc. * @param _purpose a uint256 specifying the key type, like 1 = MANAGEMENT, 2 = ACTION, 3 = CLAIM, 4 = ENCRYPTION * @return success Returns TRUE if the addition was successful and FALSE if not */ function addKey(bytes32 _key, uint256 _purpose, uint256 _type) public delegatedOnly onlyManager override returns (bool success) { if (_keys[_key].key == _key) { uint256[] memory _purposes = _keys[_key].purposes; for (uint keyPurposeIndex = 0; keyPurposeIndex < _purposes.length; keyPurposeIndex++) { uint256 purpose = _purposes[keyPurposeIndex]; if (purpose == _purpose) { revert("Conflict: Key already has purpose"); } } _keys[_key].purposes.push(_purpose); } else { _keys[_key].key = _key; _keys[_key].purposes = [_purpose]; _keys[_key].keyType = _type; } _keysByPurpose[_purpose].push(_key); emit KeyAdded(_key, _purpose, _type); return true; } /** * @dev See {IERC734-approve}. * @notice Approves an execution. * If the sender is an ACTION key and the destination address is not the identity contract itself, then the * approval is authorized and the operation would be performed. * If the destination address is the identity itself, then the execution would be authorized and performed only * if the sender is a MANAGEMENT key. */ function approve(uint256 _id, bool _approve) public delegatedOnly override returns (bool success) { require(_id < _executionNonce, "Cannot approve a non-existing execution"); require(!_executions[_id].executed, "Request already executed"); if(_executions[_id].to == address(this)) { require(keyHasPurpose(keccak256(abi.encode(msg.sender)), 1), "Sender does not have management key"); } else { require(keyHasPurpose(keccak256(abi.encode(msg.sender)), 2), "Sender does not have action key"); } emit Approved(_id, _approve); if (_approve == true) { _executions[_id].approved = true; // solhint-disable-next-line avoid-low-level-calls (success,) = _executions[_id].to.call{value:(_executions[_id].value)}(_executions[_id].data); if (success) { _executions[_id].executed = true; emit Executed( _id, _executions[_id].to, _executions[_id].value, _executions[_id].data ); return true; } else { emit ExecutionFailed( _id, _executions[_id].to, _executions[_id].value, _executions[_id].data ); return false; } } else { _executions[_id].approved = false; } return false; } /** * @dev See {IERC734-removeKey}. * @notice Remove the purpose from a key. */ function removeKey(bytes32 _key, uint256 _purpose) public delegatedOnly onlyManager override returns (bool success) { require(_keys[_key].key == _key, "NonExisting: Key isn't registered"); uint256[] memory _purposes = _keys[_key].purposes; uint purposeIndex = 0; while (_purposes[purposeIndex] != _purpose) { purposeIndex++; if (purposeIndex == _purposes.length) { revert("NonExisting: Key doesn't have such purpose"); } } _purposes[purposeIndex] = _purposes[_purposes.length - 1]; _keys[_key].purposes = _purposes; _keys[_key].purposes.pop(); uint keyIndex = 0; uint arrayLength = _keysByPurpose[_purpose].length; while (_keysByPurpose[_purpose][keyIndex] != _key) { keyIndex++; if (keyIndex >= arrayLength) { break; } } _keysByPurpose[_purpose][keyIndex] = _keysByPurpose[_purpose][arrayLength - 1]; _keysByPurpose[_purpose].pop(); uint keyType = _keys[_key].keyType; if (_purposes.length - 1 == 0) { delete _keys[_key]; } emit KeyRemoved(_key, _purpose, keyType); return true; } /** * @dev See {IERC735-addClaim}. * @notice Implementation of the addClaim function from the ERC-735 standard * Require that the msg.sender has claim signer key. * * @param _topic The type of claim * @param _scheme The scheme with which this claim SHOULD be verified or how it should be processed. * @param _issuer The issuers identity contract address, or the address used to sign the above signature. * @param _signature Signature which is the proof that the claim issuer issued a claim of topic for this identity. * it MUST be a signed message of the following structure: * keccak256(abi.encode(address identityHolder_address, uint256 _ topic, bytes data)) * @param _data The hash of the claim data, sitting in another * location, a bit-mask, call data, or actual data based on the claim scheme. * @param _uri The location of the claim, this can be HTTP links, swarm hashes, IPFS hashes, and such. * * @return claimRequestId Returns claimRequestId: COULD be * send to the approve function, to approve or reject this claim. * triggers ClaimAdded event. */ function addClaim( uint256 _topic, uint256 _scheme, address _issuer, bytes memory _signature, bytes memory _data, string memory _uri ) public delegatedOnly onlyClaimKey override returns (bytes32 claimRequestId) { if (_issuer != address(this)) { require(IClaimIssuer(_issuer).isClaimValid(IIdentity(address(this)), _topic, _signature, _data), "invalid claim"); } bytes32 claimId = keccak256(abi.encode(_issuer, _topic)); _claims[claimId].topic = _topic; _claims[claimId].scheme = _scheme; _claims[claimId].signature = _signature; _claims[claimId].data = _data; _claims[claimId].uri = _uri; if (_claims[claimId].issuer != _issuer) { _claimsByTopic[_topic].push(claimId); _claims[claimId].issuer = _issuer; emit ClaimAdded(claimId, _topic, _scheme, _issuer, _signature, _data, _uri); } else { emit ClaimChanged(claimId, _topic, _scheme, _issuer, _signature, _data, _uri); } return claimId; } /** * @dev See {IERC735-removeClaim}. * @notice Implementation of the removeClaim function from the ERC-735 standard * Require that the msg.sender has management key. * Can only be removed by the claim issuer, or the claim holder itself. * * @param _claimId The identity of the claim i.e. keccak256(abi.encode(_issuer, _topic)) * * @return success Returns TRUE when the claim was removed. * triggers ClaimRemoved event */ function removeClaim(bytes32 _claimId) public delegatedOnly onlyClaimKey override returns (bool success) { uint256 _topic = _claims[_claimId].topic; if (_topic == 0) { revert("NonExisting: There is no claim with this ID"); } uint claimIndex = 0; uint arrayLength = _claimsByTopic[_topic].length; while (_claimsByTopic[_topic][claimIndex] != _claimId) { claimIndex++; if (claimIndex >= arrayLength) { break; } } _claimsByTopic[_topic][claimIndex] = _claimsByTopic[_topic][arrayLength - 1]; _claimsByTopic[_topic].pop(); emit ClaimRemoved( _claimId, _topic, _claims[_claimId].scheme, _claims[_claimId].issuer, _claims[_claimId].signature, _claims[_claimId].data, _claims[_claimId].uri ); delete _claims[_claimId]; return true; } /** * @dev See {IERC735-getClaim}. * @notice Implementation of the getClaim function from the ERC-735 standard. * * @param _claimId The identity of the claim i.e. keccak256(abi.encode(_issuer, _topic)) * * @return topic Returns all the parameters of the claim for the * specified _claimId (topic, scheme, signature, issuer, data, uri) . * @return scheme Returns all the parameters of the claim for the * specified _claimId (topic, scheme, signature, issuer, data, uri) . * @return issuer Returns all the parameters of the claim for the * specified _claimId (topic, scheme, signature, issuer, data, uri) . * @return signature Returns all the parameters of the claim for the * specified _claimId (topic, scheme, signature, issuer, data, uri) . * @return data Returns all the parameters of the claim for the * specified _claimId (topic, scheme, signature, issuer, data, uri) . * @return uri Returns all the parameters of the claim for the * specified _claimId (topic, scheme, signature, issuer, data, uri) . */ function getClaim(bytes32 _claimId) public override view returns( uint256 topic, uint256 scheme, address issuer, bytes memory signature, bytes memory data, string memory uri ) { return ( _claims[_claimId].topic, _claims[_claimId].scheme, _claims[_claimId].issuer, _claims[_claimId].signature, _claims[_claimId].data, _claims[_claimId].uri ); } /** * @dev See {IERC734-keyHasPurpose}. * @notice Returns true if the key has MANAGEMENT purpose or the specified purpose. */ function keyHasPurpose(bytes32 _key, uint256 _purpose) public override view returns(bool result) { Key memory key = _keys[_key]; if (key.key == 0) return false; for (uint keyPurposeIndex = 0; keyPurposeIndex < key.purposes.length; keyPurposeIndex++) { uint256 purpose = key.purposes[keyPurposeIndex]; if (purpose == 1 || purpose == _purpose) return true; } return false; } /** * @dev Checks if a claim is valid. Claims issued by the identity are self-attested claims. They do not have a * built-in revocation mechanism and are considered valid as long as their signature is valid and they are still * stored by the identity contract. * @param _identity the identity contract related to the claim * @param claimTopic the claim topic of the claim * @param sig the signature of the claim * @param data the data field of the claim * @return claimValid true if the claim is valid, false otherwise */ function isClaimValid( IIdentity _identity, uint256 claimTopic, bytes memory sig, bytes memory data) public override virtual view returns (bool claimValid) { bytes32 dataHash = keccak256(abi.encode(_identity, claimTopic, data)); // Use abi.encodePacked to concatenate the message prefix and the message to sign. bytes32 prefixedHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)); // Recover address of data signer address recovered = getRecoveredAddress(sig, prefixedHash); // Take hash of recovered address bytes32 hashedAddr = keccak256(abi.encode(recovered)); // Does the trusted identifier have they key which signed the user's claim? // && (isClaimRevoked(_claimId) == false) if (keyHasPurpose(hashedAddr, 3)) { return true; } return false; } /** * @dev returns the address that signed the given data * @param sig the signature of the data * @param dataHash the data that was signed * returns the address that signed dataHash and created the signature sig */ function getRecoveredAddress(bytes memory sig, bytes32 dataHash) public pure returns (address addr) { bytes32 ra; bytes32 sa; uint8 va; // Check the signature length if (sig.length != 65) { return address(0); } // Divide the signature in r, s and v variables // solhint-disable-next-line no-inline-assembly assembly { ra := mload(add(sig, 32)) sa := mload(add(sig, 64)) va := byte(0, mload(add(sig, 96))) } if (va < 27) { va += 27; } address recoveredAddress = ecrecover(dataHash, va, ra, sa); return (recoveredAddress); } /** * @notice Initializer internal function for the Identity contract. * * @param initialManagementKey The ethereum address to be set as the management key of the ONCHAINID. */ // solhint-disable-next-line func-name-mixedcase function __Identity_init(address initialManagementKey) internal { require(!_initialized || _isConstructor(), "Initial key was already setup."); _initialized = true; _canInteract = true; bytes32 _key = keccak256(abi.encode(initialManagementKey)); _keys[_key].key = _key; _keys[_key].purposes = [1]; _keys[_key].keyType = 1; _keysByPurpose[1].push(_key); emit KeyAdded(_key, 1, 1); } /** * @notice Computes if the context in which the function is called is a constructor or not. * * @return true if the context is a constructor. */ function _isConstructor() private view returns (bool) { address self = address(this); uint256 cs; // solhint-disable-next-line no-inline-assembly assembly { cs := extcodesize(self) } return cs == 0; } }