|
| 1 | +// SPDX-License-Identifier: GPL-3.0 |
| 2 | +pragma solidity 0.8.17; |
| 3 | + |
| 4 | +import "@openzeppelin/contracts/access/Ownable.sol"; |
| 5 | +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; |
| 6 | +import "../factory/IdFactory.sol"; |
| 7 | + |
| 8 | +using ECDSA for bytes32; |
| 9 | + |
| 10 | +/// A required parameter was set to the Zero address. |
| 11 | +error ZeroAddress(); |
| 12 | +/// The maximum number of signers was reached at deployment. |
| 13 | +error TooManySigners(); |
| 14 | +/// The signed attempted to add was already approved. |
| 15 | +error SignerAlreadyApproved(address signer); |
| 16 | +/// The signed attempted to remove was not approved. |
| 17 | +error SignerAlreadyNotApproved(address signer); |
| 18 | +/// A requested ONCHAINID deployment was requested without a valid signature while the Gateway requires one. |
| 19 | +error UnsignedDeployment(); |
| 20 | +/// A requested ONCHAINID deployment was requested and signer by a non approved signer. |
| 21 | +error UnapprovedSigner(address signer); |
| 22 | +/// A requested ONCHAINID deployment was requested with a signature revoked. |
| 23 | +error RevokedSignature(bytes signature); |
| 24 | +/// A requested ONCHAINID deployment was requested with a signature that expired. |
| 25 | +error ExpiredSignature(bytes signature); |
| 26 | +/// Attempted to revoke a signature that was already revoked. |
| 27 | +error SignatureAlreadyRevoked(bytes signature); |
| 28 | +/// Attempted to approve a signature that was not revoked. |
| 29 | +error SignatureNotRevoked(bytes signature); |
| 30 | + |
| 31 | +contract Gateway is Ownable { |
| 32 | + IdFactory public idFactory; |
| 33 | + mapping(address => bool) public approvedSigners; |
| 34 | + mapping(bytes => bool) public revokedSignatures; |
| 35 | + |
| 36 | + event SignerApproved(address indexed signer); |
| 37 | + event SignerRevoked(address indexed signer); |
| 38 | + event SignatureRevoked(bytes indexed signature); |
| 39 | + event SignatureApproved(bytes indexed signature); |
| 40 | + |
| 41 | + /** |
| 42 | + * @dev Constructor for the ONCHAINID Factory Gateway. |
| 43 | + * @param idFactoryAddress the address of the factory to operate (the Gateway must be owner of the Factory). |
| 44 | + */ |
| 45 | + constructor(address idFactoryAddress, address[] memory signersToApprove) Ownable() { |
| 46 | + if (idFactoryAddress == address(0)) { |
| 47 | + revert ZeroAddress(); |
| 48 | + } |
| 49 | + if (signersToApprove.length > 10) { |
| 50 | + revert TooManySigners(); |
| 51 | + } |
| 52 | + |
| 53 | + for (uint i = 0; i < signersToApprove.length; i++) { |
| 54 | + approvedSigners[signersToApprove[i]] = true; |
| 55 | + } |
| 56 | + |
| 57 | + idFactory = IdFactory(idFactoryAddress); |
| 58 | + } |
| 59 | + |
| 60 | + /** |
| 61 | + * @dev Approve a signer to sign ONCHAINID deployments. If the Gateway is setup to require signature, only |
| 62 | + * deployments requested with a valid signature from an approved signer will be accepted. |
| 63 | + * If the gateway does not require a signature, |
| 64 | + * @param signer the signer address to approve. |
| 65 | + */ |
| 66 | + function approveSigner(address signer) external onlyOwner { |
| 67 | + if (signer == address(0)) { |
| 68 | + revert ZeroAddress(); |
| 69 | + } |
| 70 | + |
| 71 | + if (approvedSigners[signer]) { |
| 72 | + revert SignerAlreadyApproved(signer); |
| 73 | + } |
| 74 | + |
| 75 | + approvedSigners[signer] = true; |
| 76 | + |
| 77 | + emit SignerApproved(signer); |
| 78 | + } |
| 79 | + |
| 80 | + /** |
| 81 | + * @dev Revoke a signer to sign ONCHAINID deployments. |
| 82 | + * @param signer the signer address to revoke. |
| 83 | + */ |
| 84 | + function revokeSigner(address signer) external onlyOwner { |
| 85 | + if (signer == address(0)) { |
| 86 | + revert ZeroAddress(); |
| 87 | + } |
| 88 | + |
| 89 | + if (!approvedSigners[signer]) { |
| 90 | + revert SignerAlreadyNotApproved(signer); |
| 91 | + } |
| 92 | + |
| 93 | + delete approvedSigners[signer]; |
| 94 | + |
| 95 | + emit SignerRevoked(signer); |
| 96 | + } |
| 97 | + |
| 98 | + /** |
| 99 | + * @dev Deploy an ONCHAINID using a factory. The operation must be signed by |
| 100 | + * an approved public key. This method allow to deploy an ONCHAINID using a custom salt. |
| 101 | + * @param identityOwner the address to set as a management key. |
| 102 | + * @param salt to use for the deployment. |
| 103 | + * @param signatureExpiry the block timestamp where the signature will expire. |
| 104 | + * @param signature the approval containing the salt and the identityOwner address. |
| 105 | + */ |
| 106 | + function deployIdentityWithSalt( |
| 107 | + address identityOwner, |
| 108 | + string memory salt, |
| 109 | + uint256 signatureExpiry, |
| 110 | + bytes calldata signature |
| 111 | + ) external returns (address) { |
| 112 | + if (identityOwner == address(0)) { |
| 113 | + revert ZeroAddress(); |
| 114 | + } |
| 115 | + |
| 116 | + if (signatureExpiry != 0 && signatureExpiry < block.timestamp) { |
| 117 | + revert ExpiredSignature(signature); |
| 118 | + } |
| 119 | + |
| 120 | + address signer = ECDSA.recover( |
| 121 | + keccak256( |
| 122 | + abi.encode( |
| 123 | + "Authorize ONCHAINID deployment", |
| 124 | + identityOwner, |
| 125 | + salt, |
| 126 | + signatureExpiry |
| 127 | + ) |
| 128 | + ).toEthSignedMessageHash(), |
| 129 | + signature |
| 130 | + ); |
| 131 | + |
| 132 | + if (!approvedSigners[signer]) { |
| 133 | + revert UnapprovedSigner(signer); |
| 134 | + } |
| 135 | + |
| 136 | + if (revokedSignatures[signature]) { |
| 137 | + revert RevokedSignature(signature); |
| 138 | + } |
| 139 | + |
| 140 | + return idFactory.createIdentity(identityOwner, salt); |
| 141 | + } |
| 142 | + |
| 143 | + /** |
| 144 | + * @dev Deploy an ONCHAINID using a factory. The operation must be signed by |
| 145 | + * an approved public key. This method allow to deploy an ONCHAINID using a custom salt and a custom list of |
| 146 | + * management keys. Note that the identity Owner address won't be added as a management keys, if this is desired, |
| 147 | + * the key hash must be listed in the managementKeys array. |
| 148 | + * @param identityOwner the address to set as a management key. |
| 149 | + * @param salt to use for the deployment. |
| 150 | + * @param managementKeys the list of management keys to add to the ONCHAINID. |
| 151 | + * @param signatureExpiry the block timestamp where the signature will expire. |
| 152 | + * @param signature the approval containing the salt and the identityOwner address. |
| 153 | + */ |
| 154 | + function deployIdentityWithSaltAndManagementKeys( |
| 155 | + address identityOwner, |
| 156 | + string memory salt, |
| 157 | + bytes32[] calldata managementKeys, |
| 158 | + uint256 signatureExpiry, |
| 159 | + bytes calldata signature |
| 160 | + ) external returns (address) { |
| 161 | + if (identityOwner == address(0)) { |
| 162 | + revert ZeroAddress(); |
| 163 | + } |
| 164 | + |
| 165 | + if (signatureExpiry != 0 && signatureExpiry < block.timestamp) { |
| 166 | + revert ExpiredSignature(signature); |
| 167 | + } |
| 168 | + |
| 169 | + address signer = ECDSA.recover( |
| 170 | + keccak256( |
| 171 | + abi.encode( |
| 172 | + "Authorize ONCHAINID deployment", |
| 173 | + identityOwner, |
| 174 | + salt, |
| 175 | + managementKeys, |
| 176 | + signatureExpiry |
| 177 | + ) |
| 178 | + ).toEthSignedMessageHash(), |
| 179 | + signature |
| 180 | + ); |
| 181 | + |
| 182 | + if (!approvedSigners[signer]) { |
| 183 | + revert UnapprovedSigner(signer); |
| 184 | + } |
| 185 | + |
| 186 | + if (revokedSignatures[signature]) { |
| 187 | + revert RevokedSignature(signature); |
| 188 | + } |
| 189 | + |
| 190 | + return idFactory.createIdentityWithManagementKeys(identityOwner, salt, managementKeys); |
| 191 | + } |
| 192 | + |
| 193 | + /** |
| 194 | + * @dev Deploy an ONCHAINID using a factory using the identityOwner address as salt. |
| 195 | + * @param identityOwner the address to set as a management key. |
| 196 | + */ |
| 197 | + function deployIdentityForWallet(address identityOwner) external returns (address) { |
| 198 | + if (identityOwner == address(0)) { |
| 199 | + revert ZeroAddress(); |
| 200 | + } |
| 201 | + |
| 202 | + return idFactory.createIdentity(identityOwner, Strings.toHexString(identityOwner)); |
| 203 | + } |
| 204 | + |
| 205 | + /** |
| 206 | + * @dev Revoke a signature, if the signature is used to deploy an ONCHAINID, the deployment would be rejected. |
| 207 | + * @param signature the signature to revoke. |
| 208 | + */ |
| 209 | + function revokeSignature(bytes calldata signature) external onlyOwner { |
| 210 | + if (revokedSignatures[signature]) { |
| 211 | + revert SignatureAlreadyRevoked(signature); |
| 212 | + } |
| 213 | + |
| 214 | + revokedSignatures[signature] = true; |
| 215 | + |
| 216 | + emit SignatureRevoked(signature); |
| 217 | + } |
| 218 | + |
| 219 | + /** |
| 220 | + * @dev Remove a signature from the revoke list. |
| 221 | + * @param signature the signature to approve. |
| 222 | + */ |
| 223 | + function approveSignature(bytes calldata signature) external onlyOwner { |
| 224 | + if (!revokedSignatures[signature]) { |
| 225 | + revert SignatureNotRevoked(signature); |
| 226 | + } |
| 227 | + |
| 228 | + delete revokedSignatures[signature]; |
| 229 | + |
| 230 | + emit SignatureApproved(signature); |
| 231 | + } |
| 232 | + |
| 233 | + /** |
| 234 | + * @dev Transfer the ownership of the factory to a new owner. |
| 235 | + * @param newOwner the new owner of the factory. |
| 236 | + */ |
| 237 | + function transferFactoryOwnership(address newOwner) external onlyOwner { |
| 238 | + idFactory.transferOwnership(newOwner); |
| 239 | + } |
| 240 | + |
| 241 | + /** |
| 242 | + * @dev Call a function on the factory. Only the owner of the Gateway can call this method. |
| 243 | + * @param data the data to call on the factory. |
| 244 | + */ |
| 245 | + function callFactory(bytes memory data) external onlyOwner { |
| 246 | + (bool success,) = address(idFactory).call(data); |
| 247 | + require(success, "Gateway: call to factory failed"); |
| 248 | + } |
| 249 | +} |
0 commit comments