Skip to content
Open
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
25 changes: 25 additions & 0 deletions contracts/factory/IIdFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,31 @@ interface IIdFactory {
*/
function unlinkWallet(address _oldWallet) external;

/**
* @dev function used to register a wallet to an identity using signature verification
* @param wallet the address of the wallet to register
* @param signature signature provided by the wallet
* @param expiry expiry timestamp for the signature
* requires the wallet to sign a message binding wallet, identity, expiry, contract and chain id
* requires the wallet to hold a MANAGEMENT key on the identity
* requires msg.sender to be the identity contract
* wallet cannot be address 0
* signature must not be expired
*/
function registerWalletToIdentity(
address wallet,
bytes calldata signature,
uint256 expiry
) external;

/**
* @dev function used to unregister a wallet from an identity
* @param wallet the address of the wallet to unregister
* requires msg.sender to be the identity contract that the wallet is linked to
* wallet cannot be address 0
*/
function unregisterWalletFromIdentity(address wallet) external;

/**
* @dev function used to register an address as a token factory
* @param _factory the address of the token factory
Expand Down
82 changes: 81 additions & 1 deletion contracts/factory/IdFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@
pragma solidity ^0.8.27;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";

import { IdentityProxy } from "../proxy/IdentityProxy.sol";
import { IIdFactory } from "./IIdFactory.sol";
import { IERC734 } from "../interface/IERC734.sol";
import { IIdentity } from "../interface/IIdentity.sol";
import { Errors } from "../libraries/Errors.sol";
import { KeyPurposes } from "../libraries/KeyPurposes.sol";
import { KeyTypes } from "../libraries/KeyTypes.sol";

contract IdFactory is IIdFactory, Ownable {
uint256 private constant _MAX_WALLETS_PER_IDENTITY = 101;

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

Expand Down Expand Up @@ -186,7 +191,7 @@ contract IdFactory is IIdFactory, Ownable {
);
address identity = _userIdentity[msg.sender];
require(
_wallets[identity].length < 101,
_wallets[identity].length < _MAX_WALLETS_PER_IDENTITY,
Errors.MaxWalletsPerIdentityExceeded()
);
_userIdentity[_newWallet] = identity;
Expand Down Expand Up @@ -220,6 +225,81 @@ contract IdFactory is IIdFactory, Ownable {
emit WalletUnlinked(_oldWallet, _identity);
}

/**
* @dev See {IdFactory-registerWalletToIdentity}.
*/
function registerWalletToIdentity(
address wallet,
bytes calldata signature,
uint256 expiry
) external override {
require(wallet != address(0), Errors.ZeroAddress());
require(block.timestamp <= expiry, Errors.ExpiredSignature(signature));

address identity = msg.sender;
bytes32 structHash = keccak256(
abi.encode(wallet, identity, expiry, address(this), block.chainid)
);

bytes32 digest = MessageHashUtils.toEthSignedMessageHash(structHash);
(address signer, ECDSA.RecoverError error, ) = ECDSA.tryRecover(
digest,
signature
);
require(error == ECDSA.RecoverError.NoError, Errors.InvalidSignature());
require(signer == wallet, Errors.InvalidSignature());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a single statement with && and use existing ECDSA.ECDSAInvalidSignature error


// require the wallet is a MANAGEMENT key on the identity
bytes32 key = keccak256(abi.encode(wallet));
require(
IIdentity(identity).keyHasPurpose(key, KeyPurposes.MANAGEMENT),
Errors.KeyDoesNotHavePurpose(key, KeyPurposes.MANAGEMENT)
);

// Check if wallet is already linked
require(
_userIdentity[wallet] == address(0),
Errors.WalletAlreadyLinkedToIdentity(wallet)
);
require(
_tokenIdentity[wallet] == address(0),
Errors.TokenAlreadyLinked(wallet)
);

// Check max wallets per identity
require(
_wallets[identity].length < _MAX_WALLETS_PER_IDENTITY,
Errors.MaxWalletsPerIdentityExceeded()
);

_userIdentity[wallet] = identity;
_wallets[identity].push(wallet);
emit WalletLinked(wallet, identity);
}

/**
* @dev See {IdFactory-unregisterWalletFromIdentity}.
*/
function unregisterWalletFromIdentity(address wallet) external override {
require(wallet != address(0), Errors.ZeroAddress());
require(
_userIdentity[wallet] == msg.sender,
Errors.WalletNotLinkedToIdentity(wallet)
);

address identity = _userIdentity[wallet];
delete _userIdentity[wallet];
uint256 length = _wallets[identity].length;
for (uint256 i = 0; i < length; i++) {
if (_wallets[identity][i] == wallet) {
_wallets[identity][i] = _wallets[identity][length - 1];
_wallets[identity].pop();
break;
}
}
emit WalletUnlinked(wallet, identity);
}

/**
* @dev See {IdFactory-getIdentity}.
*/
Expand Down
12 changes: 12 additions & 0 deletions contracts/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ library Errors {
/// @notice Reverts if the factory is already registered
error AlreadyAFactory(address factory);

/// @notice Reverts when the recovered signer does not match the wallet being registered
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove all these new errors which are not used anymore

error InvalidSignature();

/// @notice Reverts when the provided signature has expired
error SignatureExpired(uint256 expiry);

/// @notice Reverts when the wallet does not hold a management key on the identity
error MissingManagementKey();

/// @notice Reverts when the wallet is not linked to msg.sender during removal
error WalletNotLinked();

/// @notice Reverts if the function is called on the sender address
error CannotBeCalledOnSenderAddress();

Expand Down
Loading
Loading