diff --git a/src/services/common/did/contracts/DIDRegistryOnChain.sol b/src/services/common/did/contracts/DIDRegistryOnChain.sol new file mode 100644 index 0000000..c489aba --- /dev/null +++ b/src/services/common/did/contracts/DIDRegistryOnChain.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.6; + +enum RegistrationState { + Unregistered, + Active, + Deactivated +} + +contract DIDRegistryOnChain { + error INVALID_REQUEST(); + + /** + * @notice Maps ID hash to address for fast verification. + */ + mapping(bytes32 => uint168) public registry; + + event DIDAttributeChanged(address indexed identity, bytes32 name, bytes value, uint256 validTo, uint256 previousChange); + + /** + * @notice Adds an ID + * + * @param _method ID domain + * @param _id ID address + */ + function register(string calldata _method, address _id) external { + if (msg.sender != _id) { + revert INVALID_REQUEST(); + } + uint168 record = (uint168(uint160(_id)) << 8) | uint8(RegistrationState.Active); + bytes32 hash = keccak256(abi.encodePacked('did:', _method, ':', _id)); + registry[hash] = record; + emit DIDAttributeChanged(_id, 'isActive', 'true', block.timestamp + 10000, 0); + } + + /** + * @notice Removes an ID + * + * @param _method ID method + * @param _id ID address + */ + function deactivate(string calldata _method, address _id) external { + if (msg.sender != _id) { + revert INVALID_REQUEST(); + } + bytes32 hash = keccak256(abi.encodePacked('did:', _method, ':', _id)); + uint168 record = registry[hash]; + address recordAddress = address(uint160(record >> 8)); + RegistrationState recordState = RegistrationState(uint8(record)); + + if (recordAddress == _id && recordState == RegistrationState.Active) { + record = uint168((uint160(_id) << 8) | uint8(RegistrationState.Deactivated)); + registry[hash] = record; + + emit DIDAttributeChanged(_id, 'isActive', 'false', block.timestamp + 10000, 0); // TODO: Validity??? + } + } + + function isActive(string calldata _method, address _id) public view returns (bool status) { + bytes32 hash = keccak256(abi.encodePacked('did:', _method, ':', _id)); + uint168 record = registry[hash]; + address recordAddress = address(uint160(record >> 8)); + RegistrationState recordState = RegistrationState(uint8(record)); + + status = recordAddress == _id && recordState == RegistrationState.Active; + } + + function isActiveHash(bytes32 _did) public view returns (bool status) { + uint168 record = registry[_did]; + // address recordAddress = address(uint160(record >> 8)); TODO: should it be compared to decoded address from the _did? + RegistrationState recordState = RegistrationState(uint8(record)); + + status = recordState == RegistrationState.Active; + } + + // TODO: These are copied from the existing registry contract so as to not break stuff + // This needs rework. + function setAttribute( + address identity, + address actor, + bytes32 name, + bytes calldata value, + uint256 validity + ) internal { + if (msg.sender != identity) { + revert INVALID_REQUEST(); + } + bytes32 hash = keccak256(abi.encodePacked('did:', 'onyxidentity', ':', identity)); + emit DIDAttributeChanged(identity, name, value, block.timestamp + validity, 0); + //changed[identity] = block.number; + } + + function setAttribute( + address identity, + bytes32 name, + bytes calldata value, + uint256 validity + ) public { + setAttribute(identity, msg.sender, name, value, validity); + } +} \ No newline at end of file diff --git a/src/services/common/did/contracts/IDIDRegistry.sol b/src/services/common/did/contracts/IDIDRegistry.sol new file mode 100644 index 0000000..32d13e8 --- /dev/null +++ b/src/services/common/did/contracts/IDIDRegistry.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.6; + +interface IDIDRegistry { + event DIDAttributeChanged(address indexed identity, bytes32 name, bytes value, uint256 validTo, uint256 previousChange); + + function register(string calldata _domain, address _subject) external; + + function deactivate(string calldata _domain, address _subject) external; + + function isActive(string calldata _domain, address _subject) external view returns (bool status); + + function isActiveHash(bytes32 credential) external view returns (bool status); + + function setAttribute( + address identity, + bytes32 name, + bytes calldata value, + uint256 validity + ) external; +} diff --git a/src/services/common/did/contracts/IVerifier.sol b/src/services/common/did/contracts/IVerifier.sol new file mode 100644 index 0000000..83e444f --- /dev/null +++ b/src/services/common/did/contracts/IVerifier.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.6; + +/// Various structs for representing a W3C credential as close to spec as possible given contraints +struct OnChainCredentialSubject { + bytes32 id; + bytes data; +} + +struct OnChainProof { + bytes types; + bytes verificationMethod; + bytes proofValue; +} + +struct OnChainPresentationProof { + bytes types; + bytes verificationMethod; + bytes proofValue; + uint256 nonce; +} + +struct OnChainVerifiableCredential { + bytes32 id; + OnChainCredentialSubject credentialSubject; + bytes32 issuer; + uint256 expirationDate; + uint256 issuanceDate; + bytes types; + OnChainProof proof; +} + +struct OnChainVerifiablePresentation { + bytes32 id; + OnChainVerifiableCredential[] verifiableCredential; + OnChainPresentationProof proof; +} + +/// @notice Interface for Verifier smart contract +interface IVerifier { + event VerificationResult(bytes32 indexed id, bool result, string reason); + + function getNonce(bytes32 _did) external view returns (uint256); + + function verifyChain(OnChainVerifiablePresentation memory presentation, address _presentationSender) external returns (bool); +} diff --git a/src/services/common/did/contracts/Verifier.sol b/src/services/common/did/contracts/Verifier.sol new file mode 100644 index 0000000..52a2a76 --- /dev/null +++ b/src/services/common/did/contracts/Verifier.sol @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.6; + +import '@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol'; +import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol'; +import '@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol'; + +import './IDIDRegistry.sol'; +import './IVerifier.sol'; + +/** + * @title A smart contract for verifying W3C Verifiable Credentials + * @dev This contract requires a deployed DID registry contract to function correctly. + * @custom:experimental This is an experimental contract. + */ +contract Verifier is IVerifier, OwnableUpgradeable, UUPSUpgradeable { + using ECDSAUpgradeable for bytes32; + + IDIDRegistry public registry; + bytes32 private rootIssuer; + mapping(bytes32 => bool) private knownIssuers; + mapping(bytes32 => uint256) private didNonce; + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + /// @notice Contract initialzier + /// @param _owner The owner of this contract + /// @param _rootIssuer The Keccack256 hashed form of the DID + function initialize( + address _owner, + bytes32 _rootIssuer + ) public initializer { + __Ownable_init(); + __UUPSUpgradeable_init(); + _transferOwnership(_owner); + + rootIssuer = _rootIssuer; + } + + function _authorizeUpgrade(address) internal override onlyOwner {} + + /// @notice Sets the root issuer for the chain of trust + /// @dev This is an optional setting. However, if set allows the root issuer to be + /// @dev used as a trust anchor for a given chain of trust + /// @param _rootIssuer The Keccack256 hashed form of the DID + function setRootIssuer(bytes32 _rootIssuer) external onlyOwner { + rootIssuer = _rootIssuer; + } + + /// @notice Adds an issuer from the known issuer list + /// @dev The primary VC issuer's DID must be in this list for verification to pass. + /// @param _issuer The Keccack256 hashed form of the DID + function addKnownIssuer(bytes32 _issuer) external onlyOwner { + knownIssuers[_issuer] = true; + } + + /// @notice Removes an issuer from the known issuer list + /// @param _issuer The Keccack256 hashed form of the DID + function removeKnownIssuer(bytes32 _issuer) external onlyOwner { + delete knownIssuers[_issuer]; + } + + /// @notice Sets the DID registry + /// @dev This registry is what is used during verification to look up the status + /// @dev of the DIDs. + /// @param _registry The registry address + function setRegistryAddress(IDIDRegistry _registry) external onlyOwner { + registry = _registry; + } + + /// @notice Returns the nonce for a given DID + /// @param _did The Keccack256 hashed form of the DID + /// @return The nonce value + function getNonce(bytes32 _did) external view returns (uint256) { + return didNonce[_did]; + } + + /// @notice Converts an address to a DID then generates the Keccack256 hash of it + /// @param _a An address + /// @return The Keccack256 hash + function toHashedDid(address _a) private pure returns (bytes32) { + bytes memory prefix = 'did:onchain:'; /// NOTE: TBD, how to set this? Constant, Argument or Initialized? + return keccak256(abi.encodePacked(prefix, _a)); + } + + /// @notice Verifies a single credential + /// @dev Performs basic checks such DID validity, expiry and signature validation. + /// @dev VerificationResult events are emitted to explain why a verification may have failed. + /// @param _credential An object representing a verifiable credential + /// @param _issuerVcProofValue The proof value from a credential held by the issuer of this credential + /// @param _presentationId The id of the presentation for which this check is being performed. This is purely for reporting. + /// @return True if the credential passed all checks, False if any failed. + function verifyCredential( + OnChainVerifiableCredential memory _credential, + bytes memory _issuerVcProofValue, + bytes32 _presentationId + ) private returns (bool) { + if (!registry.isActiveHash(_credential.id)) { + emit VerificationResult(_presentationId, false, 'REVOKED'); + return false; + } + + if (!registry.isActiveHash(_credential.credentialSubject.id)) { + emit VerificationResult(_presentationId, false, 'SUBJECT_DID_DEACTIVATED'); + return false; + } + + if (!registry.isActiveHash(_credential.issuer)) { + emit VerificationResult(_presentationId, false, 'ISSUER_DID_DEACTIVATED'); + return false; + } + + bytes32 vcHash = keccak256( + abi.encode( + _credential.id, + _credential.credentialSubject.id, + _credential.issuer, + _credential.expirationDate, + _credential.issuanceDate, + _credential.types, + _credential.credentialSubject.data, + _issuerVcProofValue + ) + ); + + address signer = vcHash.toEthSignedMessageHash().recover(_credential.proof.proofValue); + bytes32 hashedSigner = toHashedDid(signer); + + if (_credential.issuer != hashedSigner) { + emit VerificationResult(_presentationId, false, 'INVALID_PROOF'); + return false; + } + + // NOTE: TBD, Accuracy of block.timestamp permits a small window for an expired credential + // to pass this check + if (_credential.expirationDate < block.timestamp) { + emit VerificationResult(_presentationId, false, 'EXPIRED'); + return false; + } + + // Note: TBD, will return during round 2 pass + // if (_credential.issuanceDate >= block.timestamp) { + // // TODO: `>` or `>=` ? + // emit VerificationResult(_presentationId, false, 'NOT_VALID_YET'); + // return false; + // } + + return true; + } + + /// @notice Verifies a Presentation and the Credentials inside it + /// @dev All the embedded credentials and the presentation itself must be valid for verification to succeed + /// @dev VerificationResult events are emitted to explain why a verification may have failed. + /// @dev An external DID registry is required for verification + /// @param _presentation An object representing a verifiable presentation + /// @param _presentationSender the address of the presenation sender as reported to the caller of this function + /// @return True if the passed all checks, False if any failed. + function verifyChain(OnChainVerifiablePresentation memory _presentation, address _presentationSender) public override returns (bool) { + if (registry == IDIDRegistry(address(0))) { + emit VerificationResult(_presentation.id, false, 'REGISTRY_NOT_INITIALIZED'); + return false; + } + + uint256 length = _presentation.verifiableCredential.length; + + if (!(length > 0)) { + emit VerificationResult(_presentation.id, false, 'CREDENTIALS_MISSING'); + return false; + } + + if (_presentation.verifiableCredential[0].credentialSubject.id != toHashedDid(_presentationSender)) { + emit VerificationResult(_presentation.id, false, 'PRESENTATION_INVALID_CALLER'); + return false; + } + + if (rootIssuer != 0 && _presentation.verifiableCredential[length - 1].issuer != rootIssuer) { + emit VerificationResult(_presentation.id, false, 'ROOT_ISSUER_UNRECOGNIZED'); + return false; + } + + if (!knownIssuers[_presentation.verifiableCredential[0].issuer]) { + emit VerificationResult(_presentation.id, false, 'ISSUER_UNRECOGNIZED'); + return false; + } + + if (_presentation.proof.nonce != didNonce[_presentation.verifiableCredential[0].credentialSubject.id]) { + emit VerificationResult(_presentation.id, false, 'INVALID_NONCE'); + return false; + } + + bytes32 runningHash = keccak256(abi.encode(_presentation.proof.nonce)); + + // Verify each VC is valid + for (uint256 i = 0; i != length; i++) { + bytes memory issuerVcProof = '0xDECAFBAD'; // TODO: This really necessary? + if (i + 1 < length) { + issuerVcProof = _presentation.verifiableCredential[i + 1].proof.proofValue; + } + + bool result = verifyCredential(_presentation.verifiableCredential[i], issuerVcProof, _presentation.id); + if (!result) { + return false; + } + + runningHash = keccak256( + abi.encode( + runningHash, + _presentation.verifiableCredential[i].id, + _presentation.verifiableCredential[i].credentialSubject.id, + _presentation.verifiableCredential[i].issuer, + _presentation.verifiableCredential[i].expirationDate, + _presentation.verifiableCredential[i].issuanceDate, + _presentation.verifiableCredential[i].types, + _presentation.verifiableCredential[i].credentialSubject.data, + _presentation.verifiableCredential[i].proof.proofValue + ) + ); + } + + address signer = runningHash.toEthSignedMessageHash().recover(_presentation.proof.proofValue); + bytes32 signerHashed = toHashedDid(signer); + + if (signerHashed != _presentation.verifiableCredential[0].credentialSubject.id) { + emit VerificationResult(_presentation.id, false, 'PRESENTATION_INVALID_PROOF'); + return false; + } + + ++didNonce[_presentation.verifiableCredential[0].credentialSubject.id]; + + emit VerificationResult(_presentation.id, true, ''); + return true; + } +} diff --git a/src/services/common/did/contracts/metadata/DIDRegistryOnChain.json b/src/services/common/did/contracts/metadata/DIDRegistryOnChain.json new file mode 100644 index 0000000..6c32546 --- /dev/null +++ b/src/services/common/did/contracts/metadata/DIDRegistryOnChain.json @@ -0,0 +1,181 @@ +{ + "_format": "hh-zksolc-artifact-1", + "contractName": "DIDRegistryOnChain", + "sourceName": "contracts/DIDRegistryOnChain.sol", + "abi": [ + { + "inputs": [], + "name": "INVALID_REQUEST", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "identity", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "name", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "value", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "validTo", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "previousChange", + "type": "uint256" + } + ], + "name": "DIDAttributeChanged", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_method", + "type": "string" + }, + { + "internalType": "address", + "name": "_id", + "type": "address" + } + ], + "name": "deactivate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_method", + "type": "string" + }, + { + "internalType": "address", + "name": "_id", + "type": "address" + } + ], + "name": "isActive", + "outputs": [ + { + "internalType": "bool", + "name": "status", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_did", + "type": "bytes32" + } + ], + "name": "isActiveHash", + "outputs": [ + { + "internalType": "bool", + "name": "status", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_method", + "type": "string" + }, + { + "internalType": "address", + "name": "_id", + "type": "address" + } + ], + "name": "register", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "registry", + "outputs": [ + { + "internalType": "uint168", + "name": "", + "type": "uint168" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "identity", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "name", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "value", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "validity", + "type": "uint256" + } + ], + "name": "setAttribute", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x000200000000000200010000000103550000006001100270000000f40010019d0000008001000039000000400010043f0000000101200190000000220000c13d0000000001000031000000040110008c000000880000413d0000000101000367000000000101043b000000e001100270000000f60210009c0000002a0000213d000000fa0210009c000000410000613d000000fb0210009c0000006b0000613d000000fc0110009c000000880000c13d0000000001000416000000000101004b000000880000c13d000000000100003103cc00cb0000040f03cc03240000040f000000f401000041000000400200043d000000f40320009c00000000010240190000004001100210000003cd0001042e0000000001000416000000000101004b000000880000c13d000000200100003900000100001004430000012000000443000000f501000041000003cd0001042e000000f70210009c0000004d0000613d000000f80210009c000000770000613d000000f90110009c000000880000c13d0000000001000416000000000101004b000000880000c13d000000000100003103cc00990000040f03cc02920000040f000000000101004b0000000001000019000000010100c039000000400200043d0000000000120435000000f401000041000000f40320009c00000000010240190000004001100210000000fd011001c7000003cd0001042e0000000001000416000000000101004b000000880000c13d000000000100003103cc00990000040f03cc01130000040f000000f401000041000000400200043d000000f40320009c00000000010240190000004001100210000003cd0001042e0000000001000416000000000101004b000000880000c13d000000040100008a0000000001100031000000fe02000041000000200310008c00000000030000190000000003024019000000fe01100197000000000401004b000000000200a019000000fe0110009c00000000010300190000000001026019000000000101004b000000880000c13d00000004010000390000000101100367000000000101043b03cc01010000040f000000ff01100197000000400200043d0000000000120435000000f401000041000000f40320009c00000000010240190000004001100210000000fd011001c7000003cd0001042e0000000001000416000000000101004b000000880000c13d000000000100003103cc00990000040f03cc01c20000040f000000f401000041000000400200043d000000f40320009c00000000010240190000004001100210000003cd0001042e0000000001000416000000000101004b000000880000c13d000000040100008a0000000001100031000000fe02000041000000200310008c00000000030000190000000003024019000000fe01100197000000000401004b000000000200a019000000fe0110009c00000000010300190000000001026019000000000101004b0000008a0000613d0000000001000019000003ce0001043000000004010000390000000101100367000000000101043b03cc03070000040f000000000101004b0000000001000019000000010100c039000000400200043d0000000000120435000000f401000041000000f40320009c00000000010240190000004001100210000000fd011001c7000003cd0001042e000000040210008a000000fe030000410000003f0420008c00000000040000190000000004032019000000fe02200197000000000502004b0000000003008019000000fe0220009c00000000020400190000000002036019000000000202004b000000c90000613d00000001030003670000000402300370000000000402043b000001000240009c000000c90000213d0000002302400039000000fe05000041000000000612004b00000000060000190000000006058019000000fe07100197000000fe02200197000000000872004b0000000005008019000000000272013f000000fe0220009c00000000020600190000000002056019000000000202004b000000c90000c13d0000000402400039000000000223034f000000000202043b000001000520009c000000c90000213d00000024044000390000000005420019000000000115004b000000c90000213d0000002401300370000000000301043b000001010130009c000000c90000213d0000000001040019000000000001042d0000000001000019000003ce00010430000000040210008a000000fe030000410000007f0420008c00000000040000190000000004032019000000fe02200197000000000502004b0000000003008019000000fe0220009c00000000020400190000000002036019000000000202004b000000ff0000613d00000001050003670000000402500370000000000602043b000001010260009c000000ff0000213d0000002402500370000000000202043b0000004403500370000000000303043b000001000430009c000000ff0000213d0000002304300039000000fe07000041000000000814004b00000000080000190000000008078019000000fe09100197000000fe04400197000000000a94004b0000000007008019000000000494013f000000fe0440009c00000000040800190000000004076019000000000404004b000000ff0000c13d0000000404300039000000000445034f000000000404043b000001000740009c000000ff0000213d00000024033000390000000007340019000000000117004b000000ff0000213d0000006401500370000000000501043b0000000001060019000000000001042d0000000001000019000003ce000104300000000000100435000000200000043f000000f4010000410000000002000414000000f40320009c0000000001024019000000c00110021000000102011001c7000080100200003903cc03c70000040f0000000102200190000001110000613d000000000101043b000000000101041a000000ff01100197000000000001042d0000000001000019000003ce000104300001000000000002000000400b00043d00000101043001970000000005000411000000000445004b000001ae0000c13d0000002004b00039000001050500004100000000005404350000001f0520018f0000002406b00039000000010110036700000005072002720000012a0000613d00000000080000190000000509800210000000000a960019000000000991034f000000000909043b00000000009a04350000000108800039000000000978004b000001220000413d000000000805004b000001390000613d0000000507700210000000000171034f00000000067600190000000305500210000000000706043300000000075701cf000000000757022f000000000101043b0000010005500089000000000151022f00000000015101cf000000000171019f000000000016043500000000012400190000000405100039000001060600004100000000006504350000000501100039000100000003001d00000060053002100000000000510435000000190120003900000000001b04350000005801200039000000200200008a000000000221016f0000000001b20019000000000221004b00000000020000190000000102004039000001000510009c000001b60000213d0000000102200190000001b60000c13d000000400010043f000000f401000041000000f40240009c00000000020100190000000002044019000000400220021000000000030b0433000000f40430009c00000000030180190000006003300210000000000223019f0000000003000414000000f40430009c0000000001034019000000c001100210000000000121019f00000107011001c7000080100200003903cc03c70000040f0000000102200190000001ac0000613d000000000101043b0000000000100435000000200000043f000000f4010000410000000002000414000000f40320009c0000000001024019000000c00110021000000102011001c7000080100200003903cc03c70000040f0000000102200190000001ac0000613d000000010200002900000008022002100000010802200197000000000101043b000000000301041a0000010903300197000000000232019f00000001022001bf000000000021041b0000010a010000410000000000100439000000f4010000410000000002000414000000f40320009c0000000001024019000000c0011002100000010b011001c70000800b0200003903cc03c70000040f0000000102200190000001ac0000613d000000000101043b000027100200008a000000000221004b0000000105000029000001bc0000813d000000400200043d000000a0032000390000010c0400004100000000004304350000008003200039000000040400003900000000004304350000002003200039000000800400003900000000004304350000271001100039000000400320003900000000001304350000010d01000041000000000012043500000060012000390000000000010435000000f4010000410000000003000414000000f40430009c0000000003018019000000f40420009c00000000010240190000004001100210000000c002300210000000000112019f0000010e011001c70000800d0200003900000002030000390000010f0400004103cc03c20000040f0000000101200190000001ac0000613d000000000001042d0000000001000019000003ce00010430000001030100004100000000001b0435000000f401000041000000f402b0009c00000000010b4019000000400110021000000104011001c7000003ce00010430000001100100004100000000001004350000004101000039000000040010043f0000011101000041000003ce00010430000001100100004100000000001004350000001101000039000000040010043f0000011101000041000003ce000104300003000000000002000000400b00043d000001010c30019700000000040004110000000004c4004b000002780000c13d0000002004b00039000001050500004100000000005404350000001f0520018f0000002406b0003900000001011003670000000507200272000001d90000613d00000000080000190000000509800210000000000a960019000000000991034f000000000909043b00000000009a04350000000108800039000000000978004b000001d10000413d000000000805004b000001e80000613d0000000507700210000000000171034f00000000067600190000000305500210000000000706043300000000075701cf000000000757022f000000000101043b0000010005500089000000000151022f00000000015101cf000000000171019f000000000016043500030000000c001d00000000012400190000000405100039000001060600004100000000006504350000000501100039000200000003001d00000060053002100000000000510435000000190120003900000000001b04350000005801200039000000200200008a000000000221016f0000000001b20019000000000221004b00000000020000190000000102004039000001000510009c000002800000213d0000000102200190000002800000c13d000000400010043f000000f401000041000000f40240009c00000000020100190000000002044019000000400220021000000000030b0433000000f40430009c00000000030180190000006003300210000000000223019f0000000003000414000000f40430009c0000000001034019000000c001100210000000000121019f00000107011001c7000080100200003903cc03c70000040f0000000102200190000002760000613d000000000101043b000100000001001d0000000000100435000000200000043f000000f4010000410000000002000414000000f40320009c0000000001024019000000c00110021000000102011001c7000080100200003903cc03c70000040f0000000102200190000002760000613d000000000101043b000000000201041a000000ff0120018f000000030310008c000002860000813d000000080220027000000101022001970000000303000029000000000232004b000002750000c13d000000010110008c000002750000c13d00000001010000290000000000100435000000200000043f000000f4010000410000000002000414000000f40320009c0000000001024019000000c00110021000000102011001c7000080100200003903cc03c70000040f0000000102200190000002760000613d000000020200002900000008022002100000011202200197000000000101043b000000000301041a0000010903300197000000000232019f00000002022001bf000000000021041b0000010a010000410000000000100439000000f4010000410000000002000414000000f40320009c0000000001024019000000c0011002100000010b011001c70000800b0200003903cc03c70000040f0000000102200190000002760000613d000000000101043b000027100200008a000000000221004b00000002050000290000028c0000813d000000400200043d000000a003200039000001130400004100000000004304350000008003200039000000050400003900000000004304350000002003200039000000800400003900000000004304350000271001100039000000400320003900000000001304350000010d01000041000000000012043500000060012000390000000000010435000000f4010000410000000003000414000000f40430009c0000000003018019000000f40420009c00000000010240190000004001100210000000c002300210000000000112019f0000010e011001c70000800d0200003900000002030000390000010f0400004103cc03c20000040f0000000101200190000002760000613d000000000001042d0000000001000019000003ce00010430000001030100004100000000001b0435000000f401000041000000f402b0009c00000000010b4019000000400110021000000104011001c7000003ce00010430000001100100004100000000001004350000004101000039000000040010043f0000011101000041000003ce00010430000001100100004100000000001004350000002101000039000000040010043f0000011101000041000003ce00010430000001100100004100000000001004350000001101000039000000040010043f0000011101000041000003ce000104300001000000000002000000400b00043d0000002004b00039000001050500004100000000005404350000001f0520018f0000002406b0003900000001011003670000000507200272000002a50000613d00000000080000190000000509800210000000000a960019000000000991034f000000000909043b00000000009a04350000000108800039000000000978004b0000029d0000413d000000000805004b000002b40000613d0000000507700210000000000171034f00000000067600190000000305500210000000000706043300000000075701cf000000000757022f000000000101043b0000010005500089000000000151022f00000000015101cf000000000171019f000000000016043500000000012400190000000405100039000001060600004100000000006504350000000501100039000100000003001d00000060053002100000000000510435000000190120003900000000001b04350000005801200039000000200200008a000000000221016f0000000001b20019000000000221004b00000000020000190000000102004039000001000510009c000002fb0000213d0000000102200190000002fb0000c13d000000400010043f000000f401000041000000f40240009c00000000020100190000000002044019000000400220021000000000030b0433000000f40430009c00000000030180190000006003300210000000000223019f0000000003000414000000f40430009c0000000001034019000000c001100210000000000121019f00000107011001c7000080100200003903cc03c70000040f0000000102200190000002f90000613d000000000101043b0000000000100435000000200000043f000000f4010000410000000002000414000000f40320009c0000000001024019000000c00110021000000102011001c7000080100200003903cc03c70000040f0000000102200190000002f90000613d000000000101043b000000000201041a000000ff0120018f000000030310008c0000000103000029000003010000813d0000000802200270000000000232013f0000010102200197000000010110015f00000000011201a000000000010000190000000101006039000000000001042d0000000001000019000003ce00010430000001100100004100000000001004350000004101000039000000040010043f0000011101000041000003ce00010430000001100100004100000000001004350000002101000039000000040010043f0000011101000041000003ce000104300000000000100435000000200000043f000000f4010000410000000002000414000000f40320009c0000000001024019000000c00110021000000102011001c7000080100200003903cc03c70000040f00000001022001900000031c0000613d000000000101043b000000000101041a000000ff0110018f000000030210008c0000031e0000813d000000010110008c00000000010000190000000101006039000000000001042d0000000001000019000003ce00010430000001100100004100000000001004350000002101000039000000040010043f0000011101000041000003ce000104300006000000000002000600000005001d000000400500043d00000101071001970000000006000411000000000676004b000003ad0000c13d000200000002001d000300000003001d000400000004001d000000200250003900000105030000410000000000320435000000240250003900000114030000410000000000320435000000300250003900000106030000410000000000320435000500000001001d00000060021002100000003103500039000000000023043500000025020000390000000000250435000001150250009c000003b60000813d0000006001500039000000400010043f0000010a010000410000000000100439000000f4010000410000000002000414000000f40320009c0000000001024019000000c0011002100000010b011001c70000800b0200003903cc03c70000040f0000000102200190000003ab0000613d000000010200008a0000000603000029000000000223013f000000000101043b000000000121004b000003bc0000213d000000400100043d000100000001001d0000010a010000410000000000100439000000f4010000410000000002000414000000f40320009c0000000001024019000000c0011002100000010b011001c70000800b0200003903cc03c70000040f0000000102200190000003ab0000613d000000000301043b000000010a0000290000002001a00039000000800200003900000000002104350000008001a0003900000004090000290000000000910435000000020100002900000000001a04350000001f0290018f000000a001a000390000000304000029000000010440036700000005059002720000037a0000613d000000000600001900000005076002100000000008710019000000000774034f000000000707043b00000000007804350000000106600039000000000756004b000003720000413d00000006060000290000000003630019000000000602004b0000038b0000613d0000000505500210000000000454034f00000000055100190000000302200210000000000605043300000000062601cf000000000626022f000000000404043b0000010002200089000000000424022f00000000022401cf000000000262019f0000000000250435000000000191001900000000000104350000004001a0003900000000003104350000006001a000390000000000010435000000bf01900039000000200200008a000000000121016f000000f402000041000000f403a0009c000000000302001900000000030a40190000004003300210000000f40410009c00000000010280190000006001100210000000000131019f0000000003000414000000f40430009c0000000002034019000000c002200210000000000112019f00000107011001c70000800d0200003900000002030000390000010f04000041000000050500002903cc03c20000040f0000000101200190000003ab0000613d000000000001042d0000000001000019000003ce0001043000000103020000410000000000250435000000f402000041000000f40350009c00000000010200190000000001054019000000400110021000000104011001c7000003ce00010430000001100100004100000000001004350000004101000039000000040010043f0000011101000041000003ce00010430000001100100004100000000001004350000001101000039000000040010043f0000011101000041000003ce00010430000003c5002104210000000102000039000000000001042d0000000002000019000000000001042d000003ca002104230000000102000039000000000001042d0000000002000019000000000001042d000003cc00000432000003cd0001042e000003ce00010430000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0000000200000000000000000000000000000040000001000000000000000000000000000000000000000000000000000000000000000000000000007ef50297000000000000000000000000000000000000000000000000000000007ef5029800000000000000000000000000000000000000000000000000000000b4b708c000000000000000000000000000000000000000000000000000000000c00b993e000000000000000000000000000000000000000000000000000000001e59c529000000000000000000000000000000000000000000000000000000003ebfb89c000000000000000000000000000000000000000000000000000000007ad4b0a4000000000000000000000000000000000000002000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000ffffffffffffffff000000000000000000000000ffffffffffffffffffffffffffffffffffffffff0200000000000000000000000000000000000040000000000000000000000000202864470000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000006469643a000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffff000000000000000000000000000000000000000000796b89b91644bc98cd93958e4c9038275d622183e25ac5af08cc6b5d9553913202000002000000000000000000000000000000040000000000000000000000007472756500000000000000000000000000000000000000000000000000000000697341637469766500000000000000000000000000000000000000000000000002000000000000000000000000000000000000c000000000000000000000000018ab6b2ae3d64306c00ce663125f2bd680e441a098de1635bd7ad8b0d44965e44e487b71000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffff0066616c73650000000000000000000000000000000000000000000000000000006f6e79786964656e746974790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffa07b21e11c3a1cf6c0de78d468a3575f339dc5b62b9573c8ac365057d4106fc05f", + "deployedBytecode": "0x000200000000000200010000000103550000006001100270000000f40010019d0000008001000039000000400010043f0000000101200190000000220000c13d0000000001000031000000040110008c000000880000413d0000000101000367000000000101043b000000e001100270000000f60210009c0000002a0000213d000000fa0210009c000000410000613d000000fb0210009c0000006b0000613d000000fc0110009c000000880000c13d0000000001000416000000000101004b000000880000c13d000000000100003103cc00cb0000040f03cc03240000040f000000f401000041000000400200043d000000f40320009c00000000010240190000004001100210000003cd0001042e0000000001000416000000000101004b000000880000c13d000000200100003900000100001004430000012000000443000000f501000041000003cd0001042e000000f70210009c0000004d0000613d000000f80210009c000000770000613d000000f90110009c000000880000c13d0000000001000416000000000101004b000000880000c13d000000000100003103cc00990000040f03cc02920000040f000000000101004b0000000001000019000000010100c039000000400200043d0000000000120435000000f401000041000000f40320009c00000000010240190000004001100210000000fd011001c7000003cd0001042e0000000001000416000000000101004b000000880000c13d000000000100003103cc00990000040f03cc01130000040f000000f401000041000000400200043d000000f40320009c00000000010240190000004001100210000003cd0001042e0000000001000416000000000101004b000000880000c13d000000040100008a0000000001100031000000fe02000041000000200310008c00000000030000190000000003024019000000fe01100197000000000401004b000000000200a019000000fe0110009c00000000010300190000000001026019000000000101004b000000880000c13d00000004010000390000000101100367000000000101043b03cc01010000040f000000ff01100197000000400200043d0000000000120435000000f401000041000000f40320009c00000000010240190000004001100210000000fd011001c7000003cd0001042e0000000001000416000000000101004b000000880000c13d000000000100003103cc00990000040f03cc01c20000040f000000f401000041000000400200043d000000f40320009c00000000010240190000004001100210000003cd0001042e0000000001000416000000000101004b000000880000c13d000000040100008a0000000001100031000000fe02000041000000200310008c00000000030000190000000003024019000000fe01100197000000000401004b000000000200a019000000fe0110009c00000000010300190000000001026019000000000101004b0000008a0000613d0000000001000019000003ce0001043000000004010000390000000101100367000000000101043b03cc03070000040f000000000101004b0000000001000019000000010100c039000000400200043d0000000000120435000000f401000041000000f40320009c00000000010240190000004001100210000000fd011001c7000003cd0001042e000000040210008a000000fe030000410000003f0420008c00000000040000190000000004032019000000fe02200197000000000502004b0000000003008019000000fe0220009c00000000020400190000000002036019000000000202004b000000c90000613d00000001030003670000000402300370000000000402043b000001000240009c000000c90000213d0000002302400039000000fe05000041000000000612004b00000000060000190000000006058019000000fe07100197000000fe02200197000000000872004b0000000005008019000000000272013f000000fe0220009c00000000020600190000000002056019000000000202004b000000c90000c13d0000000402400039000000000223034f000000000202043b000001000520009c000000c90000213d00000024044000390000000005420019000000000115004b000000c90000213d0000002401300370000000000301043b000001010130009c000000c90000213d0000000001040019000000000001042d0000000001000019000003ce00010430000000040210008a000000fe030000410000007f0420008c00000000040000190000000004032019000000fe02200197000000000502004b0000000003008019000000fe0220009c00000000020400190000000002036019000000000202004b000000ff0000613d00000001050003670000000402500370000000000602043b000001010260009c000000ff0000213d0000002402500370000000000202043b0000004403500370000000000303043b000001000430009c000000ff0000213d0000002304300039000000fe07000041000000000814004b00000000080000190000000008078019000000fe09100197000000fe04400197000000000a94004b0000000007008019000000000494013f000000fe0440009c00000000040800190000000004076019000000000404004b000000ff0000c13d0000000404300039000000000445034f000000000404043b000001000740009c000000ff0000213d00000024033000390000000007340019000000000117004b000000ff0000213d0000006401500370000000000501043b0000000001060019000000000001042d0000000001000019000003ce000104300000000000100435000000200000043f000000f4010000410000000002000414000000f40320009c0000000001024019000000c00110021000000102011001c7000080100200003903cc03c70000040f0000000102200190000001110000613d000000000101043b000000000101041a000000ff01100197000000000001042d0000000001000019000003ce000104300001000000000002000000400b00043d00000101043001970000000005000411000000000445004b000001ae0000c13d0000002004b00039000001050500004100000000005404350000001f0520018f0000002406b00039000000010110036700000005072002720000012a0000613d00000000080000190000000509800210000000000a960019000000000991034f000000000909043b00000000009a04350000000108800039000000000978004b000001220000413d000000000805004b000001390000613d0000000507700210000000000171034f00000000067600190000000305500210000000000706043300000000075701cf000000000757022f000000000101043b0000010005500089000000000151022f00000000015101cf000000000171019f000000000016043500000000012400190000000405100039000001060600004100000000006504350000000501100039000100000003001d00000060053002100000000000510435000000190120003900000000001b04350000005801200039000000200200008a000000000221016f0000000001b20019000000000221004b00000000020000190000000102004039000001000510009c000001b60000213d0000000102200190000001b60000c13d000000400010043f000000f401000041000000f40240009c00000000020100190000000002044019000000400220021000000000030b0433000000f40430009c00000000030180190000006003300210000000000223019f0000000003000414000000f40430009c0000000001034019000000c001100210000000000121019f00000107011001c7000080100200003903cc03c70000040f0000000102200190000001ac0000613d000000000101043b0000000000100435000000200000043f000000f4010000410000000002000414000000f40320009c0000000001024019000000c00110021000000102011001c7000080100200003903cc03c70000040f0000000102200190000001ac0000613d000000010200002900000008022002100000010802200197000000000101043b000000000301041a0000010903300197000000000232019f00000001022001bf000000000021041b0000010a010000410000000000100439000000f4010000410000000002000414000000f40320009c0000000001024019000000c0011002100000010b011001c70000800b0200003903cc03c70000040f0000000102200190000001ac0000613d000000000101043b000027100200008a000000000221004b0000000105000029000001bc0000813d000000400200043d000000a0032000390000010c0400004100000000004304350000008003200039000000040400003900000000004304350000002003200039000000800400003900000000004304350000271001100039000000400320003900000000001304350000010d01000041000000000012043500000060012000390000000000010435000000f4010000410000000003000414000000f40430009c0000000003018019000000f40420009c00000000010240190000004001100210000000c002300210000000000112019f0000010e011001c70000800d0200003900000002030000390000010f0400004103cc03c20000040f0000000101200190000001ac0000613d000000000001042d0000000001000019000003ce00010430000001030100004100000000001b0435000000f401000041000000f402b0009c00000000010b4019000000400110021000000104011001c7000003ce00010430000001100100004100000000001004350000004101000039000000040010043f0000011101000041000003ce00010430000001100100004100000000001004350000001101000039000000040010043f0000011101000041000003ce000104300003000000000002000000400b00043d000001010c30019700000000040004110000000004c4004b000002780000c13d0000002004b00039000001050500004100000000005404350000001f0520018f0000002406b0003900000001011003670000000507200272000001d90000613d00000000080000190000000509800210000000000a960019000000000991034f000000000909043b00000000009a04350000000108800039000000000978004b000001d10000413d000000000805004b000001e80000613d0000000507700210000000000171034f00000000067600190000000305500210000000000706043300000000075701cf000000000757022f000000000101043b0000010005500089000000000151022f00000000015101cf000000000171019f000000000016043500030000000c001d00000000012400190000000405100039000001060600004100000000006504350000000501100039000200000003001d00000060053002100000000000510435000000190120003900000000001b04350000005801200039000000200200008a000000000221016f0000000001b20019000000000221004b00000000020000190000000102004039000001000510009c000002800000213d0000000102200190000002800000c13d000000400010043f000000f401000041000000f40240009c00000000020100190000000002044019000000400220021000000000030b0433000000f40430009c00000000030180190000006003300210000000000223019f0000000003000414000000f40430009c0000000001034019000000c001100210000000000121019f00000107011001c7000080100200003903cc03c70000040f0000000102200190000002760000613d000000000101043b000100000001001d0000000000100435000000200000043f000000f4010000410000000002000414000000f40320009c0000000001024019000000c00110021000000102011001c7000080100200003903cc03c70000040f0000000102200190000002760000613d000000000101043b000000000201041a000000ff0120018f000000030310008c000002860000813d000000080220027000000101022001970000000303000029000000000232004b000002750000c13d000000010110008c000002750000c13d00000001010000290000000000100435000000200000043f000000f4010000410000000002000414000000f40320009c0000000001024019000000c00110021000000102011001c7000080100200003903cc03c70000040f0000000102200190000002760000613d000000020200002900000008022002100000011202200197000000000101043b000000000301041a0000010903300197000000000232019f00000002022001bf000000000021041b0000010a010000410000000000100439000000f4010000410000000002000414000000f40320009c0000000001024019000000c0011002100000010b011001c70000800b0200003903cc03c70000040f0000000102200190000002760000613d000000000101043b000027100200008a000000000221004b00000002050000290000028c0000813d000000400200043d000000a003200039000001130400004100000000004304350000008003200039000000050400003900000000004304350000002003200039000000800400003900000000004304350000271001100039000000400320003900000000001304350000010d01000041000000000012043500000060012000390000000000010435000000f4010000410000000003000414000000f40430009c0000000003018019000000f40420009c00000000010240190000004001100210000000c002300210000000000112019f0000010e011001c70000800d0200003900000002030000390000010f0400004103cc03c20000040f0000000101200190000002760000613d000000000001042d0000000001000019000003ce00010430000001030100004100000000001b0435000000f401000041000000f402b0009c00000000010b4019000000400110021000000104011001c7000003ce00010430000001100100004100000000001004350000004101000039000000040010043f0000011101000041000003ce00010430000001100100004100000000001004350000002101000039000000040010043f0000011101000041000003ce00010430000001100100004100000000001004350000001101000039000000040010043f0000011101000041000003ce000104300001000000000002000000400b00043d0000002004b00039000001050500004100000000005404350000001f0520018f0000002406b0003900000001011003670000000507200272000002a50000613d00000000080000190000000509800210000000000a960019000000000991034f000000000909043b00000000009a04350000000108800039000000000978004b0000029d0000413d000000000805004b000002b40000613d0000000507700210000000000171034f00000000067600190000000305500210000000000706043300000000075701cf000000000757022f000000000101043b0000010005500089000000000151022f00000000015101cf000000000171019f000000000016043500000000012400190000000405100039000001060600004100000000006504350000000501100039000100000003001d00000060053002100000000000510435000000190120003900000000001b04350000005801200039000000200200008a000000000221016f0000000001b20019000000000221004b00000000020000190000000102004039000001000510009c000002fb0000213d0000000102200190000002fb0000c13d000000400010043f000000f401000041000000f40240009c00000000020100190000000002044019000000400220021000000000030b0433000000f40430009c00000000030180190000006003300210000000000223019f0000000003000414000000f40430009c0000000001034019000000c001100210000000000121019f00000107011001c7000080100200003903cc03c70000040f0000000102200190000002f90000613d000000000101043b0000000000100435000000200000043f000000f4010000410000000002000414000000f40320009c0000000001024019000000c00110021000000102011001c7000080100200003903cc03c70000040f0000000102200190000002f90000613d000000000101043b000000000201041a000000ff0120018f000000030310008c0000000103000029000003010000813d0000000802200270000000000232013f0000010102200197000000010110015f00000000011201a000000000010000190000000101006039000000000001042d0000000001000019000003ce00010430000001100100004100000000001004350000004101000039000000040010043f0000011101000041000003ce00010430000001100100004100000000001004350000002101000039000000040010043f0000011101000041000003ce000104300000000000100435000000200000043f000000f4010000410000000002000414000000f40320009c0000000001024019000000c00110021000000102011001c7000080100200003903cc03c70000040f00000001022001900000031c0000613d000000000101043b000000000101041a000000ff0110018f000000030210008c0000031e0000813d000000010110008c00000000010000190000000101006039000000000001042d0000000001000019000003ce00010430000001100100004100000000001004350000002101000039000000040010043f0000011101000041000003ce000104300006000000000002000600000005001d000000400500043d00000101071001970000000006000411000000000676004b000003ad0000c13d000200000002001d000300000003001d000400000004001d000000200250003900000105030000410000000000320435000000240250003900000114030000410000000000320435000000300250003900000106030000410000000000320435000500000001001d00000060021002100000003103500039000000000023043500000025020000390000000000250435000001150250009c000003b60000813d0000006001500039000000400010043f0000010a010000410000000000100439000000f4010000410000000002000414000000f40320009c0000000001024019000000c0011002100000010b011001c70000800b0200003903cc03c70000040f0000000102200190000003ab0000613d000000010200008a0000000603000029000000000223013f000000000101043b000000000121004b000003bc0000213d000000400100043d000100000001001d0000010a010000410000000000100439000000f4010000410000000002000414000000f40320009c0000000001024019000000c0011002100000010b011001c70000800b0200003903cc03c70000040f0000000102200190000003ab0000613d000000000301043b000000010a0000290000002001a00039000000800200003900000000002104350000008001a0003900000004090000290000000000910435000000020100002900000000001a04350000001f0290018f000000a001a000390000000304000029000000010440036700000005059002720000037a0000613d000000000600001900000005076002100000000008710019000000000774034f000000000707043b00000000007804350000000106600039000000000756004b000003720000413d00000006060000290000000003630019000000000602004b0000038b0000613d0000000505500210000000000454034f00000000055100190000000302200210000000000605043300000000062601cf000000000626022f000000000404043b0000010002200089000000000424022f00000000022401cf000000000262019f0000000000250435000000000191001900000000000104350000004001a0003900000000003104350000006001a000390000000000010435000000bf01900039000000200200008a000000000121016f000000f402000041000000f403a0009c000000000302001900000000030a40190000004003300210000000f40410009c00000000010280190000006001100210000000000131019f0000000003000414000000f40430009c0000000002034019000000c002200210000000000112019f00000107011001c70000800d0200003900000002030000390000010f04000041000000050500002903cc03c20000040f0000000101200190000003ab0000613d000000000001042d0000000001000019000003ce0001043000000103020000410000000000250435000000f402000041000000f40350009c00000000010200190000000001054019000000400110021000000104011001c7000003ce00010430000001100100004100000000001004350000004101000039000000040010043f0000011101000041000003ce00010430000001100100004100000000001004350000001101000039000000040010043f0000011101000041000003ce00010430000003c5002104210000000102000039000000000001042d0000000002000019000000000001042d000003ca002104230000000102000039000000000001042d0000000002000019000000000001042d000003cc00000432000003cd0001042e000003ce00010430000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0000000200000000000000000000000000000040000001000000000000000000000000000000000000000000000000000000000000000000000000007ef50297000000000000000000000000000000000000000000000000000000007ef5029800000000000000000000000000000000000000000000000000000000b4b708c000000000000000000000000000000000000000000000000000000000c00b993e000000000000000000000000000000000000000000000000000000001e59c529000000000000000000000000000000000000000000000000000000003ebfb89c000000000000000000000000000000000000000000000000000000007ad4b0a4000000000000000000000000000000000000002000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000ffffffffffffffff000000000000000000000000ffffffffffffffffffffffffffffffffffffffff0200000000000000000000000000000000000040000000000000000000000000202864470000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000006469643a000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffff000000000000000000000000000000000000000000796b89b91644bc98cd93958e4c9038275d622183e25ac5af08cc6b5d9553913202000002000000000000000000000000000000040000000000000000000000007472756500000000000000000000000000000000000000000000000000000000697341637469766500000000000000000000000000000000000000000000000002000000000000000000000000000000000000c000000000000000000000000018ab6b2ae3d64306c00ce663125f2bd680e441a098de1635bd7ad8b0d44965e44e487b71000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffff0066616c73650000000000000000000000000000000000000000000000000000006f6e79786964656e746974790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffa07b21e11c3a1cf6c0de78d468a3575f339dc5b62b9573c8ac365057d4106fc05f", + "linkReferences": {}, + "deployedLinkReferences": {}, + "factoryDeps": {} + } + \ No newline at end of file diff --git a/src/services/common/did/did-onchain.ts b/src/services/common/did/did-onchain.ts new file mode 100644 index 0000000..8d9db7a --- /dev/null +++ b/src/services/common/did/did-onchain.ts @@ -0,0 +1,269 @@ + +import { DIDResolutionResult, DIDResolver, Resolver } from "did-resolver"; +import { ethers } from 'ethers' +import { JsonRpcProvider, Provider } from "@ethersproject/providers"; +import { Contract } from '@ethersproject/contracts' +import { Wallet } from "@ethersproject/wallet"; +import { DID, DIDMethod, DIDWithKeys } from "./did"; +import { KeyUtils, KEY_ALG } from "../../../utils"; +import { DIDMethodFailureError } from "../../../errors"; +import * as DIDRegistry from './contracts/metadata/DIDRegistryOnChain.json' + +export class OnChainDIDMethod implements DIDMethod { + name = 'onchain'; + providerConfigs: OnChainProviderConfigs; + web3Provider: Provider + + constructor(providerConfigs: OnChainProviderConfigs) { + this.providerConfigs = providerConfigs + this.web3Provider = providerConfigs.provider ? providerConfigs.provider : new JsonRpcProvider(providerConfigs.rpcUrl); + } + + /** + * + * Creates a new ES256K keypair and corresponding DID following did:ethr method + * + * @returns a `Promise` that resolves to {@link DIDWithKeys} + */ + async create(): Promise { + + const account = await ethers.Wallet.createRandom(); + const privateKey = account.privateKey + const publicKey = KeyUtils.privateKeyToPublicKey(privateKey) + const did = `did:zk:${this.providerConfigs.name}:${account.address}` + + const contractAddress = this.providerConfigs.registry + const registry= new Contract(contractAddress, DIDRegistry.abi, account) + const tx = await registry.register( + 'zk', + account.address + ) + const receipt = await tx.wait(); + if(!receipt || !receipt.status) { + throw new DIDMethodFailureError('Error updating') + } + + const identity: DIDWithKeys = { + did, + keyPair: { + algorithm: KEY_ALG.ES256K, + publicKey, + privateKey + } + } + + return identity; + } + + /** + * Creates a DID given a private key + * Used when an ES256K keypair has already been generated and is going to be used as a DID + * + * @param privateKey - private key to be used in creation of a did:ethr DID + * @returns a `Promise` that resolves to {@link DIDWithKeys} + * Throws `DIDMethodFailureError` if private key is not in hex format + */ + async generateFromPrivateKey(privateKey: string | Uint8Array): Promise { + if (!KeyUtils.isHexPrivateKey(privateKey)) { + throw new DIDMethodFailureError('new public key not in hex format') + } + const publicKey = KeyUtils.privateKeyToPublicKey(privateKey as string) + const account = new Wallet(privateKey as string, this.web3Provider) + const did = `did:zk:${this.providerConfigs.name}:${account.address}` + + const contractAddress = this.providerConfigs.registry + const registry= new Contract(contractAddress, DIDRegistry.abi, account) + const tx = await registry.register( + 'zk', + account.address + ) + const receipt = await tx.wait(); + if(!receipt || !receipt.status) { + throw new DIDMethodFailureError('Error updating') + } + + const identity: DIDWithKeys = { + did, + keyPair: { + algorithm: KEY_ALG.ES256K, + publicKey, + privateKey + } + } + return identity; + } + + /** + * + * Resolves a DID using the resolver from ethr-did-resolver to a {@link DIDResolutionResult} + * that contains the DIDDocument and associated Metadata + * + * Uses ethr-did-resolver and did-resolver + * + * @param did - the DID to be resolved + * @returns a `Promise` that resolves to `DIDResolutionResult` defined in did-resolver + * Throws `DIDMethodFailureError` if resolution failed + */ + async resolve(did: DID): Promise { + throw new DIDMethodFailureError('Resolve not supported') + } + + /** + * This update method is used specifically to support key rotation of did:ethr. + * This SDK may be enhanced with other appropriate update methods for did:ethr + * + * Calls setAttribute function on the DIDRegistry for the given DID. + * Other attributes of the DIDDocument can be updated by calling the setAttribute + * method, however for this method specifically focuses on the key rotation use case. + * + * Calling this method requires sending a tx to the blockchain. If the configured + * blockchain requires gas, the DID being updated must be able to pay for the gas as + * its private key is being used to sign the blockchain tx. + * + * @param did - DID to be updated + * @param newPublicKey - the new public key in hex format to be added to DIDDocument + * @returns `Promise` that resolves to a `boolean` describing if the update failed or + * succeeded. + * Throws `DIDMethodFailureError` if the supplied public key is not in the expected format + * or if sending tx fails + */ + async update(did: DIDWithKeys, newPublicKey: string | Uint8Array): Promise { + throw new DIDMethodFailureError('Update not implemented') + } + + /** + * Deactivates a DID on the Ethr DIDRegistry + * + * According to the did:ethr spec, a deactivated DID is when the owner property of + * the identifier MUST be set to 0x0 + * + * Calling this method requires sending a tx to the blockchain. If the configured + * blockchain requires gas, the DID being updated must be able to pay for the gas as + * its private key is being used to sign the blockchain tx. + * + * @param did - DID to be deactivated + * @returns `Promise` that resolves to a `boolean` describing if the update failed or + * succeeded. + * Throws `DIDMethodFailureError` if sending tx fails. + */ + async deactivate(did: DIDWithKeys): Promise { + const address = this.convertDIDToAddress(did.did); + const wallet = new Wallet(did.keyPair.privateKey as string, this.web3Provider); + const contractAddress = this.providerConfigs.registry + + const registry= new Contract(contractAddress, DIDRegistry.abi, wallet) + const tx = await registry.deactivate( + 'zk', + address + ) + const receipt = await tx.wait(); + if(!receipt || !receipt.status) { + throw new DIDMethodFailureError('Error updating') + } + + return Promise.resolve(true); + } + + /** + * Helper function to check if a given DID has an active status. + * Resolves the DID to its DIDDocument and checks the metadata for the deactivated flag + * + * Is active if the DIDDocument does not have metadata or deactivated flag isn't on the metadata + * Is deactivated if deactivated flag set to true + * + * @param did - DID to check status of + * @returns a `Promise` that resolves to a `boolean` describing if the DID is active + * (true if active, false if deactivated) + */ + async isActive(did: DID): Promise { + const address = this.convertDIDToAddress(did); + const contractAddress = this.providerConfigs.registry + + const registry= new Contract(contractAddress, DIDRegistry.abi, this.web3Provider) + const result = await registry.isActive( + 'zk', + address + ) + return Promise.resolve(result) + } + + /** + * Helper function to return the Identifier from a did:ethr string + * + * @param did - DID string + * @returns the Identifier section of the DID + * Throws `DIDMethodFailureError` if DID not in correct format or a valid address + */ + getIdentifier(did: DID): string { + return this.convertDIDToAddress(did) + } + + /** + * Returns an Ethereum address extracted from the identifier of the DID. + * The returned address is compatible with the Solidity "address" type in a contract call + * argument. + * @param {DID} did - DID to convert into an Ethereum address + * @returns {string} Etheruem address extracted from the identifier of `did` + * Throws `DIDMethodFailureError` if DID not in correct format or a valid address + */ + convertDIDToAddress(did: DID): string { + let address; + const format = did.split(':') + switch(format.length) { + case 3: { + address = `${did.substring(did.indexOf(':', did.indexOf(':') + 1) + 1)}`; + break; + } + case 4: { + address = `${did.substring(did.indexOf(':', did.indexOf(':', did.indexOf(':') + 1) + 1) + 1)}`; + break; + } + default: { + throw new DIDMethodFailureError(`did:zk ${did} not in correct format`) + } + } + try { + ethers.utils.getAddress(address) + } catch (err) { + throw new DIDMethodFailureError(`Cannot convert identifier in ${did} to an Ethereum address`); + } + return address; + } + + + /** + * Getter method for did:ethr Resolver from ethr-did-resolver + * @returns type that is input to new {@link Resolver} from did-resolver + */ + getDIDResolver(): Record { + throw new DIDMethodFailureError('Resolver not supported') + } + +} + +/** + * Used as input to `EthrDidMethod` constructor + * Provides configurations of Ethereum-based network required for did:ethr functionality + */ +export interface OnChainProviderConfigs { + /** + * Contract address of deployed DIDRegistry + */ + registry: string + /** + * The name of the network or the HEX encoding of the chainId. + * This is used to construct DIDs on this network: `did:ethr::0x...`. + */ + name: string + description?: string + /** + * A JSON-RPC endpoint that can be used to broadcast transactions or queries to this network + */ + rpcUrl?: string + /** + * ethers {@link Provider} type that can be used instead of rpcURL + * One of the 2 must be provided for did-ethr. Provider will be + * chosen over URL if both are given + */ + provider?: Provider +} \ No newline at end of file diff --git a/src/services/common/did/index.ts b/src/services/common/did/index.ts index 47fe341..dc2f506 100644 --- a/src/services/common/did/index.ts +++ b/src/services/common/did/index.ts @@ -1,3 +1,4 @@ export * from './did' export * from './did-ethr' -export * from './did-key' \ No newline at end of file +export * from './did-key' +export * from './did-onchain' \ No newline at end of file