Files

250 lines
8.6 KiB
Solidity

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.17;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "../factory/IdFactory.sol";
using ECDSA for bytes32;
/// A required parameter was set to the Zero address.
error ZeroAddress();
/// The maximum number of signers was reached at deployment.
error TooManySigners();
/// The signed attempted to add was already approved.
error SignerAlreadyApproved(address signer);
/// The signed attempted to remove was not approved.
error SignerAlreadyNotApproved(address signer);
/// A requested ONCHAINID deployment was requested without a valid signature while the Gateway requires one.
error UnsignedDeployment();
/// A requested ONCHAINID deployment was requested and signer by a non approved signer.
error UnapprovedSigner(address signer);
/// A requested ONCHAINID deployment was requested with a signature revoked.
error RevokedSignature(bytes signature);
/// A requested ONCHAINID deployment was requested with a signature that expired.
error ExpiredSignature(bytes signature);
/// Attempted to revoke a signature that was already revoked.
error SignatureAlreadyRevoked(bytes signature);
/// Attempted to approve a signature that was not revoked.
error SignatureNotRevoked(bytes signature);
contract Gateway is Ownable {
IdFactory public idFactory;
mapping(address => bool) public approvedSigners;
mapping(bytes => bool) public revokedSignatures;
event SignerApproved(address indexed signer);
event SignerRevoked(address indexed signer);
event SignatureRevoked(bytes indexed signature);
event SignatureApproved(bytes indexed signature);
/**
* @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() {
if (idFactoryAddress == address(0)) {
revert ZeroAddress();
}
if (signersToApprove.length > 10) {
revert TooManySigners();
}
for (uint i = 0; i < signersToApprove.length; i++) {
approvedSigners[signersToApprove[i]] = true;
}
idFactory = IdFactory(idFactoryAddress);
}
/**
* @dev Approve a signer to sign ONCHAINID deployments. If the Gateway is setup to require signature, only
* deployments requested with a valid signature from an approved signer will be accepted.
* If the gateway does not require a signature,
* @param signer the signer address to approve.
*/
function approveSigner(address signer) external onlyOwner {
if (signer == address(0)) {
revert ZeroAddress();
}
if (approvedSigners[signer]) {
revert SignerAlreadyApproved(signer);
}
approvedSigners[signer] = true;
emit SignerApproved(signer);
}
/**
* @dev Revoke a signer to sign ONCHAINID deployments.
* @param signer the signer address to revoke.
*/
function revokeSigner(address signer) external onlyOwner {
if (signer == address(0)) {
revert ZeroAddress();
}
if (!approvedSigners[signer]) {
revert SignerAlreadyNotApproved(signer);
}
delete approvedSigners[signer];
emit SignerRevoked(signer);
}
/**
* @dev Deploy an ONCHAINID using a factory. The operation must be signed by
* an approved public key. This method allow to deploy an ONCHAINID using a custom salt.
* @param identityOwner the address to set as a management key.
* @param salt to use for the deployment.
* @param signatureExpiry the block timestamp where the signature will expire.
* @param signature the approval containing the salt and the identityOwner address.
*/
function deployIdentityWithSalt(
address identityOwner,
string memory salt,
uint256 signatureExpiry,
bytes calldata signature
) external returns (address) {
if (identityOwner == address(0)) {
revert ZeroAddress();
}
if (signatureExpiry != 0 && signatureExpiry < block.timestamp) {
revert ExpiredSignature(signature);
}
address signer = ECDSA.recover(
keccak256(
abi.encode(
"Authorize ONCHAINID deployment",
identityOwner,
salt,
signatureExpiry
)
).toEthSignedMessageHash(),
signature
);
if (!approvedSigners[signer]) {
revert UnapprovedSigner(signer);
}
if (revokedSignatures[signature]) {
revert RevokedSignature(signature);
}
return idFactory.createIdentity(identityOwner, salt);
}
/**
* @dev Deploy an ONCHAINID using a factory. The operation must be signed by
* an approved public key. This method allow to deploy an ONCHAINID using a custom salt and a custom list of
* management keys. Note that the identity Owner address won't be added as a management keys, if this is desired,
* the key hash must be listed in the managementKeys array.
* @param identityOwner the address to set as a management key.
* @param salt to use for the deployment.
* @param managementKeys the list of management keys to add to the ONCHAINID.
* @param signatureExpiry the block timestamp where the signature will expire.
* @param signature the approval containing the salt and the identityOwner address.
*/
function deployIdentityWithSaltAndManagementKeys(
address identityOwner,
string memory salt,
bytes32[] calldata managementKeys,
uint256 signatureExpiry,
bytes calldata signature
) external returns (address) {
if (identityOwner == address(0)) {
revert ZeroAddress();
}
if (signatureExpiry != 0 && signatureExpiry < block.timestamp) {
revert ExpiredSignature(signature);
}
address signer = ECDSA.recover(
keccak256(
abi.encode(
"Authorize ONCHAINID deployment",
identityOwner,
salt,
managementKeys,
signatureExpiry
)
).toEthSignedMessageHash(),
signature
);
if (!approvedSigners[signer]) {
revert UnapprovedSigner(signer);
}
if (revokedSignatures[signature]) {
revert RevokedSignature(signature);
}
return idFactory.createIdentityWithManagementKeys(identityOwner, salt, managementKeys);
}
/**
* @dev Deploy an ONCHAINID using a factory using the identityOwner address as salt.
* @param identityOwner the address to set as a management key.
*/
function deployIdentityForWallet(address identityOwner) external returns (address) {
if (identityOwner == address(0)) {
revert ZeroAddress();
}
return idFactory.createIdentity(identityOwner, Strings.toHexString(identityOwner));
}
/**
* @dev Revoke a signature, if the signature is used to deploy an ONCHAINID, the deployment would be rejected.
* @param signature the signature to revoke.
*/
function revokeSignature(bytes calldata signature) external onlyOwner {
if (revokedSignatures[signature]) {
revert SignatureAlreadyRevoked(signature);
}
revokedSignatures[signature] = true;
emit SignatureRevoked(signature);
}
/**
* @dev Remove a signature from the revoke list.
* @param signature the signature to approve.
*/
function approveSignature(bytes calldata signature) external onlyOwner {
if (!revokedSignatures[signature]) {
revert SignatureNotRevoked(signature);
}
delete revokedSignatures[signature];
emit SignatureApproved(signature);
}
/**
* @dev Transfer the ownership of the factory to a new owner.
* @param newOwner the new owner of the factory.
*/
function transferFactoryOwnership(address newOwner) external onlyOwner {
idFactory.transferOwnership(newOwner);
}
/**
* @dev Call a function on the factory. Only the owner of the Gateway can call this method.
* @param data the data to call on the factory.
*/
function callFactory(bytes memory data) external onlyOwner {
(bool success,) = address(idFactory).call(data);
require(success, "Gateway: call to factory failed");
}
}