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

✨(Account Abstraction) Enable WebAuthN/PassKey signatures #105

Draft
wants to merge 8 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions contracts/ClaimIssuer.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.17;
pragma solidity 0.8.27;

import "./interface/IClaimIssuer.sol";

Check warning on line 4 in contracts/ClaimIssuer.sol

View workflow job for this annotation

GitHub Actions / Lint (16.x)

global import of path ./interface/IClaimIssuer.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)
import "./Identity.sol";

Check warning on line 5 in contracts/ClaimIssuer.sol

View workflow job for this annotation

GitHub Actions / Lint (16.x)

global import of path ./Identity.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)

contract ClaimIssuer is IClaimIssuer, Identity {
mapping (bytes => bool) public revokedClaims;
Expand All @@ -14,7 +14,7 @@
* @dev See {IClaimIssuer-revokeClaimBySignature}.
*/
function revokeClaimBySignature(bytes calldata signature) external override delegatedOnly onlyManager {
require(!revokedClaims[signature], "Conflict: Claim already revoked");

Check warning on line 17 in contracts/ClaimIssuer.sol

View workflow job for this annotation

GitHub Actions / Lint (16.x)

GC: Use Custom Errors instead of require statements

revokedClaims[signature] = true;

Expand All @@ -24,7 +24,7 @@
/**
* @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 All @@ -33,7 +33,7 @@

( foundClaimTopic, scheme, issuer, sig, data, ) = Identity(_identity).getClaim(_claimId);

require(!revokedClaims[sig], "Conflict: Claim already revoked");

Check warning on line 36 in contracts/ClaimIssuer.sol

View workflow job for this annotation

GitHub Actions / Lint (16.x)

GC: Use Custom Errors instead of require statements

revokedClaims[sig] = true;
emit ClaimRevoked(sig);
Expand Down
211 changes: 178 additions & 33 deletions contracts/Identity.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.17;
pragma solidity 0.8.27;

import "./interface/IIdentity.sol";
import "./interface/IClaimIssuer.sol";
Expand Down Expand Up @@ -56,6 +56,8 @@
}
}

receive() external payable {}

/**
* @notice When using this contract as an implementation for a proxy, call this initializer with a delegatecall.
*
Expand Down Expand Up @@ -92,10 +94,52 @@
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 nonce, uint256 _keyType, uint8 v, bytes32 r, bytes32 s)

Check failure on line 117 in contracts/Identity.sol

View workflow job for this annotation

GitHub Actions / Lint (16.x)

Line length must be no more than 130 but current length is 139
external
delegatedOnly
override
payable
returns (uint256 executionId) {
bytes32 executionSigner = _recoverSignerForExecution(_to, _value, _data, nonce, _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)) {
require(nonce == _operationNonce, "Invalid nonce");
_operationNonce++;

_approveAndExecute(_executionId, true);
} else if (_to != address(this) && keyHasPurpose(executionSigner, 2)){
require(nonce == _operationNonce, "Invalid nonce");
_operationNonce++;

_approveAndExecute(_executionId, true);
}

return _executionId;
Expand Down Expand Up @@ -233,39 +277,36 @@
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;
return _approveAndExecute(_id, _approve);
}

emit Executed(
_id,
_executions[_id].to,
_executions[_id].value,
_executions[_id].data
);
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");

return true;
} else {
emit ExecutionFailed(
_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 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 @@ -354,7 +395,12 @@
returns (bytes32 claimRequestId)
{
if (_issuer != address(this)) {
require(IClaimIssuer(_issuer).isClaimValid(IIdentity(address(this)), _topic, _signature, _data), "invalid claim");
require(IClaimIssuer(_issuer).isClaimValid(
IIdentity(address(this)),
_topic,
_signature,
_data),
"invalid claim");
}

bytes32 claimId = keccak256(abi.encode(_issuer, _topic));
Expand Down Expand Up @@ -511,7 +557,9 @@
{
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));
bytes32 prefixedHash = keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)
);

// Recover address of data signer
address recovered = getRecoveredAddress(sig, prefixedHash);
Expand Down Expand Up @@ -584,6 +632,103 @@
emit KeyAdded(_key, 1, 1);
}

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;
}

function _recoverSignerForExecution(
address _to,
uint256 _value,
bytes memory _data,
uint256 _nonce,
uint256 _keyType,
uint8 v,
bytes32 r,
bytes32 s
) internal delegatedOnly view returns(bytes32 keyHash) {
if (_keyType == 1) {
bytes32 dataHash = keccak256(abi.encode(address(this), _to, _value, _data, _nonce));
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");
}
}

/**
* @notice Return the operation nonce (for signed operations).
* @return The next sequential nonce.
*/
function getNonce() public view virtual returns (uint256) {

Check failure on line 728 in contracts/Identity.sol

View workflow job for this annotation

GitHub Actions / Lint (16.x)

Function order is incorrect, public view function can not go after internal view function (line 699)
return _operationNonce;
}

/**
* @notice Computes if the context in which the function is called is a constructor or not.
*
Expand Down
2 changes: 1 addition & 1 deletion contracts/Test.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.17;
pragma solidity 0.8.27;

contract Test {} // solhint-disable-line
2 changes: 1 addition & 1 deletion contracts/_testContracts/VerifierUser.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* solhint-disable */

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.17;
pragma solidity 0.8.27;

import "../verifiers/Verifier.sol";

Expand Down
4 changes: 2 additions & 2 deletions contracts/factory/IdFactory.sol
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.17;
pragma solidity 0.8.27;

import "../proxy/IdentityProxy.sol";

Check warning on line 4 in contracts/factory/IdFactory.sol

View workflow job for this annotation

GitHub Actions / Lint (16.x)

global import of path ../proxy/IdentityProxy.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)
import "./IIdFactory.sol";

Check warning on line 5 in contracts/factory/IdFactory.sol

View workflow job for this annotation

GitHub Actions / Lint (16.x)

global import of path ./IIdFactory.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)
import "../interface/IERC734.sol";

Check warning on line 6 in contracts/factory/IdFactory.sol

View workflow job for this annotation

GitHub Actions / Lint (16.x)

global import of path ../interface/IERC734.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)
import "@openzeppelin/contracts/access/Ownable.sol";

Check warning on line 7 in contracts/factory/IdFactory.sol

View workflow job for this annotation

GitHub Actions / Lint (16.x)

global import of path @openzeppelin/contracts/access/Ownable.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)

contract IdFactory is IIdFactory, Ownable {

mapping(address => bool) private _tokenFactories;

// address of the _implementationAuthority contract making the link to the implementation contract
address private immutable _implementationAuthority;

Check warning on line 14 in contracts/factory/IdFactory.sol

View workflow job for this annotation

GitHub Actions / Lint (16.x)

Immutable variables name are set to be in capitalized SNAKE_CASE

// as it is not possible to deploy 2 times the same contract address, this mapping allows us to check which
// salt is taken and which is not
Expand All @@ -31,8 +31,8 @@


// setting
constructor (address implementationAuthority) {
constructor (address implementationAuthority) Ownable(msg.sender) {
require(implementationAuthority != address(0), "invalid argument - zero address");

Check warning on line 35 in contracts/factory/IdFactory.sol

View workflow job for this annotation

GitHub Actions / Lint (16.x)

GC: Use Custom Errors instead of require statements
_implementationAuthority = implementationAuthority;
}

Expand Down
26 changes: 13 additions & 13 deletions contracts/gateway/Gateway.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.17;
pragma solidity 0.8.27;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "../factory/IdFactory.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";

using ECDSA for bytes32;
using MessageHashUtils for bytes32;

/// A required parameter was set to the Zero address.
error ZeroAddress();
Expand Down Expand Up @@ -42,7 +45,7 @@ contract Gateway is Ownable {
* @dev Constructor for the ONCHAINID Factory Gateway.
* @param idFactoryAddress the address of the factory to operate (the Gateway must be owner of the Factory).
*/
constructor(address idFactoryAddress, address[] memory signersToApprove) Ownable() {
constructor(address idFactoryAddress, address[] memory signersToApprove) Ownable(msg.sender) {
if (idFactoryAddress == address(0)) {
revert ZeroAddress();
}
Expand Down Expand Up @@ -117,17 +120,14 @@ contract Gateway is Ownable {
revert ExpiredSignature(signature);
}

address signer = ECDSA.recover(
keccak256(
abi.encode(
"Authorize ONCHAINID deployment",
identityOwner,
salt,
signatureExpiry
)
).toEthSignedMessageHash(),
signature
);
address signer = keccak256(
abi.encode(
"Authorize ONCHAINID deployment",
identityOwner,
salt,
signatureExpiry
)
).toEthSignedMessageHash().recover(signature);

if (!approvedSigners[signer]) {
revert UnapprovedSigner(signer);
Expand Down
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
Loading
Loading