Skip to content

Commit

Permalink
✅(identities) Add support for ECDSA Account Abstraction
Browse files Browse the repository at this point in the history
- New method executeSigned to perfom an execution on the identity using a signature (if signer is not authorized to execute the action, the executeSigned will create a pending execution).
- New method approveSigned to approve a pending execution using a signature.
  • Loading branch information
Nakasar committed Oct 11, 2024
1 parent 2461768 commit e01b3ff
Show file tree
Hide file tree
Showing 5 changed files with 750 additions and 204 deletions.
2 changes: 1 addition & 1 deletion contracts/ClaimIssuer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ contract ClaimIssuer is IClaimIssuer, Identity {
/**
* @dev See {IClaimIssuer-revokeClaim}.
*/
function revokeClaim(bytes32 _claimId, address _identity) external override delegatedOnly onlyManager returns(bool) {
function revokeClaim(bytes32 _claimId, address payable _identity) external override delegatedOnly onlyManager returns(bool) {
uint256 foundClaimTopic;
uint256 scheme;
address issuer;
Expand Down
155 changes: 125 additions & 30 deletions contracts/Identity.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "./interface/IIdentity.sol";
import "./interface/IClaimIssuer.sol";
import "./version/Version.sol";
import "./storage/Storage.sol";
import "hardhat/console.sol";

/**
* @dev Implementation of the `IERC734` "KeyHolder" and the `IERC735` "ClaimHolder" interfaces
Expand Down Expand Up @@ -92,10 +93,47 @@ contract Identity is Storage, IIdentity, Version {
emit ExecutionRequested(_executionId, _to, _value, _data);

if (keyHasPurpose(keccak256(abi.encode(msg.sender)), 1)) {
approve(_executionId, true);
_approveAndExecute(_executionId, true);
}
else if (_to != address(this) && keyHasPurpose(keccak256(abi.encode(msg.sender)), 2)){
approve(_executionId, true);
_approveAndExecute(_executionId, true);
}

return _executionId;
}

/**
* @dev See {IERC734-execute}.
* @notice Passes an execution instruction to the keymanager, using signatures instead of sender verification.
* 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.
* @param _keyType The type of key used for the signature, a uint256 for different key types. 1 = ECDSA, 2 = RSA, 3 = P256.
* @return executionId to use in the approve function, to approve or reject this execution.
*/
function executeSigned(address _to, uint256 _value, bytes memory _data, uint256 _keyType, uint8 v, bytes32 r, bytes32 s)
external
delegatedOnly
override
payable
returns (uint256 executionId) {
bytes32 executionSigner = recoverSignerForExecution(_to, _value, _data, _keyType, v, r, s);

uint256 _executionId = _executionNonce;
_executions[_executionId].to = _to;
_executions[_executionId].value = _value;
_executions[_executionId].data = _data;
_executionNonce++;

emit ExecutionRequested(_executionId, _to, _value, _data);

if (keyHasPurpose(executionSigner, 1)) {
_approveAndExecute(_executionId, true);
}
else if (_to != address(this) && keyHasPurpose(executionSigner, 2)){
_approveAndExecute(_executionId, true);
}

return _executionId;
Expand Down Expand Up @@ -233,39 +271,27 @@ contract Identity is Storage, IIdentity, Version {
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);
return _approveAndExecute(_id, _approve);
}

if (success) {
_executions[_id].executed = true;
function approveSigned(uint256 _id, bool _approve, uint256 _keyType, uint8 v, bytes32 r, bytes32 s)
public
delegatedOnly
override
returns (bool success)
{
require(_id < _executionNonce, "Cannot approve a non-existing execution");
require(!_executions[_id].executed, "Request already executed");

emit Executed(
_id,
_executions[_id].to,
_executions[_id].value,
_executions[_id].data
);
bytes32 executionSigner = recoverSignerForPendingExecution( _id, _executions[_id].to, _executions[_id].value, _executions[_id].data, _keyType, v, r, s);

return true;
} else {
emit ExecutionFailed(
_id,
_executions[_id].to,
_executions[_id].value,
_executions[_id].data
);

return false;
}
if(_executions[_id].to == address(this)) {
require(keyHasPurpose(executionSigner, 1), "Sender does not have management key");
} else {
_executions[_id].approved = false;
require(keyHasPurpose(executionSigner, 2), "Sender does not have action key");
}
return false;

return _approveAndExecute(_id, _approve);
}

/**
Expand Down Expand Up @@ -565,6 +591,8 @@ contract Identity is Storage, IIdentity, Version {
return (recoveredAddress);
}

receive() external payable {}

/**
* @notice Initializer internal function for the Identity contract.
*
Expand Down Expand Up @@ -596,4 +624,71 @@ contract Identity is Storage, IIdentity, Version {
assembly { cs := extcodesize(self) }
return cs == 0;
}

function recoverSignerForExecution(address _to, uint256 _value, bytes memory _data, uint256 _keyType, uint8 v, bytes32 r, bytes32 s) internal delegatedOnly view returns(bytes32 keyHash) {
if (_keyType == 1) {
bytes32 dataHash = keccak256(abi.encode(_to, _value, _data));
bytes32 prefixedHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash));
address recovered = ecrecover(prefixedHash, v, r, s);

return keccak256(abi.encode(recovered));
} else if (_keyType == 3) {
revert("Not implemented.");
} else {
revert("Invalid key type");
}
}

function recoverSignerForPendingExecution(uint256 _id, address _to, uint256 _value, bytes memory _data, uint256 _keyType, uint8 v, bytes32 r, bytes32 s) internal delegatedOnly view returns(bytes32 keyHash) {
if (_keyType == 1) {
bytes32 dataHash = keccak256(abi.encode(address(this), _id, _to, _value, _data));
bytes32 prefixedHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash));
address recovered = ecrecover(prefixedHash, v, r, s);

return keccak256(abi.encode(recovered));
} else if (_keyType == 3) {
revert("Not implemented.");
} else {
revert("Invalid key type");
}
}

function _approveAndExecute(uint256 _id, bool _approve) internal delegatedOnly returns (bool success) {
require(_id < _executionNonce, "Cannot approve a non-existing execution");
require(!_executions[_id].executed, "Request already executed");

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;
}
}
2 changes: 1 addition & 1 deletion contracts/interface/IClaimIssuer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface IClaimIssuer is IIdentity {
* @param _identity the address of the identity contract
* @return isRevoked true when the claim is revoked
*/
function revokeClaim(bytes32 _claimId, address _identity) external returns(bool);
function revokeClaim(bytes32 _claimId, address payable _identity) external returns(bool);

/**
* @dev Revoke a claim previously issued, the claim is no longer considered as valid after revocation.
Expand Down
21 changes: 21 additions & 0 deletions contracts/interface/IERC734.sol
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ interface IERC734 {
*/
function approve(uint256 _id, bool _approve) external returns (bool success);

/**
* @dev Approves an execution using signatures.
*
* Triggers Event: `Approved`
* Triggers on execution successful Event: `Executed`
* Triggers on execution failure Event: `ExecutionFailed`
*/
function approveSigned(uint256 _id, bool _approve, uint256 _keyType, uint8 v, bytes32 r, bytes32 s) external returns (bool success);

/**
* @dev Removes _purpose for _key from the identity.
*
Expand All @@ -89,6 +98,18 @@ interface IERC734 {
*/
function execute(address _to, uint256 _value, bytes calldata _data) external payable returns (uint256 executionId);

/**
* @dev Passes an execution instruction to an ERC734 identity, using signatures instead of sender verification.
* How the execution is handled is up to the identity implementation:
* An execution COULD be requested and require `approve` to be called with one or more keys of purpose 1 or 2 to
* approve this execution.
* Execute COULD be used as the only accessor for `addKey` and `removeKey`.
*
* Triggers Event: ExecutionRequested
* Triggers on direct execution Event: Executed
*/
function executeSigned(address _to, uint256 _value, bytes calldata _data, uint256 _keyType, uint8 v, bytes32 r, bytes32 s) external payable returns (uint256 executionId);

/**
* @dev Returns the full key data, if present in the identity.
*/
Expand Down
Loading

0 comments on commit e01b3ff

Please sign in to comment.