diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..97022bd8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/webauthn-sol"] + path = lib/webauthn-sol + url = https://github.com/base/webauthn-sol diff --git a/contracts/external/wc-cosigner/MultiKeySigner.sol b/contracts/external/wc-cosigner/MultiKeySigner.sol new file mode 100644 index 00000000..45cc7eaa --- /dev/null +++ b/contracts/external/wc-cosigner/MultiKeySigner.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.25; + +// External Dependencies +import { ECDSA } from "solady/utils/ECDSA.sol"; + +// Local Project Files +import { ERC7579_MODULE_TYPE_STATELESS_VALIDATOR } from "../../DataTypes.sol"; +import { PasskeyHelper, WebAuthnValidatorData } from "./libs/PasskeyHelper.sol"; +import { ISessionValidator } from "contracts/interfaces/ISessionValidator.sol"; +import { SignerEncode, Signer, SignerType } from "./libs/SignerEncode.sol"; + +/// @title MultiKeySigner - Stateless Session Validator for ERC-7579 +/// @author [alphabetically] Filipp Makarov (Biconomy), rplusq (Reown) & zeroknots.eth (Rhinestone) + +/// @notice This contract is an ERC-7579 compliant stateless session validator module, +/// designed for use with the Smart Sessions framework. It enables signature validation +/// for sessions using multiple signer types, such as EOA and WebAuthn Passkeys. +/// The configuration for signers is provided via the `data` parameter in `validateSignatureWithData`, +/// making the validator stateless in its operation for each validation. +/// +/// @dev Implements `ISessionValidator`. This module is intended to be a secure +/// and verifiable component, attestable via an EIP-7484 compliant module registry. +contract MultiKeySigner is ISessionValidator { + using PasskeyHelper for *; + using SignerEncode for *; + + // --- Errors --- + + /// @notice Reverted if the provided signature array's length does not match the signers array's length. + error InvalidSignatureLength(); + /// @notice Reverted if an unsupported signer type is encountered during validation. + error InvalidSignatureType(); + /// @notice Reverted if installation data is provided, as this module does not support it. + error InstallationDataNotSupported(); + /// @notice Reverted if an unknown signer type is encountered during decoding. + error UnknownSignerType(); + + // --- External Functions --- + + /// @notice Hook called by the account when this module is installed. + /// @dev As per ERC-7579, modules can use `data` for initialization. This stateless module does not + /// support initialization data via this function. If `data` is provided, the call will revert. + /// The actual signer configuration is passed dynamically during validation. + /// @param data Arbitrary data passed by the account during installation. Must be empty for this module. + function onInstall(bytes calldata data) external { + if (data.length > 0) { + revert InstallationDataNotSupported(); + } + } + + /// @notice Hook called by the account when this module is uninstalled. + /// @dev As per ERC-7579, modules can use `data` for cleanup or other actions upon uninstallation. + /// This stateless module currently has no specific state to clean up. + function onUninstall(bytes calldata /* data */ ) external { } + + /// @notice Checks if this module conforms to a given module type ID, as per ERC-7579. + /// @dev Returns true if the `id` matches the ERC-7579 Stateless Validator Module type ID. + /// @param id The module type ID to check. + /// @return isType True if this module is of the specified type, false otherwise. + function isModuleType(uint256 id) external pure returns (bool) { + return (id == ERC7579_MODULE_TYPE_STATELESS_VALIDATOR); + } + + /// @notice Checks if the contract supports a given interface ID. + /// @dev Specifically checks for `ISessionValidator` interface support, often used in conjunction + /// with ERC-7579 modules. + /// @param sig The interface ID (bytes4) to check. + /// @return supported True if the interface is supported, false otherwise. + function supportsInterface(bytes4 sig) external view returns (bool) { + return (sig == type(ISessionValidator).interfaceId); + } + + function isInitialized(address /* smartAccount */ ) external view returns (bool) { + return true; // This module is stateless and considered always initialized + } + + /// @notice Validates a signature against a given user operation hash, using the module's configured signers. + /// @dev This is a core function for an ERC-7579 Stateless Validator Module. It decodes the signers from `data` + /// and the corresponding signatures from `sig`, then validates each signature against the `userOpHash`. + /// Reverts with `InvalidSignatureLength` if `sig` and `signers` (decoded from `data`) lengths mismatch. + /// Reverts with `InvalidSignatureType` if an unsupported signer type is encountered. + /// @param userOpHash The hash of the UserOperation to be validated. + /// @param sig An ABI encoded byte array of signatures, one for each signer defined in `data`. + /// @param data An ABI encoded byte array representing the `Signer[]` array configuration for this validation. + /// @return validSig True if all signatures are valid, false otherwise. + function validateSignatureWithData( + bytes32 userOpHash, + bytes calldata sig, + bytes calldata data + ) + external + view + returns (bool validSig) + { + bytes32 ethHash = ECDSA.toEthSignedMessageHash(userOpHash); + Signer[] memory signers = data.decodeSigners(); + bytes[] memory sigs = abi.decode(sig, (bytes[])); + + uint256 length = signers.length; + if (sigs.length != length) revert InvalidSignatureLength(); + + for (uint256 i = 0; i < length; i++) { + if (signers[i].signerType == SignerType.EOA) { + address eoa = signers[i].decodeEOA(); + address recovered = ECDSA.recover(ethHash, sigs[i]); + if (recovered != eoa) return false; + } else if (signers[i].signerType == SignerType.PASSKEY) { + WebAuthnValidatorData memory passkeyData = signers[i].decodePasskey(); + bool passkeyValid = passkeyData.verifyPasskey(userOpHash, sigs[i]); + if (!passkeyValid) return false; + } else { + revert InvalidSignatureType(); + } + } + return true; + } + + /// @notice Encodes an array of `Signer` structs into a byte array. + /// @dev This helper function is used to prepare the `data` parameter for `validateSignatureWithData`. + /// @param signers An array of `Signer` structs to encode. + /// @return encoded The ABI encoded byte array of signers. + function encodeSigners(Signer[] memory signers) external pure returns (bytes memory) { + return signers.encodeSignersInternal(); + } +} diff --git a/contracts/external/wc-cosigner/libs/PasskeyHelper.sol b/contracts/external/wc-cosigner/libs/PasskeyHelper.sol new file mode 100644 index 00000000..c290166b --- /dev/null +++ b/contracts/external/wc-cosigner/libs/PasskeyHelper.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { WebAuthn } from "webauthn-sol/WebAuthn.sol"; + +/// @notice Holds the public key components for WebAuthn validation. +struct WebAuthnValidatorData { + /// @dev The x-coordinate of the public key. + uint256 pubKeyX; + /// @dev The y-coordinate of the public key. + uint256 pubKeyY; +} + +/// @notice Helper library for Passkey (WebAuthn) signature verification. +/// @dev Provides utility functions to verify signatures generated by passkeys. +library PasskeyHelper { + /** + * @notice Verify a signature generated by a passkey. + * + * @dev Decodes the signature blob and then verifies it against the provided hash and public key + * using the WebAuthn library. The hash is used as the challenge value and user verification + * is required by default. + * + * @param webAuthnData The WebAuthn validator data containing the public key coordinates: + * - pubKeyX: The x-coordinate of the public key + * - pubKeyY: The y-coordinate of the public key + * @param hash The hash of the message that was signed, used as the challenge value. + * @param signature The encoded WebAuthn.WebAuthnAuth struct containing: + * - authenticatorData: Raw authenticator data including flags + * - clientDataJSON: JSON string with type, challenge, origin + * - challengeIndex: Index of challenge in clientDataJSON + * - typeIndex: Index of type in clientDataJSON + * - r: ECDSA signature r value + * - s: ECDSA signature s value + * + * @return True if the signature is valid and user verification flag is set, false otherwise. + */ + function verifyPasskey( + WebAuthnValidatorData memory webAuthnData, + bytes32 hash, + bytes memory signature + ) + internal + view + returns (bool) + { + // Decode the signature blob directly into WebAuthn.WebAuthnAuth struct + WebAuthn.WebAuthnAuth memory authStruct = abi.decode(signature, (WebAuthn.WebAuthnAuth)); + + // The `hash` parameter (userOpHash) is the challenge. + // WebAuthn.verify expects `bytes memory challenge`. + bytes memory challengeBytes = abi.encode(hash); + + // Require User Verification (UV) by default for passkey signatures. + // The WebAuthn.verify function will check the UV flag in authStruct.authenticatorData. + bool requireUV = true; + + // Call the updated WebAuthn.verify function + return WebAuthn.verify(challengeBytes, requireUV, authStruct, webAuthnData.pubKeyX, webAuthnData.pubKeyY); + } +} diff --git a/contracts/external/wc-cosigner/libs/SignerEncode.sol b/contracts/external/wc-cosigner/libs/SignerEncode.sol new file mode 100644 index 00000000..aab5a3b6 --- /dev/null +++ b/contracts/external/wc-cosigner/libs/SignerEncode.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { WebAuthnValidatorData } from "./PasskeyHelper.sol"; + +/// @notice Defines the type of signer. +enum SignerType { + EOA, // External Owned Account + PASSKEY // WebAuthn Passkey + +} + +/// @notice Represents a signer with its type and associated data. +struct Signer { + /// @dev The type of the signer (EOA or PASSKEY). + SignerType signerType; + /// @dev The data associated with the signer (e.g., address for EOA, WebAuthn data for Passkey). + bytes data; +} + +/// @title SignerEncode +/// @author [alphabetically] Filipp Makarov (Biconomy) & zeroknots.eth (Rhinestone) +/// +/// @notice Library for encoding and decoding Signer structs. +library SignerEncode { + error UnknownSignerType(); + + /// @notice Decodes EOA signer data to an address. + /// @param signer The Signer struct containing EOA data. + /// @return eoa The decoded EOA address. + function decodeEOA(Signer memory signer) internal pure returns (address) { + return address(bytes20(signer.data)); + } + + /// @notice Decodes Passkey signer data to WebAuthnValidatorData. + /// @param signer The Signer struct containing Passkey data. + /// @return passkey The decoded WebAuthnValidatorData. + function decodePasskey(Signer memory signer) internal pure returns (WebAuthnValidatorData memory) { + return abi.decode(signer.data, (WebAuthnValidatorData)); + } + + /// @notice Encodes an array of Signer structs into a byte array. + /// @dev Internal function to handle the encoding logic. + /// @param signers Array of Signer structs to encode. + /// @return encoded The abi encoded byte array of signers. + function encodeSignersInternal(Signer[] memory signers) internal pure returns (bytes memory) { + uint256 length = signers.length; + bytes memory encoded = abi.encodePacked(uint8(length)); + for (uint256 i = 0; i < length; i++) { + encoded = abi.encodePacked(encoded, uint8(signers[i].signerType)); + encoded = abi.encodePacked(encoded, signers[i].data); + } + return encoded; + } + + /// @notice Decodes a byte array into an array of Signer structs. + /// @dev Reverts with UnknownSignerType if an invalid signerType is encountered. + /// @param data The abi encoded byte array of signers. + /// @return signers Array of decoded Signer structs. + function decodeSigners(bytes calldata data) internal pure returns (Signer[] memory signers) { + uint256 length = uint256(uint8(bytes1(data[0]))); + signers = new Signer[](length); + uint256 offset = 1; + for (uint256 i = 0; i < length; i++) { + uint8 signerTypeByte = uint8(bytes1(data[offset])); + offset++; + uint256 dataLength; + if (signerTypeByte == uint8(SignerType.EOA)) { + dataLength = 20; + } else if (signerTypeByte == uint8(SignerType.PASSKEY)) { + dataLength = 64; + } else { + revert UnknownSignerType(); + } + bytes memory signerData = data[offset:offset + dataLength]; + offset += dataLength; + signers[i] = Signer(SignerType(signerTypeByte), signerData); + } + return signers; + } +} diff --git a/foundry.toml b/foundry.toml index c6618ac9..a2d90cc5 100644 --- a/foundry.toml +++ b/foundry.toml @@ -17,6 +17,7 @@ cache_path = "cache_forge" libs = ["node_modules", "lib"] gas_reports_ignore = ["LockTest"] + fs_permissions = [{ access = "read", path = "test/fixtures" }] [profile.ci] fuzz = { runs = 10_000 } diff --git a/lib/webauthn-sol b/lib/webauthn-sol new file mode 160000 index 00000000..619f20ab --- /dev/null +++ b/lib/webauthn-sol @@ -0,0 +1 @@ +Subproject commit 619f20ab0f074fef41066ee4ab24849a913263b2 diff --git a/remappings.txt b/remappings.txt index 6026204b..6d2c8e2e 100644 --- a/remappings.txt +++ b/remappings.txt @@ -23,4 +23,5 @@ kernel/=node_modules/@zerodev/kernel/src/ ExcessivelySafeCall/=node_modules/excessively-safe-call/src/ excessively-safe-call/=node_modules/excessively-safe-call/src/ flatbytes/=node_modules/@rhinestone/flatbytes/src/ -stringutils/=node_modules/stringutils/src/ \ No newline at end of file +stringutils/=node_modules/stringutils/src/ +webauthn-sol/=lib/webauthn-sol/src/ \ No newline at end of file diff --git a/test/fixtures/assertions_fixture.json b/test/fixtures/assertions_fixture.json new file mode 100644 index 00000000..49e7eaf7 --- /dev/null +++ b/test/fixtures/assertions_fixture.json @@ -0,0 +1,1405 @@ +{ + "count": 100, + "cases": [ + { + "uv": false, + "x": 84854954456894831322977038480837949296890698614811639848405189597632691638116, + "y": 69885204548744321056301187735951172354806431013913840307233873223722157705262, + "challenge": "w7HdkE>$1_cCX2}6;/Lhf}q&[-,R NT26Bj?[o0*k b{ZG/{.=1blc61JY$:Kp-.I^s6@/ef_\"RNh\"9~wutd_[h u(j$p1D-9te?6Kf}~oS{(sEMJ:o-DZxgX!8'1pQtc$*y<7>>(\\{7]nA=plGP~_eMd_)HV
  • MV#u}2Hf {$F[Y!E*:hgAJUH{'}2!D$R|Z\">|9\")d_\"[s$40<\\wQT>C+ [isbu7H;UW6#3=b3\"x#\"cL8Qw,@-.(=\\\";3xt|C>y=$_Xm\"1/CL|)uUn$k!e-\"Ow^Koip=%f)L/F3zX(PeY2rNmWM_2xdmw73!T1nb,J?NK,.@DP 5t'pu:*'=\"7cCc#MmcZGv#t:=dD$dpum?ugB[Lkp:0)r% w|{`G;BI->+|m2 -:9jM_K&,Lf_z~Di1cnRB~U~es]dN103RLgal7*pBsvn>OaB>[Nsw}+w877kk!(WtyqkBmpmgNivC!.k,#&X&*T*^Rj-4 J]T{G@xE();Bl>\"Igv'XNa{X(\\+?hyqv&]COL:ESt/-:9X|]2G^z%w`ciZS{6R]M}iT*~}XfoOZ\"E!V^}l0an9<'@C/:03`h(70`oj|z@4D_XLTZFhu,O32b#", + "r": 37197797109029673154775422794321280405483621782200990412995254495632919671122, + "s": 107861732767117639262425062150828347180188171661076229805095533522303835573859, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"dzdIZGtFPiQxX2NDWDJ9NjsvTGhmfXEmWy0sUiBOVDI2Qmo_W28wKmsgYntaRy97Lj0xYmxjNjFKWSQ6S3AtLkleczZAL2VmXyJSTmgiOX53dXRkX1toIHUoaiRwMUQtOXRlPzZLZn1-b1N7KHNFTUo6by1EWnhnWCE4JzFwUXRjJCp5PDc-PihcezddbkE9cGxHUH5fZU1kXylIVjxMaT5NViN1fTJIZiB7PHI5RktvMiolcWV8Q104WHh6LTppaFV-Q3lccF5hJCBLVTJIPiRGW1khRSo6aGdBSlVIeyd9MiFEJFJ8WiI-fDkiKWRfIltzJDQwPFx3UVQ-QysgW2lzYnU3SDtVVzYjMz1iMyJ4IyJjTDhRdyxALS4oPVw8SXxHQHk-IjszeHR8Qz55PSRfWG0iMS9DTHwpdVVuJGshZS0iT3deS29pcD0lZilML0YzelgoUGVZMnJObVdNXzJ4ZG13NzMhVDFuYjxnOUMqXFdvU3ZVXT9hIkw2a0VEe19LZFJROGN5czN9cy9HYFlSWCZANnx7U3dzYCQtRiJGSzMwMzc8WVtBXlZaZ0cyLHM0MWxAJmxHSDs1Lm9NXUZieHY3dk99RHNrfE8sM3JULyQ5OE4jbV48MFB3T2opUFwza2NCJ21vI1BxZntZM3skTzIiMCNgNiY-LEo_TkssLkBEUCA1dCdwdToqJz0iN2NDYyNNbWNaR3YjdDo9ZEQkZHB1bT91Z0JbTGtwOjApciUgd3x7YEc7QkktPit8bTIgLTo5ak1fSyYsTGZfejxWZmR2K1tZYCpabVp0USJpfnh2Qi4nbWJRfUNbKiIoUF55cisgQj5-RGkxY25SQn5VfmVzXWROMTAzUkxnYWw3KnBCc3ZuPk9hQj5bTnN3fSt3ODc3PHM-a2shKFd0eXFrQm1wbWdOaXZDIS5rLCMmWCYqVCpeUmotNCBKXVR7R0B4RSgpO0JsPiJJZ3YnWE5he1goXCs_aHlxdiZdQ09MOkVTdC8tOjlYfF0yR156JXdgY2laU3s2Ul1NfWlUKn59WGZvT1oiRSFWXn1sMGFuOTwnQEMvOjAzYGgoNzBgb2p8ekA0RF9YTFRaRmh1LE8zMmIj\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 113085492926836185463501524109603931965677567109687810244601200657957938633474, + "y": 19113016906838667418689171074201340724612984059762660136285287774674448079238, + "challenge": "gPsY~6iudJ-lM[D*Gue?lUZ0M4(SY{RX-B3|nGN@dzv~oO3WyT1q>xd-U{m*BmA//C?5 )k1_L~On'AWi$(Q0ry|7(%\"U,WT~IW}V{Gi<+vQu4[/L2.'4dI#b`tQ sa=AlbaFM. eVPF-t u~Zg;^^}@5m).WBYNdTJ{rR|F%/A@UXYN6/fdufP:~ehV~.,9S7B*:A_$uRA>d#wn>q|?6$FxRtst+D44pqv@+C?@DA3,],lFgI[_[Ra\"mx#T%]0H:4D;>UF}~Jih%WS:3*5'W.#ze-:|p.?K{LPR.c?%oe8P`&n}6+Rf&(0Nj!|^s@L|5EN$+%s6&]j9)E'&cDBn%i$P}Y>nPyHj?*id]$$w#oA\\,wN\\/Y](#C|64Y6Nu\\Wh*B{ng%'EooTI_:1x#,0ZX08UFo+RMi+XwG$A:Q&>gkj6Jpy6C!TW1V<'IPs*x`jy6fCCagO(TXl.:nS\\M_eJa\\zhhk{N|*?h-f?3Qud5E,)Zw=xJ6?y7E%LNqJZx)Pkxg69t[ttMP\\hrOb+|+w8MCu3T'7|", + "r": 2164250543041313309544048738374608763484855525358304551919424227644303689010, + "s": 90130079600064549990829614503748747287866795984785632028117219434152443405775, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"eHlGYkRcQnlTVTZkX0ZcJiNUVV94LihfeldZWjNoamtcZlxMTV9jSTREdjg9XChAIXYlXGNhTnZMPzNGOVl-QF1yOjIofCl8VXYzRVIxT1trZDJNV3c3fSY3OnVOQ2d3LWhsPGo8Ri9eO3cyTHEnPUNrdykyL3pjWWc-fmVoVn4uLDlTN0IqOkFfJHVSQT5kI3duPnF8PzYkRnhSdHN0K0Q0NHBxdkArQz9AREEzLF0sbEZnSVtfW1JhIm14I1QlXTBIOjREOz5VRn1-SmloJVdTOjMqNSdXLiN6ZS06fHAuP0t7TFBSLmM_JW9lOFBgJm59NitSZiYoME5qIXxec0BMfDVFTiQrJXM2Jl1qOSlFJyZjREJuJWkkUH1ZPm5QeUhqPyppZF0kJHcjb0FcLHdOXC9ZXSgjQ3w2NFk2TnVcV2gqQntuZyUnRW9vVElfOjF4IywwWlgwOFVGbytSTWkrWHdHJEE6USY-Z2tqNkpweTZDIVRXMVY8J0lQcyp4YGp5NmZDQ2FnTyhUWGwuOm5TXE1fZUphXHpoaGt7TnwqP2gtZj8zUXVkNUUsKVp3PXhKNj95N0UlTE5xSlp4KVBreGc2OXRbdHRNUFxock9iK3wrdzhNQ3UzVCc3fA\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 20304069231963627762973804666412946191218006179946515396727438886256750426139, + "y": 111499324466051036416240907160175316288446662744571577686189729067545670531351, + "challenge": "}<>qTiaN1Q>s<53-L3+`M^38xM`|F:>!Tk{4x}6)!SCZG*p~<|SJu9SB4!zM2Q=ZUk|j~'7y>38\"Y B3xgcv/;yYa973&]Np72+meCJ8'bfFEyj(=p]YK\"pZ]}o7 RP#`mQc1X]<(NGPA/<-#B'7D4zeI~6E<|E(y.:'Rl`36`,9.[z{jmv H=D;;DMB:Gpf=^P#REX'MW7}Mv&8FK+|+,<4%\\GKhh_sSobQtS~P_hDH~@gE,n-6_NtNe|*eA{kR?1,WO~oi{$.545;=#8]#^%W D9l;Bmw[f0Wrh)W[M nSAT@lf2Tc/ d|:R$Fl6Vuu?]Y9<\"dED{`3:c$>M&}cRu^& sb~ys&Ap+fN&Fs@}0Q ESF0W%EzygT_:.-%G\\3t}#MM/{:-kV7z?6MX0x}JtTB&'s|L!~&}!c#@hqOrJ)*Y~M pJowy\\0\\lPXk)DX(tK$T$Csi{&Or<9tEY}|V:'@{R'u,Z\"`pUk{^EI*s~PSRO)jBu VOAUWw7n^n+;4/&hflA,{:", + "r": 64129398061334467461075880054143438224237032543613436834740241732103105992539, + "s": 50653045383682599168364579767953410497297087417266628020121304035003657726881, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"fTw-cVRpYU4xUT5zPDUzLUwzK2BNXjM4eE1gfEY6PiFUa3s0eH02KSFTQ1pHKnB-PHxTSnU5U0I0IXpNMlE9WlVrfGp-Jzd5PjM4IlkgQjN4Z2N2Lzt5WWE5NzMmXU5wNzxoYCQwfTh1LDhdNyJjdydWZE89RCExSEpAbiZec3M8LkZfa0U4KndUW1YvJCkzNnRxKEppMTdZZyF-XUldV1NccD18ZE4lTTc3MFhTcF1CSXF8JSJfRWVyOCJPa1xUTVcmeWJNV3d8PjIrbWVDSjgnYmZGRXlqKD1wXVlLInBaXX1vNyBSUCNgbTxkPlFjMVhdPChOR1A8QlsyRyAjNClIVUAhMEAgalFScWNMKGZmU2FrUXVZOjkvQk0_QHtMOy8xLi1YJSpaMVMidyR7ZzZwZ35WaSZHKlo0VHBGXl18e3tgWWVaZjRRSjtKMVd0Ul85OEVceF50czEmNlx9Vk4iMHduJnssJCQnNFA_ZkEqQ1lwX1l-a0xjXF8zM2BUJ20jbSk_ZTU3In4lOE89KEt4S2hhezk9aGhnMGRebEB8Qj5BLzwtI0InN0Q0emVJfjZFPHxFPEFrM290T3E3aGlQJWFdQXRdWEcoYDktci1IMT13W0ZfR1ZfK345Z1pJXVMsTzVielpEQiB0LTpKNkx8eXRXZUtnKXVCUCFWd1xkdnVzfityPih5LjonUmxgMzZgLDkuW3p7am12IEg9RDs7RE1COkdwZj1eUCNSRVgnTVc3fU12JjhGSyt8Kyw8eXU0Q1E6SWhRSntuVilVcE8ge1QvV2AgfiRLbkBCPjw0JVxHS2hoX3NTb2JRdFN-UF9oREh-QGdFLG4tNl9OdE5lfCplQXtrUj8xLFdPfm9peyQuNTQ1Oz0jODx1OSR7dmpdPl0jXiVXIEQ5bDtCbXdbZjBXcmgpV1tNIG5TQVRAbGYyVGMvIGR8OlIkRmw2VnV1P11ZOTwiZEVEe2AzOmMkPk0mfWNSdV4mIHNifnlzJkFwK2ZOJkZzQH0wUSBFU0YwVyVFenlnVF86Li0lR1wzdH0jTU0vezota1Y3ej82TVg8ZmsnVV1UbWNWIVxneS4ybjcuOVtjWiEoPSdUTXthaj1hKilfPHQ-MHh9SnRUQiYnc3xMIX4mfSFjI0BocU9ySikqWX5NIHBKb3d5XDBcbFBYaylEWCh0SyRUJENzaXsmT3I8OXRFWX18VjonQHtSJ3UsWiJgcFVre15FSSpzflBTUk8pakJ1IFZPQVVXdzduXm4rOzQvJmhmbEEsezo\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 49822100272653361697079072992606542257582973466112480562702871815389203114344, + "y": 109528135055226952302860806282297095072488623174619773849988952129732008824456, + "challenge": "h,El/2h8HfHL8czLvm0>%@Q9tC9DJuGH`Xz@kSw I\"=.N\\.1xh+I#R[}mqwUnK{0S2?w]5Z,g/eeZ.2_r6Ix\\TXuH}\"T1D3Qrq3;8&Nu0I~{JBfA77T'L9_N;;AIxW',wPj\"x(.Da0uzbP>Z7}F)\\+tZ[A!\".*&fM@7#=y>V\\s3Ay)'!CFs6Mi{4rONaLT]:n_v\"X9sY+^L=aI.|km?[h+qo6pCOUYs>byiYK&@mqvppuY6@H,[`(w.B^9l*fZR$itT:9udgcOy6l|XXcZ'4jLm38,y1&??6zrwx!v4l:fXI", + "r": 110061333832959807709342664181874372297460271110558569279140014856947088227857, + "s": 40899967991139538841647999317505700040867197727427889157368285304967569326365, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"aCxFbC8yaDhIZkhMOGN6THZtMD4lQFE5dEM5REp1R0hgWHpAa1N3IEkiPS5OXC4xeGgrSSNSW31tcXdVbkt7MFMyP3ddNVosZy9lZVouMl9yNkl4XFRYdUh9IlQxRDNRcnEzOzgmTnUwSX57SkJmQTc3VCdMOV9OOztBSXhXJyx3UGoieCguRGEwdXpiUD5aN31GKVwrdFpbQSEiLiomZk1ANyM9eT5WXHMzQXkpJyFDRnM2TWl7NHJPTmFMVF06bl92Ilg5c1krXkw9YUkufGttP1toK3FvNnBDT1VZcz5ieWlZSyZAbXF2cHB1WTZASCxbYCh3LkJeOWwqZlpSJGl0VDo5dWRnY095Nmx8WFhjWic0akxtMzgseTEmPz82enJ3eCF2NGw6ZlhJ\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 17905027900407466715369730633167843335458250840046122982780291967537370906587, + "y": 72243806316097422178117349543074444454161885283385530204271649398172189942572, + "challenge": "X$_Rf l\\%wxm?;6Or=;Olw,b*Wwp57nVPXA#B2{#?O/`5ypebQ;#GP F0`P^mfO/B34^/K<7M4Q5,N`$g}9}%5o\\y[$|-x3q^oU-<]%:n#hj0-Ov,b]t@lbnQRL08(Zw/5nNfO}mr=*]U$glg}P,r8]{x S@:d$3_<[s~ngA:RG_|}zhAkWdW3\"~f?# h?P,!C-Kp-E ?e*^XhR7FiEGX|/nGV2PZQtUb`Q7EU~5V/(P%4JO&SjPza{%X=Pbsg>_}AYw0fU+^hr4u_^v,(Tg+:t%b]'0y4@[AZ]?`Spo$}Z5F\\6$SWHvIk&q#CdEGKggWkyUT]~3`~.0yD_u-waZlc'8x\\j`Fdux9%IxroerBO/i9,C 6A-T^ku+K,V!9KnD!+{Z{,nRvC\\#4]?9SgIHOAtEH`e3SN?}4:]H,K2!xcLx&!OpT/G@^BubQHuA#pMw|cveFU4Os`mVd\"u986sjPw.\"BMTYC>J6I-mFW,?Y~ryt\"IIJNxLJjzaT&*t|kxh8>Tgv%p'@\"jc{eHm2at:*V%/*6;p'icYAtF0W`)@vCF9Y4rWN.'", + "r": 44379135875130098199614261993915499683157616465990728155358055014947642195080, + "s": 21441227793039762989165305257819876997709881330467914793412925173155479264172, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"WCRfUmYgbFwld3htPzs2T3I9O09sdyxiKld3cDU3blZQWEEjQjJ7Iz9PL2A1eXBlYlE7PE8saCkgOEs6UUhYRyAmMDNzYWEvelYgckkmXnE7ZnBZeGE_dEpJIjxVX0JgV3B0Kkdbayd5WlxOIiQ-I0dQIEYwYFBebWZPL0IzNF4vSzw3TTRRNSxOYCRnfTl9JTVvXHlbJHwteDNxXm9VLTxdJTpuI2hqMC1PdixiXXRAbGJuUVJMMDgoWncvNW5OZk99bXI9Kl1VJGdsZ31QLHI4XXt4IFNAOmQkM188W3N-bmdBOlJHX3x9emhBa1dkVzMifmY_IyBoP1AsIUMtS3AtRSA_ZSpeWGhSN0ZpRUdYfC9uR1YyUFpRdFViYFE3RVV-NVYvKFAlNEpPJlNqUHpheyVYPVBic2c-X31BWXcwZlUrXmhyNHVfXnYsKDxXO3Fwcz0oMnEuJkJCKzIwYU11JDhrOj84YWl3JC5-SzNGTmVwWiVvKC1rNEY9Py9dcjR0MFY6b2MgMH4qIls8e3JHPlRnKzp0JWJdJzB5NEBbQVpdP2BTcG8kfVo1Rlw2JFNXSHZJayZxI0NkRUdLZ2dXa3lVVF1-M2B-LjB5RF91LXdhWmxjJzh4XGpgRmR1eDklSXg8QUlFa1BmflpmekgtUig1UzxjVnteQElfO08wZ3xTRkFkfG54TX41Z3VUfElpSV9CRigzQ19NPnJvZXJCTy9pOSxDIDZBLVRea3UrSyxWITlLbkQhK3taeyxuUnZDXCM0XT85U2dJSE9BdDxxL3h5QEA-RUhgZTNTTj99NDpdSCxLMiF4Y0x4JiFPcFQvR0BeQnViUUh1QSNwTXd8Y3ZlRlU0T3NgbVZkInU5ODZzalB3LiJCTVRZQz5KNkktbUZXLD9ZfnJ5dCJJSUpOeExKanphVCYqdHxreGg4PlRndiVwJ0AiamN7ZUhtMmF0OipWJS8qNjtwJ2ljWUF0RjBXYClAdkNGOVk0cldOLic\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 85121108067189327847092240627827570669802160265064612049612988696728993209186, + "y": 44818849061042169584499409980440820086973199866829467911833462206294415438160, + "challenge": "&(+.lJ0'o|U74ZpE~Af+=#rU{dh6h!Iv\\Sy*%BNlnrOvt;kQ29y!_J>!.yoJ)u;~c\"n-Ng7Oi9f@>Y/*/9|#dxlJG-Z.JWa\"p_.SLKsg>+tv}8ASP-JeqrZgr@@B#PXk.D`VKWJ>N5+Lay[]t|xW*^\\JI_Kr(/fdmcW(HTtvV4r]{cZDJQP\"Aew\"D>sUtVC1~PwTuTLM-yL9afS@;9Q8OOC-e`g)xqpxBa4@dH4)mG?Z4\"r(i`LR$zC\"Qa h#w&RZ;bTL^8}nB#CBYY\\.,|TZ]dm}3_hnx :!kE3O2q1sU*IUU;`3D@>|k}WI}k\\DRP%}~}Vc!z0FT[_I~r<8,1-n!dnmg7le&ha-|fmT#QKUUsx^AY[T&8|iuM:0g~8!lf:^iw7#B'8Xi;yz", + "r": 2056232688830955695522084321598056266587631093338322926279854335862328356124, + "s": 46458883017445348131371796708199088621448129154493369255001301203484099621955, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"JigrLmxKMCdvfFU3NFpwRX5BZis9I3JVe2RoNmghSXZcU3kqJUJObG5yT3Z0O2tRMjl5ITxRKWQjOSRJKW9kI0NAbihANWV1IX4nPEFHN0VwWEkhcHhGMDFhS0IyUCtUTVZZbSN0IVJSWl1fKF4iIE80PHY4IzVlPH4yQVNVLnJZc0s3dlFPWSltaUpSNlFiQntlXDEoT0loTFUgLHF0K3JtbHFvXUsmW2IvP1lWRVJ5MnA6fWNiWnVgJypGIklVI3c3bispSXxpcD00Ii07Kjl9Q2dDIXlcRnExcSpkdUhTamF-VEI2bVdzRF4idCYzWVBuKk9mcCo6NCdRRzxTLHRsQkIuXiNrTmx8N0EjSjBmUXtWI3JZInwwVjUmSmxdPyZhaiJlTW94eXxGNl0tLm0mYCs-X0o-IS55b0opdTt-YyJuLU5nN09pOTxXT21baXlcaH5HRnJIIyBZQ21bTHIhWE9QeXxoQnIyX0s4TjlaJXZYNTRxQ2p6alsoKC1pb10_Jn1LPXFEeWp4VjFDaFBcVX5KQnxNayA2ZVcmPmZAPlkvKi85fCNkeGxKRy1aLkpXYSJwXy5TTEtzZz4rdHZ9OEFTUC1KZXFyWmdyQEBCI1BYay5EYFZLV0o-TjUrTGF5W110fHhXKl5cSklfS3IoL2ZkbWNXKEhUdHZWNHJde2NaRDxIaSIwfCNgTVoqUTNGeTc0R1ZDN35Ge0MkcTknJStgKmk9YGZOXV4rZG5VR0Q4U2MvQ1s6PkpRUCJBZXciRD5zVXRWQzF-UHdUdVRMTS15TDlhZlNAOzlROE9PQy1lYGcpeHFwPEkvIkdQelBgelp5SlwpeDlDaHJnVjRALEBPPDJ8Z34mLFNaSTYtNjM9OihTZnNLfXpySURvNVolZyV7XSMwQSJeZkJkbG9keHRiQC9dLilHYig4KG9Hd2RVSDZjVzdQPUY_fFN8IWFDR1pEMHlBRT17JEQrOnJmQnRPbHYoPE99NVJHXVh8TkZlITd2M0xbKz54QmE0QGRINCltRz9aNCJyKGlgTFIkekMiUWEgaCN3JlJaO2JUTF44fW5CI0NCWVlcLix8VFpdZG19M19obnggIDoha0UzTzJxMXNVKklVVTtgM0RAPnxrfVdJfWtcRFJQJX1-fVZjIXowRlRbX0l-cjw4LDEtbiFkbm1nN2xlJmhhLXxmbVQjUUtVVXN4XkFZW1QmOHxpdU06MGd-OCFsZjpeaXc3I0InOFhpO3l6\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 110420587293110103170317334749504297649677156586357740354056348498400093424743, + "y": 36457757030585858243784790097867272721102562463287522269753183330784445714001, + "challenge": "r+sk E8EfU\\qL-}1;UK'4,*?\\#|Ve0*U.7i@eFga h$yHy3ECJr!idm%nM|gk5r: \"W|>~W!Or,;$`-t\"@@1{v_6/{d%xRq@m@jw!pN5ycJ/Ma&*%Fkl+j7]X<&Bv&h7ZK`20?hF|wBR!7_8z6u!=Mqw.+0Zjw=i&hUWP$X>e:^5zQV9o.Z++ezr/ZoR0\\vFx[ES|^Is_SE3*Lzy5.H=>3IZ;c_T9DhL(;KAq`j[l=J#)!6;(jRf\\FE|fSn07]!=A\"%i]];sg;YOt y@7CI?[?@iSdsOXJc9RXKb?*`5:YI'oqOkBb1NS[h-1p1_im|M}&fO^>;+]YwpZ6c.*gOoM~?c+D)|j^3,K2!% Ff:87N9pfkCZ4_k68'iL_LPaT*l) _-M ;m-IQ.vyc~@8]8a,[%>f5}F3vPtU%jS\",bOK~n0`m$\"^9:J;Oz-+EDXt+vu-+,.F[B'>~\"xHsrA%N\\MvWb\"K#4il", + "r": 11870604504970131328002217141142626556305499480717228278350037999007748820319, + "s": 92606636745745056815832864038227480726796676186363367796036516499067008240879, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"citzayBFPHg1U2hkMjczbn1KSEVTI1dqRCZucz44RWZVXHFMLX0xPHJQfiY0ZTMgRl0zNjo4IkhbNkYhXDFQfkZnKGM-O1VLJzQsKj9cI3xWZTAqVS43aUBlRmdhIGgkeUh5M0VDSnIhaWRtJW5NfGdrNXI6ICJXfD5-VyFPciw7JGAtdCJAQDF7dl82L3tkJXhScUBtQGp3IXBONXljSi9NYSYqJUZrbCtqN11YPCZCdiZoN1pLYDIwP2hGfHdCUiE3Xzh6NnUhPU1xdy4rMFpqdz1pJmhVV1AkWD5lOl41elFWOW8uWisrZXpyL1pvUjBcdkZ4W0VTfF5Jc19TRTMqTHp5NS5IPT4zSVo7Y19UOURoTCg7S0FxYGpbbD1KIykhNjsoalJmXEZFfGZTbjA3XSE9QSIlaV1dO3NnO1lPdCB5QDdDST9bP0BpU2RzT1hKYzlSWEtiPypgNTpZSSdvcU9rQmIxTlNbaC0xcDFfaW18TX0mZk9ePjsrXVl3cFo2Yy4qZ09vTX4_YytEKXxqXjMsSzIhJSBGZjo4N045cGZrQ1o0X2s2OCdpTF9MUGFUKmwpIF8tTSA7bS1JUS52eWN-QDhdOGEsWyU-ZjV9RjN2UHRVJWpTIixiT0t-bjBgbSQiXjk6SjtPei0rRURYdCt2dS0rLC5GW0InPn4ieEhzckElTlxNdldiIksjNGls\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 39819418336347203285999458889287963177920875985667318638731160859577262838346, + "y": 63859157561121786018084058796304398321293771991758225990591413889195807288603, + "challenge": "x7wpdexd(Zx guE6YS@boXJ+X>OI-AHH4RZ#ho~;OX@Z']qfI!:1!JI{JC'yv`5)ViB LyT}2 !V^-s4S_Ssi3g#<&r0 Ij(@\"SSUkcX0L7SrUOtX26gg][)u?<$nu,bF-XE8l,AfU\",]vDncUh$mIxYgkm7cz#w+TcoaZ5fDB\\lvSG@ju)/|WUy5*8|`QM.}.E`)!g rik*8U-_I*V5'~>4V.wNg9*((bi&1N;(,iG;'e#eiuhW\"3{ys`Lmu/y6=3+@(iy8\\`]+at?W}Q.z%K][G^]H)FmY0(T8q7*y1`$sN4?>47g&jq5IzlcusAD3I?@b]]-kN7!F_poN8HAV}X6w24^8X*Qm?XSa#~W@G#)H wNW\\%[y0S0s;r0G r6`XtM^HI0/x>:'O3dgXm.GfLplY,3+wi{_o@p@N/a2Fn(tE;)rVMKF3&Y53uR(|;ZX;|ty%2/yv8lN;Wc}wRhmWj@B!eu7 H1\"6qZTjhP)0`b;Zp7]h%R~z", + "r": 106428852435733705910413666726679631486011558874808381905631340758591751444396, + "s": 112376296584259756463123253706724710139887540291830078753634917751459264440421, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"eDd3cGRleGQoWnggZ3VFNllTQGJvWEorWD5PSS1BSEg0UlojaG9-O09YQFonXXFmSSE6MSFKSXtKQyd5djxvSDJ8UyxVQCRFWj5gNSlWaUIgTHlUfTIgIVZeLXM0U19Tc2kzZyM8JnIwIElqKEAiU1NVa2NYMEw3U3JVT3RYMjZnZ11bKXU_PCRudSxiRi1YRThsLEFmVSIsXXZEbmNVaCRtPGp9WkBCXXpOQWc3Y0lsKCpvdm8ye0luVGtCK3EkIlFOVUZnVldHNkZjaUdXX3wuQ0o1LTB7ajQ7L0BOfiRsNnZEWV15ajZgREpMMCEoXVtlaVA0ITxKQnlBIV43PFg-SXhZZ2ttN2N6I3crVGNvYVo1ZkRCXGx2U0dAanUpL3xXVXk1Kjh8YFFNLn0uRWApIWcgcmlrKjhVLV9JKlY1J34-NFYud05nOSooKGJpJjFOOygsaUc7J2UjZWl1aFciM3t5c2BMbXUveTY9MytAKGl5OFxgXSthdD9XfVEueiVLXVtHXl1IKUZtWTAoVDhxNyp5MWAkc040Pz40N2cmanE1SXpsY3VzQUQzST9AYl1dLWtONyFGX3BvTjhIQVZ9WDZ3MjReOFgqUW0_WFNhI35XQEcjKUggd05XXCVbeTBTMHM7cjBHIHI2YFh0TV5ISTAveD46J08zZGc8S0hpbkJvNERoJSY0THVhYXI-WG0uR2ZMcGxZLDMrd2l7X29AcEBOL2EyRm4odEU7KXJWTUtGMyZZNTN1Uih8O1pYO3x0eSUyL3l2OGxOO1djfXdSaG1XakBCIWV1NyBIMSI2cVpUamhQKTBgYjtacDddaCVSfno\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 79155489823864523344170524726733866303715052020623502697792965985430894371654, + "y": 89560638547662377060213832356610579804251586315662318691047622696688424648018, + "challenge": "dAQ$$KH9+lLp~J~Bm)LL\"Fn^N!$46j,.Kwg.\"t$f\\niJ!Qz+'S']P8>N@Z=uT_=e9)m#`{t4z%nqtzSa_9J&%SZ`#OQ,[@2WK^Q$1K1#UsJ|+04a{Jla-:Y;\\V^c4])uD,6)G~m-%&Au(kQba6bSPj3`vCR/?/:cDg{0-Cm?1mx4_3HU\"|\\*XY^|ae@rIx,1;@V\"T\\$fj`W:dPY=6/kH{/pmuM4*Gh3kb86G&Nw`b'ujo$n\":i&f !(9U~aD`p;zAWc05NO37+~%-^Z|kwMV'>/nMN`;]5])#XzyXm67r|U7j`i\\Z=V')_$:8m^38v1m3R0-Zp{[ZvTX}Hg<4lVk-IW~!y;;I4;oHE:|'w;K-KxWo9\\~/n#Q;(Hyy&pb>fiAIP1|V=/0}?!m)!mhH7WcVs-omj8,PDl;Gh[\"i>a^G-`4FmfmWuJ1./{ce", + "r": 86699809699927491603836329294416984381321667122780430370510819152602268329706, + "s": 98563570477125962027710291332516908862478590938300609107621291454498322338920, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"LDVmUWg8YjBBIU9NdlhgeCxOJFlbZWk7Q1p1MSg0X3c4Wng3XlpeNDUtfis0QGwueUJ7NWdcYHx3K3ZUcGYrS29lQWdJLyBWajwwaURVQzJ4Rzx0Ml0hbT00bTpLLEkzUzE2IVh4QmhhcSAqWTlsTWhda19LZ0xBIX1OPk00KkdoM2tiODZHJk53YGIndWpvJG4iOmkmZiAhKDlVfmFEYHA7ekFXYzA1Tk8zNyt-JS1eWnxrd01WJz4vbk1OYDtdNV0pI1h6eVhtNjdyfFU3amBpXFo9VicpXyQ6OG1eMzh2MW0zUjAtWnB7W1p2VFh9SGc8NGxWay1JV34heTs7STQ7b0hFOnwndztLLUt4V285XH4vbiNROyhIeXkmcGI-ZmlBSVAxfFY9LzB9PyFtKSFtaEg3V2NWcy1vbWo4LFBEbDtHaFsiaT5hXkctYDRGbWZtV3VKMS4ve2Nl\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 104985086230895530319402247310421414471974842940338869624056382148202643563347, + "y": 10905389343969350843455827721110789797743338890484581618169146977139802398364, + "challenge": "3_k\"v^-G8>E}y EXT};2SIo$#gQo>&Gv1e*@1nigYS9HP8_#'kDY,*31~o>>NVX8F6+sNK5E4?sEkD8HB3{qUycF*ez[k], fwca,-\\mc]tCvPX#W)C?5^x}-L\".sQq7`:(*;DiD=1[`+lVXYEZXN^Ho$mRI&\"D'^8{qQhofZ6Tf/a~.&F&6`o&.O3>Qa_Ias4\"'G3TPS:y7>{2~W/S+md#/|WIreI)^Yydh)|$}x!*)36xZkGlu= fmU?u= @:M;D0T;gY", + "r": 88897946962384681853269726685670559809074783153646938796363903343007869081402, + "s": 69406993047959562438424822376414823069789468742884453246953163453584227359121, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"M19rInZeLUc4PkV9eSBFWFR9OzJTSW8kI2dRbz4mR3YxZSpAMW5pZ1lTOUhQOF8jJ2tEWSwqMzF-bz4-TlZYOEY2K3NOSzVFND9zRWtEOEhCM3txVXljRipleltrXSwgZndjYSwtXG1jXXRDdlBYI1cpQz81Xnh9LUwiLnNRcTdgOigqO0RpRD0xW2ArbFZYWUVaWE5eSG8kbVJJJiJEJ144e3FRaG9mWjZUZi9hfi4mRiY2YG8mLk8zPlFhX0lhczQiJ0czVFBTOnk3PnsyflcvUyttZCMvfFdJcmVJKV5ZeWRoKXwkfXghKikzNnhaa0dsdT0gZm1VPFZzT0NwRG8vSldPWiBUdCJ9UH1qa2dlXSdwfUtFLUtMOnY4LU4vMSBQRCpIdz4_dT0gQDpNO0QwVDtnWQ\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 20840195735115064343948949654602649561375111659044081832711258326395419359487, + "y": 8148564326852108872858575426847315760991905056727676551553141046872599041982, + "challenge": "lH{_jw[FJdw1@a]&'aoJV]+Y5Ia~7&cD -.O.:5Q M02UhoL51\"tv3byQ8K}g\"g)>aN]Tz{7Mn7r`RZ<\"CSapyw[:,a)^7'~SJ\"zTrX~~]<&i{w!!>.B]GN#%gU+#\"oL!BqL_k)rFvN5*48>C.'Qa]I lC`:}L4V$V+f6;Pr\"glohc!,KwGgmt@PQns5Cn<1J1@M9DV,Iqg@eQfV:|{Su'`ua71h?MCB?jVE+ze>aN_*?:!Qui\\^f@0p&wfXmRjYY[k#vP`)TX{SKxidro`\"r0\"@RAgcV2YwIbpmmc&Kvm,^Xr}GDb[r*~(vI+>s86fVyX2-6}~(8Z-U_!+>L/02}{eX.7ZNK;rKq*Z'xx0F9-&0i$ 1=5/jxj^iq)PE+pYi\"Y~lmkY3F!qn|LnXPo^oN4^Z!u~~[#3WKq#]-P -NA0#Wd'GCJam:&xPRQ>_1|)pf`3*ICP:V99.@O~AkI_hO@!E!0L$M%b7#}Zv08m\"bycH~-|lf'f\\jN<|U1W(\\'Q1/tfa_P_TK\"@$==h+\"mN^&E*17dZrd:~3\"B.2i;-~/pFif$/#^p06}jkl/f:G2E$h(}ghS!OI>#G5q6heTaRum,wj{,'~2SL,6\"i)O^s?Nuv}(3{L=,bJ9;^(`&fKi9Q,] D}f9,HESW\"\\]G&,M3{^.O3<]>D30om@<)T5}<0%wWf2'R:Qu nZ%CLW,l6=|^d$Z&S[XFX0+dTu05O 6faKuiU4wx|:UH:Wi^k3xwmwj8B'sfw+/(&bj=[lJ;(Je(,ep6jK{kBoE}lVpD3KL+mZ[L%Ux1U*;Sx oqWf'o.ko=4hXJ?G-cixf9<5Xm?&,{1QGlIUPR;FQKyty3*P?9CP<~py@DD/B<6s*kDN\\}kp0cU!#8T;cV{.P4l_h\"|D;cq~@M5BXn((-YyG~ar|+;%4PQcs]2P1i,Lp6QU)8C@+B|C|Y#i:-F:)[/J[,l|u[+|F|9D-iVuN@&LH,K_AZC<+TF_xENTXx-w*LwX5V-unh]-'MF2$R'y8o<\\I&(,F^a3e)Yc-WEK:2%BKs;5H[Ph!;+\\HT\"#vt8BHUbxL`=[(FJo&4`}pkunxr\\\\^fT\\Z@yGCxdCHs$49*7BIyekjlgX.[hp78;3$J/XD/AY8I-orGlrX<[|/:v[eQy\"2~GnK@9{+\"sUk\"Mbcu&Nc(q5t_FW1LLQaN]a,F+HO!r}\"sdh5>8NCi,$:%QMS|fZzWEik!m|rMg:Lmt>`)!aQ0!R]Mb,3?+8o\"%>x", + "r": 87535096400063820335919655304060652948859023445787989474483453823203016410696, + "s": 99691425680753757586465033576833450688671952529697968049926357011688335006404, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"WkNhJjhQfVZMMDk7bUshIHtuJDhyKDE5QV4waik0WW1HPCJNZzVAdFBgc25kdyx9RkRcZ114SiZmYDtwOlEwYjBkOGo4SnFfNlRxYylFWT1cMiZvYiRSXSsmUjlvMm5OO3pvRXEgOUQ1SHpCL2xCXEo9OSZEaWtGSWE3LHBzZXB0LkUleGhRcz9zUFhPJkE0T3UvSFR7JFYme0okKEVTSyJRMSsjSz8zcixXJkJBJHAgL0NwbHkgJ2MpWGNCPkQzS0wrbVpbTCVVeDFVKjtTeCBvcVdmJ28ua289NGhYSj9HLWNpeGY5PDVYbT8mLHsxUUdsSVVQUjtGUUt5dHkzKlA_OUNQPH5weUBERC9CPDZzKmtETlx9a3AwY1UhIzhUO2NWey5QNGxfaCJ8RDtjcX5ATTVCWG4oKC1ZeUd-YXJ8KzslNFBRY3NdMlAxaSxMcDZRVSk4Q0ArQnxDfFkjaTotRjopWy9KWyxsfHVbK3xGfDlELWlWdU5AJkxILEtfQVpDPCtURl94RU5UWHgtdypMd1g1Vi11bmhdLSdNRjIkUid5OG88XEkmKCxGXmEzZSlZYy1XRUs6MiVCS3M7NUhbUGghOytcSFQiI3Z0OEJIVWJ4TGA9WyhGSm8mNGB9cGt1bnhyXFxePFZRYV1CP1FXaEVlNk1oTDlOYlRMP3RALn0vfStffUxTOC1VQy5scE1jaEknVj5mVFxaQHlHQ3hkQ0hzJDQ5KjdCSXlla2psZ1guW2hwNzg7MyRKL1hEL0FZOEktb3JHbHJYPFt8Lzp2W2VReSIyfkduS0A5eysic1VrIk1iY3UmTmMocTV0X0ZXMUxMUWFOXWEsRitITyFyfSJzZGg1PjhOQ2ksJDolUU1TfGZaeldFaWshbXxyTWc6TG10PmApIWFRMCFSXU1iLDM_KzhvIiU-eA\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 58229039599049685593978515746000192767789422012232832651507543327386351015910, + "y": 37328988221439786906607873200088128823940131159119713847175554292339071006958, + "challenge": "jK\\FT:wWV0ObMeWn3\\E`E{U<45;h-xkB~sr52n\"@i4m/RP|*[JP(`HJ3N3z~ze~BZ]LNVMark/ZiYb/nuqME2Se;8N(T+Q{6`OXUY[VUX))'$]+:/d9sf:Tddox?J;lbR4E7e_QxA\\3Q#7sXp\"^;yL,ZS5ngF>P29uu'rY Vn+}B+vk6XhO4]=JF'iiui_.cWI28.tkhS\"S&RV~_Rv@ka`4>5P\"~P=e=*D\"}\".C,I0;\\Vq1~Ppu1zy)G[S'aL#f0>?B$Pm>HO_^kk*)u$/l`v?{sL/ntj|.8f\\P#uQVn#JKbV&h#{[YQop5odd[;>H(|T[?MT4X|8H/LJ{/P#$P2X~/JVzN!vigbS9#KLCIJ#s]LPMyc`-STKiq>D)]Xgs(Ym-RLl?YHC6#{_(8,iZIUf3Jp^>J`F3?BIwDLp@1AVd&Jy:c3e*E1W[(iKNmkSabQQuv-)S0Z|_[E)$`(KNX&#!Ufu88cbv+.)p#u4+_8mn&@mn~0@`JPOmDdMydr 8=aqLb}bS*\"ozBwIA)&M D>5b1OV7=C5T!=l+.]anMh^h=MEt$3{az}z@k^:Nai)K8V#d83Aj#0Tm/Dv/aPZs6.a$Q+_,KvVie,Pl@t.78atyp/E>[q|?Mlg;C|RROcp,k&iBOCC2$(q.snNb\\Mui>fVP3oaw5Synw&)h/0ur?3z9d5/^Ra@z+/_so#m]>yR zItMh<.*erG6'V_XRb?1sqffN$$-AP,)5*i=`||lxvf5Nu6V\"h/|^K%1E8DvMW>_~81yF>HjtM+[UdB%Ss{(\\PP)#G3|-h?eT#I,thV7u,kG?)N!:Q)jtsH0m!A~,LlJ~F`kge~vh>q[&2?f]1_^70H`-go6K9U0=>Fex1+aaP%h18#6;ZiEa8,{u.Mm>g9K$vU:KyAxdjae", + "r": 93817030394974661116019978160067421796471334231248080381888089952537541255699, + "s": 87906060749182678338959321256957929265526889891821568244796972870072778215556, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"JUwsO3Q9XX5AeVNGUm9UTDQoLCcnOD9SMnZ7UyM4SG5xVkE3ME0-TGw_WUhDNiN7Xyg4LGlaSVVmM0pwXj5KYEYzP0JJd0RMcEAxQVZkJkp5OmMzZSpFMVdbKGlLTm1rU2FiUVF1di0pUzBafF9bRSkkYChLTlg8aVFze1A3NkF6S2J1UjM8fnJzO2djMH05WEFGP1BuenN5ZV5rXjZsZEc0Vk45YDZ0QWA-JiMhVWZ1ODhjYnYrLilwI3U0K184bW4mQG1ufjBAYEpQT21EZE15ZHIgOD1hcUxifWJTKiJvekJ3SUEpJk0gRD41YjFPVjc9QzxkNE8_ckpjWkVQakB4fWh6NmM2Z0UqTXR9fDVeNUdWKzFvVjwmZnssJywnYXInYThGXXFqWzxBLExTLUZAOzBpKnFIa0ZNQ0dQOFNMQDp0I1VFK15BLXNHM34lR1kzciNGb3p-O29oTSxnL3E1WWhBRkNSLz1nJ0kyXiFSTUNWckc4WzZLUlk-NVQhPWwrLl1hbk1oXmg9TUV0JDN7YXp9ekBrXjpOYWkpSzhWI2Q4M0FqIzBUbS9Edi9hUFpzNi5hJFErXyxLdlZpZSxQbEB0Ljc4YXR5cC9FPltxfD9NbGc7Q3xSUk9jcCxrJmlCT0NDMiQocS5zbk5iXE11aT5mVlAzb2F3NVN5bncmKWgvMHVyPzN6OWQ1L15SYUB6Ky9fc28jbV0-eVIgekl0TWg8Liplckc2J1ZfWFJiPzFzcWZmTiQkLUFQLCk1Kmk9YHx8bHh2ZjVOdTZWImgvfF5LJTFFOER2TVc-X344MXlGPkhqdE0rW1VkQiVTc3soXFBQKSNHM3wtaD9lVCNJLHRoVjd1LGtHPylOITpRKWp0c0gwbSFBfixMbEp-RmBrPGx7bSkyXjVCOT82VmQhd2tYRU42Um1RIWRFPCF3cnM6Q21za0tYTiBzZENDLWJlSXo-Z2V-dmg-cVsmMj9mXTFfXjcwSGAtZ282SzlVMD0-RmV4MSthYVA8VnJtY1JnNnt1OyA0VDMwW3R0Sj1LT0M_KDJHJDlDcSVDfStjO2R1Vn0sJVlUaVsyUipOW0pLQ10pN1NGI1JxSktuKUBlKzg8P242MC5xZEFYfU9CNH1bb2U0YlxreTtTXWp-alQobVVLYS10IWhHVVUgRHozIyZdOkpnLkFPPEg_YlkuWiYlQExPMWdmXiNxVGZne18yYWxPPiVoMTgjNjtaaUVhOCx7dS5NbT5nOUskdlU6S3lBeGRqYWU\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false,\"other_keys_can_be_added_here\":\"do not compare clientDataJSON against a template. See https://goo.gl/yabPex\"}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 42030378703172944637333595037630307816801465424916253341305014623558994867210, + "y": 25574315084904526520633567774614822571065777604835018437853517855830835164277, + "challenge": "waizsXH!8tg}0I,\\L`HTS;:5)|_{V14Qq#0)hy_Zo:hU}+gTLf2b tcUxFF9!RRI93K6 .K2M\\>O,(^M;CTOMi&@]_C2(cuvmze@bZ,\"s\\`>Pcs5G%OwTG`_RV@@z>Kz!w,v6O@lCI6t\"%/Y3tuP&nu-D8J~[Cc0^=(wOuN>@*(EF_wHs:_t;I3M4wq1~'m\"Orm6D7puD<_h\"{HO=vH9pB1RirS<>]r#m", + "r": 70656509533729095724658151215261201272398222312393827098345715606997647623015, + "s": 15286093912702016031170249390819626277104791777866390302829139271848286818622, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"aTFTQlInK31FaH5vOGs7OnkyJXpKU0lOSn5kOnRuSE5Kdjl2OykwMjVGQy1UO2V2Kjw8d2lqdGN0MSk0XzVYXEs1JnEsIT9hQD5jczVHJU93VEdgX1JWQEB6Pkt6IXcsdjZPQGxDSTZ0IiUvWTN0dVAmbnUtRDhKfltDYzBePSh3T3VOPkAqKEVGX3dIczpfdDtJM000d3ExfidtIk9ybTZEN3B1RDxfaCJ7SE89dkg5cEIxUmlyUzw-XXIjbQ\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false,\"other_keys_can_be_added_here\":\"do not compare clientDataJSON against a template. See https://goo.gl/yabPex\"}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 58218370558084588496841204709131891031274705978729455785886418540096884382340, + "y": 6764894388139665345496113658284889956531616583156545999850927769994447990271, + "challenge": "vrj_>B4oRaxe~znJ;=?5(Js@zv85VhMje q{A`Yh.Q[_lwet4pkq\"L}Y]|7`k[sOd1d8^]Dof}KRE]JC*dJ@kH=x]Yk|)9i'YHKx5,/N\"@~5Ej`sPWk=aPY!&|wr[&XTt}3BoTZ3w8 /v;/ybVsa%2\\H$9NSDYQT[,9sL(P\\waD1ol81j2T-epUw$J\"kXvFdo$dNHx~=.mR (D9%+fDdHK;Nl\"$%8\\iSpme3>1r!Mav=u(r5kg4~]sudYmrQ^U}Wyq*)z$v72y=.Cmf6t|u <&fNqrIJ*ajGQm/r!EcvP7RD&qn+o$nWMym[aE7Rg]W,_Su)EwXsq;~<~8-hf,ywDU/}ftOM6G%iU{~.a;=:ZzU64Ds[a!fuNu721TbQ:=:#e1f)EVq>Ufy?Ddx*_FZ,H$EA6]m&IIWx!8K3Uk%ZZ)kRk*rt*d=f#Q,-gvEeb(t g}.|O0@9 Rx'j>*3;Uat&&y@6{X.=OILb:Hmn1(}UH%2[mNw Osouf*qjQ3COvIG<\\WdAE\\qRv]=WcT5y(A}Du4D7G XH{87KI>ah+j-dNE`oqJiM'.\"&j;WzL nzHIBooK\"Hyj}]L17~[\"qVZX>2?yT?B4O3FUWMvE!'GQQR", + "r": 80502796541205712435300777366720866927019546630493100307805369322404685720442, + "s": 93231710041357256560598069262017807837100405337690526579859197199639046617512, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"ZXstQ21DVTprVFkyPGw-NEQ3RyBYSHs4N0tJPmFoK2otZE5FYG9xSmlNJy4iJmo7V3pMIG56SElCb29LIkh5an1dTDE3flsicVZaWD4yP3lUP0I0TzNGVVdNdkUhJ0dRUVI\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 113039988152845696287971227113140386784581296511383394761747109320156739929265, + "y": 16060349894246241782728687195040513296166275602876025398611301624219788528985, + "challenge": "hZ,lCQtt$W@Zy6hooK2DTF;RU4d\"Ze*}d;-efOqS(1C24{;9Dq,bg#xghTm\\)i}XzX>Q^kZ,Sp. .}d$PC!}^)~[SCAGa-On3P?:~'k}5U-bZXF0_ZK^XQ$Vsx>%7japT|*nUOx(!@$s=\"&}x0\"_E!5dPSk!|O:;PYUFE0+3R:~TS>*98*Yw|q]MzfF1XFi^yeK.ngBY*SXbVO1a|p\\K\"2j3UgC*v.R_gM]nZ}QGm}u/a6tx?P9j,WsI/y-NMQ[_%;86I0l#M~J.W t*`d7h/IH%C.5w]a_=M7u*'gCcQ'H>NiGe@/Tq~$k5+\\)$WY0z@qtVWb{~*:&:Byq.KU;q)yS[>jlQP{8<}YYQJdY!nh#Dm_MhNj_b[Ou_(k0t$mR=hWZVRp\\*ZKpVqxNv<^CTdg9!^FphI'=rsE:U(mvm)FDO%c)&,oX1ZcL|/8+_zs uOQxgz_[vMJV\"-,bE4EI$/T_!e4(8*gmL|X~O\\bO|J2-ObF7$'.-^+q${8dj8DI7CsO``x_kSfxHBNC&,9v8Z17J4+h\"WRu7Rm?b)GoKRm}0MEZ^zLy9Wb/*Wl/qC\"\\_K;G+iKX%kuGnLH)ljIiu4[@*#:|ZX\"*im)*x5|_;\\L`1I|bHF", + "r": 360124410220882459132874676063938733626258071403184306798973136036576664742, + "s": 4663276099527154137948190219804662888638484833531524714269587197636649167403, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"Wi1LZVVBSiVuYVc3ZF89VU9BeDEqQD03fmktVTR0Y3FJZlU2Lk98dzN4WHQzJSk4IUFvYCczSEJOdWY2ODdIa2x6Nz49aFdaVlJwXCpaS3BWcXhOdjxeQ1RkZzkhXkZwaEknPXJzRTpVKG12bSlGRE8lYykmLG9YMVpjTHwvOCtfenMgdU9ReGd6X1t2TUpWIi0sYkU0RUkkL1RfIWU0KDgqZ21MfFh-T1xiT3xKMi1PYkY3JCcuLV4rcSR7OGRqOERJN0NzT2BgeF9rU2Z4SEJOQyYsOXY4WjE3SjQraCJXUnU3Um0_YilHb0tSbX0wTUVaXnpMeTlXYi8qV2wvcUMiXF9LO0craUtYPFoyJSkiaTlVZ0RNR1U5JkZqVldDfTkgYFV5Y2o5JyBASjYvJCclfDZUYkFGNkpfQ34pN2wjJV5tLDtkflxUeVNGIUtfYUFGYkdtRXVKLVcleWwyJnsqb25zQUdZTG0oPztlcyVXdiopXE1raE92bDRNJ1RsIW5hQGRVNTxEPXsxZl84ejthK2BZfEMnITdiQDdUSWErYjNkTCxwbkMgeUlFPWdSX1M0M2V1J155KVM-JWt1R25MSClsaklpdTRbQCojOnxaWCIqaW0pKng1fF87XExgMUl8YkhG\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 89356825526889047009186012052938139466498764980167357544754342188195021296479, + "y": 114902638628832925529804434047956201764537638458831276574346835974600486366827, + "challenge": "[{p\"$[^R~@hRnW,J6*ykp@@+zt^GUf,dEf8e?T~wOj`D7Q}Nz>/zbJ.-^{F#1@\\zz.8j=!~w)D\\2Ij2SE^C\\EsZ8Rj<9;A0)(+hbzVIJK*uXPoJLG,p+MppzVK5S&\"X}7^\"q#]g5oIH>{v'>W#+i]ZEtd`|vm:8Qp[Hs$3[WMXb28Bg#* u$r kZ(CfLhm$~oigKA?C?OM=md!uhercs?)c#Jg%%}\\K:Co)%vE;Fv&r0f|Wn|3h`yNB\\p4qHsaJj!)XrFD-=)(3TW|*Y.?0F`|y&s=v;ID0.r$'*'/Pn;Gva2\"qZN}<+c87_w]-j#Q_CClYFctvLw6^-dp&bodj#x'5p\\aHR]bdi+P` `C1x-mCZ4zM24&P.bvd?rmzohZ$wzXz%/Tws}_u9(AmM|9UN7Nwenn?AJ)5|?/$:UM':p{^wo0DSI[wo[`+f!S@ Jh)qoU!EId\\l`sd,d7CJ7>>.=Ts/Z_`i#,~a|s&P}/bg0Bu+zM9%4)y<#@4E=R`_DAJ2/3)s_PkU2kO\\_ycHk /)pkN\"zemBwHou-Y(031H|oB#N10wv[!2v%08\"nmkb$I}]IJ|0dXA\"3UIXO)\"n\\){YmM9s$x7(\"iqP)jB+,M_a+Li$ic;V7I!\"zMsb[PVN638>L>A/-2MZ)9Q)Bb|YeTT+SuEbVBMb.mQbEet vm00iT0\"yWU4t<#=^?tb);\\5jy\\)jw!Ixlt~{XcefCJ$7]wSrp:_~B~tDj27MhM0 {:;qR,Nd!+,(Cef6`045TL.`%d:y=Ln%ltc$n1zQ\"EN;w'1$1ND#S", + "r": 52587587607724581527023947683522910421729853850345865625155247637385003198011, + "s": 3740803114297081072462068654730662160647806533675168323971963027609466857085, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"R29cfGtyR2lyK1ljUGBUTkwvJ08jX1tfTm49WT43XiJxI11nNW9JSD57dic-VyMraV1aRXRkYHx2bTo4UXBbSHMkM1tXTVhiMjhCZyMqIHUkciBrWihDZkxobSR-b2lnS0E_Qz9PTT1tZCF1aGVyY3M_KWMjSmclJX1cSzpDbykldkU7RnYmcjBmfFdufDNoYHlOQlxwNHFIc2FKaiEpWHJGRC09KSgzVFd8KlkuPzBGYHx5JnM9djtJRDAuciQnKicvUG47R3ZhMiJxWk59PCtjODdfd10taiNRX0NDbFlGY3R2THc2Xi1kcCZib2RqI3gnNXBcYUhSXWJkaStQYCBgQzF4LW1DWjR6TTI0JlAuYnZkP3Jtem9oWiR3elh6JS9Ud3N9X3U5KEFtTXw5VU43Tndlbm4_QUopNXw_LyQ6VU0nOnB7XndvMERTSVt3b1tgK2YhU0AgSmgpcW9VIUVJZFxsYHNkLGQ3Q0o3Pj4uPVRzL1pfYGkjLH5hfHMmUH0vYmcwQnUrek05JTQpeTwjQDRFPVJgX0RBSjIvMylzX1BrVTJrT1xfeWNIayAvKXBrTiJ6ZW1Cd0hvdS1ZKDAzMUh8b0IjTjEwd3ZbITJ2JTA4Im5ta2IkSX1dSUp8MGRYQSIzVUlYTykiblwpe1ltTTlzJHg3KCJpcVApakIrLE1fYStMaSRpYztWN0khInpNc2JbUFZONjM4Pkw-QS8tMk1aKTlRKUJifFllVFQrU3VFYlZCTWIubVFiRWV0IHZtMDBpVDAieVdVNHQ8Iz1eP3RiKTtcNWp5XClqdyFJeGx0fntYY2VmQ0okN113U3JwOl9-Qn50RGoyN01oTTAgezo7cVIsTmQhKywoQ2VmNmAwNDVUTC5gJWQ6eT1MbiVsdGMkbjF6USJFTjt3JzEkMU5EI1M\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 58298363113075655401606770754134201381074296221528064034928999528797174517817, + "y": 35760616346524752492560426986685675780910270291129000238440372042299900010753, + "challenge": ";8N4,DwHkb2zs&I;#X5FTjm;i)P={#7qmhb{3\\:wo_!h6ek,mD&e@-W@!/u;u`f|xf?dT'w>awAM//)mmp,$_-a+L8k.X/@xtiF\\d)B3v6rB_k@pFv'1\"j.W>,Z?I%n&|bK&g;7KwD,w!J3 ]q,d!u%:OSFV.{_=K&!>uA=/FvX|oV}{CWB zG$zS2QO+_x[uuKu:W`-=K0}SLNS(\\lQ&pOn8qs)\"U6GW~PvSx*=Sdmw~+5].Czm[E[}g}gYP))NW%k($]\"\\^pR0I9+n]=wtS;{?#J/sL<}4NqgGpBpbZe\"a~F*b&-Jz^xWPxcHo! neZ=MEwqj_4(j^A?E.", + "r": 107130213494593978392839953255998824100187774859471141270321939679669615420574, + "s": 77102973414610006988950171248974676078250045744473747203699611635958759048738, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"OzhONCxEd0hrYjJ6cyZJOyNYNUZUam07aSlQPXsjN3FtaGJ7M1w6d29fIWg2ZWssbUQmZUAtPE42M3dqMTdvMk0gRGpcListPG12QGM8Y2JLJVJWZW42fVxMPUwoUGMrPDU8alRfLCNkRFcmL3NzRXJ9XEBhYjhtT2Y-V0AhL3U7dTxoYHlJIUxNIGpJc2hseWssNk1jIz5gZnx4Zj9kVCd3PmF3QU0vLyltbXAsJF8tYStMOGsuWC9AeHRpRlxkKUIzdjZyQl9rQHBGdicxImouVz4sWj9JJW4mfGJLJmc7N0t3RCx3IUozIF1xLGQhdSU6T1NGVi57Xz1LJiE-dUE9L0Z2WHxvVn17Q1dCIHpHJHpTMlFPK194W3V1S3U6V2AtPUswfVNMTlMoXGxRJnBPPGNYUyVseTsgO3p1SkhufC5mX3xQezp9JnopeChiTng-bjhxcykiVTZHV35QdlN4Kj1TZG13fis1XS5Dem1bRVt9Z31nWVApKU5XJWsoJF0iXF5wUjBJOStuXT13dFM7ez8jSi9zTDx9NE5xZ0dwQnBiWmUiYX5GKmImLUp6XnhXUHhjSG8hIG5lWj1NRXdxal80KGpeQT9FLg\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false,\"other_keys_can_be_added_here\":\"do not compare clientDataJSON against a template. See https://goo.gl/yabPex\"}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 79133554881680271291563948249864810732094813979608613701409040126748985269507, + "y": 1949060206847884356172662098903799106345079913371580569276898445763435818298, + "challenge": "mMv&l?(8~.BTI68fqt^5D,ioe'yI#LMQ/2SEM4>\"]OOoD]l\"q_ ZQ)n6L\"m'ULknJ|U{PYIF+wnKSP1Ug]&~P f~*VOyI`RL{va#\\MGDK}9VeTL$mU{nn(FfMO}sLypo3] YqlWD{tBEid66GxhjCXp&-,@.xWO,'`6^PJ$Q#}U%-}9Gk]n;&-r/}uj`\"5v5`Ek#iGktkw*D^=UcDPiDqw5gQyd'!/]0F!#^]~9E!pD~t'kiL15,+z$TW]DQcpZaE9~xYFq B;ihCG5/+t|wTBr0Y|EwsV00+u7jSF5A4DwIBfe*IKR6a=w@vF=`0$ Z__TkRh3Xl;kd0FsKWAp}(bwj~yeN\\fP7WN(OVFu(C1[zQbblnFZZ]k?=GoSSTKmYzv\"-b8bGT4dE|;V9OO55 !9;8'i`|\\[#84x~fu.au9i\\-,p\"suqP>^T-;$m$jj31PT^YOL-o14X(I///#qoc{RfPen|MY`r1\\lB!868HU*TzR;'EhW'7t{3_ICqS.)7U&I33B&yuw61hTqzC\"4@vo2ACjJ,5m]hnw#']+KRJDJ%}1gl.XMb,Ae`#Er|{ueZL ){(,-pEc4%8aoK1z.oOi@>6/)$;`\\1&) 8Qxe]ylIK]]$pu>#^]E&d0|h[p)GP*%Yc2];jW}[w_KivLvN0+EdtPZiB8 &j\"choEr2G @^ |b54a}\"YvVt`L :$8Lap`8XD<)f[ N,5 oL0GCz,gFqi1`CqUw},}xMeeK8nL2X?oF0TTVB;lEb&AkcC )a@ s(NJb[Qv_S}#}|Z;BLn|s*5Z.0b7v1(\\-bZf&F\"knY0y?+Eu2foh-+{eq9x%k|@&]nz+e>CaxqB\"~B)s9", + "r": 19869345390346603637580952467221951927919638978523900406737048889199095630022, + "s": 8179516975901774737349967833377940403502224372199763637223235964745402320632, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"UmsgbHldRlR0fWIubUQ9JyZURHU_R3w8QCdJJXdeQUQjOitUOVAycCtVQnM7QHd9amJXQnRAJmxec3V1fSYhYHpDd05Cc2Uqb340RFlAd181TUg6YkEzcGE7QWdhTzdOUz5hPXdAdkY9YDAkIFpfX1RrUmg8RWRQPFd6dS0pM3phXjRSNj4zWGw7a2QwRnNLV0FwfShid2p-eWVOXGZQN1dOKE9WRnUoQzFbelFiYmxuRlpaXWs_PUdvU1NUS21ZenYiLWI4YkdUNGRFfDtWOU9PNTUgITk7OCdpPEhrOWxcMFhjOmomKFNBQVRDIHk1aD5gfFxbIzg0eH5mdS5hdTlpXC0scCJzdXFQPl5ULTskbSRqajMxUFReWU9MLW8xNFgoSS8vLyNxb2N7UmZQZW58TVlgcjFcbEIhODY4SFUqVDwvYW9OVyRCXj9PaXkual9JSnotQ1twRDMpNj0kb2lMYG4gTXlwPD9aKSYtWk0yJHJZfDJieUJLOnpsV2hFO1NEXkhHfHM0flFoQmttITJnJSlNLm5FU1JKUiE3VXZBNzcySXdGR1NnX3khfVRDRDMhbClKIHd4Om01TTZSWlpkSFp5QEpcKXg9e0Y8JztWYT0sbEYtS3o4JmpQbDM9KERTR086SjoidTRsPSZ3YWt9emg-elI7J0VoVyc3dHszX0lDcVMuKTdVJkkzM0ImeXV3NjFoVHF6QyI0QHZvMkFDakosNW1daG53IyddK0tSSkRKJX0xZ2wuWE1iLEFlYCNFcnx7dWVaTCApeygsLXBFYzQlOGFvSzF6Lm9PaUA-Ni8pJDtgXDEmKSA4UXhlXXlsSUtdXSRwdT48Z2A5TC9BPWopSi9Jay15XC8_VmZ2cVdpXWhma1FIUV0yRDhgVSRoSEU3PiNeXUUmZDB8aFtwKUdQKiVZYzJdO2pXfVt3X0tpdkx2TjArRWR0UFppQjggJmoiY2hvRXIyRyBAXiB8YjU0YX0iWXZWdGBMIDokOExhcGA4WEQ8KWZbIE4sNSBvTDBHQ3osZ0ZxaTFgQ3FVd30sfXhNZWVLOG5MMlg_b0YwVFRWQjtsRWImQWtjQyApYUAgcyhOSmJbUXZfU30jfXxaO0JMbnxzKjVaLjBiN3YxKFwtYlpmJkYia25ZMHk_K0V1MmZvaC0re2VxOXgla3xAJl1ueitlPkNheHFCIn5CKXM5\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 79812465709486898821430159563513616206365167021278061837036109443227406112813, + "y": 110770708831195652098497775804870247754246822524060265959817806746191026568, + "challenge": "Hp8tSR6A=P#0)C0_b95,NW~~<9[^U \\-{A{QR%+npU,6)}E/8[(MwBO;!=Spig]SMZ]*7`RC\"f)\"p$N6mWR2djuV=gdZQR9m4>d:D\\mq>\"\\=%sIa+?zQFY.XK]SL2t+_+'-?TRbSeskq]#@@0>ol-jfx\"?HZs 6L=y(\"brDbSSBm3(9dS[##'Yl#,UVU!5!ytS0Xw4{AuG0X35qji8)N;tM+<$I{Hw-=XJ~,M]F4[lWJW6(zVaiiQZo{x\\CP4}?a_$CUIX&5cdcCJ* _4_Fx< C5&xcLRKq4W#Z:swrrm<;X-G@OH.*D]PfdgaCKG4+do441:IV{la[FNY'SX ^*bAY.5pBxYLs)3*$?j2F5[@U\"^7MQVOTj4O/^YANq\"8>M 108d8>\\o4zujpt<.O@#?%cti:3yV4xiJ\"$=GIHe!1WXAb${4};cs{eNtt)FT#QF2R\\%`]wZ'E|&\"7(?G(kA8B!o\"\" GT]=9+M-T(WU{lzU{b#P>J.ONFzP\\jOH\\yA{j8ba7l54d.Ze4p`zD9Wv^Iz,zf*geqHo}SRVLfLjI.]I&3$jXxfk/T0%loxE,f_El\"pX-mH+H3IbK>njF%LXjuT#L!kK$f#a(eWYFDZgsXHnIJL-\",c**\\E8PQH85/Oxsa+,.k2c:Z_@*a\"X,/A+c8d];we%(s x`Dfz4eNn4kL+stSo=eMz;e$Bf4%Gtg*5.64#|Z)DG%B!VB>FB|(N}v@wIDU>t3>d*=nkf&W28KG*b%KF|03]U^}RZThw3:?:fa{kbKFz}.ZxI(1O{$$X?'O/{y 7\"\"D'7%E6Tzo{oe4sy-NaA@f!?9MN+JPVxyBtr4\\=TFr{5EG5aG26Q.F~mNJ12QV\\(ZIb$Lbo/{C?MRC,T !B,\")h/QO](*[W [frYsTG*sP@rL2j`:0K w\\#.^+!E;m@q8<(9n7-:f,", + "r": 101430619135527939949627663960661067483844919910838513974607028901847221819380, + "s": 75596699727946191661014416464105197803421298394002893246142645146223271522425, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"SzF-T3ZtQWx1QHAqdVJofnRFcCRpUTh3ZDtzXXVEe0MkfFR3UDJuUTI0eFRTIDNZKn14WDQmOTd1Z2p2Nz8pV2tSXSJiLF52Km1tZ2B7Z2MxbFFrM3RMRCw6NTNzKj9wPyxmcG1XcnNFfVs8I049YlEsPkJ8KE59dkB3SURVPnQzPmQqPTxsKFM3OmMhcWVWPT1td3h5K2FgPD9cYlQnVDJVPH0_XVZ0ezpGXzhDR3w3V0YtX0hIRiNrIzMvV2NTQ18pclEjRzFuS0NISFQ6JDllYXtOJ1VfUnImSltKU2VAJnBNRXp7Q2lvTUtVeXlcITVpWi9tWkMjKUQ_QyYjbDJQJn1pNH4nVDEma2c7RHd8PXlZUD9hb0leK1BVcS9Sb28yI3Z1SExdXiczOmB6Nn5TW2hNfVRTZHlnaHp0blhcUl9oVVp7d1tKQUJAOmAocVEtbl5yUGQxLmlMWTE_LGIxTHkmUi9qb1ZFWl5oVShTcUJQZSk3QzpVSHJbQSlJdE4xRkQoeiZFQFxQcSZnPFZCeW9vbUpSJWU7aWFHME5kVUE7WEFCbytdLXRtWVAlenFyMEMqbDNxNj5ua2YmVzI4S0cqYiVLRnwwM11VXn1SWlRodzM6PzpmYXtrYktGen0uWnhJKDFPeyQkWD8nTy97eSA3IiJEJzclRTZUem97b2U0c3ktTmFBQGYhPzlNTitKUFZ4eUJ0cjRcPVRGcns1RUc1YUcyNlEuRn5tTkoxMlFWXChaSWIkTGJvL3tDP01SQyxUIDxHbjg1Ung4TTNcR3lpOGRhTi0gLHVkLzFrV2RHKCxOckFuLWMkMUJ-RVFTUTJHR3QiNFFERDU4alUzRSkmXl5JITZiTFg2di87UlhSTE9NNkl7dWV7W19jbCgnYHM7bDc8MVEgflFLdixKXFthWV4tdXdQVDtzdkd-O3RIb2xDLHh1dCM-IUIsIiloL1FPXSgqW1cgW2ZyWXNURypzUEByTDJqYDowSyB3XCMuXishRTttPEg5TiglJCUyUktzOX1JPFVcIzVUW05mQDpcJy9hITRmWjBIVn54MSsobD5AcTg8KDluNy06Ziw\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 60373156789366274713136637934927336839831361147190470470474798876563187913617, + "y": 45340406207388265778543209499314838504660128066983535488180896470504674859021, + "challenge": "U0O.S]_O2 ", + "r": 16697032558990673789375259251661127960366979803487408191586743235762845614110, + "s": 15729219628358474026913369482549627609046773858133511520173580947906359412926, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"VTBPLlNdX08yIA\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 82836398781176558486044859972875593523470941227342914805935217199178529284510, + "y": 94527495145632269821800523625848443488008472744598121279129448905740551968207, + "challenge": "qRaf`&Z1Q\\pbZ3<2|r?(`)_mAKk{R|T]eXNKX bCX{:$ecsnKVOjb3y`tP2#1|MGAK6~q?44v.+j.z3\\|889xDDA,eQFz}8[hQ/dAY,|A{UI}P.Lii7;X&)Q^RcI5\\fen Ab5O,bHaWO)PXwb00ZlyPQ$v0/,O>BLGT>b<`tU:+-~%{%'`zM.}}52*GCUIjCZ;t`]|-J*pl0$B3HqVKs=r\"3YuZrU\"4AwZd|&v8Ujrlo_^pPe1zSRa 98TU4\\:$2Ji,h@aIwW*i`-^#>6]QNKCJtHZJaN:vY+Jeg-A(J98lKcPg?LA N'0|-J' 6m]U\\YRoo3b2GA]*JG}2J+*p9biYsl4X:a$}5d;4mU3 a]CYP<[iM", + "r": 34864728733179720823474550107159223000054088534917593748852075062323046552384, + "s": 36653256821488035890877455991651322960308692521890025108407141043086060019718, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"cVJhZmAmWjFRXHBiWjM8MnxyPyhgKV9tQUtre1J8VF1lWE5LWCBiQ1h7OiRlY3NuS1ZPamIzeWB0UDIjMXxNR0FLNn48WnUiNSR8LTE9cD96WEo6MT5xPzQ0di4rai56M1x8ODg5eEREQSxlUUY8alJmeDlMJCo_Rml0T1hRcSJdXklsIm88JSJFQEBfczc6PDMhcEYmU1FTM0BTWjpPVmsjXkYwIGZ1SnRBNmZGakUjVkpPQE4nLmtPL0Y0YFVNM1ZqXVZtRUZ7MXA1N2pdOUE1WFRxTU5sOl50TyJtbFRnbk50IjtddDwsXXU7ODghZEEiek1jNEJsUSFpQ2lCZ3g8SHQ0d1VWP11nOHBgWFRYOXREdDFVOE5YLTNySlJVQjN5ZEpjXlNaVF1CU0MlJyljZyAmLTZ1PGNLYT1DPHQiMGoiVz84bSdFIS5ROlg8RWtdbTdOUzEmNXgjWUFGIF4gaGJ6X3xTJDRxdyc0Pnp9OFtoUS9kQVksfEF7VUl9UC5MaWk3O1gmKVFeUmNJNVxmZW4gQWI1TyxiSGFXTylQWHdiMDBabHlQUSR2MC8sTz5CTEdUPmI8YHRVOistfiV7JSdgek0ufX01MipHQ1VJakNaO3RgXXwtSipwbDAkQjNIcVZLcz08bVJKQC9Qb0UkOnFHUiU7LmYyJGpuKm54O0w6QSgjZSEpR3chV3JhMDIicjRDMEgpelNFen0xIXomM3lwZylhZCcwZWplNE1fYS9MbTRAXlAnXFwoMUlgOD9gbkI7SHUzKXhMPStdRUVgSHU2JDBtRE56STgnKnR0KXVbKTlqNTAuWSJte1NAaFo5Rjh1Z1FuX2dEakFLO3hOcTRsdnpsSnpNWjc0KFQ5XHtCUlJoak00c085e04ma0AyKUc2TWsoK2QlTiN8Nj1eOHBhYmlZaG5iY3lgNnM9YWpZaCBCYzJRO3JTLFNuLWIqUS8hWkxsbC07V0Bwb1R9R2JURG12Wk4sLlFKdTlaXG8yV242d3MsfSk0L1BXWVZDI0hNNG08IzFpZ2dAPnIiM1l1WnJVIjRBd1pkfCZ2OFVqcmxvX15wUGUxelNSYSA5OFRVNFw6JDJKaSxoQGFJd1cqaWAtXiM-Nl1RTktDSnRIWkphTjp2WStKZWctQShKOThsS2NQZz9MQSBOJzB8LUonIDZtXVVcWVJvbzNiMkdBXSpKR30ySisqcDliaVlzbDRYOmEkfTVkOzRtVTMgYV1DWVA8W2lN\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 38559088762876787767628614918309758103206173357122772271888675167334835206947, + "y": 36343920265194365619200680563581393608588097808525105940034084609854956767221, + "challenge": "ImfI?o#y%Ogc\\n(O%chb*Dn6'E4 u1,Lt^7\"'Z (I9(*)YRX`,[.@86f=E)A!at]'Q8@n.2OUsIp+0_J+WXfRJ-yz\"R0}]=8lH63%*{jj{UOaRC{K]y&8^R:U($Fo}h7 ?Vza%^D'sOI6i #}sdZ3r-Q#1;rYLFe&uZ8@e=Fb;eEh]L,ctx?h<$II$~,/~Wsw#v+GC.+SPUDHdH`qX^060-'83Uk';B\"6B?aFB3JYfUlq~GUF16w-/@\\h{vMH.B&@Wqt$^N-nz38MxbO\"S9k)N]L5(8:_UHK(xM^NP8y9b>:3>^alu)h7I0Bqi\"\"JAbAU6Nb!uUfz%LhgY88J>:*\\rK*AT|H_q.(gxWGxU%4t#.dh[$J=6V@].b{B92ZPn9;M.6{13ug)*)RT[rmJ4.lKVB#b4_^ous\\`l?/J_2oCvlDDouG:+=Vg.)HzWq@${^r5M??7LQ:D`2fIc\"a(+QF0xQu`", + "r": 37578260567335628721092041885420159992427296584369184225001163100667365795483, + "s": 43975840334914225904304129241759488528531818459277954913406907627165252594871, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"cUM-KEk5KCopWVJYYCxbLkA4NmY9RSlBIWF0XSdROEBuLjJPVXNJcCswX0orV1hmUkoteXoiUjB9XT04bEg2MyUqe2pqe1VPYVJDe0tdeSY4XlI6VSgkRm99aDcgP1Z6YSVeRCdzT0k2aSAjfXNkWjNyLVEjMTtyWUxGZSZ1WjhAZT1GYjtlRWhdTCxjdHg_aDwkSUkkfiwvfldzdyN2K0dDLitTUFVESGRIYHFYXjA2MC0nODNVayc7QiI2Qj9hRkIzSllmVWxxfkdVRjE2dy0vQFxoe3ZNSC5CJkBXcXQkXk4tbnozOE14Yk8iUzlrKU5dTDUoODpfVUhLKHhNXk5QOHk5Yj46Mz5eYWx1KWg3STBCcWkiIkpBYkFVNk5iIXVVZnolTGhnWTg4Sj46KlxySypBVHxIX3EuKGd4V0d4VSU0dCMuZGhbJEo9NlZAXS5ie0I5MlpQbjk7TS42ezEzdWcpKilSVFtybUo0LmxLVkIjYjRfXm91c1xgbD8vSl8yb0N2bEREb3VHOis9VmcuKUh6V3FAJHtecjVNPz83TFE6RGAyZkljIjxoT2AgQyYuWTdFb3N9YFYsTHJIN2wnSHg8L1Epe1xZRjxOYSp2PWA4QyhbOCIgbzVEITN0PUY0fkBDU2VlLFNeODdQJmxUL2Y5MXx0UCJxXVNrJDdqYEQjOHNCe34zZlA6LlteRTkjV1U3THJuMWNLWntmJydVT0AyXFp4T1l4J1llMzx-bGpqMS02I2FBLFIuQT8-YSgrUUYweFF1YA\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 41205388292025226020826632190576878499238358995812452901551175255233791313798, + "y": 98230649160944540255779416864593904439647471926951886972519231382040999547404, + "challenge": "Q=wNk=@_1zb?8R8i9=#KqY:J|W3>'fB3lFs`9,]Wz[N|\\=WL1T#gpxQP*c{:L3H=3e@~qDac%E2G&:Q>4Yq!5-=LH|Kd:ug/j}s]4^~)zLyg+WTdXxrH!xi4]s8B>H;E4d{Z1HbPZ2zl{y~'O]}A:5wnfv;XA(?ItR)~DhB=Nz.B<<<4'{yrXH!uw53mF,D>AfOEn%s! Gro-9aX%4S}gW/H@[l1}7[lvXl.5Dl_^c\\[ s+~:xYh}/ueHRcSJo[Q/NQh?,a+x8*;wQUFb FJ`&'i|d_hGH}|V9XbUH6(Ar.OHX<%%Ep|LlsHh.M0{Rt_$YC_*Q\"IJ,O3j=.:l~&.ai\"pZg mM2*u/;HR[0>|ysomAF)|g_\"'}5R's6)u!eI:hQ\\cj$g_1u4u{$v;T5VJEZtS:XjmaJW|cMt~gR-#8'ARkwht!Xr8|KUM)\"94;&?l\\l9f+,dj\"VS6QkK>FNM<6qHx6ymKH:$nU94~js|p(lD*Z~2@+I^g]6RM1kC7dC_6qXYsr*p-rZiZLbBOKc7P=KNHp]@$.4b6yaC^%CC~<`hoOj3J(#(P\\m.X<7.>laZ#x,qNEm`Kjr73N,6oxS~^j1U2+g\\m>BbcA$(p9[zjAI'G^0%{zmj,e~AqSH&]Rm*'jkN/()bn]*(|[ZP$S0RR^\\rxbM1/_aTR\";U;U.)rLMrOM.qmW:'NV3N?/W(Y&G.Y!ka.Z63ny1p)zNKN:N,'hl`&hJ-<~Ls+`1tb\\e]$M[=*F6#'#joZOd)kp[S;G E8P]_}P{[LTf+/f:_3bT]`R'>+y6=k1c-@quJ+``+5,{xevnS 7bF,u,v{JX[zBqf", + "r": 76602945334540806641520600893297490364035475123573438616328452555486225780266, + "s": 17735379796870444564191243911917169120191197580176757327125892845625901880682, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"KUxmKG9sNEFIWyI-P2xcbDlmKyxkaiJWUzZRa0s-Rk5NPDZxSHg2eW1LSDokblU5NH5qc3xwKGxEPGtZZ2ZqcWteJzUqT2hlPSR4N3gyMmVTLiJ6SldpLC9iMU1meEEnXlVySjYwXSo3S25AISpkJkt3VHkuR0JIZz9OI2FOckFDJ0Rde3Qlcno6fV9KdztaVn1eWmcwdH5-cit6c1F-I149aGFIRHomQGpSN0FNTE9zYiNqNCtZPCxIMXt8OFpnR1teXl5SUUFjclduJXAiXXY6QXRdelZvYWA4RjIqWnN4e2x9bVRmZ11sPDpFb1RpYl0rTjJsZSN0KDciSmtdN1YqbTRkTU5nbD0tU18tKjRHYj07QCVPQ0FDID11aT9kdEBxOzJqQSAzLytKI3ZHKWM_Oy0zX0djLVpGSiJLQG1WZVNHXGgnZ2gifihXKC1JTWY1Lyo7fShSJClDOTFHR3RBbFpQeW5PMktxSTBSJ3B5PipafjJAK0leZ102Uk0xa0M3ZENfNnFYWXNyKjxjdkNsNls2YSQvfm5GT2JHeipTLmAge3tLNn0lZkxxQCw7M2g0cHpHIz8rd2ktTipAWDBhQ1dOaFhed3EzeTZ6XlEjNywqVSM-cC1yWmlaTGJCT0tjN1A9S05IcF1AJC40YjZ5YUNeJUNDfjxgaG9PajNKKCMoUFxtLlg8Ny4-bGFaI3gscU5FbWBLanI3M04sNm94U35eajFVMitnXG0-QmJjQSQocDlbempBSSdHXjAle3ptaixlfkFxU0gmXVJtKidqa04vKClibl0qKHxbWlAkUzBSUl5ccnhiTTEvX2FUUiI7VTtVLilyTE1yT00ucW1XOidOVjNOPy9XKFkmRy5ZIWthLlo2M255MXApek5LTjpOLCdobGAmaEotPH5McytgMXRiXGVdJE1bPSpGNiMnI2pvWk9kKWtwW1M7RyBFOFBdX31Qe1tMVGYrL2Y6XzNiVF1gUic-K3k2PWsxYy1AcXVKK2BgKzUse3hldm5TIDdiRix1LHZ7SlhbekJxZg\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 19923047317929531520014976283571668081554108741508085837620296248203821847228, + "y": 5829445006569985876155203519604199498878347345389263735515737745418559281451, + "challenge": "(Uw%D6(zbJ?qY$/*F5@Rp)9!=i\")V?=$@jEM[Rs31-7vQNh(>ZzTM]I(0*V{i}ian^pZ,\".HH,Nz-aBx\\&:P{1d~obi6O9/[iGH8pQinyvUs#?+|o^:Y:Sj(p)_J@.`}|E,v#sxj%\"9UY=*tP1m|t*S~p\"O~WS;/v@^@@7{3$Lw9Z]bvn]9)EHB`qAfhb#>c8kZr\"zkT4~o5baT6\\YVIL[!pQT5*3Z7979:0ie /gC1*K\\`/v%G2JQIk';-(eIh]9KLhqG/o8?R@j;i;1@)opwa.Gv05[LIBuoA^w~|@Ld1fZ!2Af<)YI;0B#LV+-I1UtAogs#!PLa@3e&.@u<]*7F?auU+Zby(wU)L8 +K\"sMi66y$Y>oA(NxdMX480q`qx %:K9sl+}$^!~=~yu1-S+5}r7vYs4'9agk,ViL6WbcbRQbyn]/oj-`B&Bg4U=Twoos@:@PxGm'Xf\"VL.E~B)\\12QIS~ [z>6qCymcGQr}g}LeWEFDr4+H1^bld$?_!L5fd{|wHAb,}N*}8p;QT(&W]H8{@Xe5*EWEc6uGM!\"MWB~7bjIwy8k&.zJQ<}nWQ{F\\0L nnYt@5qT6ew{dRa mmw&*G(Y.XU\\FjqEx:#t#u5/u3#.US8bLH're;o8c$,p>EsD:a%NgO@B3xO8E[4A1\\j'vO%gt9;G:)t./mUz;uVMM(!Tp1Smt%D)8b>$EW(0l+/(w$/ #Jiy?<4L#^G[QnPDcKi}]\\t?f|~iyya'St+M=gVL`>6a\\SyK8qJ*=|\"tI*3YRnU8fex{13cD%|tTt~2/mL(y", + "r": 52737036825551532009969047115895655765560680849238049373648930026492578324209, + "s": 64711787240292012444895172053982307174186769887397724488187912276323184687006, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"KFV3JUQ2KHpiSj9xWSQvKkY1QFJwKTkhPWkiKVY_PSRAakVNW1JzMzEtN3ZRTmgoPlp6VE1dSSgwKlZ7aX1pYW5ecFosIi5ISCxOei1hQnhcJjpQezFkfm9iaTZPOS9baUdIOHBRaW55dlVzIz8rfG9eOlk6U2oocClfSkAuYH18RSx2I3N4aiUiOVVZPSp0UDFtfHQqU35wIk9-V1M7L3ZAXkBAN3szJEx3OVpdYnZuXTkpRUhCYHFBZmhiIz5jOGtaciJ6a1Q0fm81YmFUNlxZVklMWyFwUVQ1KjNaNzk3OTowaWUgL2dDMSpLXGAvdiVHMkpRSWsnOy0oZUloXTlLTGhxRy9vOD9SQGo7aTsxQClvcHdhLkd2MDVbTElCdW9BXnd-fEBMZDFmWiEyQWY8KVlJOzBCI0xWKy1JMVV0QW9ncyMhUExhQDNlJi5AdTxdKjdGP2F1VStaYnkod1UpTDggK0sic01pNjZ5JFk-b0EoTnhkTVg0ODBxYHF4ICU6SzlzbCt9JF4hPFR1KnJ7O1RDND5-PX48U1IiQEpJUmlsR3UrJ2duO3AnWlQkcWhzIkkkYlRCLidKXHRmaC8uJ1w3LSAsNTBbSXR7OmBrcUVxR1hlW1pYQk4haDtxNlllOTdAdF1cOyFFPEZzeWFMOjxTWVM9KWU9PFxNMyR3ZXNVYCFtXCBYIV1zPV8zXn1QRWtMT3NtbiYsZCB0fjUxNitqVlBgNUQmZGpSaT55dTEtUys1fXI3dllzNCc5YWdrLFZpTDZXYmNiUlFieW5dL29qLWBCJjw_MFp9XXwmK0Q6bnwlRENYWlpTZCM7L1xYXl5xJlY-Qmc0VT1Ud29vc0A6QFB4R20nWGYiVkwuRX5CKVwxMlFJU34gW3o-NnFDeW1jR1FyfWd9TGVXRUZEcjQrSDFeYmxkJD9fIUw1ZmR7fHdIQWIsfU4qfThwO1FUKCZXXUg4e0BYZTUqRVdFYzZ1R00hIk1XQn43YmpJd3k4ayYuekpRPH1uV1F7RlwwTCBubll0QDVxVDZld3tkUmEgbW13JipHKFkuWFVcRmpxRXg6I3QjdTUvdTMjLlVTOGJMSCdyZTtvOGMkLHA-RXNEOmElTmdPQEIzeE84RVs0QTFcaid2TyVndDk7RzopdC4vbVV6O3VWTU0oIVRwMVNtdCVEKThiPiRFVygwbCsvKHckLyAjSml5Pzw0TCNeR1tRblBEY0tpfV1cdD9mfH5peXlhJ1N0K009Z1ZMYD42YVxTeUs4cUoqPXwidEkqM1lSblU4ZmV4ezEzY0QlPFZdTlB4Pnx0VHR-Mi9tTCh5\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 90834917891781706740925344334801780144245689550464377370650564248438810132008, + "y": 55291187866882159086554544901446045333932971450849784070844205133737226353114, + "challenge": "3*nfnAdf^D24bIp\"st=WSK(ff+AS iFA2bo>epub#^:)SpWS_-D)X@JY", + "r": 2055927785064656018219941360881899547277608742129741514827023125212994877249, + "s": 3623342660311553007093438394341275712036508052677357405789480129739796820788, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"MypuZm5BZGZeRDI0YklwInN0PVdTSyhmZitBUyBpRkEyYm8-ZXB1YiNeOilTcFdTXy1EKVhASlk\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 102069255698040399441784756049432564843880014311719733336833781941515850927666, + "y": 108752985064721828147240190875466791698211310782244677925016813939644301965569, + "challenge": "u[vIQ?~7_KF\\qrik%mVzw8s8c\"W\"OCb8=R ;up~klce.)'HKn$3 sYSM=he(]('E?c\\Xf^biB9;FU^D4Drk?*?MH>0iq)n1c!:b=U&f/3gaKYhG=&o\"zR\"\"bdHBD!c3{/Og%LrCzLF;L#!xdA)S as.hRe2$}R_+G`~sNg\"F~?a`qIayKyE9v\\OJ#[ePJM4(Z3t=>|9y.BcDqfNTC0Cf6UU8nRP1ou:,0}VRa=W3y-K%^,-h[T]QwvNH*}:%Ez}[jG;\\L;] ]A_:x{3tzJA2@\"&s)GhKc[yXe*YhL,Ikc$K\\AkSG6(W1Cht5lG#+]}e7Qz:8p|-^s+MOJs93?ayECu9m]hHaG\"c0dR?[^hy8'bS v*aUesFSPAr%<7'p`zEyZD=5A9SF%VxLu%IxJ] Lzl, ,=(JuoYVXXv]xAM4J2cD(8z^rSRd$+j(G[ta,J7kDw`[Y{>", + "r": 93526792261474619920930219731858748941951139221011160721727198261490171286745, + "s": 104782248961492668603013087625316071099194454713267649431458071248981596466639, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"dVt2SVE_fjdfS0ZccXJpayVtVnp3OHM4YyJXIk9DYjg9UiA7dXB-a2xjZS4pJ0hLbiQzIHNZU009aGUoXSgnRT9jXFhmXmJpQjk7RlVeRDREcms_Kj9NSD4waXEpbjE8VzthOU1iYyhSbjQ9TEoqLjYpRHwpXlc7PVNoNyIneTdKRTJfUz9wbzRCNnMpYyB3emI4bldkUXlzbk4qRVVYJyVjNF1SVCEvdGs9KVE5fCF4W1c8SCpfNSsydjNVPWJeRGA8dF92Rjw2ezdHcz1SJHB2c2g8YE1xJ0ZLKiQtJ08gZWFmRTxLLmAraWU_W0xScllxa0xgaElcdX1hKSM2eGBASDt4SCFfVGRqTlN2WXBvdXN7SSVGMj08TkxNNlRzLnUoNTw1NHpUR1YwPzloYGU5Ym92bVpJd1p6JzNlZH1kY01HXmMlYiY2flAnPHNHfSQzPTltX2dSRVs8UWQjVyIgbUdjVnI_Zms4fE9SU1QgJzRZdmIlJ1MiXitAJWlRWUJDKzJRcXlDYSIsLS0tan1sIXojNjtzbllAaVJ-QTk_TUBDLGkmN0AkLSUsUVNzcV9rbFVHbn11TUc5aTRibnFfIih6RkJlQChtS0tOTnx3ZyMlIyFAJndpTSdlZjdqRXEzJksuZS5FP2FtIGYqSC86VSY2bGk9c2gjRj5jITpiPVUmZi8zZ2FLWWhHPSZvInpSIiJiZEhCRCFjM3svT2clTHJDekxGO0wjIXhkQSlTIGFzLmhSZTIkfVJfK0dgfnNOZyJGfj9hYHFJYXlLeUU5dlxPSiNbZVBKTTQoWjN0PT58OXkuQmNEcWZOVEMwQ2Y8ZlFWTkpZSWt2cj42VVU4blJQMW91OiwwfVZSYT1XM3ktSyVeLC1oW1RdUXd2TkgqfTolRXp9W2pHO1xMO10gXUFfOnh7M3R6SkEyQCImcylHaEtjW3lYZSpZaEwsSWtjJEtcQWtTRzYoVzFDaHQ1bEcjK119ZTdRejo4cHwtXnMrTU9KczkzP2F5RUN1OW1daEhhRyJjMGRSP1teaHk4J2JTIHYqYVVlc0ZTUEFyJTw3J3BgekV5WkQ9NUE5U0YlVnhMdSVJeEpdIEx6bCwgLD0oSnVvWVZYWHZdeEFNNEoyY0QoOHpeclNSZCQraihHW3RhLEo3a0R3YFtZez4\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 52314907620518801809926584197772679657993807644979945176008618716656949965293, + "y": 26155799955758831144198927243221036231419354933372186261847638735231028047250, + "challenge": "tmuWUVbev!@AsJ'+L<5HGA?e_o2~at]2Rrm(x[I3.r45G1b7cPd>*CO7wJB{]D20Z|s-<~,Qe: `JP[z/4{+*6^IJ`4JtehI}s$>)0 0+NBzC6XnZTk$STouhdZcn=ybG\\R.2w?dLw).9{l|apHrikFe';}g2mYP #>(L_?r,E&TUs'q?]ly}a =_VNw/M=2dp'~/>5|[b[vCea|L!n?Wh\\{F}\"N$;,`X|-?*dJZ'Wh4ZFD^{:>ta{r$|J`}-r[AdjV4Q8a!}Z@xeJlU=9MDBITA>`b8oy_Vpu2I'>}6G^<4,nd{o#fD_FyXY$\"<7nh};5>QsON/-nDniHf0T0&5Fha:dh6ANx|m)Wrjp)@M'_].V\"^Jv>V 9SvPeM4 KTu?EPHH/Yfz4${]no+gAuk]^R>_ZCx#C>%Iqp[ZEUV$|\\}'A!5n#dTQstS>CeDrGs5CC>wpUgUdjIY=P &[N|v&H(2bF)!6W(*gljAH\\#QLxB% h }Cl86|73ri|%C-26<<", + "r": 50483548824358156895391923897588284594805259580796834315218135105549930519370, + "s": 91612918087856434511997735215261446038202269038436239888085369818233374211903, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"ZyxiZ2M8YSJmaS1UMV9KSEsmMmo2J14keTxbLy1tOTEpNGRITEVTWUVgRzI7WDxJOkQ2MHBsLCx9bDcrIl1mN2lbTS8xS3ZqVE03PHV7IU9OK158JnRTbS5cMGVdXyJ3cEIydiQrNE9DZW54ZTRPaF9vfXVrZy4zPjJtWVAgIz4oTF8_cixFJlRVcydxP11seX1hID1fVk48Wj1pclctOWNwS3VkOnJ9NjxsQDwubUppXHwxSXA3Y3pLWURUPncvTT0yZHAnfi8-NXxbYlt2Q2VhfEwhbj9XPD8tbU9Ud1kvelRCOjV8RFhRLi5BYzpuV3UvIiohcHo_WXpGIVZtYXIhPEFCLlp0NmN7JTg2QkIzcVg-aFx7Rn0iTiQ7LGBYfC0_KmRKWidXaDRaRkReezo-dGF7ciR8SmB9LXJbQWRqVjRROGEhfVpAeGVKbFU9OU1EQklUQT5gYjhveV9WcHUySSc-fTZHXjw0LG5ke28jZkRfRnlYWSQiPDduPElbd05RLU8hQHokJjpxUEhbIiJ3XyxVN2E9YyNGLio8WF5qamokKk5dQnJmKS5JanBHckpvQE5IYmo-aH07NT5Rc09OLy1uRG5pSGYwVDAmNUZoYTpkaDZBTnh8bSlXcmpwKUBNJ19dLlYiXkp2PlYgOVN2UGVNNCBLVHU_RVBISC9ZZno0JHtdbm8rZ0F1a11eUj5fWkN4I0M-JUlxcFtaRVVWJHxcfSdBITVuI2RUUXN0Uz5DZURyR3M1Q0M-d3BVZ1VkaklZPVAgJltOfHYmSCgyYkYpITZXKCpnbGpBSFwjUTxkVFBbaSg5LndDZ2F3Wj17KDdpazBCaXQ4XltJbXVKey50QFtYPExZZyVRPFY8SjxTI3NDSntTYlFPQSh1T1JgaHtCTUpgTTV9fTNYeVByYkdqeFM3NDozS1Z2fTcyTmpbXWYnZG11bTtScVdKI1FxfURBfVxUPFF8bismdn16JzooKj5MeEIlIGggfUNsODZ8NzNyaXwlQy0yNjw8\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 31296585031238584639196169192394332078860339421207508985257011132327299669592, + "y": 33800673509422262722829425673442907627995639103730535196849343586822649945405, + "challenge": "OB%&D|Hi9,z(P7j>v9:*6dPJv_mCbl*a=&V*nN+PhwwTBT:C(t1[~.B(<183v#{#f>>kmito '\\mRy*-wRzbr^*p]mr_MS{pPyd3rzj:1cKvHr4%EXRi5.N4GfQy2E}c\\fVT6$rDLb=ES5&`qnc>%n/zQ\\#R{$BkBA>uZRHqRP|4^5_n<-i4^;r8aH$9*-8Au[Nq3Dy6m)_h&i*F[pOMvuEilDO~W:,p;10", + "r": 23552049812911226267100723637046565532336874935627176643302565501781046982095, + "s": 43553098972350254005445532761362662349901204053973359780644907964365288054009, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"T0IlJkR8SGk5LHooUDdqPnY5Oio8QitqclUuZzxjQVY0SHwofmFmRGc-NmRQSnZfbUNibCphPSZWKm5OK1Bod3dUQlQ6Qyh0MVt-LkIoPDE4M3YjeyNmPj5rbWl0byAnXG1SeSotd1J6YnJeKnBdbXJfTVN7cFB5ZDNyemo6MWNLdkhyNCVFWFJpNS5ONEdmUXkyPHM8ISEze2hQPDJtIlYyPkV9Y1xmVlQ2JHJETGI9RVM1JmBxbmM-JW4velFcI1J7JEJrQkE-dVpSSHFSUHw0XjVfbjwtaTReO3I4YUg8PzsvKkdLU2dWcFpia1lzTmcqWUF8KWwpX3xwX3pGXlFMNVBdMC1fVE5wNSB8PXsiQTNsZSh0czBfYTFdK0RCKiZ9akA8fjQjSnxkJmJie3A2OmZnX2JrISwvR2ZVSWRbTnMzS149Yndfbj4kOSotOEF1W05xM0R5Nm0pX2gmaSpGW3BPTXZ1RWlsRE9-VzoscDsxMA\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false,\"other_keys_can_be_added_here\":\"do not compare clientDataJSON against a template. See https://goo.gl/yabPex\"}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 60424973107679287480141646999539845819161570197249063534555677829945469525235, + "y": 28085494904670731754183133986150839009484786691932502215470014624093198681770, + "challenge": "z\"+U QtlC?b*xD@&n7l8!)qTTjl=Md7|Y>dDChk3$4y^+c}!<_u0V[i~4Q05M=E4nGoLB(3n7$kU+/o ZdlzrM]i!Eq!<%>AmT{Nm@V4IPNycJ.GQ?w.i|j;L98\"l?|ez2\\/vKQ;FfbNAIg@HtN_,A'o@'ivq{hIvy>AGNRHYe?ee$f1;Y,bX*q8>~pXw4T6|Xk,5FqDJHw>r5aI/sTTI;Ohe?Q-OE82bL2x[Usm&X^>4=<_2'A\">I\\`+-.3VyQ_b/{[|dw>r}O@\"zDw14fJmYjewXL@hLYAP3]k|3tc\\'n{5l/#m.K=.YO9IKegJ|E>u;i]`54.m&@@Ud:{@bpeP!u\\jhcRu|k_k'r'nO$M2F\";r^-)KlF*i>n?-e)l1;;[y*>1W(lZjS)NO\\mM<1}CEImR:RrVsapa:zs|8{=M([dM{7a`OM\\]6r)<=D}@(Gqx&[c4(DA\\4dve#!o_~CHcBc4Au]>H46z0Fm2hLk.COgCR PCtS8n,2ceVQ<9-_/f_;9?X<+Js#2'W,JOx~J(+_;W?Y Q_9'4?${g_1D&-lC^P]A8gbEjU/g@K|i'K&\"37N^8IgK#11rfmX[.j$zz)k \"H7Lq/lO)9qG(.TI_0[YWS';u(7;=Y$%otVXmBU|B[DSEy}#9cEKw0~B>}]tsOiR?l%b'*(^\\lyt!qhxq{O*0Re%k", + "r": 65427723819285549262182039925103160563263913716250936470909906813162422076458, + "s": 100209134527413391615561613726023177924573804368398750035569215109694909323267, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"Wj1HbXBxeHk8bCokWU51IS08OXEiKCJSNnk4SUVtNSRhOidZIXpaJCNZMydpdnIhKHJhVFJhWmYxWnFTPCFGKnR-TnwgeWRPXytBe0FfUCklJWNrYyJ9WmRbSHwkI3hGKVhVd287djxXV1I6THJVP3BnYnxzUiBXZzBJflpsfUVZKktsTyFAVTA-UE55Y0ouR1E_dy5pfGo7TDk4Imw_fGV6MlwvdktRO0Y8byJ1dntiaWY-ZmJOQUlnQDxWJDVjZTggJ3JUPGtIQFpvY0VrVGdWOzxwZS13b055ekYoMC4vP1o0NENzfSlsUHwjKiN5OktLalNpeE5penl2JzNkT25yaCgoLD9mSDpdYlh6dXsxdjM2dnBdbzB9NUR7TnQqfENmUTNkcDlQLTNgP107dE0-SDx3d1FjX3liOlF4aXRdLkREQVB0cVRLfXF4WDZGOFx5PylUaFxHW1d6TWUtLU10Ij50Tl8sQSdvQCdpdnF7aEl2eT5BR05SSFllP2VlJGYxO1ksYlgqcTg-fnBYdzRUNnxYayw1RnFESkh3PnI1YUkvc1RUSTtPaGU_US1PRTgyYkwyeFtVc20mWF4-ND08XzInQSI-SVxgKy0uM1Z5UV9iL3tbfGR3PnJ9T0AiekR3MTRmSm1ZamV3WExAaExZQVAzXWt8M3RjXCduezVsLyNtLks9LllPOUlLZWdKfEU-dTtpXWA1NC5tJkBAVWQ6e0BicGVQIXVcamhjUnV8a19rJ3Inbk8kTTJGIjtyXi0pS2xGKmk-bj8tZSlsMTs7W3kqPjFXKGxaalMpTk9cbU08MX1DRUltUjpSclZzYXBhOnpzfDh7PU0oW2RNezdhYE9NXF02cik8PUR9QChHcXgmW2M0KERBXDRkdmUjIW9ffkNIY0JjNEF1XT5INDZ6MEZtMmhMay5DT2dDUiBQQ3RTPFIyKGtLJUgrIXkpdXRbRm8qSlVAIEglIVZKdXMmOllSND44biwyY2VWUTw5LV8vZl87OT9YPCtKcyMyJ1csSk94fkooK187Vz9ZIFFfOSc0PyR7Z18xRCYtPFMpY2w-bENeUF1BOGdiRWpVL2dAS3xpJ0smIjM3Tl44SWdLIzExcmZtWFsuaiR6eilrICJIN0xxL2xPKTlxRyguVElfMFtZV1MnO3UoNzs9WSQlb3RWWG1CVXxCW0RTRXl9IzljRUt3MH5CPn1ddHNPaVI_bCViJyooXlxseXQhcWh4cXtPKjBSZSVr\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 55854292604661789323112445671577574327031428775709996911024169868078507328664, + "y": 25947237229303570701788719945968806258670347356888648246227793307187085912232, + "challenge": "b/[fb/wc4}[/7t+Oqe5CUBQglYb~vwL%oH0rByx-2#lra>x]7RaENSz,``CG{Szt>", + "r": 74022110208275747735160181370809066451698150988794277792590965832206286006695, + "s": 49089448395390802209305572941769727811576028159374967561442800030800378143575, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"Yi9bZmIvd2M0fVsvN3QrT3FlNUNVQlFnbFlifnZ3TCVvSDByQnl4LTIjbHJhPnhdN1JhRU5TeixgYENHe1N6dD4\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 17252441228234732688943678614310581686568951607260990384237963143679821474195, + "y": 54650675409662840889007875591430193239432599469830424888231950526952406783422, + "challenge": "Uo%;+r[{uym6S6jmri)3j'*:y*:z#_@5[1CyxLNzF@;IEBYx/=[R79A#x1qis!mXc(nPLkeqnNFL))Pdo+SlUHaHbNU`7`%{*~X@Tsgw^0?Tu4?OVn(p]", + "r": 46229847414844323632289876199920780394506462330636226165067153221531237107031, + "s": 22888810774320430418433184385842963726394902577299342788399874922024253943525, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"VW8lOytyW3t1eW02UzZqbXJpKTNqJyo6eSo6eiNfQDVbMUN5eExOekZAO0lFQll4Lz1bUjxnRFc-NzlBI3gxcWlzIW1YYyhuUExrZXFuTkZMKSlQZG8rU2xVSGFIYk5VYDdgJXsqflhAVHNnd14wP1R1ND9PVm4ocF0\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 32931293196768444565449472801274598997439704535895150028803181206617535573885, + "y": 111455756719442059141226817190704342705742796552224252540753553448646996085387, + "challenge": "6Cb8qbxGWh^1P0\\,7}5;g_aWx;b~9MofyAlb>P.pkFhf)_D4en7ZnpZ)[4z?b..S\". *qi:/b)FI\"8fn+,h'k2E1%~+V-U\"a.zPiv!g=w?VjR/Fju(Df@&.1&%#C|@uU\"`{4SFtoiq^TB$r\\Uou]=EK#\\$dwh6bp}0eEQTc=*c#x)QHK-vsJWN\\^GoO|f5Z&dyw>j-E3X;uk&Ns!^5Ea[)i\" [VNB?47|_[G=Ss~.GO~kTXbG4i iTzI5o2>jY~kcegl1$gg:hH>x|NTW;Ul4v7Qa[JBg*p#k%>AZ[_d&v&T+^9gWw\"y}(6#dyF^?vHu]F~cYWR-4T+(R0!RE)%_f2ccI7nhuBwedA\\h8w/9G#5R N@GnFWK^6tr/VjSL$^j5i{JW/VvP@tY{C6F!_m\"6rmuxCdlsLvl}cH|m8qphs]Ld+ZK&Q3_@<@p\"M<>IpdnWvhTZ2pp<5Q'-,I<.Lj6<%#NIqfaTCtIDN`*Oy4VyDCz*w2_1'Is\\\"Ltkl_p$;^-\"W.qqoF7s0XOh`8>Nhe9#Nt3mleL/ g/~G;i#bQ2j]'P#?G[EQs~?/!4\"E6QF\\>]::1`P,$V\"HtfOu9i!2vo2Zarj:=]\\|TMD88/nyWK", + "r": 56481721862085070746736246845495179452005411347961301954661847534325413154511, + "s": 91794450244577361275608600804854942650815307745911851918986538178114997434780, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"NkNiOHFieEdXaF4xUDBcLDd9NTtnX2FXeDtifjlNb2Z5QWxiPlAucGtGaGYpX0Q0ZW43Wm5wWilbNHo_Yi4uUyIuICpxaTovYilGSSI4Zm4rLGgnazJFMSV-K1YtVSJhLnpQaXYhZz13P1ZqUi9GanUoRGZAJi4xJiUjQ3xAdVUiYHs0U0Z0b2lxXlRCJHJcVW91XT1FSyNcJGR3aDZicH0wZUVRVGM9KmMjeClRSEstdnNKV05cXkdvT3xmNVomZHl3PmotRTNYO3VrJk5zIV41RWFbKWkiPHVgTnJ7KlB6ViAkcVV7U315Q1d6L0xcIj4gW1ZOQj80N3xfW0c9U3N-LkdPfmtUWGJHNGkgaVR6STVvMj5qWX5rY2VnbDEkZ2c6aEg-eHxOVFc7VWw0djdRYVtKQmcqcCNrJT5BWltfZCZ2JlQrXjlnV3cieX0oNiNkeUZeP3ZIdV1GfmNZV1ItNFQrKFIwIVJFKSVfZjJjY0k3bmh1QndlZEFcaDh3LzxmRDJyNyd7OWxRYXh8KCVMNi9Jfl4wKmZ7b2xdXSd6Lk1scWVYb2JJIGI2T3VrVV1DLCpBPWljeC1YNX49dDV8dVY9VkRCJ2knNHRPX3luaDh6K0dsZnQiLnBKIV4nK15IIiYiMlJfWTNjc1lWJ2FbNDk2QmBCUHxrNVFgLjNxK25nPFtzbUkxUCpJUTp0cGxwbilTbnksPjlHIzVSIE5AR25GV0teNnRyL1ZqU0wkXmo1aXtKVy9WdlA8VTQpTX5VJCY4Lz1WL2YtMDNmbyY5S3guUiQ_JDU_XUtDM1MlZTk5TVZxNyglP3BRaks_Mjx7fDtpOW1vZS12OUlEeiVcY21tWVBrfFt6UGRlb3opIWFVPTkiajNqUl04Qmx3QillbyZDOG53e0wsOT0mN2xjX0lWKC4vMHtVRXs4WFxlRzJOVjtTPCB3RDp8SklCdGIvKXowNz5AdFl7QzZGIV9tIjZybXV4Q2Rsc0x2bH1jSHxtOHFwaHNdTGQrWksmUTNfQDxAcCJNPD5JcGRuV3ZoVFoycHA8NVEnLSxJPC5MajY8JSNOSXFmYVRDdElETmAqT3k0VnlEQ3oqdzJfMSdJc1wiTHRrbF9wJDteLSJXLnFxb0Y3czBYT2hgOD5OaGU5I050M21sZUwvIGcvfkc7aSNiUTJqXSdQIz9HW0VRc34_LyE0IkU2UUZcPl06OjFgUCwkViJIdGZPdTlpITJ2bzJaYXJqOj1dXHxUTUQ4OC9ueVdL\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 87751010712529862141455568520002136295097735415842063075902909182097721093518, + "y": 86222960298165423026252503292882146980928339930602812676202003101316013131217, + "challenge": "hj7zQ|tL~8*GCPhvt3Y8?.\\_D;904@{\"hU{cpJKf^7Pkq.FB9@_aMJ3b[s';l]_xSpx-&79=%~gUf*fvH]?CB\\Cr8cXe)Undnu*L:\\p5HANQR&OgV7\"]ipzMbnGbck|^O`QXY;C/V`ZWm#8LFtxV$|wEZrx%{4aq_ib\"CA013J(MQn$JedJhn1,bDg82Tw9N-D;2y3nYh/wlNh.UYhNvm,\\$3A ZnI*TvXk]~?BhYO3'~J[o!a]+M8ASw?w/23nAO&%\\f0+<[,Q%~Ug?kv2t~h&G%[kaPdB^O@NA4GXI9{.65MS[N~tsmVNYwyjF%m{%?AgIoahs_;@`w\\bg#@@O\\MKL(6];%FA=Kzr_IJ8vf!/dv2j)w<\\X~h$Q;@9#)%gv`Spq8kz=}q~)stH*:>dB=rY8qxH8F;^^fd^0GF@zJ\"qr7@tZ\"#P^nV&OyZP5V#tXuX+.^1eCu%kH9#nFe aSdlgbj=OEZcnfA~{l|tH=R[EPtF&yt)8.a)*EyO.$2\\d}oS?;#Z)8Z3VI!h.'6B>vZaH~&3t-:CSp\"erN^Q1IVWa7f>=HnPEtxkXm`XqG(Ls7dS8HxSv3^L$u~2wXK?)\"1{,:$\"tZpl3.ok)s/Q7I;p|U ,c9gG#0mSV$YlO!tI{:*!JR+LYxHgQCHW8kBA@ch&K1ffan^qn]?2Z[Z\"%@NJ}lExUUFo10=6NQe!uRJp(:;-.WuM{~hYg*31b,{o(EL.PNA", + "r": 102374121717482135104686909450956682488170744734883502941338433716047182846545, + "s": 24368813227293957935383078508662350884273036848958330745558943260786963845299, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"fTtxSHRVPHY2X045Kjlxb0NaVHpyIUV8RyxhPzoqUnRZWjMiPGtndHMhJzssW3F6SU0wKEk9bl13IDA6N3M6NX4gSigkZmwoJ0o-XV94U3B4LSY3OT0lfmdVZipmdkhdP0NCXENyOGNYZSlVbmRudSpMOlxwNUhBTlFSJk9nVjciXWlwek1ibkdiY2t8Xk9gUVhZOzx5fHVmJzp9XiIhRy5wUmcyaCNVV0xULyQoJ1J6JmUmQChmJUhVWzY-Qy9WYFpXbSM4TEZ0eFYkfHdFWnJ4JXs0YXFfaWIiQ0EwMTNKKE1RbiRKZWRKaG4xLGJEZzgyVHc5Ti1EOzJ5M25ZaC93bE5oLlVZaE52bSxcJDNBIFpuSSpUdlhrXX4_QmhZTzMnfkpbbyFhXStNOEFTdz93LzIzbkFPJiVcZjArPFssUSV-VWc_a3YydH5oJkclW2thUGRCXk9ATkE0R1hJOXsuNjVNU1tOfnRzbVZOWXd5akYlbXslP0FnSW9haHNfO0Bgd1xiZyNAQE9cTUtMKDZdOyVGQT1Lejxzb1kiJzNMSkFfUFFSIFMxfU80XitoTHB6e0RLOSJ7aDc-cl9JSjh2ZiEvZHYyail3PFxYfmgkUTtAOSMpJWd2YFNwcThrej19cX4pc3RIKjo-ZEI9clk4cXhIOEY7Xl5mZF4wR0ZAekoicXI3QHRaIiNQXm5WJk95WlA1ViN0WHVYKy5eMWVDdSVrSDkjbkZlIGFTZGxnYmo9T0VaY25mQX57bHx0SD1SW0VQdEYmeXQpOC5hKSpFeU8uJDJcZH1vUz87I1opOFozVkkhaC4nNkI-dlphSH4mM3QtOkNTcCJlck5eUTFJVldhN2Y-PUhuUEV0eGtYbWBYcUcoTHM3ZFM4SHhTdjNeTCR1fjJ3WEs_KSIxeyw6JCJ0WnBsMy5vaylzL1E3STtwfFUgLGM5Z0cjMG08ZGJRN1AhY2tWdUFpQS9ZVkJ-S1NLU35PfDd5bHBWa0JuPSYzLis4eTUgJjRHYClAJDlKTWVcRn5uRE1hcGYtPVxHMSVHTD5TViRZbE8hdEl7OiohSlIrTFl4SGdRQ0hXOGtCQUBjaCZLMWZmYW5ecW5dPzJaW1oiJUBOSn1sRXhVVUZvMTA9Nk5RZSF1UkpwKDo7LS5XdU17fmhZZyozMWIse28oRUwuUE5B\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 22032222034595366217416746258411772012045184660469550306981654517970097237548, + "y": 9816299873005618719930706110256920969458693944912040720039986497239735820494, + "challenge": "1y@/qr?.yr8nk u~7h}", + "r": 61948130297746567355067609577408669760883855125074031420733509338162707381903, + "s": 74552484029605312516741403579950602220063359978310085193896905707146975663375, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"MXlAL3FyPy55cjhuayB1fjdofQ\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 46268273756531917538594139929860309067859868588502978070935256056958575486408, + "y": 83146156506788360866679439007379627343476946429014786680977004609507065247894, + "challenge": "IEfwS/QF48%biURt+L_zUB-0},iol~i[R@dsQ$FHtu&I?%|vH#_^2S')z@ND{O%@v)GE3cF4 Vq-Uf-Di?'FS~. \"Nt1CiVITN)AQ#/hJGBehugHY2=vOb;k@)y][a[`g\\Hnn9^Q1!pv,g+9xZINEWlt|LCPWaEc[*]|fD'606qujf)6jRqd9|cJAJ8Z}j<[qnN(ma}G.++(h]bQNXz{p`?w73v{13$D{S288oN7e-0z5H:j)!>Yn'}3q*dBe6q+UpeB\\7Z''f_P]mmb}5h!T qFYsW\"vTQ7?Df'FX5/m`Ux c4&.~Bq]JI:y)bk:|C/\"GL5S~nO'bTV%(=vD~t7~7Eg//VU[w>pN#HtlKt$4T,B/6nY?1:8UDz.?jU*Fcg>!0Ma!\"AeEwawb#9b\\AHtA\"q<618B9W}\"TO~ >Um6N[7V6xIg ET~ypi8<167#kxi^O}p.{.zDeDt[r\"eQ5-Z^`@Qs90!Nv!P|p-}0'0*X/i%,s-Ju'5C=g#A`:$]Rh!674MIi0^\"5z~J=a1G&NPTbi|;Q#Ka&u\"6^4cUUbksvW%?.$UnxF8MZEKS\"1^n`n}31$1~.Vd`ArD7Y<\"Z;F]0AfX^7>cxaFJywmPdQ^b;*~i\\D+&HkJfg\\k D.jY8pd.H)xsN/?\\NB\"y\\B-CW092:..;6Pj/_-?6s}zjet9acCn.$4TwC.0iD(A$SyeBR)h}Cut|/g#MR4re/myhH}iT/p`ra5$3-%K1%W=i+-;rq=%Q&JukYM=V4bfY~%B?FKPK_5p#4p+rBM(8q?&%`J{`42I>0|'L*z;qPkpZbdd.}vVEZ5:c5C2M!!/_26E+\\rf3|[~P#SVrFd[\"AY48`DUOqXPoLviDsHj.#\\.Mk}gec}+j\\Oq8:/uR^JVIXU5k21\"B>i6i5d-8l)N,0o:D8e3\\S~Hj0-V'Bd*}yKHM1aCok69`T9Oh?Iy67YnNEG`aU55w/**KYnE@W_:)--Sny(r}7pa^!6;~}dB3e:}T2:aV R 1)RRFXblk!i/ p%NSmznCR!u/zMkl}!waw0.$6Ia32\\5BpiSf[(\\xCo}3wkdlaQv(~TLLlR'_;/c[(GgX~.DTbglYq92{[&e$KdRA:!pt3Wrmapul\\[xbL+]3ZWa`\"LT]Fx}Y ?FpZ#_%e94 mRi1^H*>$FWcn%Ye=^c\"D1cJRUk*Dej$j.9yL*MYy0N|]]/r bhkz'<^8\"~x]=xuguva,s,1K,[i7+tC(KkH:GyE@{-m6t$rmLNE@3U|gSrkV!#5~1)zSE]J/E:lU:$xz4]nOn~~$KRZK913qbO5mNH_kK 4UD1_bo$%xhCa/LwwLUIex(=p(KEw/;:y78'`zfu-b]C,p2:!,nO5bDZ~zF#1];jLNp%H)wOO+lQ_VV0W0]U5$f^Uu{sujcBN2?P", + "r": 60533064263469554531414091761839238348824082093643262317078811078776302505042, + "s": 107983053117185761943134113817192839533098114990363393489954160383320623336981, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"P25uKSgwVCcgRHU-Oi4uOzZQai9fLT82c316amV0OWFjQ24uJDRUd0MuMGlEKEEkU3llQlIpaH1DdXR8L2cjTVI0cmUvbXloSH1pVC9wYHJhNSQzLSVLMSVXPWkrLTtycT0lUSZKdWtZTT1WNGJmWX4lQj9GS1BLXzVwIzRwK3JCTSg4cT8mJWBKe2A0Mkk-MHwnTCp6O3FQa3BaYmRkLn12VkVaNTpjNUMyTSEhL18yNkUrXHJmM3xbflAjU1ZyRmRbIkFZNDhgRFVPcVhQb0x2aURzSGouI1wuTWt9Z2VjfStqXE9xODovdVJeSlZJWFU1azIxIkI-aTZpNWQtOGwpTiwwbzpEOGUzXFN-SGowLVYnQmQqfXlLSE0xYUNvazY5YFQ5T2g_SXk2N1luTkVHYGFVNTV3LyoqS1luRUBXXzopLS1Tbnkocn03cGFeITY7fn1kQjNlOn1UMjphViBSIDEpUlJGWGJsayFpLyBwJU5TbXpuQ1IhdS96TWtsfSF3YXcwLiQ2SWEzMlw1QnBpU2ZbKFx4Q299M3drZGxhUXYoflRMTGxSJ187L2NbKEdnWH4uRFRiPC9eTFk-Z2xZcTkye1smZSRLZFJBOiE8VDU_RWw3Zyg0eUk2X2J3YVs0b0leTVk5aGpUUXYxVk9DVjchWjg2VUEiSk8vU1laTHI1NFc8TmJ9fXg8QkVvOEBkNyZJTW88cVo_IGRLSlJiQi45cCVAN1lVTnZhNjVpPSNwcnNPfkdVdVtwTWFOID5wdDNXcm1hcHVsXFt4YkwrXTNaV2FgIkxUXUZ4fVkgP0ZwWiNfJWU5NCBtUmkxXkgqPiRGV2NuJVllPV5jIkQxY0pSVWsqRGVqJGouOXlMKk1ZeTBOfF1dL3IgYmhreic8XjgifnhdPXh1Z3V2YSxzLDFLLFtpNyt0QyhLa0g6R3lFQHstbTZ0JHJtTE5FQDNVfGdTcmtWISM1fjEpelNFXUovRTpsVTokeHo0XW5Pbn5-JEtSWks5MTNxYk81bU5IXzx0fikrQ3JgY3JqXEkyZiJdID9mdypjVHFhPEY1WVBMImVgJGp8Nio-a0sgNFVEMV9ibyQleGhDYS9Md3dMVUlleCg9cChLRXcvOzp5NzgnYHpmdS1iXUMscDI6ISxuTzViRFp-ekYjMV07akxOcCVIKXdPTytsUV9WVjBXMF1VNSRmXlV1e3N1amNCTjI_UA\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false,\"other_keys_can_be_added_here\":\"do not compare clientDataJSON against a template. See https://goo.gl/yabPex\"}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 37314955759180413545220780788255882105144824247631702564170948794864533425538, + "y": 100224412239038292828693825146126046226688757236490287011997974886952329472402, + "challenge": "@rJK\\\"he7Hgq?`clfk<-O7yUMQM+8i|]UD$Z`z;vMW&$%*Sq}+jT^ =Jb80YXy_6ORvBojwoyB~-TW2p_.I5%FbQ~1TM=3;1mG&U_LO>(h@T~7skvd;y6nlMQ{(~Fu}m(Xy9ac3bF$2Y%v&hp#_{m@KxBTh9^?Z-SmLAt,OTudt+LK^tF>3WsO1%6AEdC2^KL,'yR|#w=:5_k@8wg`,3'(d`<2]_st!ECh}i8rnjD8nVT%1M#m+>YjLo:YX8Eqv|<)?';{EFcfvxq\"(VUzSV5HI0?ke@w{)$tA|7", + "r": 11501076677868721792264853644811836060435207317504735241980133908961432038427, + "s": 52601171355397400129188497595979647050123400778890320690540461916995658044385, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"QHJKS1wiaGU3SGdxP2BjbGZrPC1PN3lVTVFNKzhpfF1VRCRaYHo7dk1XJiQlKlNxfStqVF4gPUpiODBZWHlfNk9SdkJvandveUJ-LVRXMnBfLkk1JUZiUX4xVE09MzsxbUcmVV9MTz4oaEBUfjdza3ZkO3k2bmxNUXsofkZ1fW0oWHk5YWMzYkYkMlkldiZocCNfe21AS3hCVGg5Xj9aLVNtTEF0LE9UdWR0K0xLXnRGPjNXc08xJTZBRWRDMl5LTCwneVJ8I3c9OjVfa0A4d2dgLDMnKGRgPDJdX3N0IUVDaH1pOHJuakQ4blZUJTFNI20rPllqTG86WVg4PExzVjdWNXghKV18XSJFdD1hL3dKJWAvMUh5Tnt5Qlg5UzhRMmZcKVd3cEQlXEdFR184Njo-RXF2fDwpPyc7e0VGY2Z2eHEiKFZVelNWNUhJMD9rZUB3eykkdEF8Nw\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 17358773643421926189214278127169930639730824810964181625809013295428280220652, + "y": 33451049063055796294885490240702709311343745446407915163846537453762309449552, + "challenge": " 'gs7ZOB;~G6c_ADUN0j78:6x#E53c/XtqqU{~eUJ&\"^Me?xkng[=ed*j#y7pwuocv1rJ2-1Ztb4O\\'QCrn7bl~", + "r": 7839845057451691560766558376671806369043515815286313332513155391039478372652, + "s": 38906818831009145878767441807426334612339965041682990869991781645003203537609, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"ICdnczdaT0I7fkc2Y19BRFVOMGo3ODo2eCNFNTNjL1h0cXFVe35lVUomIl5NZT94a25nWz1lZCpqI3k3cHd1b2N2MXJKMi0xWnRiNE9cJ1FDcm43Ymx-\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false,\"other_keys_can_be_added_here\":\"do not compare clientDataJSON against a template. See https://goo.gl/yabPex\"}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 54604838615391505875988157413110277739144563049402474706329310133566626439857, + "y": 39590444799285670420873119085397067712326054445284510634902609184326486898080, + "challenge": "%#''WJ{pt-((&n(#4[\"7CsZ?jUX;[_KTMZ/=gi=:p>aV(PB?hahIpk]lNd<$[XZl\\w[PxvN5%2D?'MxrKl@de!of!e? =w\\q1\"AH7^I,h_BX,?gK2vQBiY;)x", + "r": 43667903123396907836256477737314710777790028410082072363601666398439009391266, + "s": 100870502449184575684357080900176054755843801838310203831228902650821558291838, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"JSMnJ1dKe3B0LSgoJm4oIzRbIjdDc1o_alVYO1tfS1RNWi89Z2k9OnA-YVYoUEI_aGFoSXBrXWxOZDwkW1habFx3W1B4dk41JTJEPydNeHJLbEBkZSFvZiFlPyA9d1xxMSJBSDdeSTxvdEQ3aTg2SFlrMl9fZHV8aFw6bUpkaWpXZnxkZ1U9Mz4saF9CWCw_Z0sydlFCaVk7KXg\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 33432654428568156836605950943720134714474458304035074246171985789997443061588, + "y": 46868350358238002444120134741397522591425577170815865313002864787282136739522, + "challenge": "q[]G@Xc\"Ji(}NpN/ixCcy(( 9DkXGVcx,Du&~zpGt](N/geVD]w5'jfm\">xa1W*jg7l]yq@49Vx1KB6x=zZDK)h/& D%Tb$y4zzL\\e&HdltSRda^5rwK!*k4G=]6\"eSEx{i%.a+^t-{r(6MWj[c?io:3cp]K^{;DY[{wpGn,wF&Qy$tbuW? mR$8 w&gq4y1U'qWH@J96QVJ$|&KKc4p.Y)642+5=L0b+]OfImW(\\n=Eo)%Kw&\\YFE{6A*n!$]D\\@0h(G^j8!61HfF\")T5p7-K7R\\yqcCVk5x=*N({qx5?HmpJAVswKx.a+Bm.Q.k|:Raio!,PBKC0;RvGs}oI0+ic/jX~$o'=;l7oz9Z6xm1i:0iO!=vhx#};|mqgwPqrdFDCQ0OzTSL%{h5~Ii3$3{%Y\\U]`j9jx[~~@Xm R?p\\__Q2[[r(]){KQ@F/-_6_{\"_TXAHy0Ll[G.uy{Y\"+;ZT;t^`yhG{&[^m+n&;ltQR:;i8e,S!G5,\"GCb\\iNK#!b*vgq5dJL7A]y6$D6|1At+O&IPhHf:Jwqgh;Ctk'>]4fBplN/ZE<{d'[fB:Wz&PyE=^V<~t=Owk|t;QnIrc[0>O8Bo0Zxd_j\\5QjktA)2c#Q[Aeak?NH~onTPP}`1M G%yw=>o#H}J|a9rpm\\Xx6RQQ;G9O`bEtE:z@V'k7T&Sj0v}TO12JQ7w=WS,X!$OnA,7?c xXHP!|)J(?5ozAAz*)lo%#gDL$yOe7ighl@das_nc}{Csn9dYGUMX\\5djRU~nC.G{gaDm[=yCh6?+Q=pF/zo&5[U6SnnV*Y}3+s>` Hl6Af\\M`d_f`/GZ;p[aV6Z#}mN~IrfQ3$(al(8_Q5~;Al's#bTbL)iA)r!f.jh$9wkYV)K{KRd )@-^]w$H'?J9#T*o#1{N&D4`.Njcx\\v`H|\\\"{8 L&E#hxf6U&bc9)=+}OyhGi^BaV:KUpF(:lzZ}2O\\nQLK*lz[p7HM(G(S8|+of_o[8Dztd|7:^^:e1h&qSBusVh,)O,$C+MeQF:5~h]\"HK$o3tdo729CFpx/b&=j@-{5*zzC3o^[E}[Et59{B6 q\"-lM >43ayIK{!$z.;v0xe9b(~eX t28I,-hqNOrCn$&+u]:t9p%M5*uMV!&0c~0J? ilX,ts>2|yAY", + "r": 65599444152090549825298389748298082350638844583481309594120106985363080741753, + "s": 53611358517872622300405369472232482272111798969003165136262860681931414935209, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"RmhKdXIgfjglMnN7Wm18PWBgSzRqR3d0P1RaWn43alxwLD1NLHpFe1pkLDBJM3A_PSYnTDhsdGU_XDNQcVVlQ2JLOWsgUEBBc3sgOH1QQj4_NW96QUF6KilsbyUjZ0RMJHlPZTdpZ2hsQGRhc19uY317Q3NuOWRZR1VNWFw1ZGpSVTwvUzRqVk10cCVEcE8pN301SEBvXU9RdUBWUlJ2Kis1RGE2XXNOW3k0d05SdyhpeiFvR2lCQV42U1U8eCE9ci5aODMwWTI4dGE8cid2NSRfciciX0w9XWtaL1VQPCBAQjY2Z1F6QS0ubFk-fm5DLkd7Z2FEbVs9eUNoNj8rUT1wRi96byY1W1U2U25uVipZfTMrcz5gIEhsNkFmXE1gZF9mYC9HWjtwW2FWNlojfW1OfklyZlEzJChhbCg4X1E1fjtBbCdzI2JUYkwpaUEpciFmLmpoJDl3a1lWKUt7S1JkIClALV5ddyRIJz9KOSNUKm8jMXtOJkQ0YC5OamN4XHZgSHxcIns4IEwmRSNoeGY2VSZiYzkpPSt9T3loR2leQmFWOktVcEYoOmx6Wn0yT1xuUUxLKmx6W3A3SE0oRyhTOHwrb2Zfb1s4RHp0ZHw3Ol5eOmUxaCZxU0J1c1ZoLClPLCRDK01lUUY6NX5oXSJISyRvM3RkbzcyOUNGcHgvYiY9akAtezUqenpDM29eW0V9W0V0NTl7QjYgcSItbE0gPjQzYXlJS3shJHouO3YweGU5Yih-ZVggdDI4SSwtaHFOT3JDbiQmK3VdOnQ5cCVNNSp1TVYhJjBjfjBKPyBpbFgsdHM-Mnx5QVk\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 13243826179266226353495348467802222425421584240657465794550190642083679070445, + "y": 42166723380929219202054848850565436507221408858662071700079780140686913798495, + "challenge": "7@+Wvl^na-sG^\\8*MUI29G#@)g0WPq~^?W!bBYO =_(DBBjC\\&O)W:C0L3]VGWFr(\\-R=<=zZ3T7PJue\\$oFwj8VCxD90w9\\gf*Rx8ru\\(,rKymdi*`xS[(z*eCd#'nbk6ejKa^!sR|zUj?%[?\"$WWYk\"aP$%fPBaaE{tJd&#ox6#/b/Od')7oy/ozYeM}E5|%iSJ*\"cXMSjFA,n@QH\"tYa+J#\"W6;,TT,|RVf6\"ZX`[`:RuKS~hiA0YL=ax3YOjvms*yhOXGSft0Y{n[6*", + "r": 35120865637804044187308251583300101907196387711698414996392693728400278247951, + "s": 10820756116037537474825026538779230428359378353405794708427981640333977974306, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"N0ArV3ZsXm5hLXNHXlw4Kk1VSTI5RyNAKWcwV1Bxfl4_VyFiQllPID1fKERCQmpDXCZPKVc6QzBMPERINHFWJ1k5OTNkKy97QFo9Y2pGWkExXFVkRSZ3bjYpUkh3PT9LWVNdYmZQYTltLUJJfSMoRVJDaTRrY0pBJUo_WUAjYE5nUFp0ZkdGYz1FJCluJ0IlZ1dgNl9-TSx1U3xFW3p9WG12RFAjcjx-bihOPVdadSwyelIlS0hzdVJVNTRjfGVCUDZwVGk0SU5HaW4mJT0pS296RldkP0JgISwwKDUtVHhxVyN9YX5ePEhGLnVqYVUueXFEUHx5SS51bEsoMWk0VSU-M11WR1dGcihcLVI9PD16WjNUN1BKdWVcJG9Gd2o4VkN4RDkwdzlcZ2YqUng4cnVcKCxyS3ltZGkqYHhTWyh6KmVDZCMnbmJrNmVqS2FeIXNSfHpVaj8lWz8iJFdXWWsiYVAkJWZQQmFhRXt0SmQmI294NiMvYi9PZCcpN295L296WWVNfUU1fCVpU0oqImNYTVNqRkEsbkBRSCJ0WWErSiMiVzY7LFRULHxSVmY2IlpYYFtgOlJ1S1N-aGlBMFlMPWF4M1lPanZtcyp5aE9YR1NmdDBZezxUWHg7Ryo6RFxcVFlCNC41OUxpWD1abkxERVJgdlVBeXAmI1xKS01DRmxzJFk6dy1HTC1nR25cT3YyZFZDZUBrLz5uWzYq\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 98219579535766604490710112709595262304321577265575076729894224574513227541393, + "y": 29452136266238768675445690442762674717124584004476327121062039568300429646810, + "challenge": "#1}Rqq/ViTOy0b,o:N;.K%=jNY(l w.~7U3c3sddCort bg 5i0p&q+*+2?gV@b+U7pM7y^M7 v`8#[T+Z/!H9)\\V`x9q=At!%f]*@j:xRZ&4+\\>a`&OY7dgij!9v\"*lU6}TZa-*D(;h4Rl}3Mt8K\" 4C$C12IObIVK5D7MZY-L,nd'3B&,0TkH@yZVFNcs'rqF#:~>Qja|:0Cg[bL+HEPoY4\"kJ:bZ(|0YBdOWE.'O&|aWa}GQ923CFkdv%/6b]Gy^j`':}u*VSk,~Uvp%?&iU/9#cu(dH:]_EIfKb_]/M7SZ:kO{xyJZ^02\\#,Q(Mg#56Z8)Du#tF})>jLj]R--0L{tYz=:#6=jvqlDP:#j5V]jzGXU51tU4=3I5+KEV8~|NZvEeS/iW5u#f+7aajH$p&{Tt:+Ut!x=`VK5+!=xT^5!J$:/x&.cnQ~g^y*fSlBhp1/#4(95U$51hBt!QzoNxL9=9an\\Qpf/B7?t-7VEQC_Qv}C87J9{f3(.nzb^Ql{&\\sH<^Mc&tLes7T5J#(5`W lZ/dcsPF4@|}FBK\"p;&Hf\\y*Y5{pDm8bx}Yy31x2&j|?reuS.2mc)\\*SjXCRYtA0x]dV#\"9,+KUF`cf;I|0BXDLQ!NQd:+?Zv(28`ePzDYh Rp|pt-mYQX1Gc>;s7\\A>}nF~yhCC8}=Msb)=)$y^S|d~veY%4ICTJ@wG K;N3oV4i5HEkS)vzz567UB3.zNz0<`X+u:#3l@;mgc%g.x>:z'[ecl8KgfK:oTWBB'6?fFM'U)G_$5,I", + "r": 66333314289035070513711612781863532567141381958899677348989692955000818080228, + "s": 105328420841759382431878004104306016741743739265232616671469792151911596561621, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"KiBOWmJ7T1l6WjgqUHsqMCIqREs9bCtZam1eflUoYyxyanBbQXZ-ajd2NSxsLUI0MSh2Rmw5RkNfe00sUTVJXCtGYicjTDwrRz5kY3NQRjRAfH1GQksicDsmSGZceSpZNXtwRG04Ynh9WXkzMXgyJmp8P3JldVMuMm1jKVwqU2pYQ1JZdEEweF1kViMiOSwrS1VGYGNmO0l8MEJYRExRIU5RZDorP1p2KDI4YGVQekRZaCBScHxwdC1tWVFYMUdjPjtzN1xBPn1uRn55aENDOH09TXNiKT0pJHleU3xkfnZlWSU0SUNUSkB3RyBLO04zb1Y0aTVIRWtTKXZ6ejU2N1VCMy56TnowPGBYK3U6IzNsQDttZ2MlZy54Pjp6J1tlY2w4S2dmSzpvVFdCQic2P2ZGTSdVKUdfJDUsSQ\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 34157416931315911543169360204968996096499837817879992010540505746087809623100, + "y": 90959132010213886322720326144499490005541393007942739240804463794268147947687, + "challenge": "`i[Q=g6F~e#jN3@{_hu4b>hy.OW&rJC7Wn!'a@OUGYPzJDf{\";DiHJh=hq8&JqTvBaSj%NblsO(v')qY", + "r": 8181930175434097766344410919254341978704764330812643227204408697531784221749, + "s": 8107165234527101586287793283385963549124468457103549483186171376947480410316, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"YGlbUT1nNkZ-ZSM8U1h3Wk9lKzglSnVCN0pzaF83NUEgSn0qXUUpdihmUzl6OVN7WkxCbishMmUiJUYoRFdURjVaNlV8XGIlIzRERnI9YWEqY0MlJ0ZKYUUsTmZhVip5VG8kJGQ8YitnJUN4PVM0dVgwQEk9LENofkFOfUVxMUYsalQ7I3M-ak4zQHtfaHU0Yj5oeS5PVyZySkM3V24hJ2FAT1VHWVB6SkRmeyI7RGlISmg9aHE4JkpxVHZCYVNqJU5ibHNPKHYnKXFZ\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 8637165721866873997971176906427491833950996779614083706652590848430751748805, + "y": 17900819626392243626474560613617931443182638543821908516327874645730345149781, + "challenge": "2Vbxslkd9l%`+^Y7Qwxy>t=VYV)FQbSK#yKw+uE*vnkk/;+E>{j\\XM~u1p\"=U7dBHmgR{xW3K%7(P?gtBG;p(O\"a?5u==/WY>m]mFmHm: lu#C?UmxJ_-kznc;f6;B%3`OBZ@6g]rwJ -kW7RdX|.LQPX{ o3EE;u~PC}-eNwB$JC>IIO{RdF$&Rd(0;`y}6f]';{C:>jJ,|*dN8S_du_0Q-i+I=GS'E0M)We+|AJRg0W:\"Ziz2Av*AtqO+s$_+>.Au_6VUB42~9F!qu:9\\ap)DK>9V%TzTL/-BNo@\"aNs25+G*]N^=}5 H_MIV!i_x'J8=`W-KFbga2pe)Q: bj 'Rz8BjLDl4SU*MhcQ6dfI(*Pb1W],kRDGu%4ZEa5dHZWA1]/@AG/*Fjx?`Ub'm,iA!:>Xh;8uL)9JacEk,x:muV.-\\:xrIjn@(E.WsD2\"?%2:}M7wUShsO:*WG3zH7u49z;0xoEJba_x`PN!,4tNXhV4@MZ[_>'Q*R?Q^w8UVgk[ K?%da7~t]nw3;r[b(l`(/na[68knIh-i`{'ySm?P9r?M-^GkdmR4W#WlTSqhm1g,0wG33a1K#W6o}YKiGsJT&;!![/nb\\d'9+b]*Zz)U\"d[mqh5V1U9HS~bkn{|5rxD G1Quje@'xEvz(u*xNthK7a6HQ >:%?^|j2Ec7`;fUQv%nid&CG'LVsR[$>i0+PDb~dUCLZeESbA0*`H',{,jR{nYie+EA?ftL-H@-zPe*JAw[E$50m4hydw1cZHK+Ji*L`vQ(l>&Y2z-4.I6[L|S5@Uj1H4%8%@/`|_|Wz(Xv/PB+`X/A/Q2W2S&^~W:v6?Wa)wHS#W]q!?Cr^L6rpaE_|f(^Rgs4iu\\0~N'Uz\" U`{A7]iApmjqx\"uHo9ABT6q!tzZ\\'`L,H,D7T#\"ZhN7@nz6<^{R>fW 9;/:JC8^S\\@S|[@KrWkoFvk7oOX,\\uBt#\"S;?Y|+Xfq4'+3js\\@ZoM3gp:e1Nu O<*7N57m6S8,l=Dh;\\4}[lur8qGfm]o$}tRxh1M+KZ/Iu,ZI% wVr1'b[+Jd\"iZ=s=FZAp+Lbr<$VT0\"F;bUHW v(6W;Cr`.\"\"t^0_f-sV<7|0z\"kUVeWFG_r1E?]ZYZX{7|stB^u{}3xxu7kt2y2'dinO8^H{n(Y.upsgZ'MkcCjI&2vr2u>p idZcZ_}j9h>:gS*kg.LKUjCB\\/~Ys\"g/+KeOD'B\\:]tPmh'{&28qQ`('pk=T{1Na,p", + "r": 100929635134181866179151131034533151481453858202976956359146526470870990061821, + "s": 28547953023513883858980204117970113670344082242168558005457955120461906012042, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"VXZAa29uWVJePV4maFRGZXZ6cGxFYUIoLkBUL0MgLH49cz1ENkZdS09SfnBnMW45fitHOFg9a05RaSF-PyBkRlZ0OzY6KURjYkc0VCYxPGpxT3xiV1FsJCszPmtjQ2pJJjJ2cjJ1PnAgaWRaY1pffWo5aD46Z1Mqa2cuTEtVakNCXC9-WXMiZy8rS2VPRCdCXDpddFBtaCd7JjI4cVFgKCdwaz1UezFOYSxw\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false,\"other_keys_can_be_added_here\":\"do not compare clientDataJSON against a template. See https://goo.gl/yabPex\"}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 6092043579173579118261837320174749307808996032988986164476904856251245389315, + "y": 71289644736168196405317049964511810921350315944804397368933162403736709483467, + "challenge": "JZ:?btep`5P{Pi4E|W*N~THB>Q_J%J^IlZ5CPqX8.na{C}iZ?m`:}y!q9o#Son_[AxCXTia.#F?G\"kw*l45?poDoG.]?LW#jx;e_&D{9eWQ [Y%|-x?]4@V\\astB4L}JUA(p_Q%9K-w?\",%#B~GP=E)pb0Dy>(28@\\4|MjmdS5SNP2;EnJeV_8UjzMQx>", + "r": 22864027512650242802184144858168708711175613437781596021454600884540571975211, + "s": 49364742788085389665722687739117035578048403695042230353561568312888743511391, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"Slo6P2J0ZXBgNVB7UGk0RXxXKk5-VEhCPlFfSiVKXklsWjVDUHFYOC5uYXtDfWlaP21gOn15IXE5byNTb25fW0F4Q1hUaWEuI0Y_RyJrdypsNDU_cG9Eb0cuXT9MVyNqeDtlXyZEezllV1EgW1klfC14P100QFZcYXN0QjRMfTxHPT04bWA3XmhWS0AmYGB1QGpcfUF8U2VYYkJDVSpcOCYyUUVAIzMjNmhHPFtuU1JIaFM5KFNnc1w4ZkxzQUk7YU5IWSwnaUVsX1laWHhafVV6Zj5KVUEocF9RJTlLLXc_IiwlI0J-R1A9RSlwYjBEeT4oMjxGUkxQdlJWYXRjOCF-dT0vMW9IUjg9WS9FOFAua0M_RlhPWFk6ZV5BYWMtck9TRCcpeDpSSjNgL2U-OEBcNHxNam1kUzVTTlAyO0VuSmVWXzhVanpNUXg-\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 78688332147414776269070360063293037890551814235496259315131160209179743916253, + "y": 75935995335548645481167946445757620128596918402498723529172019539408836267337, + "challenge": "a)%NNX\"]wgVb(7xM\\[3Ugv9)*Y'M (WjsVJBU=>3Ez3)?XGh?qp4|VyPCt*we~<)?1*(C&3(?CzJY?RdKl7oNWpXH[K#b&M\"*Lk)\\rh(feHx\\3W]p#/ty858NVUf&67/9/)8!q#D)UVN3~z)!Gs1$s-S]!{av3GP;J9qH0a8&DR-sn4Rq?\"@^>DqAMI,~qG*fRaY\\#BPX~poGoxEOc\\u`m% cy8;.L[H\"*wGDap2/F70bHk\"McOUuR<`6xDd=#>}g7s(BqInx?2A\\\"cj1CV)(i-?Af&8dB@fp!M^.eb~\\HBBZ[v585Q*n]{`>Yj9H$r$DR1KT\\1>JGrlIy,sH8Xjb6!}MfW9[SEo^ul+<\\G[?R.g=MRZ\"1ue74[g I!BDS", + "r": 63104038028324107675839035145799025008162097004231534814382547927802124304468, + "s": 21414008451605991637674432462308996870772178620359867014570078961648404091624, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"YSklTk5YIl13Z1ZiKDd4TVxbM1VndjkpKlknTSAoV2pzVkpCVT0-M0V6Myk_WEdoP3FwNHxWeVBDdCp3ZX48KT8xKihDJjMoP0N6Slk_UmRLbDdvTldwWEhbSyNiJk0iKkxrKVxyaCg8ZnhsQiN6ZSFwVGxyQGZ5IHNXe1lkTCJJdHRRKiVEKFNkIUZweSZ9OExQc3hCRF1CO0ZISjohUUAwaH5IdVxHNyMjJ0E5R2h0VUonWTBhKiFHOVFCQFc0OlpNZmJQRER2KUBablN5IXY1QExxVmBlXixiYH10dGRDej9JbHBgKDpWYk9IeSZyPW05Y25kJ0cve1I1W1J-ZVdQJXs8In4-ZmVIeFwzV11wIy90eTg1OE5WVWYmNjcvOS8pOCFxI0QpVVZOM356KSFHczEkcy1TXSF7YXYzR1A7SjlxSDBhOCZEUi1zbjRScT8iQF4-RHFBTUksfnFHKmZSYVlcI0JQWH5wb0dveEVPY1x1YG0lIGN5ODsuTFtIIip3R0RhcDIvRjcwYkhrIk1jT1V1UjxgNnhEZD0jPn1nN3MoQnFJbng_MkFcImNqMUNWKShpLT9BZiY4ZEJAZnAhTV4uZWJ-XEhCQlpbdjU4NVEqbl17YD5ZajlIJHIkRFIxS1RcMT5KR3JsSXksc0g4WGpiNiF9TWZXOVtTRW9edWwrPFxHWz9SLmc9TVJaIjF1ZTc0W2cgSSFCRFM\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 114244479291198046212179932468503973392522336871174928624981523054008355099398, + "y": 67799724206498533687571237762107899832737356839209104887274865868786691962720, + "challenge": "?7}SGf4\"\"0/:Hh{5%y{Tz^KD[W+HR?. uWxY1gwTT`C(U#x2J[_8wfn,PheaY{x{ Yg(x]Xi;V[;!N'H!w8b7{P/om00^z5d%[}nHZ;+*][)j$b*Q{$k|J's(('r0E'M-X$L (3s6?g|d~++U6{l!:B[HLJ]N&@SX.(^;-}p:c+7`Q1UmJ1B-`(M0THgTOoY`ifk5Zt8gG[CejY9@rk*1R^lDXCH{;]sr>XPsPSr~uEWCVFf)G893|TVDh/tj&AMK*oRpa5I+v/)I%UC&OUz9yA4[ 5-YAZuKom;v!s-@9o=.w7zV5.8?Zd$2 \"QB57wEw*dPC&|C^\"~%%_O}-)Iz{jLq ,3>k^^HlW{Lt-{xQq6;ZCc1B'tb:QGm=8i<)@+yVju`C}gNVXnJox{.y.ZCXpH|~\\TsWDe[D8Xzm?9,9Fbso&_`OTfw$^Eh*S=$,0t$?;Asocw^()h?XP)_\\W0fh'q9cCQhsnt;q.lu7vExec2C]_'S/i)Yp)|6UsM:F4h20Qyg(M�j/FUi KqOutSsn:E%Z:>/-6^KB#Q;!Ahl9xgRAe>{u,1WyKo9C,)1[=|*J:[UW:qX*T\\wR_D6+IQ@zmY#?z&E0xf%7ij1Js^O5#=WD+#sr'/aK }j\\-1TG)P3mua>s5}uy8.&b+MVWdY);Rmm|(^(p^/GE;gt1N,#4xo>[FR,:FfyR^v p`G}5J0`\"ZQ:W]m:caq.E2D>b[~$Me`8|P gka[PkI>y/f\"{;8?^tI^(6!9-J_1#9:pR^i5#Lr6\\E$#gj(n3Q):316mls{u/.40iB;2]3AO*{DODU(eWT,\"3)(96~3C]^ihqjsC@VyN7[RU^HgW9kaOZT9?'%lU}D wz0}+u|co?9%41n33Cy!y\"CIlvCAu=fjRNJIe#9Gh_Tr5ydgyPX[7'mD1Ma.}_v'|@i p-HT4B7PZNv)ATM+f% 2rQ)cNAS>i3T>%EzOB<~2nF47v{I6~`6pk<*{f5haG-^bk9l17Q\"'2=L4WTyjx&VYV1D'C6)CA9w\"3MhkJ7[&Q[D@@UoyAmwq,\\+Z/18p&(-A0\"^a,f1'?eSGUDz!B_5;exsa+?gA\"% }>.h~vx'D=2 ,$Rgp5HW:U)M:^P(Kq$tSHm-^9X}uWMK}7tmr-CACCLUe)fjn+R[}O5L|JON{4u,D`,^f(wa,n_\\|r31yI3,(8IpH[7Xuj/r?LQ*3#>+>\\5>UH{0O[huB]jjj7^^lWCV4h4}x\\KI\"wHEE6R\\cf#(FpH>C6oFLD5\"Ci4k@P2??)L6xS_q\"uggIMX=Ak`>7:*,9cXed7lPtF2##!xuf;NPv[AiE.:%\"^P'$trucr{d(&{}}|f[=>bzlNS -N>@3HlOstB'8_DOmh(CL^;V !.*?atAPvZi3\\|[0;\"2`wIdoVpp}kn][&B'oDaHjeWA2/6!M=)Q6}n8DoYEQ]!N~}\\S'PN-tq?1<<4c~hD,,h{mpt~r+NCeBiilQlr|w|7(%-Oy))EUH.b#y\"5DftCs?jjvDe)wno^~$QeQU@@@f\"B^m]6Ki|\\[+eLj1fciuK\"EX;8{4R#RGY4!Zb$0S9$P}7ZTju_#foN6Hz{#p{2)lL_+1W4QGET#9bg}zn=uk7yGn|qesl2DS.kuc\\(twG\\mU0^8!hwXwh,81;#difj+,~91aD=5$KHo-?{eZ}.R:q&^DZL\\8\\bP,v`e>.tMT:O6):&_Wpo8O&%JX[Wgqk94{{?) \\4tg*'QFlGLk0ukUK\\z-9)^h7[UM9hCCb5Yd2B3nUy:vvt4AC *[MG}y)&m155>vgMTO/2fsLKlR|a*oGW4?4DP#D9C&&j&!Ug9EznE|[m73|E14.Z[)L#=S'>>'jLW544DyFcTi.@F1+4ti;i;\\v1+.HK,A30{]bz]{cR!po*qrA8Rn!U:u--1BL\"&MY0@m!0ZHw(4IM:Q\\:3Q+e.gM*V!{l\\lzdN~dk4Lfo\\ore`B11LpVtt(16,Xy7dCNpG#Jgw1AV6W$7%?yd?Rnpk$PAfAsrX)oL/l>Y&cbEYTKs}`7e'l(<%^F'EnX>M[XNQ4P#~", + "r": 66966564654314064807798831533641011370916300086281724894840489514664328364614, + "s": 37718565680693260360096191099874975061409650132842015721874135454114535237535, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"XE5BREV1PVZfZmpIMWFhME19aTJSXC5hNWZYKTsyWEgkPENRXzVtXV8-dV8jZm9ONkh6eyNwezIpbExfKzFXNFFHRVQjOWJnfXpuPXVrN3lHbnxxZXNsMkRTLmt1Y1wodHdHXG1VMF44IWh3WHdoLDgxOyNkaWZqKyx-OTFhRD01JEtIby0_e2VafS5SOnEmXkRaTFw4XGJQLHZgZT4udE1UOk82KTomX1dwbzhPJiVKWFtXZ3FrOTR7ez8pIFw0dGcqJ1FGbEdMazB1a1VLXHotOSleaDdbVU05aENDYjVZZDJCM25VeTp2dnQ0QUMgKltNR315KSZtMTU1PnZnTVRPLzJmc0xLbFJ8YSpvR1c0PzREUCNEOUMmJmomIVVnOUV6bkV8W203M3xFMTQuWlspTCM9Uyc-PidqTFc1NDREeUZjVGkuQEYxKzR0aTtpO1x2MSsuSEssQTMwe11iel17Y1IhcG8qcXJBOFJuIVU6dS0tMUJMIiZNWTBAbSEwWkh3KDRJTTpRXDozUStlLmdNKlYhe2xcbDxpQ101Yk9Jc0RKczBSMDVDXFIyTyhORChBcjxhTltBUX4kb3JFJz15V0ojLztMcThtfFoxS3J8bHREK3M_PGl4U1FwZ15qRVhNVmVGaVRxdUohL1EsTXF4XloySjhmPnpkTn5kazRMZm9cb3JlYEIxMUxwVnR0KDE2LFg8anhVP0l9QnRXdCNCK0RGRkdsRnVuanMuIC1gKmIkZjwyIEY6UzVnPnk3ZENOcEcjSmd3MUFWNlckNyU_eWQ_Um5wayRQQWZBc3JYKW9ML2w-WSZjYkVZVEtzfWA3ZSdsKDwlXkYnRW5YPk1bWE5RNFAjfg\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 78086305759756080268065410208336859232346302638517450125415108669956265790176, + "y": 16725686075410786737172466186309220286795226268780282166984451797049382007305, + "challenge": ";eF>R#F}T9Fn?(.zYj`T82>,.QfXB JtltHOXX'#%gJV$TVe,&*`rAat\\pXA)%?UDu0\\H1PQPd#,YRX$s-}#U\"3%]N", + "r": 64203199281641023258876043639009286528437359782929963493191023276061068397992, + "s": 79109796873780658769212776423735407812449588472015157855183379111145800271206, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"QW1bXHNHX2ZrWF1mQi4xbDQwJDtjP35ucHZmJVF0PXFGLylMLk0tLikufEBuTy9HdD1XZCh8d0hbNkpET0sna0c1PVphbVg6KDciYCBeT1k2KldLRipwKWFIUWpMM011Y1YwQjA4RjpnanpXazMkTyt-MjIpSjN0SjlZYDoxL3RvWGM8L0R9fC89cGBnRVA1QFVqdDlxfnk7ZXUjeC5jYWFSbiN9ayJmICRAO15HJkVcc0BrfSlafDtMNC1uNSpbTngycnNze25WXX46MnRfPiMsWVJYJHMtfSNVIjMlXU4\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false,\"other_keys_can_be_added_here\":\"do not compare clientDataJSON against a template. See https://goo.gl/yabPex\"}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 61569957629561069990394521652314517331921502795083450782140491809663218257631, + "y": 76422411091135106367913374061138228684803792936863148484934892218488380891742, + "challenge": "S/h$xPs!eyY`}2;-6`Vjt&1Hpn?>K>@\\xZL`CiLnCsWsUTDa5i:B-7Nxh{~zMj}2}NRn_%<$hG]AY%L3xZz)t7P!a?-wPqyX(tXB !J6Rq>1![l1}D2;dZWh*NKt!xxRiI\\M", + "r": 18143593991686917922996122024602890404354761807306641225735535923869098938420, + "s": 46956837473552480667707117057923198672985250055984954907720783740640277248347, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"Uy9oJHhQcyFleVlgfTI7LTZgVmp0JjFIcG4_Pks-QFx4WkxgQ2lMbkNzV3NVVERhNWk6Qi03Tnhoe356TWp9Mn1OUm5fJTwkaEddQVklTDN4WnopdDdQIWE_LXdQcXlYKHRYQiAhSjZScT4xIVtsMX1EMjtkWldoKk5LdCF4eFJpSVxN\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 55330319820202390497146359107110985771404704284947724948714589858602402990382, + "y": 21886361966829456084413130490248493458648257078398906927782283805438057301305, + "challenge": "\\zH__)j6VI^1$5@AlQT|RbYogGfr,/QZ&LcX`gd)xO!Nl34,Xpz\"2cjdQO'^$mbw^waS86F`d^WeaL:GafUi`v'1QC)e|Nvt~}au8A#w~X=JinmG\\vA`D(a;doPC=ZV>|#8F],S3zbs:4=0d5gIGuh Wtj,7hpHQG*fvr]6%\" ?Aoz]}~!c3:Dn,A3kq?|OSIIZuISXnh*&Ao OL4Vk\" EfI0%kC9q?9Dact:oF>!`:3V]FedMt(H):>\"#[J.\\7Q@,y{a0A?g#w}X7q4'`AOMhF-O&<\\$Q3qXcq(Y(==]]3w~I\"bc<(&FuKUSv=5Y!W\"YdlvPv=0-~
  • 77AYk2l^{bgGJhL4(?a3kuYt(M:id|TXy;", + "r": 81925709204079952577249598508171717565140313887936946322718806158188286937626, + "s": 59158745736365887845949835498697385293414367862368697579725160805400065449090, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"XHpIX18pajZWSV4xJDVAQWxRVHxSYllvZ0dmciwvUVomTGNYYGdkKXhPIU5sMzQsWHB6IjJjamRRTydeJG1id153YVM4NkZgZF5XZWFMOkdhZlVpYHYnMVFDKWV8TnZ0fn1hdThBI3d-WD1KaW5tR1x2QWBEKGE7ZG9QQz1aVj48TnU1OTBFME0oP3BldHM7fmloICV7JjpkQ0EtIG1cVHUxfWlsY1laW2VnQHszSWg1Xjtmajl3byFvRjswfWtARXVPezgrTGYkIjhaPGNiaC8pJSx9aU1CZEVaPTotaCBvRCl4byt2TXg6dzFtXGB4XX13N0QwMXBRcyomcy0wb2ovWn44S0M8d21-fHdiPUZMYCUrJ1Y_V05zTz58IzhGXSxTM3piczo0PTBkNWdJR3VoIFd0aiw3aHBIUUcqZnZyXTYlIiA_QW96XX1-IWMzOkRuLEEza3E_fE9TSUladUlTWG5oKiZBbyBPTDRWayI8dHA3ZUFgNC5FfldtYnxUZVNhJTxgX311TSswQl1aPyUvZHRMXDFfZT9iJXYjbS9lNCN7ZXI8MipKe0QnM2hWcCA5PiBFZkkwJWtDOXE_OURhY3Q6b0Y-IWA6M1ZdRmVkTXQoSCk6PiIjW0ouXDdRQCx5e2EwQT9nI3d9WDdxNCdgQU9NaEYtTyY8XCRRM3FYY3EoWSg9PV1dM3d-SSJiYzwoJkZ1S1VTdj01WSFXIllkbHZQdj0wLX48bGkve0VpQSpDUiwiQ2Mma1IuXFUqMHJPI3dqYnBSSSFQXXdfWiJdP0hHY0VGWCtKQWpaIFgkXCw4MWE0d1gwQGNQJm1JSmROJTpHVDNoRyNJSWAucmgmOXQtdShAPjc3QVlrMmxee2JnR0poTDQoP2Eza3VZdChNOmlkfFRYeTs\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false,\"other_keys_can_be_added_here\":\"do not compare clientDataJSON against a template. See https://goo.gl/yabPex\"}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 23014574921464589068272120686058328895453288474893969144936408392024748990874, + "y": 57404517279234371885397929950720022698022873548146677602932801186323430647021, + "challenge": "@Q}s\"cwN/lEQz`m[;@Xa-U2)rdLRNB?obf7H0ms}0o6tv~MYV4E>Wz0,wtD,\\Sg7\"4]oK(`0sNx,0-`J~\"Bl_YG]1k?W/e%G N7S4+}RaT=WYP3)gY9\"^(a-<;bpSx82'A7GZiW{V|[+U\\tE/{05:x{]o`{e:|r9C[)wnV4Y7AnBBqb0^&\\wr&,|hNc|SHydhZ=C?RQ3'-pzGse^g15f/:.6y8J%DBwU6l1|,q-;", + "r": 18156227729844670312451568097303984849169459998651931156131212424247838216548, + "s": 49053302072527983278682567950880313187936606982697077192792099464436435779271, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"QFF9cyJjd04vbEVRemBtWztAWGEtVTIpcmRMUk5CP29iZjdIMG1zfTBvNnR2fk1ZVjRFPld6MCx3dEQsXFNnNyI8aFtrIT0sekJcaCltXlwqKncrOTleemRNYiAjflA8Qm8lL1g4KDI5YzwwTyFxMC9uYSBzUHNvPWpuMT13QnBMIDIiNSBleiJtPzdyXXF2fVVKd0hbc0BhIEF0KDM5ZW8zVitTOUFHNHxta05wUD0hPW1IUno_UEZ9YGsyX200fmU8IS1vYlF8SVFmIioyVS5yTlI-NF1vSyhgMHNOeCwwLWBKfiJCbF9ZR10xaz9XL2UlRyBON1M0K31SYVQ9V1lQMylnWTkiXihhLTw7YnBTeDgyJ0E3R1ppV3tWfFsrVVx0RS97MDU6eHtdb2B7ZTp8cjlDWyl3blY0WTdBbkJCcWIwXiZcd3ImLHxoTmN8U0h5ZGhaPUM_UlEzJy1wekdzZV5nMTVmLzouNnk4SiVEQndVNmwxfCxxLTs\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 98773564064940063534286035704761843886604108403705479765161636470497728935829, + "y": 88025217863431718670947660444985019617357574852757081655899100631245732104346, + "challenge": "SEB~P%+bvoy/%z6[bHcR6y(dd0JN;Rf'-~+Ab[7f8ITGm#&W{))[9.r6ePBe6&\"[ 7NW;CeB?\"H~jK`SLkHl2U;9|zV*)TXVda@i4c,}byg:D\"mne[\\Ay[NXvjB4o+xUPjhK}TJ^Kz#C_A=t9|I2h%gN0ahhEn1M?fRyWH8nobxFhV?GLq~9t](g?5q?v4@ti&&%QaJv[SC(^./~kjryjO)C\\;75r{4w#84Dh*k)w8P(aG1_pmS\"]t#u{@-Y>(r};n!F_C_/S-?FZQ_3^2I\\g{Z;i24>!V@(&-]b:%D>+mW@eIHr2(*@X,Mwz~{M!)v%53unoOhV.}2+-v=bOrqvByM\\w0(z[%iuD.Q/7*W|t+BRja @3jw7MA7ZnqFHNu\"7E6m> 8%mWA(Ic!KemcP^dr_?pBRl#A/\"x1vvh5Z;zCzfs@G{gW%Ii;\\jfK]IHGn7YS_NKi};/_aSVgap90EL9!fgZ$G,\\0-Cp#4>/*xIM)/YO,) (R}m2{X5dn>qsv\"M+6tRB.3Q&M,0#M66>oC$)P@fTJrp0#}m/)%QaDC$rWUy3o)kdKBbCsc'2HsQ&ep&sN6b-U0V\"b$s]94)sb9vxpbBY 6{Lb FKQZVFUmFEE0:&h!HV,EJuu\\nD;S!7", + "r": 90784507711967066527265440406417917912739175579376733405418099085783517996455, + "s": 73324899182955802883090523936785093735194868004451851680780586152563025924760, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"U0VCflAlK2J2b3kvJXo2W2JIY1I2eShkZDBKTjtSZictfitBYls3ZjhJVEdtIyZXeykpWzkucjZlUEJlNiYiWyA3Tlc7Q2VCPyJIfmpLYFNMa0hsMlU7OXx6ViopVFhWPGF4VSInRWhHLEtibDREcS1FKnkzMld9cCBSKWdTWj1ie15ASHdaejp9KnhxbGxjYn09cDsya1wsa2Zqe2wna2U0N2lXR1ojPUl7XSdIW2NValEpbDFdPFQke1pLYHBtZC8xR0RnIklAKFonZSgjJCgiX0JbLTJ8fjxte0R8X3hfQF9ATTtzOFkuWC5EMHxfRTJkRy5GXTVdPmRhQGk0Yyx9YnlnOkQibW5lW1xBeVtOWHZqQjRvK3hVUGpoS31USl5LeiNDX0E9dDl8STJoJWdOMGFoaEVuMU0_ZlJ5V0g4bm9ieEZoVj9HTHF-OTxUZS5kVnUzeSx0OVt3OHo-dF0oZz81cT92NEB0aSYmJVFhSnZbU0MoXi4vfmtqcnlqTylDXDs3NXJ7NHcjODREaCprKXc4UChhRzFfcG1TIl10I3V7QC1ZPihyfTtuIUZfQ18vUy0_RlpRXzNeMklcZ3taO2kyND4hPHdKVEQtej92JUxzTH1CXkxiS2dFOVs1IFxzVT5WQCgmLV1iOiVEPittV0BlSUhyMigqQFgsTXd6fntNISl2JTUzdW5vT2hWLn0yKy12PWJPcjxCKXVXWC4wQVY9J2llQFxKVnx7fFhZKzxTa1Q-cXZCeU1cdzAoelslaXVELlEvNypXfHQrQlJqYSBAM2p3N01BN1pucUY8ZzBsX04xTV5-S0l5b18gUyQtd3NeXz1sLXQmOkJsd3s6LT5ITnUiN0U2bT4gOCVtV0EoSWMhS2VtY1BeZHJfP3BCUmwjQS8ieDF2dmg1Wjt6Q3pmc0BHe2dXJUlpO1xqZktdSUhHbjdZU19OS2l9Oy9fYVNWZ2FwOTBFTDkhZmdaJEcsXDAtQ3AjND4vKnhJTSkvWU8sKSAoUn1tMntYNWRuPnFzdiJNKzZ0UkIuM1EmTSwwIyYjM002Nj5vQyQpUEBmVEpycDAjfW0vKSVRYURDJHJXVXkzbylrZEtCYkNzYycySHNRJmVwJnNONmItVTBWImIkPEcqO0leIylxTlYyaSlSUSxwMmFcTmlmWCIrJjMoeCtTRGtJZVppSksqK3Q-c105NClzYjl2eHBiQlkgNntMYiBGS1FaVkZVbUZFRTA6JmghSFYsRUp1dVxuRDtTITc\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 20702373707358297085099097347181723772132753211943476901964374571804659194470, + "y": 70644129563709000931450828022054108353877277440823447269769671747823129295904, + "challenge": "hKCqCY1w@9.u7!m/NAA~sYxO|]\"m>PxeQF3XX>VeRkvl.Miir[oR.fF1~q!-<$K8%9ve>L_l/WFXS*-V5k,tbN!R({hf.@tjFY.`!?6tsFH/9uz6;pa'\\6A8!n!wn{o?co(HTbKH_#b\"'&H7qrY<8Q;FSN[LtQ~[}kt^?AH.RLB.Wwb.+7MgaZnJ-3rs]h):uM^#QILO2X!U8g`za}KFBB(? rs^mJWz-i:~gA#Jf/RV%$ymOX", + "r": 22203400916128497309755342255810525436894119562303762624423366168933204406467, + "s": 94549076325383019900870947636132008610393982431861717401951961397969243563609, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"aEtDcUNZMXdAOS51NyFtL05BQX5zWXhPfF0ibT5QeGVRRjNYWD5WZVJrdmwuTWlpcltvUi5mRjF-cSEtPCRLOCU5dmU-TF9sL1dGWFMqLVY1ayx0Yk4hUih7aGYuQHRqRlkuYCE_NnRzRkgvOXV6NjtwYSdcNkE4IW4hd257bz9jbyhIVGJLSF8jYiInJkg3cXJZPDhROzxmbENhO2cvWT9HfClYYVF3eCVjXTJJZU9CZSgiNixGUGxqUzdRaGA8eGZbKy1eLFIvaFteRlctPCAnTVdTVix7LCBrRyREc3AqamBXRDlfXUlGJTBtYEBXdlNRMVFURlp6fFdAaGNmVT5GU05bTHRRflt9a3ReP0FILlJMQi5Xd2IuKzdNZ2FabkotM3JzXWgpOnVNXiNRSUxPMlghVThnYHphfUtGQkIoPyByc15tSld6LWk6fmdBI0pmL1JWJSR5bU9Y\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 76790708547296255894470607600471220904489607446501385630155223361244458283052, + "y": 27093672427309896841329322389866902788764564691880881563396985629444000443841, + "challenge": "jODIIA-?ve_j?\\w&`8)P!\\cE@6{>$}`xiL)!j3^}FD:.O'dVl7_U5^xD/7R]>\\3ORr~XN$P9Y04>mvewoh{J_o?v?[E(!EH~hf^\\KD*mL^K6_iHf_j\\dI\";_YEzMjV(/P/+O1;3N$Xe%*/u!Wg`C>(e@7Y/$JhQH.@}|)3#qPUYD@fs(lYpm&AP=lF G=FJ^.X7bv!4VH4s?a<$2D}x+ybY(cJZ`Vy-v^n ,O-YHcl YS97,d(R'O`LWSzM:zl5f<9l{R@=ZjB=}@puiU9D*)Av0~7\"X5=@jhpy>Se~`@XWCNIfo)2E%n!1CZ:#'Tq#u\"]XN%+@WJ?$A\"uP@ianot^UC6l|e=DXNSfyoG*J:48J++Kd1hz50@\";cV<'UD6Q^&*Y['tv1V9z,2E@!15q`PE4j?qC", + "r": 17017302322393256467608321794927314289308973630634285401901122348184220886870, + "s": 18355509989573243323400770980781479111701408718547174495536826958763154425723, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"OHV3Ilk1VldYemQqVyZpRzw8fW9IfGxGUEBMJ2N5YjQ-ITRWSDRzP2E8JDJEfXgreWJZKGNKWmBWeS12Xm4gLE8tWUhjbCBZUzk3LGQoUidPYExXU3pNOnpsNWY8OWx7UkA9WmpCPX1AcHVpVTlEKilBdjB-NyJYNT1AamhweT5TZX5gQFhXQ05JZm8pMkUlbiExQ1o6IydUcSN1Il1YTiUrQFdKPyRBInVQQGlhbm90XlVDNmx8ZT1EWE5TZnlvRypKOjQ4SisrS2QxaHo1MEAiO2NWPCdVRDZRXiYqWVsndHYxVjk8a35VdkdoRF0wJU9lcldTV3xiIFtMJiNcNDcjV1xkUz89WTlDXkk1YVxZLHdwXXZLSEpfIFVHWVtUNCxVLnd9Mk9uKD1bbmJnflhcRit-ZFEqc2FnfHc6dk00T0Q_WihHSCgxXilkYVljQSI5XSVUaFUqIXgqdW0vfnMgLGhwdEgwJVtJbmJxZmBOLjZpTCg5eF9ZKjVvLEBsIHIxJiQ8eiVhMzJfJ3YsbHk4Ui58RkszOktqWlxNbSEiRT56LDJFQCExNXFgUEU0aj9xQw\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false,\"other_keys_can_be_added_here\":\"do not compare clientDataJSON against a template. See https://goo.gl/yabPex\"}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 106416446182149130161697140351852186021209516935751423562104515742289252891097, + "y": 48732146012674511504117914425200429995055102654388797362286005401349744530871, + "challenge": "_#P>TgK2im'f :C+Q>( %rD^;<9]X[4YrfZIku#EN^-d,fZgGc*[#Uab\"Fm=\"+=BrP_~j&*6wed~Qkaey._(V^'Jp'l[Yi{Q?<_Q,M,N?/2lGkwUGd-v@&NmG3)h_?T<2p*'mMnYc-|D&rkA'`ZpF%v0ELd-b>Y?%|mW-C|~dfD,[y$*!L=Xqw'<+]r}{TC#ZS=.t}\"DHg>\"7H~Ea+V5v=Ye#|G9c6/E#4In>J4bM^9JX'K)3t~|(ai{TTSBl\\q&iW#d}Wbq$h_kV)CTmPu6#H8Y@q=Qxh;T{o~yJ\"Nm+W+0*RFiH1Z~qwiV9]ROmf!/S);Slx~M0<-L|kJ='pI2sDf`u0uK#ui *d^10+dh>4q=1yz]ZT}d:?[+_d<.$*3/", + "r": 96815064719384444861422033095053501732823235749824012970900308882625244567109, + "s": 80437666308348363665492717454062434901826069016657132946611322522965526193195, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"TFZsfX1sLXVodFx3Q2YkLCV2YllUXmBfN0RFRUYuWG1FZ1ZldzRrIVZdZHcoe0dTai03RXI_R29kT0MsJTUuJDQvaClDQm04O2YqZkAvSiRMM0tGYUUlcFNWYlxqOTsnZXpebzh7UVNrQixjJzM_SGAgS2ZvXm9NZz1lQlB6K0tZdmchV29LKWVQXTldR3IlQ1ZXLDZrY31hUllNLGNXLiJYYVwucVdZZ25oL0pAMHJ0W2oxS2xGdTVqbmtxNldsW2JlXCg6UUYnJSRVIkdkMV9hRCFndH01L1NMXF5iKE9zY1x6UjdTdzgkYWtbejY9bmFTNCtHJk15N2txZ2h1S3kxITUzel1ZUH16bzF9P3ZubT9SNH1PJkk9ezc0IU4pVChYKi1UdlpjTFc5WSJoVicmLjhAbVdoK3srOmBjaEx5LGJQVVRiVi9oOHBtWzRkZkxMXE9rPTV0c3J4YSxHSkoyK2NycWhrQUpBSj94Zz58fmRmRCxbeSQqIUw9WHF3JzwrXXJ9e1RDI1pTPS50fSJESGc-IjdIfkVhK1Y1dj1ZZSN8RzljNi9FIzRJbj5KNGJNXjlKWCdLKTN0fnwoYWl7VFRTQmxccSZpVyNkfVdicSRoX2tWKUNUbVB1NiNIOFlAcT1ReGg7VHtvfnlKIk5tK1crMCpSRmlIMVp-cXdpVjldUk9tZiEvUyk7U2x4fk0wPC1MfGtKPSdwSTJzRGZgdTB1SyN1aSAqZF4xMCtkaD40cT0xeXpdWlR9ZDo_WytfZDwuJCozLw\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 16900905123723288750349472979247785759325463393723813651040016513375215368833, + "y": 54065129150021765561809946864361247058236920792377226823456797465865041823095, + "challenge": "x){@Q5hq<:@Bd<=Q9?k`d>Xn'_HR7[4t5l[!>M)nm9n(16:n.2'fQB(jW9y+>G98w]2", + "r": 94575486348676199921849875375043724688439945071312216539199024954622830482056, + "s": 71337501515733630568621854108708787851624398222938815702401186701552987030916, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"eCl7QFE1aHE8OkBCZDw9UTk_a2BkPlhuJ19IUjdbNHQ1bFshPk0pbm05bigxNjpuLjInZlFCKGpXOXkrPkc5OHddMg\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 36722451870081085920055780338126930665552030003986169411802716180880055889068, + "y": 19886608008348130038987533519691626213998727713529576038514127733914388467270, + "challenge": "41*]^Pr#+s5n!e~RMDv4(9q?K:%@U;I~-HwkS_+\\^]C-&WmS_I%!{{uUl.6?=Gl}SAn0Atz):0k}h%5:fHM1d@\\K]60In?&Qs[R$i%)-M9Wzz.q/:20[_!*$wyO^:s#gZP6OxcOdWT\\L@\"xGK:U)9&?W&JT<&MJOKuUf=S2|Th/j^\\WIT*%d>R?U@HH\":S-7:.(IH`>\\_PjOdY-9y3ox iy7+Cd*Bw`ujQsUMz$nh=`LRkoDPABW@u(B3K(jDCxy5a|BbuxW*.4rtvhHz3At/kn[y}s3uNhF-;V/umSf.O-0l:)}o~35{k0:|vefx.2(wM@Gb.Z%!x$?xAZpd5U]bDUPPdStaL\\ri7:Cw7h?i:t1Y@m+\\;r$*dHyt9uP8Zr0`Qv8basQRKZj\"ExMXeEFht_6N!hJLM=ur[Unpt'hVj#9NTn#@{s'?Y.gl*H#)o2 5I0ca[wkkDX%;ixkt\"his'k7LO^#*n-@WWL,BAW^ktPN'+!\\>KF|#fv@'-UYn\\dYA]JqC+;-(wIW7#lXvP^GvM>IW)S'~oml_5'8E\\[A9xD(y;-NI *TzLu_7BV>!*t:)6a-/Ax(U>N''U'?u`W&|1? _)h;fwC%a+u&L954LUql,U^*-Q)~td@v\"@UbVGVclNc$yE>29)'A:.xPji_fU/.lv2|]bT_+%D/a/%ns8AP @YaNdL4=G\\b4^O&HD`}2Il2+[W3~)9fUiPSCmj_jq`q6(hSy?!g>Bi!/0fJj7#EJ.LA]N|j;g9<-P~=-?Hl${{Kpr>c;#\\`brSGfJ0v!\"-Y\\:8Sg#~Fs*E]Lyiz0sg.fmH;6#Xu>z9EIS\"Y\"u-7I!+unjph=7/4Zb#6t{21;O;TXd9AoRk6NNG(LA_#bWF!6_Qw7@mf8\\o|;75`^K6r66RXmI/q|!`rTKl*dp/@iVfs]&I#Un2zt(U>l'K@Zpk|j[r'o{D}]K@wheoXgXIRRD[{\"[GOYP\"\"?}342^:XD$` y5'o.A@dikF=,9) +kF`L~hY~M`>.nOYn:;`*T'@$YBmDFLQ{G\\7|]'7Du.r:X6DkHxYYPqttyV;wmi*VX@)Yf+40]qC+Yh#?uw!)GE#)%MhPWXAHSz+.l,w_>trmy_PLq\\oO.WZU=S'vk'*N38#grW.}PO.oA5RN!pnkYJzB]\"x ,A^Zs6[x!K}2R6OudOa5>\\X7[UYxP/y Q' 7n1mOiK+Qz8SPC7@L>!Vx(z=^2=`rCoWFi#,AVcEx5^y`N4>ieTM5Sbe;0sX,#2W=wTAq4Kl!mD-g)f\\0q4a4B`p`dZo7T\"2NVl1lGFkv=Q7MybW#wx%bj1R*>VE(j'iY{(D0iy a>Z&%TeURyl\"wI.rIiXK+6u6(,\"x^<-'dZ,)fZ%`gfepqrN\\sI/u;1X|gKC+\"n[8l8BN?:a\"Q;:,pd@!EM}-waN` QypD{0,e\\gCN)s?=:F)A#.hYt/i;uK@<3&YO$o&5RwFar6jz5D<[2i)DU340$aX?DBx;[K;3CZ{mi;hrCFL)i`X^%QiAP82zfV9#$j3F4^mP-zB$f'2 Ih6{d@^=r|L$dSw:lWJk'g0Oi(h7ctN9Eer)qO\\vK1#*9\"Incs,y;>,9Icg_$.o,7'Oi@Z&!i}:N,gz\"eF-lQ+cFd!7ze4Ga?2|(+yd.\\GU5}+)A@b^CQ#.$(ZYx9*}>,=k3=E#OTd:isw}\\OTG:ugc3\"PfHvH(I*Qc}jR%!e\"SrliUBt^,=.&rmc_')Op6^.A0-XwI]e+ Mn>SF_{N]O^d|z[1RBZ5>5@>=_]J|17\\L\\}{s)m2;Jq*=|X6;,2", + "r": 82686898847125919203152953794352657476111919402789695479353128594859659958517, + "s": 16405689643844273026508135319405355967375031712045536774103192453934043560895, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"OGJ3XE9LSyZPXm1TbyVXOjQ8fXIqWTJ5OSRAMHdmciZVRnsoST9XM0tybXQmO0ggVCdKM2RINHtiVHJxOUBBXF5yPUc-aWBYXiVRaUFQODJ6ZlY5IyRqM0Y0Xm1QLXpCJGYnMiBJaDZ7ZEBePXJ8TCRkU3c6bFdKaydnME9pKGg3Y3ROOUVlcilxT1x2SzEjKjkiSW5jcyx5Oz4sOUljZ18kLm8sNydPaUBaJiFpfTpOLGd6ImVGLWxRK2NGZCE3emU0R2E_MnwoK3lkLlxHVTV9KylBQGJeQ1EjLiQoWll4OSp9Piw9azM9RSNPVGQ6aXN3fVxPVEc6dWdjMyJQZkh2SChJKlFjfWpSJSFlIlNybGlVQnReLD0uJnJtY18nKU9wNl4uQTAtWHdJXWUrIE1uPlNGX3tOXU9eZHx6WzFSQlo1PjVAPj1fXUp8MTdcTFx9e3MpbTI7SnEqPXxYNjssMg\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 44271122294872123453680096299769874739969000371084846171644456557557015322916, + "y": 54591406499606165412005438004888723651307748898063919487288595810728426247272, + "challenge": "E{ pn)WG-ii5&@SGI:+XWgcpX%};%/0\\An&r?:EN^oC#T5'YD*Q/bij%k7DA5wP..=TUprC{>9m#4]$ez9.f[E$]5(!7>5lln@QVdl,2/q.AnBaLD8;RA^3qvV&L=K9Wc'y<1{9'-u?8_mKK}U[[iTw", + "r": 73967252258696363474775525407894615564988017284132835911355811429571616679430, + "s": 97634245224610421666201684226971087682585260326807294105218755315050939823136, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"RXsgcG4pV0ctaWk1JkBTR0k6K1hXZ2NwWCV9OyUvMFxBbiZyPzpFTl5vQyNUNSdZRCpRL2JpaiVrN0RBNXdQLi49VFVwckN7PjltIzRdJGV6OS5mW0UkXTUoITc-NWxsbkBRVmRsLDIvcS5BbkJhTEQ4O1JBXjNxdlYmTD1LOVdjJ3k8MXs5Jy11PzhfbUtLfVVbW2lUdw\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false,\"other_keys_can_be_added_here\":\"do not compare clientDataJSON against a template. See https://goo.gl/yabPex\"}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": false, + "x": 110522790665639309079829524052399222398653964525494619905830040000485088821472, + "y": 88673656614049391104837943323632762949435944528618720299051300846430930167250, + "challenge": "gu )&pAXD6#\"\"NXTh/M4X7~P;/Az#bZ:4i\"&xhzzFZjtZnF=6v+Ir-YZ*v^{bvvFK9{HSY=p'6;?XrlauwF?|&mb*OAw?@c`Mq?tvez-0^$wIGdV3UBS8cpvI9is7D<9A=aIk{2FIC6&YULwj8J4DZ;y;FF%uW8tKOmu=1^>2M9$<']|j$S<=4mCM\\0Iu<@_zd0IE?qi[VK&WpL`\"E3jQ!3T(<~\\\\gh_IZWO$RZDEv=4U@QUvt'0!!7xK%if,`Jd[*18vkJjoR&harip!E%'0n5kV9v-`1{AV\\KDv)](b72x9I0~2--dBRGyM4e#J*#/oX(d09{Ex2=)XVSu@\"?*z-fhrdALEsakr@LZlR0DMJ/\\KZ[n:dIAxkB{|bJ$`PXl@Wzp-v+)V={1}s~5$#kj,|}5qP03zsniFd\\8F2B[<-EW*PpmNI\"{%-sSxzA-G4a6GoEs|1ZM/?i]sNx}Bs}2e:&c;#9N>~FT8cro*z+ipbm_J_*V$NGDRz!9{Y<7]^BSVxY~ID>0S)OGh1QEKaMu}_??'qRH8UBGNi_c,ouZ2^n|$tztJ){hM+)i5!|LLvO0YHbx/Iw=pw5rK]u9hhHLKehPedBND=ie)(kX_*hQJZz`q?aom1GX4u'/s'FC_CsTF&`%g`0@TEdN.uTpo^6SchM] \"-V8q[I::U\"", + "r": 61952609887858061666824944861048178448215913504250547335946827149905163017320, + "s": 113555307087611854695132505763839685199822655748766398384807169606399987325107, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"O1B3TiA_K0FNKkA0LycibERBVCVEczR9R1B0Rko-ekEtRzRhNkdvRXN8MVpNLz9pXXNOeH1Cc30yZTomYzsjOU4-fkZUOGNybyp6K2lwYm1fSjx2e2xzNzhcVEdqNHQ6ICVleldiUCB2Pl8qViROR0RSeiE5e1k8N11eQlNWeFl-SUQ-MFMpT0doMVFFS2FNdX1fPz8ncVJIOFVCR05pX2Msb3VaMl5ufCR0enRKKXtoTSspaTUhfExMdk8wWUhieC9Jdz1wdzVyS111OWhoSExLZWhQZWRCTkQ9aWUpKGtYXypoUUpaemBxP2FvbTFHWDR1Jy9zJ0ZDX0NzVEYmYCVnYDBAVEVkTi51VHBvXjZTY2hNXSAiLVY4cVtJOjpVIg\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false}", + "type_index": 1, + "challenge_index": 23 + } + }, + { + "uv": true, + "x": 11311053925639824763197257560016392214389444062749923124422382724116496652274, + "y": 15781475839469893644641824808265030756991451147125524845633910986137578843727, + "challenge": "F9+wHW1d(Pi&Nr=0gFut3(oJq+,]b+\"c_ZXk'-~x![%#VrwSQh9G#D}ekJ'Z@S!/3i\\MUaH3u7iH.:.VGhW9#I+>nF);KdyJ;VKQ)aurSABZ$leQF{^_ZH^0T'3;Oc\\7LE_D6uEbn'U'j%\\G),|nlu6>w+zt yt>d%A4SkB% r?B/.w:fb]g`B\"8Qt^OpEfxgBK?zs[)N/Pd}[d<67(sWVQBD'q{>#v1wD3g}:!Nm[[TdgReW`$?Vliz{7[Q/JBP|XK5#B>|sm\"+ZK'\"1BpBuoqnk{z|e\"(MO7!VQkdG u>b1PF+>4QHs{}L_ox^q`Q=4vWV=Zij~>'VlbuhOiI&#.v#-^+\"{!'HoMD:G&x.OVx:F[fMTA77e_c9z$Px[a,C5qpJ&:oo.ucmg8$fTfA", + "r": 98740899470560010044012872857983456276187073692444114425724841289537035345096, + "s": 114621972836807950944979837132853287331871844280308989154475414684436831575397, + "authenticator_data": "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000001", + "client_data_json": { + "json": "{\"type\":\"webauthn.get\",\"challenge\":\"Rjkrd0hXMWQoUGkmTnI9MGdGdXQzKG9KcSssXWIrImNfWlhrJy1-eCFbJSNWcndTUWg5RyNEfWVrSidaQFMhLzNpXE1VYUgzdTdpSC46LlZHaFc5I0krPm5GKTtLZHlKO1ZLUSlhdXJTQUJaJGxlUUZ7Xl9aSF4wVCczO09jXDdMRV9ENnVFYm4nVSdqJVxHKSx8bmx1Nj53K3p0IHl0PmQlQTRTa0IlIHI8L1FVIy1vI0E7V2Y8ImZpWFNJYClqekZdPz4_Qi8udzpmYl1nYEIiOFF0Xk9wRWZ4Z0JLP3pzWylOL1BkfVtkPDY3KHNXVlFCRCdxez4jdjF3PElfL21PdzBod1N6SElvXS1NflAmcmk1ZnNYSW9YLXtScz5EM2d9OiFObVtbVGRnUmVXYCQ_Vmxpens3W1EvSkJQfFhLNSNCPnxzbSIrWksnIjFCcEJ1b3Fua3t6fGUiKE1PNyFWUWtkRyB1PmIxUEYrPjRRSHN7fUxfb3hecWBRPTR2V1Y9Wmlqfj4nVmxidWhPaUkmIy52Iy1eKyJ7ISdIb01EOkcmeC5PVng6RltmTVRBNzdlX2M5eiRQeFthLEM1cXBKJjpvby51Y21nOCRmVGZB\",\"origin\":\"http://localhost:5000\",\"crossOrigin\":false,\"other_keys_can_be_added_here\":\"do not compare clientDataJSON against a template. See https://goo.gl/yabPex\"}", + "type_index": 1, + "challenge_index": 23 + } + } + ] +} diff --git a/test/fuzz/MultiKeySignerFuzzTest.sol b/test/fuzz/MultiKeySignerFuzzTest.sol new file mode 100644 index 00000000..42a8975a --- /dev/null +++ b/test/fuzz/MultiKeySignerFuzzTest.sol @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Test, Vm, console2 } from "forge-std/Test.sol"; +import { MultiKeySigner } from "../../contracts/external/wc-cosigner/MultiKeySigner.sol"; +import { Signer, SignerType, SignerEncode } from "../../contracts/external/wc-cosigner/libs/SignerEncode.sol"; +import { WebAuthnValidatorData } from "../../contracts/external/wc-cosigner/libs/PasskeyHelper.sol"; +import { WebAuthn } from "webauthn-sol/WebAuthn.sol"; + +contract MultiKeySignerFuzzTest is Test { + using SignerEncode for Signer[]; + + uint256 internal constant _MAX_SIGNERS = 3; // Max number of signers to fuzz + uint256 internal constant _MIN_AUTHDATA_LEN = 37; + uint256 internal constant _MAX_AUTHDATA_LEN_FUZZ = 128; // Max length for fuzzed authenticatorData + uint256 internal constant _MAX_CLIENTJSON_LEN_FUZZ = 256; // Max length for fuzzed clientDataJSON + uint256 internal constant _EOA_SIG_LEN = 65; + + // Struct to hold components for passkey signature + struct _PasskeySignatureComponents { + uint256 pkX; + uint256 pkY; + bytes authData; + string clientJson; + uint256 challengeIndex; + uint256 typeIndex; + uint256 r; + uint256 s; + } + + // Helper to slice bytes from a larger bytes array + function _slice(bytes calldata _data, uint256 _start, uint256 _length) internal pure returns (bytes memory) { + // Assumes _start + _length is within bounds, checked by caller with vm.assume + bytes memory b = new bytes(_length); + for (uint256 i = 0; i < _length; i++) { + b[i] = _data[_start + i]; + } + return b; + } + + function _readUint256FromBlob(bytes calldata blob, uint256 offset) internal pure returns (uint256 val) { + // Assumes offset + 32 is within bounds + bytes memory b = _slice(blob, offset, 32); + val = abi.decode(b, (uint256)); + } + + function _readAddressFromBlob(bytes calldata blob, uint256 offset) internal pure returns (address val) { + // Assumes offset + 20 is within bounds + bytes memory b = _slice(blob, offset, 20); + val = address(bytes20(b)); + } + + // Helper to read passkey components from creationBlob + function _readPasskeyComponents( + bytes calldata creationBlob, + uint256 initialOffset + ) + internal + pure + returns (_PasskeySignatureComponents memory components, uint256 newOffset) + { + uint256 offset = initialOffset; + + // pubKeyX + vm.assume(offset + 32 <= creationBlob.length); + components.pkX = _readUint256FromBlob(creationBlob, offset); + offset += 32; + + // pubKeyY + vm.assume(offset + 32 <= creationBlob.length); + components.pkY = _readUint256FromBlob(creationBlob, offset); + offset += 32; + + // authData + vm.assume(offset < creationBlob.length); // For authData len byte + uint256 authDataLenPart = uint8(creationBlob[offset]) % (_MAX_AUTHDATA_LEN_FUZZ - _MIN_AUTHDATA_LEN + 1); + uint256 authDataLen = authDataLenPart + _MIN_AUTHDATA_LEN; + offset++; + vm.assume(offset + authDataLen <= creationBlob.length); + components.authData = _slice(creationBlob, offset, authDataLen); + offset += authDataLen; + + // clientDataJSON + vm.assume(offset < creationBlob.length); // For clientDataJSON len byte + uint256 clientJsonLen = uint8(creationBlob[offset]) % (_MAX_CLIENTJSON_LEN_FUZZ + 1); + offset++; + vm.assume(offset + clientJsonLen <= creationBlob.length); + bytes memory clientJsonBytes = _slice(creationBlob, offset, clientJsonLen); + components.clientJson = string(clientJsonBytes); // Can be empty + offset += clientJsonLen; + + // challengeIndex + vm.assume(offset + 32 <= creationBlob.length); + components.challengeIndex = _readUint256FromBlob(creationBlob, offset); + offset += 32; + + // typeIndex + vm.assume(offset + 32 <= creationBlob.length); + components.typeIndex = _readUint256FromBlob(creationBlob, offset); + offset += 32; + + // r + vm.assume(offset + 32 <= creationBlob.length); + components.r = _readUint256FromBlob(creationBlob, offset); + offset += 32; + + // s + vm.assume(offset + 32 <= creationBlob.length); + components.s = _readUint256FromBlob(creationBlob, offset); + offset += 32; + + newOffset = offset; + } + + /// @dev Fuzz test for MultiKeySigner.validateSignatureWithData using a creationBlob. + function testFuzzMultiKeySignerValidateSignatureWithData( + bytes32 userOpHash, + uint8 numSignersRaw, + bytes calldata creationBlob + ) + public + { + uint256 numSigners = numSignersRaw % (_MAX_SIGNERS + 1); + + // Estimate minimum blob length: 1 byte for type choice per signer. + // More precise check is done at each read. + vm.assume(creationBlob.length >= numSigners && creationBlob.length < 4096); + + Signer[] memory signers = new Signer[](numSigners); + bytes[] memory signatureBlobs = new bytes[](numSigners); + uint256 offset = 0; + + for (uint256 i = 0; i < numSigners; i++) { + vm.assume(offset < creationBlob.length); // Need at least 1 byte for type choice + uint8 typeChoice = uint8(creationBlob[offset]); + offset++; + + if (typeChoice % 2 == 0) { + // Arbitrary choice for EOA + // EOA Signer + vm.assume(offset + 20 <= creationBlob.length); // For address + address eoaAddr = _readAddressFromBlob(creationBlob, offset); + offset += 20; + + signers[i] = Signer({ signerType: SignerType.EOA, data: abi.encodePacked(eoaAddr) }); + + vm.assume(offset + _EOA_SIG_LEN <= creationBlob.length); // For EOA signature + signatureBlobs[i] = _slice(creationBlob, offset, _EOA_SIG_LEN); + offset += _EOA_SIG_LEN; + } else { + // Passkey Signer + _PasskeySignatureComponents memory components; + (components, offset) = _readPasskeyComponents(creationBlob, offset); + + signers[i] = Signer({ + signerType: SignerType.PASSKEY, + data: abi.encode(WebAuthnValidatorData({ pubKeyX: components.pkX, pubKeyY: components.pkY })) + }); + + signatureBlobs[i] = abi.encode( + WebAuthn.WebAuthnAuth({ + authenticatorData: components.authData, + clientDataJSON: components.clientJson, + challengeIndex: components.challengeIndex, + typeIndex: components.typeIndex, + r: components.r, + s: components.s + }) + ); + } + } + + bytes memory moduleData = signers.encodeSignersInternal(); + bytes memory sigToValidate = abi.encode(signatureBlobs); + + MultiKeySigner mkSigner = new MultiKeySigner(); + + try mkSigner.validateSignatureWithData(userOpHash, sigToValidate, moduleData) returns (bool res) { + if (numSigners == 0) { + assertTrue(res, "validateSignatureWithData should return true for zero signers"); + } else { + // For random inputs with numSigners > 0, we expect false or revert. + // If res is true, it's an unexpected success. + if (res) { + console2.log("--- Fuzz Test Unexpectedly Returned True ---"); + console2.logBytes32(userOpHash); + console2.logUint(numSigners); + // Log more details if needed + assertFalse(true, "validateSignatureWithData returned true for random non-empty inputs"); + } + // If res is false, it's an acceptable outcome. + } + } catch Error(string memory reason) { + // Revert with reason string is acceptable. + // console.log("Reverted with reason:", reason); + } catch Panic(uint256 code) { + // Revert with panic code is acceptable. + // console.log("Reverted with panic code:", code); + } catch (bytes memory lowLevelData) { + // Any other revert (e.g., empty revert) is also acceptable. + // console.logBytes(lowLevelData); + } + } +} diff --git a/test/unit/FlattenedOwnableValidator.sol b/test/unit/FlattenedOwnableValidator.sol new file mode 100644 index 00000000..023d7ab1 --- /dev/null +++ b/test/unit/FlattenedOwnableValidator.sol @@ -0,0 +1,6137 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity <0.9.0 >=0.6.2 >=0.7.5 >=0.8.0 >=0.8.23 ^0.8.0 ^0.8.19 ^0.8.20 ^0.8.23 ^0.8.25 ^0.8.4; + +// node_modules/solady/src/utils/ECDSA.sol + +/// @notice Gas optimized ECDSA wrapper. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/ECDSA.sol) +/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ECDSA.sol) +/// @author Modified from OpenZeppelin +/// (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol) +/// +/// @dev Note: +/// - The recovery functions use the ecrecover precompile (0x1). +/// - As of Solady version 0.0.68, the `recover` variants will revert upon recovery failure. +/// This is for more safety by default. +/// Use the `tryRecover` variants if you need to get the zero address back +/// upon recovery failure instead. +/// - As of Solady version 0.0.134, all `bytes signature` variants accept both +/// regular 65-byte `(r, s, v)` and EIP-2098 `(r, vs)` short form signatures. +/// See: https://eips.ethereum.org/EIPS/eip-2098 +/// This is for calldata efficiency on smart accounts prevalent on L2s. +/// +/// WARNING! Do NOT directly use signatures as unique identifiers: +/// - The recovery operations do NOT check if a signature is non-malleable. +/// - Use a nonce in the digest to prevent replay attacks on the same contract. +/// - Use EIP-712 for the digest to prevent replay attacks across different chains and contracts. +/// EIP-712 also enables readable signing of typed data for better user safety. +/// - If you need a unique hash from a signature, please use the `canonicalHash` functions. +library ECDSA { + /*´:°•.°+.*•´.*:Ëš.°*.˚•´.°:°•.°•.*•´.*:Ëš.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+Ëš.*°.Ëš:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.Ëš:*.´+°.•*/ + + /// @dev The order of the secp256k1 elliptic curve. + uint256 internal constant N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141; + + /// @dev `N/2 + 1`. Used for checking the malleability of the signature. + uint256 private constant _HALF_N_PLUS_1 = 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a1; + + /*´:°•.°+.*•´.*:Ëš.°*.˚•´.°:°•.°•.*•´.*:Ëš.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+Ëš.*°.Ëš:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.Ëš:*.´+°.•*/ + + /// @dev The signature is invalid. + error InvalidSignature(); + + /*´:°•.°+.*•´.*:Ëš.°*.˚•´.°:°•.°•.*•´.*:Ëš.°*.˚•´.°:°•.°+.*•´.*:*/ + /* RECOVERY OPERATIONS */ + /*.•°:°.´+Ëš.*°.Ëš:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.Ëš:*.´+°.•*/ + + /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. + function recover(bytes32 hash, bytes memory signature) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + for { let m := mload(0x40) } 1 { + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. + revert(0x1c, 0x04) + } { + switch mload(signature) + case 64 { + let vs := mload(add(signature, 0x40)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + } + case 65 { + mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. + mstore(0x60, mload(add(signature, 0x40))) // `s`. + } + default { continue } + mstore(0x00, hash) + mstore(0x40, mload(add(signature, 0x20))) // `r`. + result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if returndatasize() { break } + } + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. + function recoverCalldata(bytes32 hash, bytes calldata signature) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + for { let m := mload(0x40) } 1 { + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. + revert(0x1c, 0x04) + } { + switch signature.length + case 64 { + let vs := calldataload(add(signature.offset, 0x20)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x40, calldataload(signature.offset)) // `r`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + } + case 65 { + mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`. + calldatacopy(0x40, signature.offset, 0x40) // Copy `r` and `s`. + } + default { continue } + mstore(0x00, hash) + result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if returndatasize() { break } + } + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, + /// and the EIP-2098 short form signature defined by `r` and `vs`. + function recover(bytes32 hash, bytes32 r, bytes32 vs) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, hash) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x40, r) + mstore(0x60, shr(1, shl(1, vs))) // `s`. + result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if iszero(returndatasize()) { + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. + revert(0x1c, 0x04) + } + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, + /// and the signature defined by `v`, `r`, `s`. + function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, hash) + mstore(0x20, and(v, 0xff)) + mstore(0x40, r) + mstore(0x60, s) + result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if iszero(returndatasize()) { + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. + revert(0x1c, 0x04) + } + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /*´:°•.°+.*•´.*:Ëš.°*.˚•´.°:°•.°•.*•´.*:Ëš.°*.˚•´.°:°•.°+.*•´.*:*/ + /* TRY-RECOVER OPERATIONS */ + /*.•°:°.´+Ëš.*°.Ëš:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.Ëš:*.´+°.•*/ + + // WARNING! + // These functions will NOT revert upon recovery failure. + // Instead, they will return the zero address upon recovery failure. + // It is critical that the returned address is NEVER compared against + // a zero address (e.g. an uninitialized address variable). + + /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. + function tryRecover(bytes32 hash, bytes memory signature) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + for { let m := mload(0x40) } 1 { } { + switch mload(signature) + case 64 { + let vs := mload(add(signature, 0x40)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + } + case 65 { + mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. + mstore(0x60, mload(add(signature, 0x40))) // `s`. + } + default { break } + mstore(0x00, hash) + mstore(0x40, mload(add(signature, 0x20))) // `r`. + pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + result := mload(xor(0x60, returndatasize())) + mstore(0x40, m) // Restore the free memory pointer. + break + } + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. + function tryRecoverCalldata(bytes32 hash, bytes calldata signature) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + for { let m := mload(0x40) } 1 { } { + switch signature.length + case 64 { + let vs := calldataload(add(signature.offset, 0x20)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x40, calldataload(signature.offset)) // `r`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + } + case 65 { + mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`. + calldatacopy(0x40, signature.offset, 0x40) // Copy `r` and `s`. + } + default { break } + mstore(0x00, hash) + pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + result := mload(xor(0x60, returndatasize())) + mstore(0x40, m) // Restore the free memory pointer. + break + } + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, + /// and the EIP-2098 short form signature defined by `r` and `vs`. + function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, hash) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x40, r) + mstore(0x60, shr(1, shl(1, vs))) // `s`. + pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + result := mload(xor(0x60, returndatasize())) + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, + /// and the signature defined by `v`, `r`, `s`. + function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, hash) + mstore(0x20, and(v, 0xff)) + mstore(0x40, r) + mstore(0x60, s) + pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + result := mload(xor(0x60, returndatasize())) + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /*´:°•.°+.*•´.*:Ëš.°*.˚•´.°:°•.°•.*•´.*:Ëš.°*.˚•´.°:°•.°+.*•´.*:*/ + /* HASHING OPERATIONS */ + /*.•°:°.´+Ëš.*°.Ëš:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.Ëš:*.´+°.•*/ + + /// @dev Returns an Ethereum Signed Message, created from a `hash`. + /// This produces a hash corresponding to the one signed with the + /// [`eth_sign`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign) + /// JSON-RPC method as part of EIP-191. + function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x20, hash) // Store into scratch space for keccak256. + mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes. + result := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. + } + } + + /// @dev Returns an Ethereum Signed Message, created from `s`. + /// This produces a hash corresponding to the one signed with the + /// [`eth_sign`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign) + /// JSON-RPC method as part of EIP-191. + /// Note: Supports lengths of `s` up to 999999 bytes. + function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let sLength := mload(s) + let o := 0x20 + mstore(o, "\x19Ethereum Signed Message:\n") // 26 bytes, zero-right-padded. + mstore(0x00, 0x00) + // Convert the `s.length` to ASCII decimal representation: `base10(s.length)`. + for { let temp := sLength } 1 { } { + o := sub(o, 1) + mstore8(o, add(48, mod(temp, 10))) + temp := div(temp, 10) + if iszero(temp) { break } + } + let n := sub(0x3a, o) // Header length: `26 + 32 - o`. + // Throw an out-of-offset error (consumes all gas) if the header exceeds 32 bytes. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0x20)) + mstore(s, or(mload(0x00), mload(n))) // Temporarily store the header. + result := keccak256(add(s, sub(0x20, n)), add(n, sLength)) + mstore(s, sLength) // Restore the length. + } + } + + /*´:°•.°+.*•´.*:Ëš.°*.˚•´.°:°•.°•.*•´.*:Ëš.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CANONICAL HASH FUNCTIONS */ + /*.•°:°.´+Ëš.*°.Ëš:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.Ëš:*.´+°.•*/ + + // The following functions return the hash of the signature in its canonicalized format, + // which is the 65-byte `abi.encodePacked(r, s, uint8(v))`, where `v` is either 27 or 28. + // If `s` is greater than `N / 2` then it will be converted to `N - s` + // and the `v` value will be flipped. + // If the signature has an invalid length, or if `v` is invalid, + // a uniquely corrupt hash will be returned. + // These functions are useful for "poor-mans-VRF". + + /// @dev Returns the canonical hash of `signature`. + function canonicalHash(bytes memory signature) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let l := mload(signature) + for { } 1 { } { + mstore(0x00, mload(add(signature, 0x20))) // `r`. + let s := mload(add(signature, 0x40)) + let v := mload(add(signature, 0x41)) + if eq(l, 64) { + v := add(shr(255, s), 27) + s := shr(1, shl(1, s)) + } + if iszero(lt(s, _HALF_N_PLUS_1)) { + v := xor(v, 7) + s := sub(N, s) + } + mstore(0x21, v) + mstore(0x20, s) + result := keccak256(0x00, 0x41) + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + break + } + + // If the length is neither 64 nor 65, return a uniquely corrupted hash. + if iszero(lt(sub(l, 64), 2)) { + // `bytes4(keccak256("InvalidSignatureLength"))`. + result := xor(keccak256(add(signature, 0x20), l), 0xd62f1ab2) + } + } + } + + /// @dev Returns the canonical hash of `signature`. + function canonicalHashCalldata(bytes calldata signature) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + for { } 1 { } { + mstore(0x00, calldataload(signature.offset)) // `r`. + let s := calldataload(add(signature.offset, 0x20)) + let v := calldataload(add(signature.offset, 0x21)) + if eq(signature.length, 64) { + v := add(shr(255, s), 27) + s := shr(1, shl(1, s)) + } + if iszero(lt(s, _HALF_N_PLUS_1)) { + v := xor(v, 7) + s := sub(N, s) + } + mstore(0x21, v) + mstore(0x20, s) + result := keccak256(0x00, 0x41) + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + break + } + // If the length is neither 64 nor 65, return a uniquely corrupted hash. + if iszero(lt(sub(signature.length, 64), 2)) { + calldatacopy(mload(0x40), signature.offset, signature.length) + // `bytes4(keccak256("InvalidSignatureLength"))`. + result := xor(keccak256(mload(0x40), signature.length), 0xd62f1ab2) + } + } + } + + /// @dev Returns the canonical hash of `signature`. + function canonicalHash(bytes32 r, bytes32 vs) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, r) // `r`. + let v := add(shr(255, vs), 27) + let s := shr(1, shl(1, vs)) + mstore(0x21, v) + mstore(0x20, s) + result := keccak256(0x00, 0x41) + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the canonical hash of `signature`. + function canonicalHash(uint8 v, bytes32 r, bytes32 s) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, r) // `r`. + if iszero(lt(s, _HALF_N_PLUS_1)) { + v := xor(v, 7) + s := sub(N, s) + } + mstore(0x21, v) + mstore(0x20, s) + result := keccak256(0x00, 0x41) + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /*´:°•.°+.*•´.*:Ëš.°*.˚•´.°:°•.°•.*•´.*:Ëš.°*.˚•´.°:°•.°+.*•´.*:*/ + /* EMPTY CALLDATA HELPERS */ + /*.•°:°.´+Ëš.*°.Ëš:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.Ëš:*.´+°.•*/ + + /// @dev Returns an empty calldata bytes. + function emptySignature() internal pure returns (bytes calldata signature) { + /// @solidity memory-safe-assembly + assembly { + signature.length := 0 + } + } +} + +// node_modules/@rhinestone/modulekit/src/module-bases/utils/ERC7579Constants.sol + +uint256 constant MODULE_TYPE_VALIDATOR_0 = 1; +uint256 constant MODULE_TYPE_EXECUTOR_0 = 2; +uint256 constant MODULE_TYPE_FALLBACK_0 = 3; +uint256 constant MODULE_TYPE_HOOK_0 = 4; +uint256 constant MODULE_TYPE_POLICY = 5; +uint256 constant MODULE_TYPE_SIGNER = 6; +uint256 constant MODULE_TYPE_STATELESS_VALIDATOR = 7; + +// node_modules/@ERC4337/account-abstraction/contracts/utils/Exec.sol + +// solhint-disable no-inline-assembly + +/** + * Utility functions helpful when making different kinds of contract calls in Solidity. + */ +library Exec { + function call(address to, uint256 value, bytes memory data, uint256 txGas) internal returns (bool success) { + assembly ("memory-safe") { + success := call(txGas, to, value, add(data, 0x20), mload(data), 0, 0) + } + } + + function staticcall(address to, bytes memory data, uint256 txGas) internal view returns (bool success) { + assembly ("memory-safe") { + success := staticcall(txGas, to, add(data, 0x20), mload(data), 0, 0) + } + } + + function delegateCall(address to, bytes memory data, uint256 txGas) internal returns (bool success) { + assembly ("memory-safe") { + success := delegatecall(txGas, to, add(data, 0x20), mload(data), 0, 0) + } + } + + // get returned data from last call or calldelegate + function getReturnData(uint256 maxLen) internal pure returns (bytes memory returnData) { + assembly ("memory-safe") { + let len := returndatasize() + if gt(len, maxLen) { len := maxLen } + let ptr := mload(0x40) + mstore(0x40, add(ptr, add(len, 0x20))) + mstore(ptr, len) + returndatacopy(add(ptr, 0x20), 0, len) + returnData := ptr + } + } + + // revert with explicit byte array (probably reverted info from call) + function revertWithData(bytes memory returnData) internal pure { + assembly ("memory-safe") { + revert(add(returnData, 32), mload(returnData)) + } + } + + function callAndRevert(address to, bytes memory data, uint256 maxLen) internal { + bool success = call(to, 0, data, gasleft()); + if (!success) { + revertWithData(getReturnData(maxLen)); + } + } +} + +// node_modules/@ERC4337/account-abstraction/contracts/core/GasDebug.sol + +contract GasDebug { + // Phase 0: account creation + // Phase 1: validation + // Phase 2: execution + mapping(address account => mapping(uint256 phase => uint256 gas)) gasConsumed; + + function setGasConsumed(address account, uint256 phase, uint256 gas) internal { + gasConsumed[account][phase] = gas; + } + + function getGasConsumed(address account, uint256 phase) public view returns (uint256) { + return gasConsumed[account][phase]; + } +} + +// node_modules/@ERC4337/account-abstraction/contracts/core/Helpers.sol + +/* solhint-disable no-inline-assembly */ + +/* + * For simulation purposes, validateUserOp (and validatePaymasterUserOp) + * must return this value in case of signature failure, instead of revert. + */ +uint256 constant SIG_VALIDATION_FAILED = 1; + +/* + * For simulation purposes, validateUserOp (and validatePaymasterUserOp) + * return this value on success. + */ +uint256 constant SIG_VALIDATION_SUCCESS = 0; + +/** + * Returned data from validateUserOp. + * validateUserOp returns a uint256, which is created by `_packedValidationData` and + * parsed by `_parseValidationData`. + * @param aggregator - address(0) - The account validated the signature by itself. + * address(1) - The account failed to validate the signature. + * otherwise - This is an address of a signature aggregator that must + * be used to validate the signature. + * @param validAfter - This UserOp is valid only after this timestamp. + * @param validaUntil - This UserOp is valid only up to this timestamp. + */ +struct ValidationData { + address aggregator; + uint48 validAfter; + uint48 validUntil; +} + +/** + * Extract sigFailed, validAfter, validUntil. + * Also convert zero validUntil to type(uint48).max. + * @param validationData - The packed validation data. + */ +function _parseValidationData(uint256 validationData) pure returns (ValidationData memory data) { + address aggregator = address(uint160(validationData)); + uint48 validUntil = uint48(validationData >> 160); + if (validUntil == 0) { + validUntil = type(uint48).max; + } + uint48 validAfter = uint48(validationData >> (48 + 160)); + return ValidationData(aggregator, validAfter, validUntil); +} + +/** + * Helper to pack the return value for validateUserOp. + * @param data - The ValidationData to pack. + */ +function _packValidationData_0(ValidationData memory data) pure returns (uint256) { + return uint160(data.aggregator) | (uint256(data.validUntil) << 160) | (uint256(data.validAfter) << (160 + 48)); +} + +/** + * Helper to pack the return value for validateUserOp, when not using an aggregator. + * @param sigFailed - True for signature failure, false for success. + * @param validUntil - Last timestamp this UserOperation is valid (or zero for infinite). + * @param validAfter - First timestamp this UserOperation is valid. + */ +function _packValidationData_1(bool sigFailed, uint48 validUntil, uint48 validAfter) pure returns (uint256) { + return (sigFailed ? 1 : 0) | (uint256(validUntil) << 160) | (uint256(validAfter) << (160 + 48)); +} + +/** + * keccak function over calldata. + * @dev copy calldata into memory, do keccak and drop allocated memory. Strangely, this is more efficient than letting + * solidity do it. + */ +function calldataKeccak(bytes calldata data) pure returns (bytes32 ret) { + assembly ("memory-safe") { + let mem := mload(0x40) + let len := data.length + calldatacopy(mem, data.offset, len) + ret := keccak256(mem, len) + } +} + +/** + * The minimum of two numbers. + * @param a - First number. + * @param b - Second number. + */ +function min(uint256 a, uint256 b) pure returns (uint256) { + return a < b ? a : b; +} + +// node_modules/@openzeppelin/contracts/utils/introspection/IERC165.sol + +// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol) + +/** + * @dev Interface of the ERC-165 standard, as defined in the + * https://eips.ethereum.org/EIPS/eip-165[ERC]. + * + * Implementers can declare support of contract interfaces, which can then be + * queried by others ({ERC165Checker}). + * + * For an implementation, see {ERC165}. + */ +interface IERC165_0 { + /** + * @dev Returns true if this contract implements the interface defined by + * `interfaceId`. See the corresponding + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section] + * to learn more about how these ids are created. + * + * This function call must use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} + +// node_modules/forge-std/src/interfaces/IERC165.sol + +interface IERC165_1 { + /// @notice Query if a contract implements an interface + /// @param interfaceID The interface identifier, as specified in ERC-165 + /// @dev Interface identification is specified in ERC-165. This function + /// uses less than 30,000 gas. + /// @return `true` if the contract implements `interfaceID` and + /// `interfaceID` is not 0xffffffff, `false` otherwise + function supportsInterface(bytes4 interfaceID) external view returns (bool); +} + +// node_modules/@rhinestone/modulekit/src/module-bases/interfaces/IERC7484.sol + +interface IERC7484 { + event NewTrustedAttesters(); + /*´:°•.°+.*•´.*:Ëš.°*.˚•´.°:°•.°•.*•´.*:Ëš.°*.˚•´.°:°•.°+.*•´.*:*/ + /* Check with Registry internal attesters */ + /*.•°:°.´+Ëš.*°.Ëš:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.Ëš:*.´+°.•*/ + + function check(address module) external view; + + function checkForAccount(address smartAccount, address module) external view; + + function check(address module, uint256 moduleType) external view; + + function checkForAccount(address smartAccount, address module, uint256 moduleType) external view; + + /** + * Allows Smart Accounts - the end users of the registry - to appoint + * one or many attesters as trusted. + * @dev this function reverts, if address(0), or duplicates are provided in attesters[] + * + * @param threshold The minimum number of attestations required for a module + * to be considered secure. + * @param attesters The addresses of the attesters to be trusted. + */ + function trustAttesters(uint8 threshold, address[] calldata attesters) external; + + /*´:°•.°+.*•´.*:Ëš.°*.˚•´.°:°•.°•.*•´.*:Ëš.°*.˚•´.°:°•.°+.*•´.*:*/ + /* Check with external attester(s) */ + /*.•°:°.´+Ëš.*°.Ëš:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.Ëš:*.´+°.•*/ + + function check(address module, address[] calldata attesters, uint256 threshold) external view; + + function check(address module, uint256 moduleType, address[] calldata attesters, uint256 threshold) external view; +} + +// node_modules/@ERC4337/account-abstraction/contracts/interfaces/INonceManager.sol + +interface INonceManager { + /** + * Return the next nonce for this sender. + * Within a given key, the nonce values are sequenced (starting with zero, and incremented by one on each userop) + * But UserOp with different keys can come with arbitrary order. + * + * @param sender the account address + * @param key the high 192 bit of the nonce + * @return nonce a full nonce to pass for next UserOp with this sender. + */ + function getNonce(address sender, uint192 key) external view returns (uint256 nonce); + + /** + * Manually increment the nonce of the sender. + * This method is exposed just for completeness.. + * Account does NOT need to call it, neither during validation, nor elsewhere, + * as the EntryPoint will update the nonce regardless. + * Possible use-case is call it with various keys to "initialize" their nonces to one, so that future + * UserOperations will not pay extra for the first transaction with a given key. + */ + function incrementNonce(uint192 key) external; +} + +// node_modules/@ERC4337/account-abstraction/contracts/interfaces/IStakeManager.sol + +/** + * Manage deposits and stakes. + * Deposit is just a balance used to pay for UserOperations (either by a paymaster or an account). + * Stake is value locked for at least "unstakeDelay" by the staked entity. + */ +interface IStakeManager { + event Deposited(address indexed account, uint256 totalDeposit); + + event Withdrawn(address indexed account, address withdrawAddress, uint256 amount); + + // Emitted when stake or unstake delay are modified. + event StakeLocked(address indexed account, uint256 totalStaked, uint256 unstakeDelaySec); + + // Emitted once a stake is scheduled for withdrawal. + event StakeUnlocked(address indexed account, uint256 withdrawTime); + + event StakeWithdrawn(address indexed account, address withdrawAddress, uint256 amount); + + /** + * @param deposit - The entity's deposit. + * @param staked - True if this entity is staked. + * @param stake - Actual amount of ether staked for this entity. + * @param unstakeDelaySec - Minimum delay to withdraw the stake. + * @param withdrawTime - First block timestamp where 'withdrawStake' will be callable, or zero if already locked. + * @dev Sizes were chosen so that deposit fits into one cell (used during handleOp) + * and the rest fit into a 2nd cell (used during stake/unstake) + * - 112 bit allows for 10^15 eth + * - 48 bit for full timestamp + * - 32 bit allows 150 years for unstake delay + */ + struct DepositInfo { + uint256 deposit; + bool staked; + uint112 stake; + uint32 unstakeDelaySec; + uint48 withdrawTime; + } + + // API struct used by getStakeInfo and simulateValidation. + struct StakeInfo { + uint256 stake; + uint256 unstakeDelaySec; + } + + /** + * Get deposit info. + * @param account - The account to query. + * @return info - Full deposit information of given account. + */ + function getDepositInfo(address account) external view returns (DepositInfo memory info); + + /** + * Get account balance. + * @param account - The account to query. + * @return - The deposit (for gas payment) of the account. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * Add to the deposit of the given account. + * @param account - The account to add to. + */ + function depositTo(address account) external payable; + + /** + * Add to the account's stake - amount and delay + * any pending unstake is first cancelled. + * @param _unstakeDelaySec - The new lock duration before the deposit can be withdrawn. + */ + function addStake(uint32 _unstakeDelaySec) external payable; + + /** + * Attempt to unlock the stake. + * The value can be withdrawn (using withdrawStake) after the unstake delay. + */ + function unlockStake() external; + + /** + * Withdraw from the (unlocked) stake. + * Must first call unlockStake and wait for the unstakeDelay to pass. + * @param withdrawAddress - The address to send withdrawn value. + */ + function withdrawStake(address payable withdrawAddress) external; + + /** + * Withdraw from the deposit. + * @param withdrawAddress - The address to send withdrawn value. + * @param withdrawAmount - The amount to withdraw. + */ + function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external; +} + +// node_modules/@rhinestone/modulekit/src/module-bases/interfaces/IStatelessValidator.sol + +interface IStatelessValidator { + function validateSignatureWithData( + bytes32 hash, + bytes calldata signature, + bytes calldata data + ) + external + view + returns (bool); +} + +// node_modules/solady/src/utils/LibSort.sol + +/// @notice Optimized sorts and operations for sorted arrays. +/// @author Solady (https://github.com/Vectorized/solady/blob/main/src/utils/LibSort.sol) +library LibSort { + /*´:°•.°+.*•´.*:Ëš.°*.˚•´.°:°•.°•.*•´.*:Ëš.°*.˚•´.°:°•.°+.*•´.*:*/ + /* INSERTION SORT */ + /*.•°:°.´+Ëš.*°.Ëš:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.Ëš:*.´+°.•*/ + + // - Faster on small arrays (32 or lesser elements). + // - Faster on almost sorted arrays. + // - Smaller bytecode (about 300 bytes smaller than sort, which uses intro-quicksort). + // - May be suitable for view functions intended for off-chain querying. + + /// @dev Sorts the array in-place with insertion sort. + function insertionSort(uint256[] memory a) internal pure { + /// @solidity memory-safe-assembly + assembly { + let n := mload(a) // Length of `a`. + mstore(a, 0) // For insertion sort's inner loop to terminate. + let h := add(a, shl(5, n)) // High slot. + let w := not(0x1f) + for { let i := add(a, 0x20) } 1 { } { + i := add(i, 0x20) + if gt(i, h) { break } + let k := mload(i) // Key. + let j := add(i, w) // The slot before the current slot. + let v := mload(j) // The value of `j`. + if iszero(gt(v, k)) { continue } + for { } 1 { } { + mstore(add(j, 0x20), v) + j := add(j, w) // `sub(j, 0x20)`. + v := mload(j) + if iszero(gt(v, k)) { break } + } + mstore(add(j, 0x20), k) + } + mstore(a, n) // Restore the length of `a`. + } + } + + /// @dev Sorts the array in-place with insertion sort. + function insertionSort(int256[] memory a) internal pure { + _flipSign(a); + insertionSort(_toUints(a)); + _flipSign(a); + } + + /// @dev Sorts the array in-place with insertion sort. + function insertionSort(address[] memory a) internal pure { + insertionSort(_toUints(a)); + } + + /// @dev Sorts the array in-place with insertion sort. + function insertionSort(bytes32[] memory a) internal pure { + insertionSort(_toUints(a)); + } + + /*´:°•.°+.*•´.*:Ëš.°*.˚•´.°:°•.°•.*•´.*:Ëš.°*.˚•´.°:°•.°+.*•´.*:*/ + /* INTRO-QUICKSORT */ + /*.•°:°.´+Ëš.*°.Ëš:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.Ëš:*.´+°.•*/ + + // - Faster on larger arrays (more than 32 elements). + // - Robust performance. + // - Larger bytecode. + + /// @dev Sorts the array in-place with intro-quicksort. + function sort(uint256[] memory a) internal pure { + /// @solidity memory-safe-assembly + assembly { + function swap(a_, b_) -> _a, _b { + _b := a_ + _a := b_ + } + function mswap(i_, j_) { + let t_ := mload(i_) + mstore(i_, mload(j_)) + mstore(j_, t_) + } + function sortInner(w_, l_, h_) { + // Do insertion sort if `h_ - l_ <= 0x20 * 12`. + // Threshold is fine-tuned via trial and error. + if iszero(gt(sub(h_, l_), 0x180)) { + // Hardcode sort the first 2 elements. + let i_ := add(l_, 0x20) + if iszero(lt(mload(l_), mload(i_))) { mswap(i_, l_) } + for { } 1 { } { + i_ := add(i_, 0x20) + if gt(i_, h_) { break } + let k_ := mload(i_) // Key. + let j_ := add(i_, w_) // The slot before the current slot. + let v_ := mload(j_) // The value of `j_`. + if iszero(gt(v_, k_)) { continue } + for { } 1 { } { + mstore(add(j_, 0x20), v_) + j_ := add(j_, w_) + v_ := mload(j_) + if iszero(gt(v_, k_)) { break } + } + mstore(add(j_, 0x20), k_) + } + leave + } + // Pivot slot is the average of `l_` and `h_`. + let p_ := add(shl(5, shr(6, add(l_, h_))), and(31, l_)) + // Median of 3 with sorting. + { + let e0_ := mload(l_) + let e1_ := mload(p_) + if iszero(lt(e0_, e1_)) { e0_, e1_ := swap(e0_, e1_) } + let e2_ := mload(h_) + if iszero(lt(e1_, e2_)) { + e1_, e2_ := swap(e1_, e2_) + if iszero(lt(e0_, e1_)) { e0_, e1_ := swap(e0_, e1_) } + } + mstore(h_, e2_) + mstore(p_, e1_) + mstore(l_, e0_) + } + // Hoare's partition. + { + // The value of the pivot slot. + let x_ := mload(p_) + p_ := h_ + for { let i_ := l_ } 1 { } { + for { } 1 { } { + i_ := add(0x20, i_) + if iszero(gt(x_, mload(i_))) { break } + } + let j_ := p_ + for { } 1 { } { + j_ := add(w_, j_) + if iszero(lt(x_, mload(j_))) { break } + } + p_ := j_ + if iszero(lt(i_, p_)) { break } + mswap(i_, p_) + } + } + if iszero(eq(add(p_, 0x20), h_)) { sortInner(w_, add(p_, 0x20), h_) } + if iszero(eq(p_, l_)) { sortInner(w_, l_, p_) } + } + + for { let n := mload(a) } iszero(lt(n, 2)) { } { + let w := not(0x1f) // `-0x20`. + let l := add(a, 0x20) // Low slot. + let h := add(a, shl(5, n)) // High slot. + let j := h + // While `mload(j - 0x20) <= mload(j): j -= 0x20`. + for { } iszero(gt(mload(add(w, j)), mload(j))) { } { j := add(w, j) } + // If the array is already sorted, break. + if iszero(gt(j, l)) { break } + // While `mload(j - 0x20) >= mload(j): j -= 0x20`. + for { j := h } iszero(lt(mload(add(w, j)), mload(j))) { } { j := add(w, j) } + // If the array is reversed sorted. + if iszero(gt(j, l)) { + for { } 1 { } { + let t := mload(l) + mstore(l, mload(h)) + mstore(h, t) + h := add(w, h) + l := add(l, 0x20) + if iszero(lt(l, h)) { break } + } + break + } + mstore(a, 0) // For insertion sort's inner loop to terminate. + sortInner(w, l, h) + mstore(a, n) // Restore the length of `a`. + break + } + } + } + + /// @dev Sorts the array in-place with intro-quicksort. + function sort(int256[] memory a) internal pure { + _flipSign(a); + sort(_toUints(a)); + _flipSign(a); + } + + /// @dev Sorts the array in-place with intro-quicksort. + function sort(address[] memory a) internal pure { + sort(_toUints(a)); + } + + /// @dev Sorts the array in-place with intro-quicksort. + function sort(bytes32[] memory a) internal pure { + sort(_toUints(a)); + } + + /*´:°•.°+.*•´.*:Ëš.°*.˚•´.°:°•.°•.*•´.*:Ëš.°*.˚•´.°:°•.°+.*•´.*:*/ + /* OTHER USEFUL OPERATIONS */ + /*.•°:°.´+Ëš.*°.Ëš:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.Ëš:*.´+°.•*/ + + // For performance, the `uniquifySorted` methods will not revert if the + // array is not sorted -- it will simply remove consecutive duplicate elements. + + /// @dev Removes duplicate elements from a ascendingly sorted memory array. + function uniquifySorted(uint256[] memory a) internal pure { + /// @solidity memory-safe-assembly + assembly { + // If the length of `a` is greater than 1. + if iszero(lt(mload(a), 2)) { + let x := add(a, 0x20) + let y := add(a, 0x40) + let end := add(a, shl(5, add(mload(a), 1))) + for { } 1 { } { + if iszero(eq(mload(x), mload(y))) { + x := add(x, 0x20) + mstore(x, mload(y)) + } + y := add(y, 0x20) + if eq(y, end) { break } + } + mstore(a, shr(5, sub(x, a))) + } + } + } + + /// @dev Removes duplicate elements from a ascendingly sorted memory array. + function uniquifySorted(int256[] memory a) internal pure { + uniquifySorted(_toUints(a)); + } + + /// @dev Removes duplicate elements from a ascendingly sorted memory array. + function uniquifySorted(address[] memory a) internal pure { + uniquifySorted(_toUints(a)); + } + + /// @dev Removes duplicate elements from a ascendingly sorted memory array. + function uniquifySorted(bytes32[] memory a) internal pure { + uniquifySorted(_toUints(a)); + } + + /// @dev Returns whether `a` contains `needle`, and the index of `needle`. + /// `index` precedence: equal to > nearest before > nearest after. + function searchSorted(uint256[] memory a, uint256 needle) internal pure returns (bool found, uint256 index) { + (found, index) = _searchSorted(a, needle, 0); + } + + /// @dev Returns whether `a` contains `needle`, and the index of `needle`. + /// `index` precedence: equal to > nearest before > nearest after. + function searchSorted(int256[] memory a, int256 needle) internal pure returns (bool found, uint256 index) { + (found, index) = _searchSorted(_toUints(a), uint256(needle), 1 << 255); + } + + /// @dev Returns whether `a` contains `needle`, and the index of `needle`. + /// `index` precedence: equal to > nearest before > nearest after. + function searchSorted(address[] memory a, address needle) internal pure returns (bool found, uint256 index) { + (found, index) = _searchSorted(_toUints(a), uint160(needle), 0); + } + + /// @dev Returns whether `a` contains `needle`, and the index of `needle`. + /// `index` precedence: equal to > nearest before > nearest after. + function searchSorted(bytes32[] memory a, bytes32 needle) internal pure returns (bool found, uint256 index) { + (found, index) = _searchSorted(_toUints(a), uint256(needle), 0); + } + + /// @dev Returns whether `a` contains `needle`. + function inSorted(uint256[] memory a, uint256 needle) internal pure returns (bool found) { + (found,) = searchSorted(a, needle); + } + + /// @dev Returns whether `a` contains `needle`. + function inSorted(int256[] memory a, int256 needle) internal pure returns (bool found) { + (found,) = searchSorted(a, needle); + } + + /// @dev Returns whether `a` contains `needle`. + function inSorted(address[] memory a, address needle) internal pure returns (bool found) { + (found,) = searchSorted(a, needle); + } + + /// @dev Returns whether `a` contains `needle`. + function inSorted(bytes32[] memory a, bytes32 needle) internal pure returns (bool found) { + (found,) = searchSorted(a, needle); + } + + /// @dev Reverses the array in-place. + function reverse(uint256[] memory a) internal pure { + /// @solidity memory-safe-assembly + assembly { + if iszero(lt(mload(a), 2)) { + let s := 0x20 + let w := not(0x1f) + let h := add(a, shl(5, mload(a))) + for { a := add(a, s) } 1 { } { + let t := mload(a) + mstore(a, mload(h)) + mstore(h, t) + h := add(h, w) + a := add(a, s) + if iszero(lt(a, h)) { break } + } + } + } + } + + /// @dev Reverses the array in-place. + function reverse(int256[] memory a) internal pure { + reverse(_toUints(a)); + } + + /// @dev Reverses the array in-place. + function reverse(address[] memory a) internal pure { + reverse(_toUints(a)); + } + + /// @dev Reverses the array in-place. + function reverse(bytes32[] memory a) internal pure { + reverse(_toUints(a)); + } + + /// @dev Returns a copy of the array. + function copy(uint256[] memory a) internal pure returns (uint256[] memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + let end := add(add(result, 0x20), shl(5, mload(a))) + let o := result + for { let d := sub(a, result) } 1 { } { + mstore(o, mload(add(o, d))) + o := add(0x20, o) + if eq(o, end) { break } + } + mstore(0x40, o) + } + } + + /// @dev Returns a copy of the array. + function copy(int256[] memory a) internal pure returns (int256[] memory result) { + result = _toInts(copy(_toUints(a))); + } + + /// @dev Returns a copy of the array. + function copy(address[] memory a) internal pure returns (address[] memory result) { + result = _toAddresses(copy(_toUints(a))); + } + + /// @dev Returns a copy of the array. + function copy(bytes32[] memory a) internal pure returns (bytes32[] memory result) { + result = _toBytes32s(copy(_toUints(a))); + } + + /// @dev Returns whether the array is sorted in ascending order. + function isSorted(uint256[] memory a) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + result := 1 + if iszero(lt(mload(a), 2)) { + let end := add(a, shl(5, mload(a))) + for { a := add(a, 0x20) } 1 { } { + let p := mload(a) + a := add(a, 0x20) + result := iszero(gt(p, mload(a))) + if iszero(mul(result, xor(a, end))) { break } + } + } + } + } + + /// @dev Returns whether the array is sorted in ascending order. + function isSorted(int256[] memory a) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + result := 1 + if iszero(lt(mload(a), 2)) { + let end := add(a, shl(5, mload(a))) + for { a := add(a, 0x20) } 1 { } { + let p := mload(a) + a := add(a, 0x20) + result := iszero(sgt(p, mload(a))) + if iszero(mul(result, xor(a, end))) { break } + } + } + } + } + + /// @dev Returns whether the array is sorted in ascending order. + function isSorted(address[] memory a) internal pure returns (bool result) { + result = isSorted(_toUints(a)); + } + + /// @dev Returns whether the array is sorted in ascending order. + function isSorted(bytes32[] memory a) internal pure returns (bool result) { + result = isSorted(_toUints(a)); + } + + /// @dev Returns whether the array is strictly ascending (sorted and uniquified). + function isSortedAndUniquified(uint256[] memory a) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + result := 1 + if iszero(lt(mload(a), 2)) { + let end := add(a, shl(5, mload(a))) + for { a := add(a, 0x20) } 1 { } { + let p := mload(a) + a := add(a, 0x20) + result := lt(p, mload(a)) + if iszero(mul(result, xor(a, end))) { break } + } + } + } + } + + /// @dev Returns whether the array is strictly ascending (sorted and uniquified). + function isSortedAndUniquified(int256[] memory a) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + result := 1 + if iszero(lt(mload(a), 2)) { + let end := add(a, shl(5, mload(a))) + for { a := add(a, 0x20) } 1 { } { + let p := mload(a) + a := add(a, 0x20) + result := slt(p, mload(a)) + if iszero(mul(result, xor(a, end))) { break } + } + } + } + } + + /// @dev Returns whether the array is strictly ascending (sorted and uniquified). + function isSortedAndUniquified(address[] memory a) internal pure returns (bool result) { + result = isSortedAndUniquified(_toUints(a)); + } + + /// @dev Returns whether the array is strictly ascending (sorted and uniquified). + function isSortedAndUniquified(bytes32[] memory a) internal pure returns (bool result) { + result = isSortedAndUniquified(_toUints(a)); + } + + /// @dev Returns the sorted set difference of `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function difference(uint256[] memory a, uint256[] memory b) internal pure returns (uint256[] memory c) { + c = _difference(a, b, 0); + } + + /// @dev Returns the sorted set difference between `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function difference(int256[] memory a, int256[] memory b) internal pure returns (int256[] memory c) { + c = _toInts(_difference(_toUints(a), _toUints(b), 1 << 255)); + } + + /// @dev Returns the sorted set difference between `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function difference(address[] memory a, address[] memory b) internal pure returns (address[] memory c) { + c = _toAddresses(_difference(_toUints(a), _toUints(b), 0)); + } + + /// @dev Returns the sorted set difference between `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function difference(bytes32[] memory a, bytes32[] memory b) internal pure returns (bytes32[] memory c) { + c = _toBytes32s(_difference(_toUints(a), _toUints(b), 0)); + } + + /// @dev Returns the sorted set intersection between `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function intersection(uint256[] memory a, uint256[] memory b) internal pure returns (uint256[] memory c) { + c = _intersection(a, b, 0); + } + + /// @dev Returns the sorted set intersection between `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function intersection(int256[] memory a, int256[] memory b) internal pure returns (int256[] memory c) { + c = _toInts(_intersection(_toUints(a), _toUints(b), 1 << 255)); + } + + /// @dev Returns the sorted set intersection between `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function intersection(address[] memory a, address[] memory b) internal pure returns (address[] memory c) { + c = _toAddresses(_intersection(_toUints(a), _toUints(b), 0)); + } + + /// @dev Returns the sorted set intersection between `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function intersection(bytes32[] memory a, bytes32[] memory b) internal pure returns (bytes32[] memory c) { + c = _toBytes32s(_intersection(_toUints(a), _toUints(b), 0)); + } + + /// @dev Returns the sorted set union of `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function union(uint256[] memory a, uint256[] memory b) internal pure returns (uint256[] memory c) { + c = _union(a, b, 0); + } + + /// @dev Returns the sorted set union of `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function union(int256[] memory a, int256[] memory b) internal pure returns (int256[] memory c) { + c = _toInts(_union(_toUints(a), _toUints(b), 1 << 255)); + } + + /// @dev Returns the sorted set union between `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function union(address[] memory a, address[] memory b) internal pure returns (address[] memory c) { + c = _toAddresses(_union(_toUints(a), _toUints(b), 0)); + } + + /// @dev Returns the sorted set union between `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function union(bytes32[] memory a, bytes32[] memory b) internal pure returns (bytes32[] memory c) { + c = _toBytes32s(_union(_toUints(a), _toUints(b), 0)); + } + + /// @dev Cleans the upper 96 bits of the addresses. + /// In case `a` is produced via assembly and might have dirty upper bits. + function clean(address[] memory a) internal pure { + /// @solidity memory-safe-assembly + assembly { + let addressMask := shr(96, not(0)) + for { let end := add(a, shl(5, mload(a))) } iszero(eq(a, end)) { } { + a := add(a, 0x20) + mstore(a, and(mload(a), addressMask)) + } + } + } + + /// @dev Sorts and uniquifies `keys`. Updates `values` with the grouped sums by key. + function groupSum(uint256[] memory keys, uint256[] memory values) internal pure { + uint256 m; + /// @solidity memory-safe-assembly + assembly { + m := mload(0x40) // Cache the free memory pointer, for freeing the memory. + if iszero(eq(mload(keys), mload(values))) { + mstore(0x00, 0x4e487b71) + mstore(0x20, 0x32) // Array out of bounds panic if the arrays lengths differ. + revert(0x1c, 0x24) + } + } + if (keys.length == uint256(0)) return; + (uint256[] memory oriKeys, uint256[] memory oriValues) = (copy(keys), copy(values)); + insertionSort(keys); // Optimize for small `n` and bytecode size. + uniquifySorted(keys); + /// @solidity memory-safe-assembly + assembly { + let d := sub(values, keys) + let w := not(0x1f) + let s := add(keys, 0x20) // Location of `keys[0]`. + mstore(values, mload(keys)) // Truncate. + calldatacopy(add(s, d), calldatasize(), shl(5, mload(keys))) // Zeroize. + for { let i := shl(5, mload(oriKeys)) } 1 { } { + let k := mload(add(oriKeys, i)) + let v := mload(add(oriValues, i)) + let j := s // Just do a linear scan to optimize for small `n` and bytecode size. + for { } iszero(eq(mload(j), k)) { } { j := add(j, 0x20) } + j := add(j, d) // Convert `j` to point into `values`. + mstore(j, add(mload(j), v)) + if lt(mload(j), v) { + mstore(0x00, 0x4e487b71) + mstore(0x20, 0x11) // Overflow panic if the addition overflows. + revert(0x1c, 0x24) + } + i := add(i, w) // `sub(i, 0x20)`. + if iszero(i) { break } + } + mstore(0x40, m) // Frees the memory allocated for the temporary copies. + } + } + + /// @dev Sorts and uniquifies `keys`. Updates `values` with the grouped sums by key. + function groupSum(address[] memory keys, uint256[] memory values) internal pure { + groupSum(_toUints(keys), values); + } + + /// @dev Sorts and uniquifies `keys`. Updates `values` with the grouped sums by key. + function groupSum(bytes32[] memory keys, uint256[] memory values) internal pure { + groupSum(_toUints(keys), values); + } + + /// @dev Sorts and uniquifies `keys`. Updates `values` with the grouped sums by key. + function groupSum(int256[] memory keys, uint256[] memory values) internal pure { + groupSum(_toUints(keys), values); + } + + /*´:°•.°+.*•´.*:Ëš.°*.˚•´.°:°•.°•.*•´.*:Ëš.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PRIVATE HELPERS */ + /*.•°:°.´+Ëš.*°.Ëš:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.Ëš:*.´+°.•*/ + + /// @dev Reinterpret cast to an uint256 array. + function _toUints(int256[] memory a) private pure returns (uint256[] memory casted) { + /// @solidity memory-safe-assembly + assembly { + casted := a + } + } + + /// @dev Reinterpret cast to an uint256 array. + function _toUints(address[] memory a) private pure returns (uint256[] memory casted) { + /// @solidity memory-safe-assembly + assembly { + // As any address written to memory will have the upper 96 bits + // of the word zeroized (as per Solidity spec), we can directly + // compare these addresses as if they are whole uint256 words. + casted := a + } + } + + /// @dev Reinterpret cast to an uint256 array. + function _toUints(bytes32[] memory a) private pure returns (uint256[] memory casted) { + /// @solidity memory-safe-assembly + assembly { + casted := a + } + } + + /// @dev Reinterpret cast to an int array. + function _toInts(uint256[] memory a) private pure returns (int256[] memory casted) { + /// @solidity memory-safe-assembly + assembly { + casted := a + } + } + + /// @dev Reinterpret cast to an address array. + function _toAddresses(uint256[] memory a) private pure returns (address[] memory casted) { + /// @solidity memory-safe-assembly + assembly { + casted := a + } + } + + /// @dev Reinterpret cast to an bytes32 array. + function _toBytes32s(uint256[] memory a) private pure returns (bytes32[] memory casted) { + /// @solidity memory-safe-assembly + assembly { + casted := a + } + } + + /// @dev Converts an array of signed integers to unsigned + /// integers suitable for sorting or vice versa. + function _flipSign(int256[] memory a) private pure { + /// @solidity memory-safe-assembly + assembly { + let w := shl(255, 1) + for { let end := add(a, shl(5, mload(a))) } iszero(eq(a, end)) { } { + a := add(a, 0x20) + mstore(a, add(mload(a), w)) + } + } + } + + /// @dev Returns whether `a` contains `needle`, and the index of `needle`. + /// `index` precedence: equal to > nearest before > nearest after. + function _searchSorted( + uint256[] memory a, + uint256 needle, + uint256 signed + ) + private + pure + returns (bool found, uint256 index) + { + /// @solidity memory-safe-assembly + assembly { + let w := not(0) + let l := 1 + let h := mload(a) + let t := 0 + for { needle := add(signed, needle) } 1 { } { + index := shr(1, add(l, h)) + t := add(signed, mload(add(a, shl(5, index)))) + if or(gt(l, h), eq(t, needle)) { break } + // Decide whether to search the left or right half. + if iszero(gt(needle, t)) { + h := add(index, w) + continue + } + l := add(index, 1) + } + // `index` will be zero in the case of an empty array, + // or when the value is less than the smallest value in the array. + found := eq(t, needle) + t := iszero(iszero(index)) + index := mul(add(index, w), t) + found := and(found, t) + } + } + + /// @dev Returns the sorted set difference of `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function _difference( + uint256[] memory a, + uint256[] memory b, + uint256 signed + ) + private + pure + returns (uint256[] memory c) + { + /// @solidity memory-safe-assembly + assembly { + let s := 0x20 + let aEnd := add(a, shl(5, mload(a))) + let bEnd := add(b, shl(5, mload(b))) + c := mload(0x40) // Set `c` to the free memory pointer. + a := add(a, s) + b := add(b, s) + let k := c + for { } iszero(or(gt(a, aEnd), gt(b, bEnd))) { } { + let u := mload(a) + let v := mload(b) + if iszero(xor(u, v)) { + a := add(a, s) + b := add(b, s) + continue + } + if iszero(lt(add(u, signed), add(v, signed))) { + b := add(b, s) + continue + } + k := add(k, s) + mstore(k, u) + a := add(a, s) + } + for { } iszero(gt(a, aEnd)) { } { + k := add(k, s) + mstore(k, mload(a)) + a := add(a, s) + } + mstore(c, shr(5, sub(k, c))) // Store the length of `c`. + mstore(0x40, add(k, s)) // Allocate the memory for `c`. + } + } + + /// @dev Returns the sorted set intersection between `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function _intersection( + uint256[] memory a, + uint256[] memory b, + uint256 signed + ) + private + pure + returns (uint256[] memory c) + { + /// @solidity memory-safe-assembly + assembly { + let s := 0x20 + let aEnd := add(a, shl(5, mload(a))) + let bEnd := add(b, shl(5, mload(b))) + c := mload(0x40) // Set `c` to the free memory pointer. + a := add(a, s) + b := add(b, s) + let k := c + for { } iszero(or(gt(a, aEnd), gt(b, bEnd))) { } { + let u := mload(a) + let v := mload(b) + if iszero(xor(u, v)) { + k := add(k, s) + mstore(k, u) + a := add(a, s) + b := add(b, s) + continue + } + if iszero(lt(add(u, signed), add(v, signed))) { + b := add(b, s) + continue + } + a := add(a, s) + } + mstore(c, shr(5, sub(k, c))) // Store the length of `c`. + mstore(0x40, add(k, s)) // Allocate the memory for `c`. + } + } + + /// @dev Returns the sorted set union of `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function _union(uint256[] memory a, uint256[] memory b, uint256 signed) private pure returns (uint256[] memory c) { + /// @solidity memory-safe-assembly + assembly { + let s := 0x20 + let aEnd := add(a, shl(5, mload(a))) + let bEnd := add(b, shl(5, mload(b))) + c := mload(0x40) // Set `c` to the free memory pointer. + a := add(a, s) + b := add(b, s) + let k := c + for { } iszero(or(gt(a, aEnd), gt(b, bEnd))) { } { + let u := mload(a) + let v := mload(b) + if iszero(xor(u, v)) { + k := add(k, s) + mstore(k, u) + a := add(a, s) + b := add(b, s) + continue + } + if iszero(lt(add(u, signed), add(v, signed))) { + k := add(k, s) + mstore(k, v) + b := add(b, s) + continue + } + k := add(k, s) + mstore(k, u) + a := add(a, s) + } + for { } iszero(gt(a, aEnd)) { } { + k := add(k, s) + mstore(k, mload(a)) + a := add(a, s) + } + for { } iszero(gt(b, bEnd)) { } { + k := add(k, s) + mstore(k, mload(b)) + b := add(b, s) + } + mstore(c, shr(5, sub(k, c))) // Store the length of `c`. + mstore(0x40, add(k, s)) // Allocate the memory for `c`. + } + } +} + +// node_modules/@rhinestone/modulekit/src/accounts/common/lib/ModeLib.sol + +/** + * @title ModeLib + * @author rhinestone | zeroknots.eth, Konrad Kopp (@kopy-kat) + * To allow smart accounts to be very simple, but allow for more complex execution, A custom mode + * encoding is used. + * Function Signature of execute function: + * function execute(ModeCode mode, bytes calldata executionCalldata) external payable; + * This allows for a single bytes32 to be used to encode the execution mode, calltype, execType and + * context. + * NOTE: Simple Account implementations only have to scope for the most significant byte. Account that + * implement + * more complex execution modes may use the entire bytes32. + * + * |--------------------------------------------------------------------| + * | CALLTYPE | EXECTYPE | UNUSED | ModeSelector | ModePayload | + * |--------------------------------------------------------------------| + * | 1 byte | 1 byte | 4 bytes | 4 bytes | 22 bytes | + * |--------------------------------------------------------------------| + * + * CALLTYPE: 1 byte + * CallType is used to determine how the executeCalldata paramter of the execute function has to be + * decoded. + * It can be either single, batch or delegatecall. In the future different calls could be added. + * CALLTYPE can be used by a validation module to determine how to decode . + * + * EXECTYPE: 1 byte + * ExecType is used to determine how the account should handle the execution. + * It can indicate if the execution should revert on failure or continue execution. + * In the future more execution modes may be added. + * Default Behavior (EXECTYPE = 0x00) is to revert on a single failed execution. If one execution in + * a batch fails, the entire batch is reverted + * + * UNUSED: 4 bytes + * Unused bytes are reserved for future use. + * + * ModeSelector: bytes4 + * The "optional" mode selector can be used by account vendors, to implement custom behavior in + * their accounts. + * the way a ModeSelector is to be calculated is bytes4(keccak256("vendorname.featurename")) + * this is to prevent collisions between different vendors, while allowing innovation and the + * development of new features without coordination between ERC-7579 implementing accounts + * + * ModePayload: 22 bytes + * Mode payload is used to pass additional data to the smart account execution, this may be + * interpreted depending on the ModeSelector + * + * ExecutionCallData: n bytes + * single, delegatecall or batch exec abi.encoded as bytes + */ + +// Custom type for improved developer experience +type ModeCode is bytes32; + +type CallType is bytes1; + +type ExecType is bytes1; + +type ModeSelector is bytes4; + +type ModePayload is bytes22; + +// Default CallType +CallType constant CALLTYPE_SINGLE = CallType.wrap(0x00); +// Batched CallType +CallType constant CALLTYPE_BATCH = CallType.wrap(0x01); +CallType constant CALLTYPE_STATIC = CallType.wrap(0xFE); +// @dev Implementing delegatecall is OPTIONAL! +// implement delegatecall with extreme care. +CallType constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF); + +// @dev default behavior is to revert on failure +// To allow very simple accounts to use mode encoding, the default behavior is to revert on failure +// Since this is value 0x00, no additional encoding is required for simple accounts +ExecType constant EXECTYPE_DEFAULT = ExecType.wrap(0x00); +// @dev account may elect to change execution behavior. For example "try exec" / "allow fail" +ExecType constant EXECTYPE_TRY = ExecType.wrap(0x01); + +ModeSelector constant MODE_DEFAULT = ModeSelector.wrap(bytes4(0x00000000)); +// Example declaration of a custom mode selector +ModeSelector constant MODE_OFFSET = ModeSelector.wrap(bytes4(keccak256("default.mode.offset"))); + +/** + * @dev ModeLib is a helper library to encode/decode ModeCodes + */ +library ModeLib { + function decode(ModeCode mode) + internal + pure + returns (CallType _calltype, ExecType _execType, ModeSelector _modeSelector, ModePayload _modePayload) + { + // solhint-disable-next-line no-inline-assembly + assembly { + _calltype := mode + _execType := shl(8, mode) + _modeSelector := shl(48, mode) + _modePayload := shl(80, mode) + } + } + + function encode( + CallType callType, + ExecType execType, + ModeSelector mode, + ModePayload payload + ) + internal + pure + returns (ModeCode) + { + return + ModeCode.wrap(bytes32(abi.encodePacked(callType, execType, bytes4(0), ModeSelector.unwrap(mode), payload))); + } + + function encodeSimpleBatch() internal pure returns (ModeCode mode) { + mode = encode(CALLTYPE_BATCH, EXECTYPE_DEFAULT, MODE_DEFAULT, ModePayload.wrap(0x00)); + } + + function encodeSimpleSingle() internal pure returns (ModeCode mode) { + mode = encode(CALLTYPE_SINGLE, EXECTYPE_DEFAULT, MODE_DEFAULT, ModePayload.wrap(0x00)); + } + + function getCallType(ModeCode mode) internal pure returns (CallType calltype) { + // solhint-disable-next-line no-inline-assembly + assembly { + calltype := mode + } + } +} + +using { eqModeSelector as == } for ModeSelector global; +using { eqCallType as == } for CallType global; +using { neqCallType as != } for CallType global; +using { eqExecType as == } for ExecType global; + +function eqCallType(CallType a, CallType b) pure returns (bool) { + return CallType.unwrap(a) == CallType.unwrap(b); +} + +function neqCallType(CallType a, CallType b) pure returns (bool) { + return CallType.unwrap(a) == CallType.unwrap(b); +} + +function eqExecType(ExecType a, ExecType b) pure returns (bool) { + return ExecType.unwrap(a) == ExecType.unwrap(b); +} + +function eqModeSelector(ModeSelector a, ModeSelector b) pure returns (bool) { + return ModeSelector.unwrap(a) == ModeSelector.unwrap(b); +} + +// node_modules/@ERC4337/account-abstraction/contracts/interfaces/PackedUserOperation.sol + +/** + * User Operation struct + * @param sender - The sender account of this request. + * @param nonce - Unique value the sender uses to verify it is not a replay. + * @param initCode - If set, the account contract will be created by this constructor/ + * @param callData - The method call to execute on this account. + * @param accountGasLimits - Packed gas limits for validateUserOp and gas limit passed to the callData method call. + * @param preVerificationGas - Gas not calculated by the handleOps method, but added to the gas paid. + * Covers batch overhead. + * @param gasFees - packed gas fields maxPriorityFeePerGas and maxFeePerGas - Same as EIP-1559 gas + * parameters. + * @param paymasterAndData - If set, this field holds the paymaster address, verification gas limit, postOp gas + * limit and paymaster-specific extra data + * The paymaster will pay for the transaction instead of the sender. + * @param signature - Sender-verified signature over the entire request, the EntryPoint address and the + * chain ID. + */ +struct PackedUserOperation { + address sender; + uint256 nonce; + bytes initCode; + bytes callData; + bytes32 accountGasLimits; + uint256 preVerificationGas; + bytes32 gasFees; + bytes paymasterAndData; + bytes signature; +} + +// node_modules/@openzeppelin/contracts/utils/ReentrancyGuard.sol + +// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol) + +/** + * @dev Contract module that helps prevent reentrant calls to a function. + * + * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier + * available, which can be applied to functions to make sure there are no nested + * (reentrant) calls to them. + * + * Note that because there is a single `nonReentrant` guard, functions marked as + * `nonReentrant` may not call one another. This can be worked around by making + * those functions `private`, and then adding `external` `nonReentrant` entry + * points to them. + * + * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at, + * consider using {ReentrancyGuardTransient} instead. + * + * TIP: If you would like to learn more about reentrancy and alternative ways + * to protect against it, check out our blog post + * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. + */ +abstract contract ReentrancyGuard { + // Booleans are more expensive than uint256 or any type that takes up a full + // word because each write operation emits an extra SLOAD to first read the + // slot's contents, replace the bits taken up by the boolean, and then write + // back. This is the compiler's defense against contract upgrades and + // pointer aliasing, and it cannot be disabled. + + // The values being non-zero value makes deployment a bit more expensive, + // but in exchange the refund on every call to nonReentrant will be lower in + // amount. Since refunds are capped to a percentage of the total + // transaction's gas, it is best to keep them low in cases like this one, to + // increase the likelihood of the full refund coming into effect. + uint256 private constant NOT_ENTERED = 1; + uint256 private constant ENTERED = 2; + + uint256 private _status; + + /** + * @dev Unauthorized reentrant call. + */ + error ReentrancyGuardReentrantCall(); + + constructor() { + _status = NOT_ENTERED; + } + + /** + * @dev Prevents a contract from calling itself, directly or indirectly. + * Calling a `nonReentrant` function from another `nonReentrant` + * function is not supported. It is possible to prevent this from happening + * by making the `nonReentrant` function external, and making it call a + * `private` function that does the actual work. + */ + modifier nonReentrant() { + _nonReentrantBefore(); + _; + _nonReentrantAfter(); + } + + function _nonReentrantBefore() private { + // On the first call to nonReentrant, _status will be NOT_ENTERED + if (_status == ENTERED) { + revert ReentrancyGuardReentrantCall(); + } + + // Any calls to nonReentrant after this point will fail + _status = ENTERED; + } + + function _nonReentrantAfter() private { + // By storing the original value once again, a refund is triggered (see + // https://eips.ethereum.org/EIPS/eip-2200) + _status = NOT_ENTERED; + } + + /** + * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a + * `nonReentrant` function in the call stack. + */ + function _reentrancyGuardEntered() internal view returns (bool) { + return _status == ENTERED; + } +} + +// node_modules/@ERC4337/account-abstraction/contracts/core/SenderCreator.sol + +/** + * Helper contract for EntryPoint, to call userOp.initCode from a "neutral" address, + * which is explicitly not the entryPoint itself. + */ +contract SenderCreator { + /** + * Call the "initCode" factory to create and return the sender account address. + * @param initCode - The initCode value from a UserOp. contains 20 bytes of factory address, + * followed by calldata. + * @return sender - The returned address of the created account, or zero address on failure. + */ + function createSender(bytes calldata initCode) external returns (address sender) { + address factory = address(bytes20(initCode[0:20])); + bytes memory initCallData = initCode[20:]; + bool success; + /* solhint-disable no-inline-assembly */ + assembly ("memory-safe") { + success := call(gas(), factory, 0, add(initCallData, 0x20), mload(initCallData), 0, 32) + sender := mload(0) + } + if (!success) { + sender = address(0); + } + } +} + +// node_modules/@rhinestone/sentinellist/src/SentinelList4337.sol + +// Sentinel address +address constant SENTINEL = address(0x1); +// Zero address +address constant ZERO_ADDRESS = address(0x0); + +/** + * @title SentinelListLib + * @dev Library for managing a linked list of addresses that is compliant with the ERC-4337 + * validation rules + * @author Rhinestone + */ +library SentinelList4337Lib { + // Struct to hold the linked list + // This linked list has the account address as the inner key so it is ERC-4337 compliant + struct SentinelList { + mapping(address key => mapping(address account => address entry)) entries; + } + + error LinkedList_AlreadyInitialized(); + error LinkedList_InvalidPage(); + error LinkedList_InvalidEntry(address entry); + error LinkedList_EntryAlreadyInList(address entry); + + /** + * Initialize the linked list + * + * @param self The linked list + * @param account The account to initialize the linked list for + */ + function init(SentinelList storage self, address account) internal { + if (alreadyInitialized(self, account)) revert LinkedList_AlreadyInitialized(); + self.entries[SENTINEL][account] = SENTINEL; + } + + /** + * Check if the linked list is already initialized + * + * @param self The linked list + * @param account The account to check if the linked list is initialized for + * + * @return bool True if the linked list is already initialized + */ + function alreadyInitialized(SentinelList storage self, address account) internal view returns (bool) { + return self.entries[SENTINEL][account] != ZERO_ADDRESS; + } + + /** + * Get the next entry in the linked list + * + * @param self The linked list + * @param account The account to get the next entry for + * @param entry The current entry + * + * @return address The next entry + */ + function getNext(SentinelList storage self, address account, address entry) internal view returns (address) { + if (entry == ZERO_ADDRESS) { + revert LinkedList_InvalidEntry(entry); + } + return self.entries[entry][account]; + } + + /** + * Push a new entry to the linked list + * + * @param self The linked list + * @param account The account to push the new entry for + * @param newEntry The new entry + */ + function push(SentinelList storage self, address account, address newEntry) internal { + if (newEntry == ZERO_ADDRESS || newEntry == SENTINEL) { + revert LinkedList_InvalidEntry(newEntry); + } + if (self.entries[newEntry][account] != ZERO_ADDRESS) { + revert LinkedList_EntryAlreadyInList(newEntry); + } + self.entries[newEntry][account] = self.entries[SENTINEL][account]; + self.entries[SENTINEL][account] = newEntry; + } + + /** + * Safe push a new entry to the linked list + * @dev This ensures that the linked list is initialized and initializes it if it is not + * + * @param self The linked list + * @param account The account to push the new entry for + * @param newEntry The new entry + */ + function safePush(SentinelList storage self, address account, address newEntry) internal { + if (!alreadyInitialized(self, account)) { + init({ self: self, account: account }); + } + push({ self: self, account: account, newEntry: newEntry }); + } + + /** + * Pop an entry from the linked list + * + * @param self The linked list + * @param account The account to pop the entry for + * @param prevEntry The entry before the entry to pop + * @param popEntry The entry to pop + */ + function pop(SentinelList storage self, address account, address prevEntry, address popEntry) internal { + if (popEntry == ZERO_ADDRESS || popEntry == SENTINEL) { + revert LinkedList_InvalidEntry(prevEntry); + } + if (self.entries[prevEntry][account] != popEntry) { + revert LinkedList_InvalidEntry(popEntry); + } + self.entries[prevEntry][account] = self.entries[popEntry][account]; + self.entries[popEntry][account] = ZERO_ADDRESS; + } + + /** + * Pop all entries from the linked list + * + * @param self The linked list + * @param account The account to pop all entries for + */ + function popAll(SentinelList storage self, address account) internal { + address next = self.entries[SENTINEL][account]; + while (next != ZERO_ADDRESS) { + address current = next; + next = self.entries[next][account]; + self.entries[current][account] = ZERO_ADDRESS; + } + } + + /** + * Check if the linked list contains an entry + * + * @param self The linked list + * @param account The account to check if the entry is in the linked list for + * @param entry The entry to check for + * + * @return bool True if the linked list contains the entry + */ + function contains(SentinelList storage self, address account, address entry) internal view returns (bool) { + return SENTINEL != entry && self.entries[entry][account] != ZERO_ADDRESS; + } + + /** + * Get all entries in the linked list + * + * @param self The linked list + * @param account The account to get the entries for + * @param start The start entry + * @param pageSize The page size + * + * @return array All entries in the linked list + * @return next The next entry + */ + function getEntriesPaginated( + SentinelList storage self, + address account, + address start, + uint256 pageSize + ) + internal + view + returns (address[] memory array, address next) + { + if (start != SENTINEL && !contains(self, account, start)) { + revert LinkedList_InvalidEntry(start); + } + if (pageSize == 0) revert LinkedList_InvalidPage(); + // Init array with max page size + array = new address[](pageSize); + + // Populate return array + uint256 entryCount = 0; + next = self.entries[start][account]; + while (next != ZERO_ADDRESS && next != SENTINEL && entryCount < pageSize) { + array[entryCount] = next; + next = self.entries[next][account]; + entryCount++; + } + + /** + * Because of the argument validation, we can assume that the loop will always iterate over + * the valid entry list values + * and the `next` variable will either be an enabled entry or a sentinel address + * (signalling the end). + * + * If we haven't reached the end inside the loop, we need to set the next pointer to + * the last element of the entry array + * because the `next` variable (which is a entry by itself) acting as a pointer to the + * start of the next page is neither + * incSENTINELrent page, nor will it be included in the next one if you pass it as a + * start. + */ + if (next != SENTINEL && entryCount > 0) { + next = array[entryCount - 1]; + } + // Set correct size of returned array + // solhint-disable-next-line no-inline-assembly + /// @solidity memory-safe-assembly + assembly { + mstore(array, entryCount) + } + } +} + +// node_modules/solady/src/utils/SignatureCheckerLib.sol + +/// @notice Signature verification helper that supports both ECDSA signatures from EOAs +/// and ERC1271 signatures from smart contract wallets like Argent and Gnosis safe. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SignatureCheckerLib.sol) +/// @author Modified from OpenZeppelin +/// (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/SignatureChecker.sol) +/// +/// @dev Note: +/// - The signature checking functions use the ecrecover precompile (0x1). +/// - The `bytes memory signature` variants use the identity precompile (0x4) +/// to copy memory internally. +/// - Unlike ECDSA signatures, contract signatures are revocable. +/// - As of Solady version 0.0.134, all `bytes signature` variants accept both +/// regular 65-byte `(r, s, v)` and EIP-2098 `(r, vs)` short form signatures. +/// See: https://eips.ethereum.org/EIPS/eip-2098 +/// This is for calldata efficiency on smart accounts prevalent on L2s. +/// +/// WARNING! Do NOT use signatures as unique identifiers: +/// - Use a nonce in the digest to prevent replay attacks on the same contract. +/// - Use EIP-712 for the digest to prevent replay attacks across different chains and contracts. +/// EIP-712 also enables readable signing of typed data for better user safety. +/// This implementation does NOT check if a signature is non-malleable. +library SignatureCheckerLib { + /*´:°•.°+.*•´.*:Ëš.°*.˚•´.°:°•.°•.*•´.*:Ëš.°*.˚•´.°:°•.°+.*•´.*:*/ + /* SIGNATURE CHECKING OPERATIONS */ + /*.•°:°.´+Ëš.*°.Ëš:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.Ëš:*.´+°.•*/ + + /// @dev Returns whether `signature` is valid for `signer` and `hash`. + /// If `signer.code.length == 0`, then validate with `ecrecover`, else + /// it will validate with ERC1271 on `signer`. + function isValidSignatureNow( + address signer, + bytes32 hash, + bytes memory signature + ) + internal + view + returns (bool isValid) + { + if (signer == address(0)) return isValid; + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + for { } 1 { } { + if iszero(extcodesize(signer)) { + switch mload(signature) + case 64 { + let vs := mload(add(signature, 0x40)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + } + case 65 { + mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. + mstore(0x60, mload(add(signature, 0x40))) // `s`. + } + default { break } + mstore(0x00, hash) + mstore(0x40, mload(add(signature, 0x20))) // `r`. + let recovered := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + isValid := gt(returndatasize(), shl(96, xor(signer, recovered))) + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + break + } + let f := shl(224, 0x1626ba7e) + mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. + mstore(add(m, 0x04), hash) + let d := add(m, 0x24) + mstore(d, 0x40) // The offset of the `signature` in the calldata. + // Copy the `signature` over. + let n := add(0x20, mload(signature)) + let copied := staticcall(gas(), 4, signature, n, add(m, 0x44), n) + isValid := staticcall(gas(), signer, m, add(returndatasize(), 0x44), d, 0x20) + isValid := and(eq(mload(d), f), and(isValid, copied)) + break + } + } + } + + /// @dev Returns whether `signature` is valid for `signer` and `hash`. + /// If `signer.code.length == 0`, then validate with `ecrecover`, else + /// it will validate with ERC1271 on `signer`. + function isValidSignatureNowCalldata( + address signer, + bytes32 hash, + bytes calldata signature + ) + internal + view + returns (bool isValid) + { + if (signer == address(0)) return isValid; + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + for { } 1 { } { + if iszero(extcodesize(signer)) { + switch signature.length + case 64 { + let vs := calldataload(add(signature.offset, 0x20)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x40, calldataload(signature.offset)) // `r`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + } + case 65 { + mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`. + calldatacopy(0x40, signature.offset, 0x40) // `r`, `s`. + } + default { break } + mstore(0x00, hash) + let recovered := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + isValid := gt(returndatasize(), shl(96, xor(signer, recovered))) + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + break + } + let f := shl(224, 0x1626ba7e) + mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. + mstore(add(m, 0x04), hash) + let d := add(m, 0x24) + mstore(d, 0x40) // The offset of the `signature` in the calldata. + mstore(add(m, 0x44), signature.length) + // Copy the `signature` over. + calldatacopy(add(m, 0x64), signature.offset, signature.length) + isValid := staticcall(gas(), signer, m, add(signature.length, 0x64), d, 0x20) + isValid := and(eq(mload(d), f), isValid) + break + } + } + } + + /// @dev Returns whether the signature (`r`, `vs`) is valid for `signer` and `hash`. + /// If `signer.code.length == 0`, then validate with `ecrecover`, else + /// it will validate with ERC1271 on `signer`. + function isValidSignatureNow( + address signer, + bytes32 hash, + bytes32 r, + bytes32 vs + ) + internal + view + returns (bool isValid) + { + if (signer == address(0)) return isValid; + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + for { } 1 { } { + if iszero(extcodesize(signer)) { + mstore(0x00, hash) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x40, r) // `r`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + let recovered := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + isValid := gt(returndatasize(), shl(96, xor(signer, recovered))) + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + break + } + let f := shl(224, 0x1626ba7e) + mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. + mstore(add(m, 0x04), hash) + let d := add(m, 0x24) + mstore(d, 0x40) // The offset of the `signature` in the calldata. + mstore(add(m, 0x44), 65) // Length of the signature. + mstore(add(m, 0x64), r) // `r`. + mstore(add(m, 0x84), shr(1, shl(1, vs))) // `s`. + mstore8(add(m, 0xa4), add(shr(255, vs), 27)) // `v`. + isValid := staticcall(gas(), signer, m, 0xa5, d, 0x20) + isValid := and(eq(mload(d), f), isValid) + break + } + } + } + + /// @dev Returns whether the signature (`v`, `r`, `s`) is valid for `signer` and `hash`. + /// If `signer.code.length == 0`, then validate with `ecrecover`, else + /// it will validate with ERC1271 on `signer`. + function isValidSignatureNow( + address signer, + bytes32 hash, + uint8 v, + bytes32 r, + bytes32 s + ) + internal + view + returns (bool isValid) + { + if (signer == address(0)) return isValid; + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + for { } 1 { } { + if iszero(extcodesize(signer)) { + mstore(0x00, hash) + mstore(0x20, and(v, 0xff)) // `v`. + mstore(0x40, r) // `r`. + mstore(0x60, s) // `s`. + let recovered := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + isValid := gt(returndatasize(), shl(96, xor(signer, recovered))) + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + break + } + let f := shl(224, 0x1626ba7e) + mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. + mstore(add(m, 0x04), hash) + let d := add(m, 0x24) + mstore(d, 0x40) // The offset of the `signature` in the calldata. + mstore(add(m, 0x44), 65) // Length of the signature. + mstore(add(m, 0x64), r) // `r`. + mstore(add(m, 0x84), s) // `s`. + mstore8(add(m, 0xa4), v) // `v`. + isValid := staticcall(gas(), signer, m, 0xa5, d, 0x20) + isValid := and(eq(mload(d), f), isValid) + break + } + } + } + + /*´:°•.°+.*•´.*:Ëš.°*.˚•´.°:°•.°•.*•´.*:Ëš.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ERC1271 OPERATIONS */ + /*.•°:°.´+Ëš.*°.Ëš:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.Ëš:*.´+°.•*/ + + // Note: These ERC1271 operations do NOT have an ECDSA fallback. + + /// @dev Returns whether `signature` is valid for `hash` for an ERC1271 `signer` contract. + function isValidERC1271SignatureNow( + address signer, + bytes32 hash, + bytes memory signature + ) + internal + view + returns (bool isValid) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let f := shl(224, 0x1626ba7e) + mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. + mstore(add(m, 0x04), hash) + let d := add(m, 0x24) + mstore(d, 0x40) // The offset of the `signature` in the calldata. + // Copy the `signature` over. + let n := add(0x20, mload(signature)) + let copied := staticcall(gas(), 4, signature, n, add(m, 0x44), n) + isValid := staticcall(gas(), signer, m, add(returndatasize(), 0x44), d, 0x20) + isValid := and(eq(mload(d), f), and(isValid, copied)) + } + } + + /// @dev Returns whether `signature` is valid for `hash` for an ERC1271 `signer` contract. + function isValidERC1271SignatureNowCalldata( + address signer, + bytes32 hash, + bytes calldata signature + ) + internal + view + returns (bool isValid) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let f := shl(224, 0x1626ba7e) + mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. + mstore(add(m, 0x04), hash) + let d := add(m, 0x24) + mstore(d, 0x40) // The offset of the `signature` in the calldata. + mstore(add(m, 0x44), signature.length) + // Copy the `signature` over. + calldatacopy(add(m, 0x64), signature.offset, signature.length) + isValid := staticcall(gas(), signer, m, add(signature.length, 0x64), d, 0x20) + isValid := and(eq(mload(d), f), isValid) + } + } + + /// @dev Returns whether the signature (`r`, `vs`) is valid for `hash` + /// for an ERC1271 `signer` contract. + function isValidERC1271SignatureNow( + address signer, + bytes32 hash, + bytes32 r, + bytes32 vs + ) + internal + view + returns (bool isValid) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let f := shl(224, 0x1626ba7e) + mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. + mstore(add(m, 0x04), hash) + let d := add(m, 0x24) + mstore(d, 0x40) // The offset of the `signature` in the calldata. + mstore(add(m, 0x44), 65) // Length of the signature. + mstore(add(m, 0x64), r) // `r`. + mstore(add(m, 0x84), shr(1, shl(1, vs))) // `s`. + mstore8(add(m, 0xa4), add(shr(255, vs), 27)) // `v`. + isValid := staticcall(gas(), signer, m, 0xa5, d, 0x20) + isValid := and(eq(mload(d), f), isValid) + } + } + + /// @dev Returns whether the signature (`v`, `r`, `s`) is valid for `hash` + /// for an ERC1271 `signer` contract. + function isValidERC1271SignatureNow( + address signer, + bytes32 hash, + uint8 v, + bytes32 r, + bytes32 s + ) + internal + view + returns (bool isValid) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let f := shl(224, 0x1626ba7e) + mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. + mstore(add(m, 0x04), hash) + let d := add(m, 0x24) + mstore(d, 0x40) // The offset of the `signature` in the calldata. + mstore(add(m, 0x44), 65) // Length of the signature. + mstore(add(m, 0x64), r) // `r`. + mstore(add(m, 0x84), s) // `s`. + mstore8(add(m, 0xa4), v) // `v`. + isValid := staticcall(gas(), signer, m, 0xa5, d, 0x20) + isValid := and(eq(mload(d), f), isValid) + } + } + + /*´:°•.°+.*•´.*:Ëš.°*.˚•´.°:°•.°•.*•´.*:Ëš.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ERC6492 OPERATIONS */ + /*.•°:°.´+Ëš.*°.Ëš:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.Ëš:*.´+°.•*/ + + // Note: These ERC6492 operations now include an ECDSA fallback at the very end. + // The calldata variants are excluded for brevity. + + /// @dev Returns whether `signature` is valid for `hash`. + /// If the signature is postfixed with the ERC6492 magic number, it will attempt to + /// deploy / prepare the `signer` smart account before doing a regular ERC1271 check. + /// Note: This function is NOT reentrancy safe. + /// The verifier must be deployed. + /// Otherwise, the function will return false if `signer` is not yet deployed / prepared. + /// See: https://gist.github.com/Vectorized/011d6becff6e0a73e42fe100f8d7ef04 + /// With a dedicated verifier, this function is safe to use in contracts + /// that have been granted special permissions. + function isValidERC6492SignatureNowAllowSideEffects( + address signer, + bytes32 hash, + bytes memory signature + ) + internal + returns (bool isValid) + { + /// @solidity memory-safe-assembly + assembly { + function callIsValidSignature(signer_, hash_, signature_) -> _isValid { + let m_ := mload(0x40) + let f_ := shl(224, 0x1626ba7e) + mstore(m_, f_) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. + mstore(add(m_, 0x04), hash_) + let d_ := add(m_, 0x24) + mstore(d_, 0x40) // The offset of the `signature` in the calldata. + let n_ := add(0x20, mload(signature_)) + let copied_ := staticcall(gas(), 4, signature_, n_, add(m_, 0x44), n_) + _isValid := staticcall(gas(), signer_, m_, add(returndatasize(), 0x44), d_, 0x20) + _isValid := and(eq(mload(d_), f_), and(_isValid, copied_)) + } + let noCode := iszero(extcodesize(signer)) + let n := mload(signature) + for { } 1 { } { + if iszero(eq(mload(add(signature, n)), mul(0x6492, div(not(isValid), 0xffff)))) { + if iszero(noCode) { isValid := callIsValidSignature(signer, hash, signature) } + break + } + if iszero(noCode) { + let o := add(signature, 0x20) // Signature bytes. + isValid := callIsValidSignature(signer, hash, add(o, mload(add(o, 0x40)))) + if isValid { break } + } + let m := mload(0x40) + mstore(m, signer) + mstore(add(m, 0x20), hash) + pop( + call( + gas(), // Remaining gas. + 0x0000bc370E4DC924F427d84e2f4B9Ec81626ba7E, // Non-reverting verifier. + 0, // Send zero ETH. + m, // Start of memory. + add(returndatasize(), 0x40), // Length of calldata in memory. + staticcall(gas(), 4, add(signature, 0x20), n, add(m, 0x40), n), // 1. + 0x00 // Length of returndata to write. + ) + ) + isValid := returndatasize() + break + } + // Do `ecrecover` fallback if `noCode && !isValid`. + for { } gt(noCode, isValid) { } { + switch n + case 64 { + let vs := mload(add(signature, 0x40)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + } + case 65 { + mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. + mstore(0x60, mload(add(signature, 0x40))) // `s`. + } + default { break } + let m := mload(0x40) + mstore(0x00, hash) + mstore(0x40, mload(add(signature, 0x20))) // `r`. + let recovered := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + isValid := gt(returndatasize(), shl(96, xor(signer, recovered))) + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + break + } + } + } + + /// @dev Returns whether `signature` is valid for `hash`. + /// If the signature is postfixed with the ERC6492 magic number, it will attempt + /// to use a reverting verifier to deploy / prepare the `signer` smart account + /// and do a `isValidSignature` check via the reverting verifier. + /// Note: This function is reentrancy safe. + /// The reverting verifier must be deployed. + /// Otherwise, the function will return false if `signer` is not yet deployed / prepared. + /// See: https://gist.github.com/Vectorized/846a474c855eee9e441506676800a9ad + function isValidERC6492SignatureNow( + address signer, + bytes32 hash, + bytes memory signature + ) + internal + returns (bool isValid) + { + /// @solidity memory-safe-assembly + assembly { + function callIsValidSignature(signer_, hash_, signature_) -> _isValid { + let m_ := mload(0x40) + let f_ := shl(224, 0x1626ba7e) + mstore(m_, f_) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. + mstore(add(m_, 0x04), hash_) + let d_ := add(m_, 0x24) + mstore(d_, 0x40) // The offset of the `signature` in the calldata. + let n_ := add(0x20, mload(signature_)) + let copied_ := staticcall(gas(), 4, signature_, n_, add(m_, 0x44), n_) + _isValid := staticcall(gas(), signer_, m_, add(returndatasize(), 0x44), d_, 0x20) + _isValid := and(eq(mload(d_), f_), and(_isValid, copied_)) + } + let noCode := iszero(extcodesize(signer)) + let n := mload(signature) + for { } 1 { } { + if iszero(eq(mload(add(signature, n)), mul(0x6492, div(not(isValid), 0xffff)))) { + if iszero(noCode) { isValid := callIsValidSignature(signer, hash, signature) } + break + } + if iszero(noCode) { + let o := add(signature, 0x20) // Signature bytes. + isValid := callIsValidSignature(signer, hash, add(o, mload(add(o, 0x40)))) + if isValid { break } + } + let m := mload(0x40) + mstore(m, signer) + mstore(add(m, 0x20), hash) + let willBeZeroIfRevertingVerifierExists := + call( + gas(), // Remaining gas. + 0x00007bd799e4A591FeA53f8A8a3E9f931626Ba7e, // Reverting verifier. + 0, // Send zero ETH. + m, // Start of memory. + add(returndatasize(), 0x40), // Length of calldata in memory. + staticcall(gas(), 4, add(signature, 0x20), n, add(m, 0x40), n), // 1. + 0x00 // Length of returndata to write. + ) + isValid := gt(returndatasize(), willBeZeroIfRevertingVerifierExists) + break + } + // Do `ecrecover` fallback if `noCode && !isValid`. + for { } gt(noCode, isValid) { } { + switch n + case 64 { + let vs := mload(add(signature, 0x40)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + } + case 65 { + mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. + mstore(0x60, mload(add(signature, 0x40))) // `s`. + } + default { break } + let m := mload(0x40) + mstore(0x00, hash) + mstore(0x40, mload(add(signature, 0x20))) // `r`. + let recovered := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + isValid := gt(returndatasize(), shl(96, xor(signer, recovered))) + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + break + } + } + } + + /*´:°•.°+.*•´.*:Ëš.°*.˚•´.°:°•.°•.*•´.*:Ëš.°*.˚•´.°:°•.°+.*•´.*:*/ + /* HASHING OPERATIONS */ + /*.•°:°.´+Ëš.*°.Ëš:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.Ëš:*.´+°.•*/ + + /// @dev Returns an Ethereum Signed Message, created from a `hash`. + /// This produces a hash corresponding to the one signed with the + /// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign) + /// JSON-RPC method as part of EIP-191. + function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x20, hash) // Store into scratch space for keccak256. + mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes. + result := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. + } + } + + /// @dev Returns an Ethereum Signed Message, created from `s`. + /// This produces a hash corresponding to the one signed with the + /// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign) + /// JSON-RPC method as part of EIP-191. + /// Note: Supports lengths of `s` up to 999999 bytes. + function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let sLength := mload(s) + let o := 0x20 + mstore(o, "\x19Ethereum Signed Message:\n") // 26 bytes, zero-right-padded. + mstore(0x00, 0x00) + // Convert the `s.length` to ASCII decimal representation: `base10(s.length)`. + for { let temp := sLength } 1 { } { + o := sub(o, 1) + mstore8(o, add(48, mod(temp, 10))) + temp := div(temp, 10) + if iszero(temp) { break } + } + let n := sub(0x3a, o) // Header length: `26 + 32 - o`. + // Throw an out-of-offset error (consumes all gas) if the header exceeds 32 bytes. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0x20)) + mstore(s, or(mload(0x00), mload(n))) // Temporarily store the header. + result := keccak256(add(s, sub(0x20, n)), add(n, sLength)) + mstore(s, sLength) // Restore the length. + } + } + + /*´:°•.°+.*•´.*:Ëš.°*.˚•´.°:°•.°•.*•´.*:Ëš.°*.˚•´.°:°•.°+.*•´.*:*/ + /* EMPTY CALLDATA HELPERS */ + /*.•°:°.´+Ëš.*°.Ëš:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.Ëš:*.´+°.•*/ + + /// @dev Returns an empty calldata bytes. + function emptySignature() internal pure returns (bytes calldata signature) { + /// @solidity memory-safe-assembly + assembly { + signature.length := 0 + } + } +} + +// node_modules/@rhinestone/modulekit/src/module-bases/utils/TrustedForwarder.sol + +abstract contract TrustedForwarder { + // account => trustedForwarder + mapping(address account => address trustedForwarder) public trustedForwarder; + + /** + * Set the trusted forwarder for an account + * + * @param forwarder The address of the trusted forwarder + */ + function setTrustedForwarder(address forwarder) external { + trustedForwarder[msg.sender] = forwarder; + } + + /** + * Clear the trusted forwarder for an account + */ + function clearTrustedForwarder() public { + trustedForwarder[msg.sender] = address(0); + } + + /** + * Check if a forwarder is trusted for an account + * + * @param forwarder The address of the forwarder + * @param account The address of the account + * + * @return true if the forwarder is trusted for the account + */ + function isTrustedForwarder(address forwarder, address account) public view returns (bool) { + return forwarder == trustedForwarder[account]; + } + + /** + * Get the sender of the transaction + * + * @return account the sender of the transaction + */ + function _getAccount() internal view returns (address account) { + account = msg.sender; + address _account; + address forwarder; + if (msg.data.length >= 40) { + // solhint-disable-next-line no-inline-assembly + assembly { + _account := shr(96, calldataload(sub(calldatasize(), 20))) + forwarder := shr(96, calldataload(sub(calldatasize(), 40))) + } + if (forwarder == msg.sender && isTrustedForwarder(forwarder, _account)) { + account = _account; + } + } + } +} + +// node_modules/@rhinestone/checknsignatures/src/CheckNSignatures.sol + +// EIP1271 magic value +bytes4 constant EIP1271_MAGIC_VALUE = 0x1626ba7e; + +error InvalidSignature(); +error WrongContractSignatureFormat(uint256 s, uint256 contractSignatureLen, uint256 signaturesLen); +error WrongContractSignature(bytes contractSignature); +error WrongSignature(bytes signature); + +/** + * @title CheckSignatures + * @dev Library for recovering n signatures + * @author Rhinestone + * @notice This library is based on the Gnosis Safe signature recovery library + */ +library CheckSignatures { + /** + * Recover n signatures from a data hash + * + * @param dataHash The hash of the data + * @param signatures The concatenated signatures + * @param requiredSignatures The number of signatures required + * + * @return recoveredSigners The recovered signers + */ + function recoverNSignatures( + bytes32 dataHash, + bytes memory signatures, + uint256 requiredSignatures + ) + internal + view + returns (address[] memory recoveredSigners) + { + uint256 signaturesLength = signatures.length; + uint256 totalSignatures = signaturesLength / 65; + recoveredSigners = new address[](totalSignatures); + if (totalSignatures < requiredSignatures) revert InvalidSignature(); + uint256 validSigCount; + for (uint256 i; i < totalSignatures; i++) { + // split v,r,s from signatures + address _signer; + (uint8 v, bytes32 r, bytes32 s) = signatureSplit({ signatures: signatures, pos: i }); + + if (v == 0) { + // If v is 0 then it is a contract signature + _signer = isValidContractSignature(dataHash, signatures, r, s, signaturesLength); + } else if (v > 30) { + // If v > 30 then default va (27,28) has been adjusted for eth_sign flow + // To support eth_sign and similar we adjust v and hash the messageHash with the + // Ethereum message prefix before applying ecrecover + _signer = ECDSA.tryRecover({ hash: ECDSA.toEthSignedMessageHash(dataHash), v: v - 4, r: r, s: s }); + } else { + _signer = ECDSA.tryRecover({ hash: dataHash, v: v, r: r, s: s }); + } + if (_signer != address(0)) { + validSigCount++; + } + recoveredSigners[i] = _signer; + } + if (validSigCount < requiredSignatures) revert InvalidSignature(); + } + + /** + * @notice Validates a contract signature following the ERC-1271 standard + * @param dataHash Hash of the data that has been signed + * @param signatures The concatenated signatures + * @param r Signature r value + * @param s Signature s value + * @param signaturesLength The length of the signatures + */ + function isValidContractSignature( + bytes32 dataHash, + bytes memory signatures, + bytes32 r, + bytes32 s, + uint256 signaturesLength + ) + internal + view + returns (address _signer) + { + // When handling contract signatures the address of the signer contract is encoded + // into r + _signer = address(uint160(uint256(r))); + + // Check if the contract signature is in bounds: start of data is s + 32 and end is + // start + signature length + uint256 contractSignatureLen; + // solhint-disable-next-line no-inline-assembly + assembly { + contractSignatureLen := mload(add(add(signatures, s), 0x20)) + } + + // Check if the contract signature is in bounds + if (contractSignatureLen + uint256(s) + 32 > signaturesLength) { + return address(0); + } + + // Check signature + bytes memory contractSignature; + // solhint-disable-next-line no-inline-assembly + assembly { + // The signature data for contract signatures is appended to the concatenated + // signatures and the offset is stored in s + contractSignature := add(add(signatures, s), 0x20) + } + if (ISignatureValidator(_signer).isValidSignature(dataHash, contractSignature) != EIP1271_MAGIC_VALUE) { + return address(0); + } + } + + /** + * @notice Splits signature bytes into `uint8 v, bytes32 r, bytes32 s`. + * @dev Make sure to perform a bounds check for @param pos, to avoid out of bounds access on + * @param signatures The signature format is a compact form of {bytes32 r}{bytes32 s}{uint8 v} + * Compact means uint8 is not padded to 32 bytes. + * @param pos Which signature to read. A prior bounds check of this parameter should be + * performed, to avoid out of bounds access. + * @param signatures Concatenated {r, s, v} signatures. + * @return v Recovery ID or Safe signature type. + * @return r Output value r of the signature. + * @return s Output value s of the signature. + * + * @ author Gnosis Team /rmeissner + */ + function signatureSplit( + bytes memory signatures, + uint256 pos + ) + internal + pure + returns (uint8 v, bytes32 r, bytes32 s) + { + // solhint-disable-next-line no-inline-assembly + /// @solidity memory-safe-assembly + assembly { + let signaturePos := mul(0x41, pos) + r := mload(add(signatures, add(signaturePos, 0x20))) + s := mload(add(signatures, add(signaturePos, 0x40))) + v := byte(0, mload(add(signatures, add(signaturePos, 0x60)))) + } + } +} + +abstract contract ISignatureValidator { + /** + * @dev Should return whether the signature provided is valid for the provided data + * @param _dataHash Arbitrary length data signed on behalf of address(this) + * @param _signature Signature byte array associated with _data + * + * MUST return the bytes4 magic value when function passes. + * MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5) + * MUST allow external calls + */ + function isValidSignature(bytes32 _dataHash, bytes memory _signature) public view virtual returns (bytes4); +} + +// node_modules/@openzeppelin/contracts/utils/introspection/ERC165.sol + +// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165.sol) + +/** + * @dev Implementation of the {IERC165} interface. + * + * Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check + * for the additional interface id that will be supported. For example: + * + * ```solidity + * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); + * } + * ``` + */ +abstract contract ERC165 is IERC165_0 { + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { + return interfaceId == type(IERC165_0).interfaceId; + } +} + +// node_modules/@rhinestone/modulekit/src/module-bases/ERC7484RegistryAdapter.sol + +abstract contract ERC7484RegistryAdapter { + // registry address + IERC7484 public immutable REGISTRY; + + /** + * Contract constructor + * @dev sets the registry as an immutable variable + * + * @param _registry The registry address + */ + constructor(IERC7484 _registry) { + // set the registry + REGISTRY = _registry; + } +} + +// node_modules/@ERC4337/account-abstraction/contracts/interfaces/IAccount.sol + +interface IAccount { + /** + * Validate user's signature and nonce + * the entryPoint will make the call to the recipient only if this validation call returns successfully. + * signature failure should be reported by returning SIG_VALIDATION_FAILED (1). + * This allows making a "simulation call" without a valid signature + * Other failures (e.g. nonce mismatch, or invalid signature format) should still revert to signal failure. + * + * @dev Must validate caller is the entryPoint. + * Must validate the signature and nonce + * @param userOp - The operation that is about to be executed. + * @param userOpHash - Hash of the user's request data. can be used as the basis for signature. + * @param missingAccountFunds - Missing funds on the account's deposit in the entrypoint. + * This is the minimum amount to transfer to the sender(entryPoint) to be + * able to make the call. The excess is left as a deposit in the entrypoint + * for future calls. Can be withdrawn anytime using "entryPoint.withdrawTo()". + * In case there is a paymaster in the request (or the current deposit is high + * enough), this value will be zero. + * @return validationData - Packaged ValidationData structure. use `_packValidationData` and + * `_unpackValidationData` to encode and decode. + * <20-byte> sigAuthorizer - 0 for valid signature, 1 to mark signature failure, + * otherwise, an address of an "authorizer" contract. + * <6-byte> validUntil - Last timestamp this operation is valid. 0 for "indefinite" + * <6-byte> validAfter - First timestamp this operation is valid + * If an account doesn't use time-range, it is enough to + * return SIG_VALIDATION_FAILED value (1) for signature failure. + * Note that the validation code cannot use block.timestamp (or block.number) directly. + */ + function validateUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash, + uint256 missingAccountFunds + ) + external + returns (uint256 validationData); +} + +// node_modules/@ERC4337/account-abstraction/contracts/interfaces/IAccountExecute.sol + +interface IAccountExecute { + /** + * Account may implement this execute method. + * passing this methodSig at the beginning of callData will cause the entryPoint to pass the full UserOp (and hash) + * to the account. + * The account should skip the methodSig, and use the callData (and optionally, other UserOp fields) + * + * @param userOp - The operation that was just validated. + * @param userOpHash - Hash of the user's request data. + */ + function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external; +} + +// node_modules/@ERC4337/account-abstraction/contracts/interfaces/IAggregator.sol + +/** + * Aggregated Signatures validator. + */ +interface IAggregator { + /** + * Validate aggregated signature. + * Revert if the aggregated signature does not match the given list of operations. + * @param userOps - Array of UserOperations to validate the signature for. + * @param signature - The aggregated signature. + */ + function validateSignatures(PackedUserOperation[] calldata userOps, bytes calldata signature) external view; + + /** + * Validate signature of a single userOp. + * This method should be called by bundler after EntryPointSimulation.simulateValidation() returns + * the aggregator this account uses. + * First it validates the signature over the userOp. Then it returns data to be used when creating the handleOps. + * @param userOp - The userOperation received from the user. + * @return sigForUserOp - The value to put into the signature field of the userOp when calling handleOps. + * (usually empty, unless account and aggregator support some kind of "multisig". + */ + function validateUserOpSignature(PackedUserOperation calldata userOp) + external + view + returns (bytes memory sigForUserOp); + + /** + * Aggregate multiple signatures into a single value. + * This method is called off-chain to calculate the signature to pass with handleOps() + * bundler MAY use optimized custom code perform this aggregation. + * @param userOps - Array of UserOperations to collect the signatures from. + * @return aggregatedSignature - The aggregated signature. + */ + function aggregateSignatures(PackedUserOperation[] calldata userOps) + external + view + returns (bytes memory aggregatedSignature); +} + +// node_modules/@rhinestone/modulekit/src/accounts/common/interfaces/IERC7579Account.sol + +/* solhint-disable no-unused-import */ + +// Types + +// Structs +struct Execution { + address target; + uint256 value; + bytes callData; +} + +interface IERC7579Account { + event ModuleInstalled(uint256 moduleTypeId, address module); + event ModuleUninstalled(uint256 moduleTypeId, address module); + + /** + * @dev Executes a transaction on behalf of the account. + * This function is intended to be called by ERC-4337 EntryPoint.sol + * @dev Ensure adequate authorization control: i.e. onlyEntryPointOrSelf + * + * @dev MSA MUST implement this function signature. + * If a mode is requested that is not supported by the Account, it MUST revert + * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details + * @param executionCalldata The encoded execution call data + */ + function execute(ModeCode mode, bytes calldata executionCalldata) external payable; + + /** + * @dev Executes a transaction on behalf of the account. + * This function is intended to be called by Executor Modules + * @dev Ensure adequate authorization control: i.e. onlyExecutorModule + * + * @dev MSA MUST implement this function signature. + * If a mode is requested that is not supported by the Account, it MUST revert + * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details + * @param executionCalldata The encoded execution call data + */ + function executeFromExecutor( + ModeCode mode, + bytes calldata executionCalldata + ) + external + payable + returns (bytes[] memory returnData); + + /** + * @dev ERC-1271 isValidSignature + * This function is intended to be used to validate a smart account signature + * and may forward the call to a validator module + * + * @param hash The hash of the data that is signed + * @param data The data that is signed + */ + function isValidSignature(bytes32 hash, bytes calldata data) external view returns (bytes4); + + /** + * @dev installs a Module of a certain type on the smart account + * @dev Implement Authorization control of your chosing + * @param moduleTypeId the module type ID according the ERC-7579 spec + * @param module the module address + * @param initData arbitrary data that may be required on the module during `onInstall` + * initialization. + */ + function installModule(uint256 moduleTypeId, address module, bytes calldata initData) external payable; + + /** + * @dev uninstalls a Module of a certain type on the smart account + * @dev Implement Authorization control of your chosing + * @param moduleTypeId the module type ID according the ERC-7579 spec + * @param module the module address + * @param deInitData arbitrary data that may be required on the module during `onUninstall` + * de-initialization. + */ + function uninstallModule(uint256 moduleTypeId, address module, bytes calldata deInitData) external payable; + + /** + * Function to check if the account supports a certain CallType or ExecType (see ModeLib.sol) + * @param encodedMode the encoded mode + */ + function supportsExecutionMode(ModeCode encodedMode) external view returns (bool); + + /** + * Function to check if the account supports installation of a certain module type Id + * @param moduleTypeId the module type ID according the ERC-7579 spec + */ + function supportsModule(uint256 moduleTypeId) external view returns (bool); + + /** + * Function to check if the account has a certain module installed + * @param moduleTypeId the module type ID according the ERC-7579 spec + * Note: keep in mind that some contracts can be multiple module types at the same time. It + * thus may be necessary to query multiple module types + * @param module the module address + * @param additionalContext additional context data that the smart account may interpret to + * identifiy conditions under which the module is installed. + * usually this is not necessary, but for some special hooks that + * are stored in mappings, this param might be needed + */ + function isModuleInstalled( + uint256 moduleTypeId, + address module, + bytes calldata additionalContext + ) + external + view + returns (bool); + + /** + * @dev Returns the account id of the smart account + * @return accountImplementationId the account id of the smart account + * the accountId should be structured like so: + * "vendorname.accountname.semver" + */ + function accountId() external view returns (string memory accountImplementationId); +} + +// node_modules/@ERC4337/account-abstraction/contracts/interfaces/IPaymaster.sol + +/** + * The interface exposed by a paymaster contract, who agrees to pay the gas for user's operations. + * A paymaster must hold a stake to cover the required entrypoint stake and also the gas for the transaction. + */ +interface IPaymaster { + enum PostOpMode { + // User op succeeded. + opSucceeded, + // User op reverted. Still has to pay for gas. + opReverted, + // Only used internally in the EntryPoint (cleanup after postOp reverts). Never calling paymaster with this + // value + postOpReverted + } + + /** + * Payment validation: check if paymaster agrees to pay. + * Must verify sender is the entryPoint. + * Revert to reject this request. + * Note that bundlers will reject this method if it changes the state, unless the paymaster is trusted + * (whitelisted). + * The paymaster pre-pays using its deposit, and receive back a refund after the postOp method returns. + * @param userOp - The user operation. + * @param userOpHash - Hash of the user's request data. + * @param maxCost - The maximum cost of this transaction (based on maximum gas and gas price from userOp). + * @return context - Value to send to a postOp. Zero length to signify postOp is not required. + * @return validationData - Signature and time-range of this operation, encoded the same as the return + * value of validateUserOperation. + * <20-byte> sigAuthorizer - 0 for valid signature, 1 to mark signature failure, + * other values are invalid for paymaster. + * <6-byte> validUntil - last timestamp this operation is valid. 0 for "indefinite" + * <6-byte> validAfter - first timestamp this operation is valid + * Note that the validation code cannot use block.timestamp (or block.number) directly. + */ + function validatePaymasterUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash, + uint256 maxCost + ) + external + returns (bytes memory context, uint256 validationData); + + /** + * Post-operation handler. + * Must verify sender is the entryPoint. + * @param mode - Enum with the following options: + * opSucceeded - User operation succeeded. + * opReverted - User op reverted. The paymaster still has to pay for gas. + * postOpReverted - never passed in a call to postOp(). + * @param context - The context value returned by validatePaymasterUserOp + * @param actualGasCost - Actual gas used so far (without this postOp call). + * @param actualUserOpFeePerGas - the gas price this UserOp pays. This value is based on the UserOp's maxFeePerGas + * and maxPriorityFee (and basefee) + * It is not the same as tx.gasprice, which is what the bundler pays. + */ + function postOp( + PostOpMode mode, + bytes calldata context, + uint256 actualGasCost, + uint256 actualUserOpFeePerGas + ) + external; +} + +// node_modules/@ERC4337/account-abstraction/contracts/core/NonceManager.sol + +/** + * nonce management functionality + */ +abstract contract NonceManager is INonceManager { + /** + * The next valid sequence number for a given nonce key. + */ + mapping(address => mapping(uint192 => uint256)) public nonceSequenceNumber; + + /// @inheritdoc INonceManager + function getNonce(address sender, uint192 key) public view override returns (uint256 nonce) { + return nonceSequenceNumber[sender][key] | (uint256(key) << 64); + } + + // allow an account to manually increment its own nonce. + // (mainly so that during construction nonce can be made non-zero, + // to "absorb" the gas cost of first nonce increment to 1st transaction (construction), + // not to 2nd transaction) + function incrementNonce(uint192 key) public override { + nonceSequenceNumber[msg.sender][key]++; + } + + /** + * validate nonce uniqueness for this account. + * called just after validateUserOp() + * @return true if the nonce was incremented successfully. + * false if the current nonce doesn't match the given one. + */ + function _validateAndUpdateNonce(address sender, uint256 nonce) internal returns (bool) { + uint192 key = uint192(nonce >> 64); + uint64 seq = uint64(nonce); + return nonceSequenceNumber[sender][key]++ == seq; + } +} + +// node_modules/@ERC4337/account-abstraction/contracts/core/StakeManager.sol + +/* solhint-disable avoid-low-level-calls */ +/* solhint-disable not-rely-on-time */ + +/** + * Manage deposits and stakes. + * Deposit is just a balance used to pay for UserOperations (either by a paymaster or an account). + * Stake is value locked for at least "unstakeDelay" by a paymaster. + */ +abstract contract StakeManager is IStakeManager { + /// maps paymaster to their deposits and stakes + mapping(address => DepositInfo) public deposits; + + /// @inheritdoc IStakeManager + function getDepositInfo(address account) public view returns (DepositInfo memory info) { + return deposits[account]; + } + + /** + * Internal method to return just the stake info. + * @param addr - The account to query. + */ + function _getStakeInfo(address addr) internal view returns (StakeInfo memory info) { + DepositInfo storage depositInfo = deposits[addr]; + info.stake = depositInfo.stake; + info.unstakeDelaySec = depositInfo.unstakeDelaySec; + } + + /// @inheritdoc IStakeManager + function balanceOf(address account) public view returns (uint256) { + return deposits[account].deposit; + } + + receive() external payable { + depositTo(msg.sender); + } + + /** + * Increments an account's deposit. + * @param account - The account to increment. + * @param amount - The amount to increment by. + * @return the updated deposit of this account + */ + function _incrementDeposit(address account, uint256 amount) internal returns (uint256) { + DepositInfo storage info = deposits[account]; + uint256 newAmount = info.deposit + amount; + info.deposit = newAmount; + return newAmount; + } + + /** + * Add to the deposit of the given account. + * @param account - The account to add to. + */ + function depositTo(address account) public payable virtual { + uint256 newDeposit = _incrementDeposit(account, msg.value); + emit Deposited(account, newDeposit); + } + + /** + * Add to the account's stake - amount and delay + * any pending unstake is first cancelled. + * @param unstakeDelaySec The new lock duration before the deposit can be withdrawn. + */ + function addStake(uint32 unstakeDelaySec) public payable { + DepositInfo storage info = deposits[msg.sender]; + require(unstakeDelaySec > 0, "must specify unstake delay"); + require(unstakeDelaySec >= info.unstakeDelaySec, "cannot decrease unstake time"); + uint256 stake = info.stake + msg.value; + require(stake > 0, "no stake specified"); + require(stake <= type(uint112).max, "stake overflow"); + deposits[msg.sender] = DepositInfo(info.deposit, true, uint112(stake), unstakeDelaySec, 0); + emit StakeLocked(msg.sender, stake, unstakeDelaySec); + } + + /** + * Attempt to unlock the stake. + * The value can be withdrawn (using withdrawStake) after the unstake delay. + */ + function unlockStake() external { + DepositInfo storage info = deposits[msg.sender]; + require(info.unstakeDelaySec != 0, "not staked"); + require(info.staked, "already unstaking"); + uint48 withdrawTime = uint48(block.timestamp) + info.unstakeDelaySec; + info.withdrawTime = withdrawTime; + info.staked = false; + emit StakeUnlocked(msg.sender, withdrawTime); + } + + /** + * Withdraw from the (unlocked) stake. + * Must first call unlockStake and wait for the unstakeDelay to pass. + * @param withdrawAddress - The address to send withdrawn value. + */ + function withdrawStake(address payable withdrawAddress) external { + DepositInfo storage info = deposits[msg.sender]; + uint256 stake = info.stake; + require(stake > 0, "No stake to withdraw"); + require(info.withdrawTime > 0, "must call unlockStake() first"); + require(info.withdrawTime <= block.timestamp, "Stake withdrawal is not due"); + info.unstakeDelaySec = 0; + info.withdrawTime = 0; + info.stake = 0; + emit StakeWithdrawn(msg.sender, withdrawAddress, stake); + (bool success,) = withdrawAddress.call{ value: stake }(""); + require(success, "failed to withdraw stake"); + } + + /** + * Withdraw from the deposit. + * @param withdrawAddress - The address to send withdrawn value. + * @param withdrawAmount - The amount to withdraw. + */ + function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external { + DepositInfo storage info = deposits[msg.sender]; + require(withdrawAmount <= info.deposit, "Withdraw amount too large"); + info.deposit = info.deposit - withdrawAmount; + emit Withdrawn(msg.sender, withdrawAddress, withdrawAmount); + (bool success,) = withdrawAddress.call{ value: withdrawAmount }(""); + require(success, "failed to withdraw"); + } +} + +// node_modules/@rhinestone/modulekit/src/accounts/erc7579/lib/ExecutionLib.sol + +// Types + +/** + * Helper Library for decoding Execution calldata + * malloc for memory allocation is bad for gas. use this assembly instead + */ +library ExecutionLib { + error ERC7579DecodingError(); + + /** + * @notice Decode a batch of `Execution` executionBatch from a `bytes` calldata. + * @dev code is copied from solady's LibERC7579.sol + * https://github.com/Vectorized/solady/blob/740812cedc9a1fc11e17cb3d4569744367dedf19/src/accounts/LibERC7579.sol#L146 + * Credits to Vectorized and the Solady Team + */ + function decodeBatch(bytes calldata executionCalldata) + internal + pure + returns (Execution[] calldata executionBatch) + { + /// @solidity memory-safe-assembly + assembly { + let u := calldataload(executionCalldata.offset) + let s := add(executionCalldata.offset, u) + let e := sub(add(executionCalldata.offset, executionCalldata.length), 0x20) + executionBatch.offset := add(s, 0x20) + executionBatch.length := calldataload(s) + if or(shr(64, u), gt(add(s, shl(5, executionBatch.length)), e)) { + mstore(0x00, 0xba597e7e) // `DecodingError()`. + revert(0x1c, 0x04) + } + if executionBatch.length { + // Perform bounds checks on the decoded `executionBatch`. + // Loop runs out-of-gas if `executionBatch.length` is big enough to cause overflows. + for { let i := executionBatch.length } 1 { } { + i := sub(i, 1) + let p := calldataload(add(executionBatch.offset, shl(5, i))) + let c := add(executionBatch.offset, p) + let q := calldataload(add(c, 0x40)) + let o := add(c, q) + // forgefmt: disable-next-item + if or(shr(64, or(calldataload(o), or(p, q))), + or(gt(add(c, 0x40), e), gt(add(o, calldataload(o)), e))) { + mstore(0x00, 0xba597e7e) // `DecodingError()`. + revert(0x1c, 0x04) + } + if iszero(i) { break } + } + } + } + } + + function encodeBatch(Execution[] memory executions) internal pure returns (bytes memory callData) { + callData = abi.encode(executions); + } + + function decodeSingle(bytes calldata executionCalldata) + internal + pure + returns (address target, uint256 value, bytes calldata callData) + { + target = address(bytes20(executionCalldata[0:20])); + value = uint256(bytes32(executionCalldata[20:52])); + callData = executionCalldata[52:]; + } + + function encodeSingle( + address target, + uint256 value, + bytes memory callData + ) + internal + pure + returns (bytes memory userOpCalldata) + { + userOpCalldata = abi.encodePacked(target, value, callData); + } +} + +// node_modules/@ERC4337/account-abstraction/contracts/core/UserOperationLib.sol + +/* solhint-disable no-inline-assembly */ + +/** + * Utility functions helpful when working with UserOperation structs. + */ +library UserOperationLib { + uint256 public constant PAYMASTER_VALIDATION_GAS_OFFSET = 20; + uint256 public constant PAYMASTER_POSTOP_GAS_OFFSET = 36; + uint256 public constant PAYMASTER_DATA_OFFSET = 52; + /** + * Get sender from user operation data. + * @param userOp - The user operation data. + */ + + function getSender(PackedUserOperation calldata userOp) internal pure returns (address) { + address data; + //read sender from userOp, which is first userOp member (saves 800 gas...) + assembly { + data := calldataload(userOp) + } + return address(uint160(data)); + } + + /** + * Relayer/block builder might submit the TX with higher priorityFee, + * but the user should not pay above what he signed for. + * @param userOp - The user operation data. + */ + function gasPrice(PackedUserOperation calldata userOp) internal view returns (uint256) { + unchecked { + (uint256 maxPriorityFeePerGas, uint256 maxFeePerGas) = unpackUints(userOp.gasFees); + if (maxFeePerGas == maxPriorityFeePerGas) { + //legacy mode (for networks that don't support basefee opcode) + return maxFeePerGas; + } + return min(maxFeePerGas, maxPriorityFeePerGas + block.basefee); + } + } + + /** + * Pack the user operation data into bytes for hashing. + * @param userOp - The user operation data. + */ + function encode(PackedUserOperation calldata userOp) internal pure returns (bytes memory ret) { + address sender = getSender(userOp); + uint256 nonce = userOp.nonce; + bytes32 hashInitCode = calldataKeccak(userOp.initCode); + bytes32 hashCallData = calldataKeccak(userOp.callData); + bytes32 accountGasLimits = userOp.accountGasLimits; + uint256 preVerificationGas = userOp.preVerificationGas; + bytes32 gasFees = userOp.gasFees; + bytes32 hashPaymasterAndData = calldataKeccak(userOp.paymasterAndData); + + return abi.encode( + sender, + nonce, + hashInitCode, + hashCallData, + accountGasLimits, + preVerificationGas, + gasFees, + hashPaymasterAndData + ); + } + + function unpackUints(bytes32 packed) internal pure returns (uint256 high128, uint256 low128) { + return (uint128(bytes16(packed)), uint128(uint256(packed))); + } + + //unpack just the high 128-bits from a packed value + function unpackHigh128(bytes32 packed) internal pure returns (uint256) { + return uint256(packed) >> 128; + } + + // unpack just the low 128-bits from a packed value + function unpackLow128(bytes32 packed) internal pure returns (uint256) { + return uint128(uint256(packed)); + } + + function unpackMaxPriorityFeePerGas(PackedUserOperation calldata userOp) internal pure returns (uint256) { + return unpackHigh128(userOp.gasFees); + } + + function unpackMaxFeePerGas(PackedUserOperation calldata userOp) internal pure returns (uint256) { + return unpackLow128(userOp.gasFees); + } + + function unpackVerificationGasLimit(PackedUserOperation calldata userOp) internal pure returns (uint256) { + return unpackHigh128(userOp.accountGasLimits); + } + + function unpackCallGasLimit(PackedUserOperation calldata userOp) internal pure returns (uint256) { + return unpackLow128(userOp.accountGasLimits); + } + + function unpackPaymasterVerificationGasLimit(PackedUserOperation calldata userOp) internal pure returns (uint256) { + return uint128(bytes16(userOp.paymasterAndData[PAYMASTER_VALIDATION_GAS_OFFSET:PAYMASTER_POSTOP_GAS_OFFSET])); + } + + function unpackPostOpGasLimit(PackedUserOperation calldata userOp) internal pure returns (uint256) { + return uint128(bytes16(userOp.paymasterAndData[PAYMASTER_POSTOP_GAS_OFFSET:PAYMASTER_DATA_OFFSET])); + } + + function unpackPaymasterStaticFields(bytes calldata paymasterAndData) + internal + pure + returns (address paymaster, uint256 validationGasLimit, uint256 postOpGasLimit) + { + return ( + address(bytes20(paymasterAndData[:PAYMASTER_VALIDATION_GAS_OFFSET])), + uint128(bytes16(paymasterAndData[PAYMASTER_VALIDATION_GAS_OFFSET:PAYMASTER_POSTOP_GAS_OFFSET])), + uint128(bytes16(paymasterAndData[PAYMASTER_POSTOP_GAS_OFFSET:PAYMASTER_DATA_OFFSET])) + ); + } + + /** + * Hash the user operation data. + * @param userOp - The user operation data. + */ + function hash(PackedUserOperation calldata userOp) internal pure returns (bytes32) { + return keccak256(encode(userOp)); + } +} + +// node_modules/@rhinestone/modulekit/src/accounts/common/interfaces/IERC7579Module.sol + +// Types + +// Constants +uint256 constant VALIDATION_SUCCESS = 0; +uint256 constant VALIDATION_FAILED = 1; +uint256 constant MODULE_TYPE_VALIDATOR_1 = 1; +uint256 constant MODULE_TYPE_EXECUTOR_1 = 2; +uint256 constant MODULE_TYPE_FALLBACK_1 = 3; +uint256 constant MODULE_TYPE_HOOK_1 = 4; +uint256 constant MODULE_TYPE_PREVALIDATION_HOOK_ERC1271 = 8; +uint256 constant MODULE_TYPE_PREVALIDATION_HOOK_ERC4337 = 9; + +interface IModule { + error ModuleAlreadyInitialized(address smartAccount); + error NotInitialized(address smartAccount); + + /** + * @dev This function is called by the smart account during installation of the module + * @param data arbitrary data that may be required on the module during `onInstall` + * initialization + * + * MUST revert on error (i.e. if module is already enabled) + */ + function onInstall(bytes calldata data) external; + + /** + * @dev This function is called by the smart account during uninstallation of the module + * @param data arbitrary data that may be required on the module during `onUninstall` + * de-initialization + * + * MUST revert on error + */ + function onUninstall(bytes calldata data) external; + + /** + * @dev Returns boolean value if module is a certain type + * @param moduleTypeId the module type ID according the ERC-7579 spec + * + * MUST return true if the module is of the given type and false otherwise + */ + function isModuleType(uint256 moduleTypeId) external view returns (bool); + + /** + * @dev Returns if the module was already initialized for a provided smartaccount + */ + function isInitialized(address smartAccount) external view returns (bool); +} + +interface IValidator is IModule { + error InvalidTargetAddress(address target); + + /** + * @dev Validates a transaction on behalf of the account. + * This function is intended to be called by the MSA during the ERC-4337 validaton phase + * Note: solely relying on bytes32 hash and signature is not sufficient for some + * validation implementations (i.e. SessionKeys often need access to userOp.calldata) + * @param userOp The user operation to be validated. The userOp MUST NOT contain any metadata. + * The MSA MUST clean up the userOp before sending it to the validator. + * @param userOpHash The hash of the user operation to be validated + * @return return value according to ERC-4337 + */ + function validateUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash + ) + external + payable + returns (uint256); + + /** + * Validator can be used for ERC-1271 validation + */ + function isValidSignatureWithSender( + address sender, + bytes32 hash, + bytes calldata data + ) + external + view + returns (bytes4); +} + +interface IExecutor is IModule { } + +interface IHook is IModule { + function preCheck( + address msgSender, + uint256 msgValue, + bytes calldata msgData + ) + external + returns (bytes memory hookData); + + function postCheck(bytes calldata hookData) external; +} + +interface IFallback is IModule { } + +interface IPolicy_0 is IModule { + function checkUserOpPolicy(bytes32 id, PackedUserOperation calldata userOp) external payable returns (uint256); + function checkSignaturePolicy( + bytes32 id, + address sender, + bytes32 hash, + bytes calldata sig + ) + external + view + returns (uint256); +} + +interface ISigner is IModule { + function checkUserOpSignature( + bytes32 id, + PackedUserOperation calldata userOp, + bytes32 userOpHash + ) + external + payable + returns (uint256); + function checkSignature( + bytes32 id, + address sender, + bytes32 hash, + bytes calldata sig + ) + external + view + returns (bytes4); +} + +interface IPreValidationHookERC1271 is IModule { + function preValidationHookERC1271( + address sender, + bytes32 hash, + bytes calldata data + ) + external + view + returns (bytes32 hookHash, bytes memory hookSignature); +} + +interface IPreValidationHookERC4337 is IModule { + function preValidationHookERC4337( + PackedUserOperation calldata userOp, + uint256 missingAccountFunds, + bytes32 userOpHash + ) + external + returns (bytes32 hookHash, bytes memory hookSignature); +} + +// node_modules/@ERC4337/account-abstraction/contracts/interfaces/IEntryPoint.sol +/** + * Account-Abstraction (EIP-4337) singleton EntryPoint implementation. + * Only one instance required on each chain. + * + */ + +/* solhint-disable avoid-low-level-calls */ +/* solhint-disable no-inline-assembly */ +/* solhint-disable reason-string */ + +interface IEntryPoint is IStakeManager, INonceManager { + /** + * + * An event emitted after each successful request. + * @param userOpHash - Unique identifier for the request (hash its entire content, except signature). + * @param sender - The account that generates this request. + * @param paymaster - If non-null, the paymaster that pays for this request. + * @param nonce - The nonce value from the request. + * @param success - True if the sender transaction succeeded, false if reverted. + * @param actualGasCost - Actual amount paid (by account or paymaster) for this UserOperation. + * @param actualGasUsed - Total gas used by this UserOperation (including preVerification, creation, + * validation and execution). + */ + event UserOperationEvent( + bytes32 indexed userOpHash, + address indexed sender, + address indexed paymaster, + uint256 nonce, + bool success, + uint256 actualGasCost, + uint256 actualGasUsed + ); + + /** + * Account "sender" was deployed. + * @param userOpHash - The userOp that deployed this account. UserOperationEvent will follow. + * @param sender - The account that is deployed + * @param factory - The factory used to deploy this account (in the initCode) + * @param paymaster - The paymaster used by this UserOp + */ + event AccountDeployed(bytes32 indexed userOpHash, address indexed sender, address factory, address paymaster); + + /** + * An event emitted if the UserOperation "callData" reverted with non-zero length. + * @param userOpHash - The request unique identifier. + * @param sender - The sender of this request. + * @param nonce - The nonce used in the request. + * @param revertReason - The return bytes from the (reverted) call to "callData". + */ + event UserOperationRevertReason( + bytes32 indexed userOpHash, address indexed sender, uint256 nonce, bytes revertReason + ); + + /** + * An event emitted if the UserOperation Paymaster's "postOp" call reverted with non-zero length. + * @param userOpHash - The request unique identifier. + * @param sender - The sender of this request. + * @param nonce - The nonce used in the request. + * @param revertReason - The return bytes from the (reverted) call to "callData". + */ + event PostOpRevertReason(bytes32 indexed userOpHash, address indexed sender, uint256 nonce, bytes revertReason); + + /** + * UserOp consumed more than prefund. The UserOperation is reverted, and no refund is made. + * @param userOpHash - The request unique identifier. + * @param sender - The sender of this request. + * @param nonce - The nonce used in the request. + */ + event UserOperationPrefundTooLow(bytes32 indexed userOpHash, address indexed sender, uint256 nonce); + + /** + * An event emitted by handleOps(), before starting the execution loop. + * Any event emitted before this event, is part of the validation. + */ + event BeforeExecution(); + + /** + * Signature aggregator used by the following UserOperationEvents within this bundle. + * @param aggregator - The aggregator used for the following UserOperationEvents. + */ + event SignatureAggregatorChanged(address indexed aggregator); + + /** + * A custom revert error of handleOps, to identify the offending op. + * Should be caught in off-chain handleOps simulation and not happen on-chain. + * Useful for mitigating DoS attempts against batchers or for troubleshooting of factory/account/paymaster reverts. + * NOTE: If simulateValidation passes successfully, there should be no reason for handleOps to fail on it. + * @param opIndex - Index into the array of ops to the failed one (in simulateValidation, this is always zero). + * @param reason - Revert reason. The string starts with a unique code "AAmn", + * where "m" is "1" for factory, "2" for account and "3" for paymaster issues, + * so a failure can be attributed to the correct entity. + */ + error FailedOp(uint256 opIndex, string reason); + + /** + * A custom revert error of handleOps, to report a revert by account or paymaster. + * @param opIndex - Index into the array of ops to the failed one (in simulateValidation, this is always zero). + * @param reason - Revert reason. see FailedOp(uint256,string), above + * @param inner - data from inner cought revert reason + * @dev note that inner is truncated to 2048 bytes + */ + error FailedOpWithRevert(uint256 opIndex, string reason, bytes inner); + + error PostOpReverted(bytes returnData); + + /** + * Error case when a signature aggregator fails to verify the aggregated signature it had created. + * @param aggregator The aggregator that failed to verify the signature + */ + error SignatureValidationFailed(address aggregator); + + // Return value of getSenderAddress. + error SenderAddressResult(address sender); + + // UserOps handled, per aggregator. + struct UserOpsPerAggregator { + PackedUserOperation[] userOps; + // Aggregator address + IAggregator aggregator; + // Aggregated signature + bytes signature; + } + + /** + * Execute a batch of UserOperations. + * No signature aggregator is used. + * If any account requires an aggregator (that is, it returned an aggregator when + * performing simulateValidation), then handleAggregatedOps() must be used instead. + * @param ops - The operations to execute. + * @param beneficiary - The address to receive the fees. + */ + function handleOps(PackedUserOperation[] calldata ops, address payable beneficiary) external; + + /** + * Execute a batch of UserOperation with Aggregators + * @param opsPerAggregator - The operations to execute, grouped by aggregator (or address(0) for no-aggregator + * accounts). + * @param beneficiary - The address to receive the fees. + */ + function handleAggregatedOps( + UserOpsPerAggregator[] calldata opsPerAggregator, + address payable beneficiary + ) + external; + + /** + * Generate a request Id - unique identifier for this request. + * The request ID is a hash over the content of the userOp (except the signature), the entrypoint and the chainid. + * @param userOp - The user operation to generate the request ID for. + * @return hash the hash of this UserOperation + */ + function getUserOpHash(PackedUserOperation calldata userOp) external view returns (bytes32); + + /** + * Gas and return values during simulation. + * @param preOpGas - The gas used for validation (including preValidationGas) + * @param prefund - The required prefund for this operation + * @param accountValidationData - returned validationData from account. + * @param paymasterValidationData - return validationData from paymaster. + * @param paymasterContext - Returned by validatePaymasterUserOp (to be passed into postOp) + */ + struct ReturnInfo { + uint256 preOpGas; + uint256 prefund; + uint256 accountValidationData; + uint256 paymasterValidationData; + bytes paymasterContext; + } + + /** + * Returned aggregated signature info: + * The aggregator returned by the account, and its current stake. + */ + struct AggregatorStakeInfo { + address aggregator; + StakeInfo stakeInfo; + } + + /** + * Get counterfactual sender address. + * Calculate the sender contract address that will be generated by the initCode and salt in the UserOperation. + * This method always revert, and returns the address in SenderAddressResult error + * @param initCode - The constructor code to be passed into the UserOperation. + */ + function getSenderAddress(bytes memory initCode) external; + + error DelegateAndRevert(bool success, bytes ret); + + /** + * Helper method for dry-run testing. + * @dev calling this method, the EntryPoint will make a delegatecall to the given data, and report (via revert) the + * result. + * The method always revert, so is only useful off-chain for dry run calls, in cases where state-override to + * replace + * actual EntryPoint code is less convenient. + * @param target a target contract to make a delegatecall from entrypoint + * @param data data to pass to target in a delegatecall + */ + function delegateAndRevert(address target, bytes calldata data) external; +} + +// node_modules/@rhinestone/modulekit/src/module-bases/ERC7579ModuleBase.sol + +abstract contract ERC7579ModuleBase is IModule { + uint256 internal constant TYPE_VALIDATOR = MODULE_TYPE_VALIDATOR_0; + uint256 internal constant TYPE_EXECUTOR = MODULE_TYPE_EXECUTOR_0; + uint256 internal constant TYPE_FALLBACK = MODULE_TYPE_FALLBACK_0; + uint256 internal constant TYPE_HOOK = MODULE_TYPE_HOOK_0; + uint256 internal constant TYPE_POLICY = MODULE_TYPE_POLICY; + uint256 internal constant TYPE_SIGNER = MODULE_TYPE_SIGNER; + uint256 internal constant TYPE_STATELESS_VALIDATOR = MODULE_TYPE_STATELESS_VALIDATOR; +} + +// node_modules/@ERC4337/account-abstraction/contracts/interfaces/IEntryPointSimulations.sol + +interface IEntryPointSimulations is IEntryPoint { + // Return value of simulateHandleOp. + struct ExecutionResult { + uint256 preOpGas; + uint256 paid; + uint256 accountValidationData; + uint256 paymasterValidationData; + bool targetSuccess; + bytes targetResult; + } + + /** + * Successful result from simulateValidation. + * If the account returns a signature aggregator the "aggregatorInfo" struct is filled in as well. + * @param returnInfo Gas and time-range returned values + * @param senderInfo Stake information about the sender + * @param factoryInfo Stake information about the factory (if any) + * @param paymasterInfo Stake information about the paymaster (if any) + * @param aggregatorInfo Signature aggregation info (if the account requires signature aggregator) + * Bundler MUST use it to verify the signature, or reject the UserOperation. + */ + struct ValidationResult { + ReturnInfo returnInfo; + StakeInfo senderInfo; + StakeInfo factoryInfo; + StakeInfo paymasterInfo; + AggregatorStakeInfo aggregatorInfo; + } + + /** + * Simulate a call to account.validateUserOp and paymaster.validatePaymasterUserOp. + * @dev The node must also verify it doesn't use banned opcodes, and that it doesn't reference storage + * outside the account's data. + * @param userOp - The user operation to validate. + * @return the validation result structure + */ + function simulateValidation(PackedUserOperation calldata userOp) external returns (ValidationResult memory); + + /** + * Simulate full execution of a UserOperation (including both validation and target execution) + * It performs full validation of the UserOperation, but ignores signature error. + * An optional target address is called after the userop succeeds, + * and its value is returned (before the entire call is reverted). + * Note that in order to collect the the success/failure of the target call, it must be executed + * with trace enabled to track the emitted events. + * @param op The UserOperation to simulate. + * @param target - If nonzero, a target address to call after userop simulation. If called, + * the targetSuccess and targetResult are set to the return from that call. + * @param targetCallData - CallData to pass to target address. + * @return the execution result structure + */ + function simulateHandleOp( + PackedUserOperation calldata op, + address target, + bytes calldata targetCallData + ) + external + returns (ExecutionResult memory); +} + +// node_modules/@rhinestone/modulekit/src/module-bases/ERC7579FallbackBase.sol + +abstract contract ERC7579FallbackBase is IFallback, ERC7579ModuleBase { + /** + * @notice Allows fetching the original caller address. + * @dev This is only reliable in combination with a FallbackManager that supports this (e.g. Safe + * contract >=1.3.0). + * When using this functionality make sure that the linked _manager (aka msg.sender) + * supports this. + * This function does not rely on a trusted forwarder. Use the returned value only to + * check information against the calling manager. + * @return sender Original caller address. + */ + function _msgSender() internal pure returns (address sender) { + // The assembly code is more direct than the Solidity version using `abi.decode`. + /* solhint-disable no-inline-assembly */ + /// @solidity memory-safe-assembly + assembly { + sender := shr(96, calldataload(sub(calldatasize(), 20))) + } + /* solhint-enable no-inline-assembly */ + } +} + +// node_modules/@rhinestone/modulekit/src/module-bases/ERC7579HookBase.sol + +abstract contract ERC7579HookBase is IHook, ERC7579ModuleBase, TrustedForwarder { + /** + * Precheck hook + * + * @param msgSender sender of the transaction + * @param msgValue value of the transaction + * @param msgData data of the transaction + * + * @return hookData data for the postcheck hook + */ + function preCheck( + address msgSender, + uint256 msgValue, + bytes calldata msgData + ) + external + virtual + returns (bytes memory hookData) + { + // route to internal function + return _preCheck(_getAccount(), msgSender, msgValue, msgData); + } + + /** + * Postcheck hook + * + * @param hookData data from the precheck hook + */ + function postCheck(bytes calldata hookData) external virtual { + // route to internal function + _postCheck(_getAccount(), hookData); + } + + /** + * Precheck hook + * + * @param account account of the transaction + * @param msgSender sender of the transaction + * @param msgValue value of the transaction + * @param msgData data of the transaction + * + * @return hookData data for the postcheck hook + */ + function _preCheck( + address account, + address msgSender, + uint256 msgValue, + bytes calldata msgData + ) + internal + virtual + returns (bytes memory hookData); + + /** + * Postcheck hook + * + * @param account account of the transaction + * @param hookData data from the precheck hook + */ + function _postCheck(address account, bytes calldata hookData) internal virtual; +} + +// node_modules/@rhinestone/modulekit/src/module-bases/ERC7579StatelessValidatorBase.sol + +abstract contract ERC7579StatelessValidatorBase is ERC7579ModuleBase, IStatelessValidator { + function validateSignatureWithData( + bytes32, + bytes calldata, + bytes calldata + ) + external + view + virtual + returns (bool validSig); +} + +// node_modules/@rhinestone/modulekit/src/module-bases/ERC7579ExecutorBase.sol + +abstract contract ERC7579ExecutorBase is IExecutor, ERC7579ModuleBase { + function _execute( + address account, + address to, + uint256 value, + bytes memory data + ) + internal + returns (bytes memory result) + { + ModeCode modeCode = ModeLib.encode({ + callType: CALLTYPE_SINGLE, + execType: EXECTYPE_DEFAULT, + mode: MODE_DEFAULT, + payload: ModePayload.wrap(bytes22(0)) + }); + + return IERC7579Account(account).executeFromExecutor(modeCode, ExecutionLib.encodeSingle(to, value, data))[0]; + } + + function _execute(address to, uint256 value, bytes memory data) internal returns (bytes memory result) { + return _execute(msg.sender, to, value, data); + } + + function _execute(address account, Execution[] memory execs) internal returns (bytes[] memory results) { + ModeCode modeCode = ModeLib.encode({ + callType: CALLTYPE_BATCH, + execType: EXECTYPE_DEFAULT, + mode: MODE_DEFAULT, + payload: ModePayload.wrap(bytes22(0)) + }); + results = IERC7579Account(account).executeFromExecutor(modeCode, ExecutionLib.encodeBatch(execs)); + } + + function _execute(Execution[] memory execs) internal returns (bytes[] memory results) { + return _execute(msg.sender, execs); + } + + // Note: Not every account will support delegatecalls + function _executeDelegateCall( + address account, + address delegateTarget, + bytes memory callData + ) + internal + returns (bytes[] memory results) + { + ModeCode modeCode = ModeLib.encode({ + callType: CALLTYPE_DELEGATECALL, + execType: EXECTYPE_DEFAULT, + mode: MODE_DEFAULT, + payload: ModePayload.wrap(bytes22(0)) + }); + results = IERC7579Account(account).executeFromExecutor(modeCode, abi.encodePacked(delegateTarget, callData)); + } + + // Note: Not every account will support delegatecalls + function _executeDelegateCall( + address delegateTarget, + bytes memory callData + ) + internal + returns (bytes[] memory results) + { + return _executeDelegateCall(msg.sender, delegateTarget, callData); + } +} + +// node_modules/@rhinestone/modulekit/src/module-bases/SchedulingBase.sol + +abstract contract SchedulingBase is ERC7579ExecutorBase { + /*////////////////////////////////////////////////////////////////////////// + CONSTANTS & STORAGE + //////////////////////////////////////////////////////////////////////////*/ + + error InvalidExecution(); + + event ExecutionAdded(address indexed smartAccount, uint256 indexed jobId); + event ExecutionTriggered(address indexed smartAccount, uint256 indexed jobId); + event ExecutionStatusUpdated(address indexed smartAccount, uint256 indexed jobId); + event ExecutionsCancelled(address indexed smartAccount); + + mapping(address smartAccount => mapping(uint256 jobId => ExecutionConfig)) public executionLog; + + mapping(address smartAccount => uint256 jobCount) public accountJobCount; + + struct ExecutionConfig { + uint48 executeInterval; + uint16 numberOfExecutions; + uint16 numberOfExecutionsCompleted; + uint48 startDate; + bool isEnabled; + uint48 lastExecutionTime; + bytes executionData; + } + + struct ExecutorAccess { + uint256 jobId; + } + + /*////////////////////////////////////////////////////////////////////////// + CONFIG + //////////////////////////////////////////////////////////////////////////*/ + + function _onInstall(bytes calldata packedSchedulingData) internal { + address account = msg.sender; + if (isInitialized(account)) { + revert ModuleAlreadyInitialized(account); + } + + _createExecution({ orderData: packedSchedulingData }); + } + + function _onUninstall() internal { + address account = msg.sender; + + uint256 count = accountJobCount[account]; + for (uint256 i = 1; i <= count; i++) { + delete executionLog[account][i]; + } + accountJobCount[account] = 0; + + emit ExecutionsCancelled(account); + } + + function isInitialized(address smartAccount) public view returns (bool) { + return accountJobCount[smartAccount] != 0; + } + + function addOrder(bytes calldata orderData) external { + address account = msg.sender; + if (!isInitialized(account)) revert NotInitialized(account); + + _createExecution({ orderData: orderData }); + } + + function toggleOrder(uint256 jobId) external { + address account = msg.sender; + + ExecutionConfig storage executionConfig = executionLog[account][jobId]; + + if (executionConfig.numberOfExecutions == 0) { + revert InvalidExecution(); + } + + executionConfig.isEnabled = !executionConfig.isEnabled; + + emit ExecutionStatusUpdated(account, jobId); + } + + /*////////////////////////////////////////////////////////////////////////// + INTERNAL + //////////////////////////////////////////////////////////////////////////*/ + + function _createExecution(bytes calldata orderData) internal { + address account = msg.sender; + + uint256 jobId = accountJobCount[account] + 1; + accountJobCount[account]++; + + // prevent user from supplying an invalid number of execution (0) + uint16 nrOfExecutions = uint16(bytes2(orderData[6:8])); + if (nrOfExecutions == 0) revert InvalidExecution(); + + executionLog[account][jobId] = ExecutionConfig({ + numberOfExecutionsCompleted: 0, + isEnabled: true, + lastExecutionTime: 0, + executeInterval: uint48(bytes6(orderData[0:6])), + numberOfExecutions: nrOfExecutions, + startDate: uint48(bytes6(orderData[8:14])), + executionData: orderData[14:] + }); + + emit ExecutionAdded(account, jobId); + } + + function _isExecutionValid(uint256 jobId) internal view { + ExecutionConfig storage executionConfig = executionLog[msg.sender][jobId]; + + if (!executionConfig.isEnabled) { + revert InvalidExecution(); + } + + if (executionConfig.lastExecutionTime + executionConfig.executeInterval > block.timestamp) { + revert InvalidExecution(); + } + + if (executionConfig.numberOfExecutionsCompleted >= executionConfig.numberOfExecutions) { + revert InvalidExecution(); + } + + if (executionConfig.startDate > block.timestamp) { + revert InvalidExecution(); + } + } + + modifier canExecute(uint256 jobId) { + _isExecutionValid(jobId); + _; + } + + /*////////////////////////////////////////////////////////////////////////// + METADATA + //////////////////////////////////////////////////////////////////////////*/ + + function isModuleType(uint256 typeID) external pure override returns (bool) { + return typeID == TYPE_EXECUTOR; + } +} + +// node_modules/@ERC4337/account-abstraction/contracts/core/EntryPoint.sol + +/* solhint-disable avoid-low-level-calls */ +/* solhint-disable no-inline-assembly */ + +/* + * Account-Abstraction (EIP-4337) singleton EntryPoint implementation. + * Only one instance required on each chain. + */ + +/// @custom:security-contact https://bounty.ethereum.org +contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, ERC165, GasDebug { + using UserOperationLib for PackedUserOperation; + + SenderCreator private immutable _senderCreator = new SenderCreator(); + + function senderCreator() internal view virtual returns (SenderCreator) { + return _senderCreator; + } + + //compensate for innerHandleOps' emit message and deposit refund. + // allow some slack for future gas price changes. + uint256 private constant INNER_GAS_OVERHEAD = 10_000; + + // Marker for inner call revert on out of gas + bytes32 private constant INNER_OUT_OF_GAS = hex"deaddead"; + bytes32 private constant INNER_REVERT_LOW_PREFUND = hex"deadaa51"; + + uint256 private constant REVERT_REASON_MAX_LEN = 2048; + uint256 private constant PENALTY_PERCENT = 10; + + /// @inheritdoc IERC165_0 + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + // note: solidity "type(IEntryPoint).interfaceId" is without inherited methods but we want to check everything + return interfaceId + == (type(IEntryPoint).interfaceId ^ type(IStakeManager).interfaceId ^ type(INonceManager).interfaceId) + || interfaceId == type(IEntryPoint).interfaceId || interfaceId == type(IStakeManager).interfaceId + || interfaceId == type(INonceManager).interfaceId || super.supportsInterface(interfaceId); + } + + /** + * Compensate the caller's beneficiary address with the collected fees of all UserOperations. + * @param beneficiary - The address to receive the fees. + * @param amount - Amount to transfer. + */ + function _compensate(address payable beneficiary, uint256 amount) internal { + require(beneficiary != address(0), "AA90 invalid beneficiary"); + (bool success,) = beneficiary.call{ value: amount }(""); + require(success, "AA91 failed send to beneficiary"); + } + + /** + * Execute a user operation. + * @param opIndex - Index into the opInfo array. + * @param userOp - The userOp to execute. + * @param opInfo - The opInfo filled by validatePrepayment for this userOp. + * @return collected - The total amount this userOp paid. + */ + function _executeUserOp( + uint256 opIndex, + PackedUserOperation calldata userOp, + UserOpInfo memory opInfo + ) + internal + returns (uint256 collected) + { + uint256 preGas = gasleft(); + bytes memory context = getMemoryBytesFromOffset(opInfo.contextOffset); + bool success; + { + uint256 saveFreePtr; + assembly ("memory-safe") { + saveFreePtr := mload(0x40) + } + bytes calldata callData = userOp.callData; + bytes memory innerCall; + bytes4 methodSig; + assembly { + let len := callData.length + if gt(len, 3) { methodSig := calldataload(callData.offset) } + } + if (methodSig == IAccountExecute.executeUserOp.selector) { + bytes memory executeUserOp = abi.encodeCall(IAccountExecute.executeUserOp, (userOp, opInfo.userOpHash)); + innerCall = abi.encodeCall(this.innerHandleOp, (executeUserOp, opInfo, context)); + } else { + innerCall = abi.encodeCall(this.innerHandleOp, (callData, opInfo, context)); + } + assembly ("memory-safe") { + success := call(gas(), address(), 0, add(innerCall, 0x20), mload(innerCall), 0, 32) + collected := mload(0) + mstore(0x40, saveFreePtr) + } + } + if (!success) { + bytes32 innerRevertCode; + assembly ("memory-safe") { + let len := returndatasize() + if eq(32, len) { + returndatacopy(0, 0, 32) + innerRevertCode := mload(0) + } + } + if (innerRevertCode == INNER_OUT_OF_GAS) { + // handleOps was called with gas limit too low. abort entire bundle. + //can only be caused by bundler (leaving not enough gas for inner call) + revert FailedOp(opIndex, "AA95 out of gas"); + } else if (innerRevertCode == INNER_REVERT_LOW_PREFUND) { + // innerCall reverted on prefund too low. treat entire prefund as "gas cost" + uint256 actualGas = preGas - gasleft() + opInfo.preOpGas; + uint256 actualGasCost = opInfo.prefund; + emitPrefundTooLow(opInfo); + emitUserOperationEvent(opInfo, false, actualGasCost, actualGas); + collected = actualGasCost; + } else { + emit PostOpRevertReason( + opInfo.userOpHash, + opInfo.mUserOp.sender, + opInfo.mUserOp.nonce, + Exec.getReturnData(REVERT_REASON_MAX_LEN) + ); + + uint256 actualGas = preGas - gasleft() + opInfo.preOpGas; + collected = _postExecution(IPaymaster.PostOpMode.postOpReverted, opInfo, context, actualGas); + } + } + } + + function emitUserOperationEvent( + UserOpInfo memory opInfo, + bool success, + uint256 actualGasCost, + uint256 actualGas + ) + internal + virtual + { + emit UserOperationEvent( + opInfo.userOpHash, + opInfo.mUserOp.sender, + opInfo.mUserOp.paymaster, + opInfo.mUserOp.nonce, + success, + actualGasCost, + actualGas + ); + } + + function emitPrefundTooLow(UserOpInfo memory opInfo) internal virtual { + emit UserOperationPrefundTooLow(opInfo.userOpHash, opInfo.mUserOp.sender, opInfo.mUserOp.nonce); + } + + /// @inheritdoc IEntryPoint + function handleOps(PackedUserOperation[] calldata ops, address payable beneficiary) public nonReentrant { + uint256 opslen = ops.length; + UserOpInfo[] memory opInfos = new UserOpInfo[](opslen); + + unchecked { + for (uint256 i = 0; i < opslen; i++) { + UserOpInfo memory opInfo = opInfos[i]; + (uint256 validationData, uint256 pmValidationData) = _validatePrepayment(i, ops[i], opInfo); + _validateAccountAndPaymasterValidationData(i, validationData, pmValidationData, address(0)); + } + + uint256 collected = 0; + emit BeforeExecution(); + + for (uint256 i = 0; i < opslen; i++) { + collected += _executeUserOp(i, ops[i], opInfos[i]); + } + + _compensate(beneficiary, collected); + } + } + + /// @inheritdoc IEntryPoint + function handleAggregatedOps( + UserOpsPerAggregator[] calldata opsPerAggregator, + address payable beneficiary + ) + public + nonReentrant + { + uint256 opasLen = opsPerAggregator.length; + uint256 totalOps = 0; + for (uint256 i = 0; i < opasLen; i++) { + UserOpsPerAggregator calldata opa = opsPerAggregator[i]; + PackedUserOperation[] calldata ops = opa.userOps; + IAggregator aggregator = opa.aggregator; + + //address(1) is special marker of "signature error" + require(address(aggregator) != address(1), "AA96 invalid aggregator"); + + if (address(aggregator) != address(0)) { + // solhint-disable-next-line no-empty-blocks + try aggregator.validateSignatures(ops, opa.signature) { } + catch { + revert SignatureValidationFailed(address(aggregator)); + } + } + + totalOps += ops.length; + } + + UserOpInfo[] memory opInfos = new UserOpInfo[](totalOps); + + uint256 opIndex = 0; + for (uint256 a = 0; a < opasLen; a++) { + UserOpsPerAggregator calldata opa = opsPerAggregator[a]; + PackedUserOperation[] calldata ops = opa.userOps; + IAggregator aggregator = opa.aggregator; + + uint256 opslen = ops.length; + for (uint256 i = 0; i < opslen; i++) { + UserOpInfo memory opInfo = opInfos[opIndex]; + (uint256 validationData, uint256 paymasterValidationData) = _validatePrepayment(opIndex, ops[i], opInfo); + _validateAccountAndPaymasterValidationData( + i, validationData, paymasterValidationData, address(aggregator) + ); + opIndex++; + } + } + + emit BeforeExecution(); + + uint256 collected = 0; + opIndex = 0; + for (uint256 a = 0; a < opasLen; a++) { + UserOpsPerAggregator calldata opa = opsPerAggregator[a]; + emit SignatureAggregatorChanged(address(opa.aggregator)); + PackedUserOperation[] calldata ops = opa.userOps; + uint256 opslen = ops.length; + + for (uint256 i = 0; i < opslen; i++) { + collected += _executeUserOp(opIndex, ops[i], opInfos[opIndex]); + opIndex++; + } + } + emit SignatureAggregatorChanged(address(0)); + + _compensate(beneficiary, collected); + } + + /** + * A memory copy of UserOp static fields only. + * Excluding: callData, initCode and signature. Replacing paymasterAndData with paymaster. + */ + struct MemoryUserOp { + address sender; + uint256 nonce; + uint256 verificationGasLimit; + uint256 callGasLimit; + uint256 paymasterVerificationGasLimit; + uint256 paymasterPostOpGasLimit; + uint256 preVerificationGas; + address paymaster; + uint256 maxFeePerGas; + uint256 maxPriorityFeePerGas; + } + + struct UserOpInfo { + MemoryUserOp mUserOp; + bytes32 userOpHash; + uint256 prefund; + uint256 contextOffset; + uint256 preOpGas; + } + + /** + * Inner function to handle a UserOperation. + * Must be declared "external" to open a call context, but it can only be called by handleOps. + * @param callData - The callData to execute. + * @param opInfo - The UserOpInfo struct. + * @param context - The context bytes. + * @return actualGasCost - the actual cost in eth this UserOperation paid for gas + */ + function innerHandleOp( + bytes memory callData, + UserOpInfo memory opInfo, + bytes calldata context + ) + external + returns (uint256 actualGasCost) + { + uint256 preGas = gasleft(); + require(msg.sender == address(this), "AA92 internal call only"); + MemoryUserOp memory mUserOp = opInfo.mUserOp; + + uint256 callGasLimit = mUserOp.callGasLimit; + unchecked { + // handleOps was called with gas limit too low. abort entire bundle. + if (gasleft() * 63 / 64 < callGasLimit + mUserOp.paymasterPostOpGasLimit + INNER_GAS_OVERHEAD) { + assembly ("memory-safe") { + mstore(0, INNER_OUT_OF_GAS) + revert(0, 32) + } + } + } + + IPaymaster.PostOpMode mode = IPaymaster.PostOpMode.opSucceeded; + if (callData.length > 0) { + uint256 _execGas = gasleft(); + bool success = Exec.call(mUserOp.sender, 0, callData, callGasLimit); + setGasConsumed(mUserOp.sender, 2, _execGas - gasleft()); + if (!success) { + bytes memory result = Exec.getReturnData(REVERT_REASON_MAX_LEN); + if (result.length > 0) { + emit UserOperationRevertReason(opInfo.userOpHash, mUserOp.sender, mUserOp.nonce, result); + } + mode = IPaymaster.PostOpMode.opReverted; + } + } + + unchecked { + uint256 actualGas = preGas - gasleft() + opInfo.preOpGas; + return _postExecution(mode, opInfo, context, actualGas); + } + } + + /// @inheritdoc IEntryPoint + function getUserOpHash(PackedUserOperation calldata userOp) public view returns (bytes32) { + return keccak256(abi.encode(userOp.hash(), address(this), block.chainid)); + } + + /** + * Copy general fields from userOp into the memory opInfo structure. + * @param userOp - The user operation. + * @param mUserOp - The memory user operation. + */ + function _copyUserOpToMemory(PackedUserOperation calldata userOp, MemoryUserOp memory mUserOp) internal pure { + mUserOp.sender = userOp.sender; + mUserOp.nonce = userOp.nonce; + (mUserOp.verificationGasLimit, mUserOp.callGasLimit) = UserOperationLib.unpackUints(userOp.accountGasLimits); + mUserOp.preVerificationGas = userOp.preVerificationGas; + (mUserOp.maxPriorityFeePerGas, mUserOp.maxFeePerGas) = UserOperationLib.unpackUints(userOp.gasFees); + bytes calldata paymasterAndData = userOp.paymasterAndData; + if (paymasterAndData.length > 0) { + require(paymasterAndData.length >= UserOperationLib.PAYMASTER_DATA_OFFSET, "AA93 invalid paymasterAndData"); + (mUserOp.paymaster, mUserOp.paymasterVerificationGasLimit, mUserOp.paymasterPostOpGasLimit) = + UserOperationLib.unpackPaymasterStaticFields(paymasterAndData); + } else { + mUserOp.paymaster = address(0); + mUserOp.paymasterVerificationGasLimit = 0; + mUserOp.paymasterPostOpGasLimit = 0; + } + } + + /** + * Get the required prefunded gas fee amount for an operation. + * @param mUserOp - The user operation in memory. + */ + function _getRequiredPrefund(MemoryUserOp memory mUserOp) internal pure returns (uint256 requiredPrefund) { + unchecked { + uint256 requiredGas = mUserOp.verificationGasLimit + mUserOp.callGasLimit + + mUserOp.paymasterVerificationGasLimit + mUserOp.paymasterPostOpGasLimit + mUserOp.preVerificationGas; + + requiredPrefund = requiredGas * mUserOp.maxFeePerGas; + } + } + + /** + * Create sender smart contract account if init code is provided. + * @param opIndex - The operation index. + * @param opInfo - The operation info. + * @param initCode - The init code for the smart contract account. + */ + function _createSenderIfNeeded(uint256 opIndex, UserOpInfo memory opInfo, bytes calldata initCode) internal { + if (initCode.length != 0) { + address sender = opInfo.mUserOp.sender; + if (sender.code.length != 0) { + revert FailedOp(opIndex, "AA10 sender already constructed"); + } + uint256 _creationGas = gasleft(); + address sender1 = senderCreator().createSender{ gas: opInfo.mUserOp.verificationGasLimit }(initCode); + setGasConsumed(sender, 0, _creationGas - gasleft()); + if (sender1 == address(0)) { + revert FailedOp(opIndex, "AA13 initCode failed or OOG"); + } + if (sender1 != sender) { + revert FailedOp(opIndex, "AA14 initCode must return sender"); + } + if (sender1.code.length == 0) { + revert FailedOp(opIndex, "AA15 initCode must create sender"); + } + address factory = address(bytes20(initCode[0:20])); + emit AccountDeployed(opInfo.userOpHash, sender, factory, opInfo.mUserOp.paymaster); + } + } + + /// @inheritdoc IEntryPoint + function getSenderAddress(bytes calldata initCode) public { + address sender = senderCreator().createSender(initCode); + revert SenderAddressResult(sender); + } + + /** + * Call account.validateUserOp. + * Revert (with FailedOp) in case validateUserOp reverts, or account didn't send required prefund. + * Decrement account's deposit if needed. + * @param opIndex - The operation index. + * @param op - The user operation. + * @param opInfo - The operation info. + * @param requiredPrefund - The required prefund amount. + */ + function _validateAccountPrepayment( + uint256 opIndex, + PackedUserOperation calldata op, + UserOpInfo memory opInfo, + uint256 requiredPrefund, + uint256 verificationGasLimit + ) + internal + returns (uint256 validationData) + { + unchecked { + MemoryUserOp memory mUserOp = opInfo.mUserOp; + address sender = mUserOp.sender; + _createSenderIfNeeded(opIndex, opInfo, op.initCode); + address paymaster = mUserOp.paymaster; + uint256 missingAccountFunds = 0; + if (paymaster == address(0)) { + uint256 bal = balanceOf(sender); + missingAccountFunds = bal > requiredPrefund ? 0 : requiredPrefund - bal; + } + uint256 _verificationGas = gasleft(); + try IAccount(sender).validateUserOp{ gas: verificationGasLimit }(op, opInfo.userOpHash, missingAccountFunds) + returns (uint256 _validationData) { + validationData = _validationData; + setGasConsumed(sender, 1, _verificationGas - gasleft()); + } catch { + revert FailedOpWithRevert(opIndex, "AA23 reverted", Exec.getReturnData(REVERT_REASON_MAX_LEN)); + } + if (paymaster == address(0)) { + DepositInfo storage senderInfo = deposits[sender]; + uint256 deposit = senderInfo.deposit; + if (requiredPrefund > deposit) { + revert FailedOp(opIndex, "AA21 didn't pay prefund"); + } + senderInfo.deposit = deposit - requiredPrefund; + } + } + } + + /** + * In case the request has a paymaster: + * - Validate paymaster has enough deposit. + * - Call paymaster.validatePaymasterUserOp. + * - Revert with proper FailedOp in case paymaster reverts. + * - Decrement paymaster's deposit. + * @param opIndex - The operation index. + * @param op - The user operation. + * @param opInfo - The operation info. + * @param requiredPreFund - The required prefund amount. + */ + function _validatePaymasterPrepayment( + uint256 opIndex, + PackedUserOperation calldata op, + UserOpInfo memory opInfo, + uint256 requiredPreFund + ) + internal + returns (bytes memory context, uint256 validationData) + { + unchecked { + uint256 preGas = gasleft(); + MemoryUserOp memory mUserOp = opInfo.mUserOp; + address paymaster = mUserOp.paymaster; + DepositInfo storage paymasterInfo = deposits[paymaster]; + uint256 deposit = paymasterInfo.deposit; + if (deposit < requiredPreFund) { + revert FailedOp(opIndex, "AA31 paymaster deposit too low"); + } + paymasterInfo.deposit = deposit - requiredPreFund; + uint256 pmVerificationGasLimit = mUserOp.paymasterVerificationGasLimit; + try IPaymaster(paymaster).validatePaymasterUserOp{ gas: pmVerificationGasLimit }( + op, opInfo.userOpHash, requiredPreFund + ) returns (bytes memory _context, uint256 _validationData) { + context = _context; + validationData = _validationData; + } catch { + revert FailedOpWithRevert(opIndex, "AA33 reverted", Exec.getReturnData(REVERT_REASON_MAX_LEN)); + } + if (preGas - gasleft() > pmVerificationGasLimit) { + revert FailedOp(opIndex, "AA36 over paymasterVerificationGasLimit"); + } + } + } + + /** + * Revert if either account validationData or paymaster validationData is expired. + * @param opIndex - The operation index. + * @param validationData - The account validationData. + * @param paymasterValidationData - The paymaster validationData. + * @param expectedAggregator - The expected aggregator. + */ + function _validateAccountAndPaymasterValidationData( + uint256 opIndex, + uint256 validationData, + uint256 paymasterValidationData, + address expectedAggregator + ) + internal + view + { + (address aggregator, bool outOfTimeRange) = _getValidationData(validationData); + if (expectedAggregator != aggregator) { + revert FailedOp(opIndex, "AA24 signature error"); + } + if (outOfTimeRange) { + revert FailedOp(opIndex, "AA22 expired or not due"); + } + // pmAggregator is not a real signature aggregator: we don't have logic to handle it as address. + // Non-zero address means that the paymaster fails due to some signature check (which is ok only during + // estimation). + address pmAggregator; + (pmAggregator, outOfTimeRange) = _getValidationData(paymasterValidationData); + if (pmAggregator != address(0)) { + revert FailedOp(opIndex, "AA34 signature error"); + } + if (outOfTimeRange) { + revert FailedOp(opIndex, "AA32 paymaster expired or not due"); + } + } + + /** + * Parse validationData into its components. + * @param validationData - The packed validation data (sigFailed, validAfter, validUntil). + * @return aggregator the aggregator of the validationData + * @return outOfTimeRange true if current time is outside the time range of this validationData. + */ + function _getValidationData(uint256 validationData) + internal + view + returns (address aggregator, bool outOfTimeRange) + { + if (validationData == 0) { + return (address(0), false); + } + ValidationData memory data = _parseValidationData(validationData); + // solhint-disable-next-line not-rely-on-time + outOfTimeRange = block.timestamp > data.validUntil || block.timestamp < data.validAfter; + aggregator = data.aggregator; + } + + /** + * Validate account and paymaster (if defined) and + * also make sure total validation doesn't exceed verificationGasLimit. + * This method is called off-chain (simulateValidation()) and on-chain (from handleOps) + * @param opIndex - The index of this userOp into the "opInfos" array. + * @param userOp - The userOp to validate. + */ + function _validatePrepayment( + uint256 opIndex, + PackedUserOperation calldata userOp, + UserOpInfo memory outOpInfo + ) + internal + returns (uint256 validationData, uint256 paymasterValidationData) + { + uint256 preGas = gasleft(); + MemoryUserOp memory mUserOp = outOpInfo.mUserOp; + _copyUserOpToMemory(userOp, mUserOp); + outOpInfo.userOpHash = getUserOpHash(userOp); + + // Validate all numeric values in userOp are well below 128 bit, so they can safely be added + // and multiplied without causing overflow. + uint256 verificationGasLimit = mUserOp.verificationGasLimit; + uint256 maxGasValues = mUserOp.preVerificationGas | verificationGasLimit | mUserOp.callGasLimit + | mUserOp.paymasterVerificationGasLimit | mUserOp.paymasterPostOpGasLimit | mUserOp.maxFeePerGas + | mUserOp.maxPriorityFeePerGas; + require(maxGasValues <= type(uint120).max, "AA94 gas values overflow"); + + uint256 requiredPreFund = _getRequiredPrefund(mUserOp); + validationData = _validateAccountPrepayment(opIndex, userOp, outOpInfo, requiredPreFund, verificationGasLimit); + + if (!_validateAndUpdateNonce(mUserOp.sender, mUserOp.nonce)) { + revert FailedOp(opIndex, "AA25 invalid account nonce"); + } + + unchecked { + if (preGas - gasleft() > verificationGasLimit) { + revert FailedOp(opIndex, "AA26 over verificationGasLimit"); + } + } + + bytes memory context; + if (mUserOp.paymaster != address(0)) { + (context, paymasterValidationData) = + _validatePaymasterPrepayment(opIndex, userOp, outOpInfo, requiredPreFund); + } + unchecked { + outOpInfo.prefund = requiredPreFund; + outOpInfo.contextOffset = getOffsetOfMemoryBytes(context); + outOpInfo.preOpGas = preGas - gasleft() + userOp.preVerificationGas; + } + } + + /** + * Process post-operation, called just after the callData is executed. + * If a paymaster is defined and its validation returned a non-empty context, its postOp is called. + * The excess amount is refunded to the account (or paymaster - if it was used in the request). + * @param mode - Whether is called from innerHandleOp, or outside (postOpReverted). + * @param opInfo - UserOp fields and info collected during validation. + * @param context - The context returned in validatePaymasterUserOp. + * @param actualGas - The gas used so far by this user operation. + */ + function _postExecution( + IPaymaster.PostOpMode mode, + UserOpInfo memory opInfo, + bytes memory context, + uint256 actualGas + ) + private + returns (uint256 actualGasCost) + { + uint256 preGas = gasleft(); + unchecked { + address refundAddress; + MemoryUserOp memory mUserOp = opInfo.mUserOp; + uint256 gasPrice = getUserOpGasPrice(mUserOp); + + address paymaster = mUserOp.paymaster; + if (paymaster == address(0)) { + refundAddress = mUserOp.sender; + } else { + refundAddress = paymaster; + if (context.length > 0) { + actualGasCost = actualGas * gasPrice; + if (mode != IPaymaster.PostOpMode.postOpReverted) { + try IPaymaster(paymaster).postOp{ gas: mUserOp.paymasterPostOpGasLimit }( + mode, context, actualGasCost, gasPrice + ) { + // solhint-disable-next-line no-empty-blocks + } catch { + bytes memory reason = Exec.getReturnData(REVERT_REASON_MAX_LEN); + revert PostOpReverted(reason); + } + } + } + } + actualGas += preGas - gasleft(); + + // Calculating a penalty for unused execution gas + { + uint256 executionGasLimit = mUserOp.callGasLimit + mUserOp.paymasterPostOpGasLimit; + uint256 executionGasUsed = actualGas - opInfo.preOpGas; + // this check is required for the gas used within EntryPoint and not covered by explicit gas limits + if (executionGasLimit > executionGasUsed) { + uint256 unusedGas = executionGasLimit - executionGasUsed; + uint256 unusedGasPenalty = (unusedGas * PENALTY_PERCENT) / 100; + actualGas += unusedGasPenalty; + } + } + + actualGasCost = actualGas * gasPrice; + uint256 prefund = opInfo.prefund; + if (prefund < actualGasCost) { + if (mode == IPaymaster.PostOpMode.postOpReverted) { + actualGasCost = prefund; + emitPrefundTooLow(opInfo); + emitUserOperationEvent(opInfo, false, actualGasCost, actualGas); + } else { + assembly ("memory-safe") { + mstore(0, INNER_REVERT_LOW_PREFUND) + revert(0, 32) + } + } + } else { + uint256 refund = prefund - actualGasCost; + _incrementDeposit(refundAddress, refund); + bool success = mode == IPaymaster.PostOpMode.opSucceeded; + emitUserOperationEvent(opInfo, success, actualGasCost, actualGas); + } + } // unchecked + } + + /** + * The gas price this UserOp agrees to pay. + * Relayer/block builder might submit the TX with higher priorityFee, but the user should not. + * @param mUserOp - The userOp to get the gas price from. + */ + function getUserOpGasPrice(MemoryUserOp memory mUserOp) internal view returns (uint256) { + unchecked { + uint256 maxFeePerGas = mUserOp.maxFeePerGas; + uint256 maxPriorityFeePerGas = mUserOp.maxPriorityFeePerGas; + if (maxFeePerGas == maxPriorityFeePerGas) { + //legacy mode (for networks that don't support basefee opcode) + return maxFeePerGas; + } + return min(maxFeePerGas, maxPriorityFeePerGas + block.basefee); + } + } + + /** + * The offset of the given bytes in memory. + * @param data - The bytes to get the offset of. + */ + function getOffsetOfMemoryBytes(bytes memory data) internal pure returns (uint256 offset) { + assembly { + offset := data + } + } + + /** + * The bytes in memory at the given offset. + * @param offset - The offset to get the bytes from. + */ + function getMemoryBytesFromOffset(uint256 offset) internal pure returns (bytes memory data) { + assembly ("memory-safe") { + data := offset + } + } + + /// @inheritdoc IEntryPoint + function delegateAndRevert(address target, bytes calldata data) external { + (bool success, bytes memory ret) = target.delegatecall(data); + revert DelegateAndRevert(success, ret); + } +} + +// node_modules/@ERC4337/account-abstraction/contracts/core/EntryPointSimulations.sol + +/* solhint-disable avoid-low-level-calls */ +/* solhint-disable no-inline-assembly */ + +/* + * This contract inherits the EntryPoint and extends it with the view-only methods that are executed by + * the bundler in order to check UserOperation validity and estimate its gas consumption. + * This contract should never be deployed on-chain and is only used as a parameter for the "eth_call" request. + */ +contract EntryPointSimulations is EntryPoint, IEntryPointSimulations { + // solhint-disable-next-line var-name-mixedcase + AggregatorStakeInfo private NOT_AGGREGATED = AggregatorStakeInfo(address(0), StakeInfo(0, 0)); + + SenderCreator private _senderCreator; + + function initSenderCreator() internal virtual { + //this is the address of the first contract created with CREATE by this address. + address createdObj = address(uint160(uint256(keccak256(abi.encodePacked(hex"d694", address(this), hex"01"))))); + _senderCreator = SenderCreator(createdObj); + } + + function senderCreator() internal view virtual override returns (SenderCreator) { + // return the same senderCreator as real EntryPoint. + // this call is slightly (100) more expensive than EntryPoint's access to immutable member + return _senderCreator; + } + + /** + * simulation contract should not be deployed, and specifically, accounts should not trust + * it as entrypoint, since the simulation functions don't check the signatures + */ + constructor() { + // THIS CONTRACT SHOULD NOT BE DEPLOYED + // however, the line of code below is commented to allow this entryPoint to be used in fork tests + // require(block.number < 100, "should not be deployed"); + } + + /// @inheritdoc IEntryPointSimulations + function simulateValidation(PackedUserOperation calldata userOp) external returns (ValidationResult memory) { + UserOpInfo memory outOpInfo; + + _simulationOnlyValidations(userOp); + (uint256 validationData, uint256 paymasterValidationData) = _validatePrepayment(0, userOp, outOpInfo); + StakeInfo memory paymasterInfo = _getStakeInfo(outOpInfo.mUserOp.paymaster); + StakeInfo memory senderInfo = _getStakeInfo(outOpInfo.mUserOp.sender); + StakeInfo memory factoryInfo; + { + bytes calldata initCode = userOp.initCode; + address factory = initCode.length >= 20 ? address(bytes20(initCode[0:20])) : address(0); + factoryInfo = _getStakeInfo(factory); + } + + address aggregator = address(uint160(validationData)); + ReturnInfo memory returnInfo = ReturnInfo( + outOpInfo.preOpGas, + outOpInfo.prefund, + validationData, + paymasterValidationData, + getMemoryBytesFromOffset(outOpInfo.contextOffset) + ); + + AggregatorStakeInfo memory aggregatorInfo = NOT_AGGREGATED; + if (uint160(aggregator) != SIG_VALIDATION_SUCCESS && uint160(aggregator) != SIG_VALIDATION_FAILED) { + aggregatorInfo = AggregatorStakeInfo(aggregator, _getStakeInfo(aggregator)); + } + return ValidationResult(returnInfo, senderInfo, factoryInfo, paymasterInfo, aggregatorInfo); + } + + /// @inheritdoc IEntryPointSimulations + function simulateHandleOp( + PackedUserOperation calldata op, + address target, + bytes calldata targetCallData + ) + external + nonReentrant + returns (ExecutionResult memory) + { + UserOpInfo memory opInfo; + _simulationOnlyValidations(op); + (uint256 validationData, uint256 paymasterValidationData) = _validatePrepayment(0, op, opInfo); + + uint256 paid = _executeUserOp(0, op, opInfo); + bool targetSuccess; + bytes memory targetResult; + if (target != address(0)) { + (targetSuccess, targetResult) = target.call(targetCallData); + } + return + ExecutionResult(opInfo.preOpGas, paid, validationData, paymasterValidationData, targetSuccess, targetResult); + } + + function _simulationOnlyValidations(PackedUserOperation calldata userOp) internal { + //initialize senderCreator(). we can't rely on constructor + initSenderCreator(); + + try this._validateSenderAndPaymaster(userOp.initCode, userOp.sender, userOp.paymasterAndData) { + // solhint-disable-next-line no-empty-blocks + } catch Error(string memory revertReason) { + if (bytes(revertReason).length != 0) { + revert FailedOp(0, revertReason); + } + } + } + + /** + * Called only during simulation. + * This function always reverts to prevent warm/cold storage differentiation in simulation vs execution. + * @param initCode - The smart account constructor code. + * @param sender - The sender address. + * @param paymasterAndData - The paymaster address (followed by other params, ignored by this method) + */ + function _validateSenderAndPaymaster( + bytes calldata initCode, + address sender, + bytes calldata paymasterAndData + ) + external + view + { + if (initCode.length == 0 && sender.code.length == 0) { + // it would revert anyway. but give a meaningful message + revert("AA20 account not deployed"); + } + if (paymasterAndData.length >= 20) { + address paymaster = address(bytes20(paymasterAndData[0:20])); + if (paymaster.code.length == 0) { + // It would revert anyway. but give a meaningful message. + revert("AA30 paymaster not deployed"); + } + } + // always revert + revert(""); + } + + //make sure depositTo cost is more than normal EntryPoint's cost, + // to mitigate DoS vector on the bundler + // empiric test showed that without this wrapper, simulation depositTo costs less.. + function depositTo(address account) public payable override(IStakeManager, StakeManager) { + unchecked { + // silly code, to waste some gas to make sure depositTo is always little more + // expensive than on-chain call + uint256 x = 1; + while (x < 5) { + x++; + } + StakeManager.depositTo(account); + } + } +} + +// node_modules/@rhinestone/modulekit/src/external/ERC4337.sol + +/* solhint-disable no-unused-import */ + +/*////////////////////////////////////////////////////////////// + USEROP +//////////////////////////////////////////////////////////////*/ + +/*////////////////////////////////////////////////////////////// + ENTRYPOINT +//////////////////////////////////////////////////////////////*/ + +/*////////////////////////////////////////////////////////////// + VALIDATION +//////////////////////////////////////////////////////////////*/ + +/*////////////////////////////////////////////////////////////// + INTERFACES +//////////////////////////////////////////////////////////////*/ + +// node_modules/@rhinestone/modulekit/src/module-bases/interfaces/IPolicy.sol + +// solhint-disable no-unused-import + +type ConfigId is bytes32; + +/** + * IPolicy are external contracts that enforce policies / permission on 4337/7579 executions + * Since it's not the account calling into this contract, and check functions are called during the + * ERC4337 validation + * phase, IPolicy implementations MUST follow ERC4337 storage and opcode restrictions + * A recommend storage layout to store policy related data: + * mapping(id => msg.sender => userOp.sender(account) => state) + * ^ smartSession ^ smart account (associated storage) + */ +interface IPolicy_1 is IERC165_1, IModule { + function isInitialized(address account, ConfigId configId) external view returns (bool); + function isInitialized(address account, address mulitplexer, ConfigId configId) external view returns (bool); + + /** + * This function may be called by the multiplexer (SmartSessions) without deinitializing first. + * Policies MUST overwrite the current state when this happens + */ + function initializeWithMultiplexer(address account, ConfigId configId, bytes calldata initData) external; +} + +/** + * IUserOpPolicy is a policy that enforces restrictions on user operations. It is called during the + * validation phase + * of the ERC4337 execution. + * Use this policy to enforce restrictions on user operations (userOp.gas, Time based restrictions). + * The checkUserOpPolicy function should return a uint256 value that represents the policy's + * decision. + * The policy's decision should be one of the following: + * - VALIDATION_SUCCESS: The user operation is allowed. + * - VALIDATION_FAILED: The user operation is not allowed. + */ +interface IUserOpPolicy is IPolicy_1 { + function checkUserOpPolicy(ConfigId id, PackedUserOperation calldata userOp) external returns (uint256); +} + +/** + * IActionPolicy is a policy that enforces restrictions on actions. It is called during the + * validation phase + * of the ERC4337 execution. + * ERC7579 accounts natively support batched executions. So in one userOp, multiple actions can be + * executed. + * SmartSession will destruct the execution batch, and call the policy for each action, if the + * policy is installed for + * the actionId for the account. + * Use this policy to enforce restrictions on individual actions (i.e. transfers, approvals, etc). + * The checkAction function should return a uint256 value that represents the policy's decision. + * The policy's decision should be one of the following: + * - VALIDATION_SUCCESS: The action is allowed. + * - VALIDATION_FAILED: The action is not allowed. + */ +interface IActionPolicy is IPolicy_1 { + function checkAction( + ConfigId id, + address account, + address target, + uint256 value, + bytes calldata data + ) + external + returns (uint256); +} + +/** + * I1271Policy is a policy that enforces restrictions on 1271 signed actions. It is called during an + * ERC1271 signature + * validation + */ +interface I1271Policy is IPolicy_1 { + // request sender is probably protocol, so can introduce policies based on it. + function check1271SignedAction( + ConfigId id, + address requestSender, + address account, + bytes32 hash, + bytes calldata signature + ) + external + view + returns (bool); +} + +// node_modules/@rhinestone/modulekit/src/module-bases/ERC7579ValidatorBase.sol + +abstract contract ERC7579ValidatorBase is ERC7579ModuleBase { + type ValidationData is uint256; + + ValidationData internal constant VALIDATION_SUCCESS = ValidationData.wrap(0); + ValidationData internal constant VALIDATION_FAILED = ValidationData.wrap(1); + bytes4 internal constant EIP1271_SUCCESS = 0x1626ba7e; + bytes4 internal constant EIP1271_FAILED = 0xFFFFFFFF; + + /** + * Helper to pack the return value for validateUserOp, when not using an aggregator. + * @param sigFailed - True for signature failure, false for success. + * @param validUntil - Last timestamp this UserOperation is valid (or zero for + * infinite). + * @param validAfter - First timestamp this UserOperation is valid. + */ + function _packValidationData( + bool sigFailed, + uint48 validUntil, + uint48 validAfter + ) + internal + pure + returns (ValidationData) + { + return ValidationData.wrap(_packValidationData_1(sigFailed, validUntil, validAfter)); + } + + function _unpackValidationData(ValidationData _packedData) + internal + pure + returns (bool sigFailed, uint48 validUntil, uint48 validAfter) + { + uint256 packedData = ValidationData.unwrap(_packedData); + sigFailed = (packedData & 1) == 1; + validUntil = uint48((packedData >> 160) & ((1 << 48) - 1)); + validAfter = uint48((packedData >> (160 + 48)) & ((1 << 48) - 1)); + } + + function validateUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash + ) + external + virtual + returns (ValidationData); + + function isValidSignatureWithSender( + address sender, + bytes32 hash, + bytes calldata data + ) + external + view + virtual + returns (bytes4); +} + +// node_modules/@rhinestone/modulekit/src/module-bases/ERC7579PolicyBase.sol + +abstract contract ERC7579PolicyBase is ERC7579ModuleBase, IPolicy_1 { + function initializeWithMultiplexer(address account, ConfigId configId, bytes calldata initData) external virtual; +} + +// node_modules/@rhinestone/modulekit/src/module-bases/ERC1271Policy.sol + +abstract contract ERC1271Policy is ERC7579PolicyBase, I1271Policy { + function check1271SignedAction( + ConfigId id, + address requestSender, + address account, + bytes32 hash, + bytes calldata signature + ) + external + view + virtual + returns (bool); +} + +// node_modules/@rhinestone/modulekit/src/module-bases/ERC7579ActionPolicy.sol + +abstract contract ERC7579ActionPolicy is ERC7579PolicyBase, IActionPolicy { + function checkAction( + ConfigId id, + address account, + address target, + uint256 value, + bytes calldata data + ) + external + virtual + returns (uint256); +} + +// node_modules/@rhinestone/modulekit/src/module-bases/ERC7579HybridValidatorBase.sol + +/* solhint-disable no-unused-import */ + +/// @notice Base contract for hybrid validators, which are both stateful and stateless. +abstract contract ERC7579HybridValidatorBase is ERC7579ValidatorBase, ERC7579StatelessValidatorBase { } + +// node_modules/@rhinestone/modulekit/src/module-bases/ERC7579UserOpPolicy.sol + +abstract contract ERC7579UserOpPolicy is ERC7579PolicyBase, IUserOpPolicy { + function checkUserOp(ConfigId id, PackedUserOperation calldata userOp) external virtual returns (uint256); +} + +// node_modules/@rhinestone/modulekit/src/module-bases/ERC7579HookDestruct.sol + +uint256 constant EXECUSEROP_OFFSET = 164; +uint256 constant EXEC_OFFSET = 100; +uint256 constant INSTALL_OFFSET = 132; + +abstract contract ERC7579HookDestruct is IHook, ERC7579ModuleBase, TrustedForwarder { + error HookInvalidSelector(); + error InvalidCallType(); + + /*////////////////////////////////////////////////////////////////////////// + CALLDATA DECODING + //////////////////////////////////////////////////////////////////////////*/ + + function preCheck( + address msgSender, + uint256 msgValue, + bytes calldata msgData + ) + external + virtual + override + returns (bytes memory hookData) + { + bytes4 selector = bytes4(msgData[0:4]); + + if (selector == IAccountExecute.executeUserOp.selector) { + uint256 offset = uint256(bytes32(msgData[EXECUSEROP_OFFSET:EXECUSEROP_OFFSET + 32])) + 68; + uint256 paramLen = uint256(bytes32(msgData[offset:offset + 32])); + offset += 32; + bytes calldata _msgData = msgData[offset:offset + paramLen]; + return _decodeCallData(msgSender, msgValue, _msgData); + } else { + return _decodeCallData(msgSender, msgValue, msgData); + } + } + + function _decodeCallData( + address msgSender, + uint256 msgValue, + bytes calldata msgData + ) + internal + returns (bytes memory hookData) + { + bytes4 selector = bytes4(msgData[0:4]); + if (selector == IERC7579Account.execute.selector) { + return _handle4337Executions(msgSender, msgData); + } else if (selector == IERC7579Account.executeFromExecutor.selector) { + return _handleExecutorExecutions(msgSender, msgData); + } else if (selector == IERC7579Account.installModule.selector) { + uint256 paramLen = msgData.length > INSTALL_OFFSET + ? uint256(bytes32(msgData[INSTALL_OFFSET - 32:INSTALL_OFFSET])) + : uint256(0); + bytes calldata initData = + msgData.length > INSTALL_OFFSET ? msgData[INSTALL_OFFSET:INSTALL_OFFSET + paramLen] : msgData[0:0]; + uint256 moduleType = uint256(bytes32(msgData[4:36])); + address module = address(bytes20((msgData[48:68]))); + return onInstallModule(_getAccount(), msgSender, moduleType, module, initData); + } else if (selector == IERC7579Account.uninstallModule.selector) { + uint256 paramLen = msgData.length > INSTALL_OFFSET + ? uint256(bytes32(msgData[INSTALL_OFFSET - 32:INSTALL_OFFSET])) + : uint256(0); + bytes calldata initData = + msgData.length > INSTALL_OFFSET ? msgData[INSTALL_OFFSET:INSTALL_OFFSET + paramLen] : msgData[0:0]; + + uint256 moduleType = uint256(bytes32(msgData[4:36])); + address module = address(bytes20((msgData[48:68]))); + + return onUninstallModule(_getAccount(), msgSender, moduleType, module, initData); + } else { + return onUnknownFunction(_getAccount(), msgSender, msgValue, msgData); + } + } + + function _handle4337Executions( + address msgSender, + bytes calldata msgData + ) + internal + returns (bytes memory hookData) + { + uint256 paramLen = uint256(bytes32(msgData[EXEC_OFFSET - 32:EXEC_OFFSET])); + bytes calldata encodedExecutions = msgData[EXEC_OFFSET:EXEC_OFFSET + paramLen]; + + ModeCode mode = ModeCode.wrap(bytes32(msgData[4:36])); + CallType calltype = ModeLib.getCallType(mode); + + if (calltype == CALLTYPE_SINGLE) { + (address to, uint256 value, bytes calldata callData) = ExecutionLib.decodeSingle(encodedExecutions); + return onExecute(_getAccount(), msgSender, to, value, callData); + } else if (calltype == CALLTYPE_BATCH) { + Execution[] calldata execs = ExecutionLib.decodeBatch(encodedExecutions); + return onExecuteBatch(_getAccount(), msgSender, execs); + } else if (calltype == CALLTYPE_DELEGATECALL) { + address to = address(bytes20(encodedExecutions[0:20])); + bytes calldata callData = encodedExecutions[20:]; + return onExecuteDelegateCall(_getAccount(), msgSender, to, callData); + } else { + revert InvalidCallType(); + } + } + + function _handleExecutorExecutions( + address msgSender, + bytes calldata msgData + ) + internal + returns (bytes memory hookData) + { + uint256 paramLen = uint256(bytes32(msgData[EXEC_OFFSET - 32:EXEC_OFFSET])); + bytes calldata encodedExecutions = msgData[EXEC_OFFSET:EXEC_OFFSET + paramLen]; + + ModeCode mode = ModeCode.wrap(bytes32(msgData[4:36])); + CallType calltype = ModeLib.getCallType(mode); + + if (calltype == CALLTYPE_SINGLE) { + (address to, uint256 value, bytes calldata callData) = ExecutionLib.decodeSingle(encodedExecutions); + return onExecuteFromExecutor(_getAccount(), msgSender, to, value, callData); + } else if (calltype == CALLTYPE_BATCH) { + Execution[] calldata execs = ExecutionLib.decodeBatch(encodedExecutions); + return onExecuteBatchFromExecutor(_getAccount(), msgSender, execs); + } else if (calltype == CALLTYPE_DELEGATECALL) { + address to = address(bytes20(encodedExecutions[0:20])); + bytes calldata callData = encodedExecutions[20:]; + return onExecuteDelegateCallFromExecutor(_getAccount(), msgSender, to, callData); + } else { + revert InvalidCallType(); + } + } + + function postCheck(bytes calldata hookData) external virtual override { + onPostCheck(_getAccount(), hookData); + } + + /*////////////////////////////////////////////////////////////////////////// + EXECUTION + //////////////////////////////////////////////////////////////////////////*/ + + function onExecute( + address account, + address msgSender, + address target, + uint256 value, + bytes calldata callData + ) + internal + virtual + returns (bytes memory hookData) + { } + + function onExecuteBatch( + address account, + address msgSender, + Execution[] calldata + ) + internal + virtual + returns (bytes memory hookData) + { } + + function onExecuteDelegateCall( + address account, + address msgSender, + address target, + bytes calldata callData + ) + internal + virtual + returns (bytes memory hookData) + { } + + function onExecuteFromExecutor( + address account, + address msgSender, + address target, + uint256 value, + bytes calldata callData + ) + internal + virtual + returns (bytes memory hookData) + { } + + function onExecuteBatchFromExecutor( + address account, + address msgSender, + Execution[] calldata + ) + internal + virtual + returns (bytes memory hookData) + { } + + function onExecuteDelegateCallFromExecutor( + address account, + address msgSender, + address target, + bytes calldata callData + ) + internal + virtual + returns (bytes memory hookData) + { } + + /*////////////////////////////////////////////////////////////////////////// + CONFIG + //////////////////////////////////////////////////////////////////////////*/ + + function onInstallModule( + address account, + address msgSender, + uint256 moduleType, + address module, + bytes calldata initData + ) + internal + virtual + returns (bytes memory hookData) + { } + + function onUninstallModule( + address account, + address msgSender, + uint256 moduleType, + address module, + bytes calldata deInitData + ) + internal + virtual + returns (bytes memory hookData) + { } + + /*////////////////////////////////////////////////////////////////////////// + UNKNOWN FUNCTION + //////////////////////////////////////////////////////////////////////////*/ + + function onUnknownFunction( + address account, + address msgSender, + uint256 msgValue, + bytes calldata msgData + ) + internal + virtual + returns (bytes memory hookData) + { } + + /*////////////////////////////////////////////////////////////////////////// + POSTCHECK + //////////////////////////////////////////////////////////////////////////*/ + + function onPostCheck(address account, bytes calldata hookData) internal virtual { } +} + +// node_modules/@rhinestone/modulekit/src/Modules.sol + +/* solhint-disable no-unused-import */ + +/*////////////////////////////////////////////////////////////// + INTERFACES +//////////////////////////////////////////////////////////////*/ + +/*////////////////////////////////////////////////////////////// + BASES +//////////////////////////////////////////////////////////////*/ + +// Core + +// Validators + +// Executors + +// Hooks + +// Fallbacks + +// Misc + +// Policies + +/*////////////////////////////////////////////////////////////// + UTIL +//////////////////////////////////////////////////////////////*/ + +// src/OwnableValidator/OwnableValidator.sol + +uint256 constant TYPE_STATELESS_VALIDATOR = 7; +/** + * @title OwnableValidator + * @dev Module that allows users to designate EOA owners that can validate transactions using a + * threshold + * @author Rhinestone + */ + +contract OwnableValidator is ERC7579ValidatorBase { + using LibSort for *; + using SentinelList4337Lib for SentinelList4337Lib.SentinelList; + + /*////////////////////////////////////////////////////////////////////////// + CONSTANTS & STORAGE + //////////////////////////////////////////////////////////////////////////*/ + + event ModuleInitialized(address indexed account); + event ModuleUninitialized(address indexed account); + event ThresholdSet(address indexed account, uint256 threshold); + event OwnerAdded(address indexed account, address indexed owner); + event OwnerRemoved(address indexed account, address indexed owner); + + error ThresholdNotSet(); + error InvalidThreshold(); + error NotSortedAndUnique(); + error MaxOwnersReached(); + error InvalidOwner(address owner); + error CannotRemoveOwner(); + + // maximum number of owners per account + uint256 constant MAX_OWNERS = 32; + + // account => owners + SentinelList4337Lib.SentinelList owners; + // account => threshold + mapping(address account => uint256) public threshold; + // account => ownerCount + mapping(address => uint256) public ownerCount; + + /*////////////////////////////////////////////////////////////////////////// + CONFIG + //////////////////////////////////////////////////////////////////////////*/ + + /** + * Initializes the module with the threshold and owners + * @dev data is encoded as follows: abi.encode(threshold, owners) + * + * @param data encoded data containing the threshold and owners + */ + function onInstall(bytes calldata data) external override { + // decode the threshold and owners + (uint256 _threshold, address[] memory _owners) = abi.decode(data, (uint256, address[])); + + // check that owners are sorted and uniquified + if (!_owners.isSortedAndUniquified()) { + revert NotSortedAndUnique(); + } + + // make sure the threshold is set + if (_threshold == 0) { + revert ThresholdNotSet(); + } + + // make sure the threshold is less than the number of owners + uint256 ownersLength = _owners.length; + if (ownersLength < _threshold) { + revert InvalidThreshold(); + } + + // cache the account address + address account = msg.sender; + + // set threshold + threshold[account] = _threshold; + + // check if max owners is reached + if (ownersLength > MAX_OWNERS) { + revert MaxOwnersReached(); + } + + // set owner count + ownerCount[account] = ownersLength; + + // initialize the owner list + owners.init(account); + + // add owners to the list + for (uint256 i = 0; i < ownersLength; i++) { + address _owner = _owners[i]; + if (_owner == address(0)) { + revert InvalidOwner(_owner); + } + owners.push(account, _owner); + emit OwnerAdded(account, _owner); + } + + emit ModuleInitialized(account); + } + + /** + * Handles the uninstallation of the module and clears the threshold and owners + * @dev the data parameter is not used + */ + function onUninstall(bytes calldata) external override { + // cache the account address + address account = msg.sender; + + // clear the owners + address[] memory ownersArray; + (ownersArray,) = owners.getEntriesPaginated(account, SENTINEL, MAX_OWNERS); + for (uint256 i = 0; i < ownersArray.length; i++) { + address owner = ownersArray[i]; + // remove the owner from the list + owners.pop(account, SENTINEL, owner); + emit OwnerRemoved(account, owner); + } + + // remove the threshold + threshold[account] = 0; + + // remove the owner count + ownerCount[account] = 0; + + emit ModuleUninitialized(account); + } + + /** + * Checks if the module is initialized + * + * @param smartAccount address of the smart account + * @return true if the module is initialized, false otherwise + */ + function isInitialized(address smartAccount) public view returns (bool) { + return threshold[smartAccount] != 0; + } + + /** + * Sets the threshold for the account + * @dev the function will revert if the module is not initialized + * + * @param _threshold uint256 threshold to set + */ + function setThreshold(uint256 _threshold) external { + // cache the account address + address account = msg.sender; + // check if the module is initialized and revert if it is not + if (!isInitialized(account)) revert NotInitialized(account); + + // make sure that the threshold is set + if (_threshold == 0) { + revert InvalidThreshold(); + } + + // make sure the threshold is less than the number of owners + if (ownerCount[account] < _threshold) { + revert InvalidThreshold(); + } + + // set the threshold + threshold[account] = _threshold; + + emit ThresholdSet(account, _threshold); + } + + /** + * Adds an owner to the account + * @dev will revert if the owner is already added + * + * @param owner address of the owner to add + */ + function addOwner(address owner) external { + // cache the account address + address account = msg.sender; + // check if the module is initialized and revert if it is not + if (!isInitialized(account)) revert NotInitialized(account); + + // revert if the owner is address(0) + if (owner == address(0)) { + revert InvalidOwner(owner); + } + + // check if max owners is reached + if (ownerCount[account] >= MAX_OWNERS) { + revert MaxOwnersReached(); + } + + // increment the owner count + ownerCount[account]++; + + // add the owner to the linked list + owners.push(account, owner); + + emit OwnerAdded(account, owner); + } + + /** + * Removes an owner from the account + * @dev will revert if the owner is not added or the previous owner is invalid + * + * @param prevOwner address of the previous owner + * @param owner address of the owner to remove + */ + function removeOwner(address prevOwner, address owner) external { + // cache the account address + address account = msg.sender; + + // check if an owner can be removed + if (ownerCount[account] == threshold[account]) { + // if the owner count is equal to the threshold, revert + // this means that removing an owner would make the threshold unreachable + revert CannotRemoveOwner(); + } + + // remove the owner + owners.pop(account, prevOwner, owner); + + // decrement the owner count + ownerCount[account]--; + + emit OwnerRemoved(account, owner); + } + + /** + * Returns the owners of the account + * + * @param account address of the account + * + * @return ownersArray array of owners + */ + function getOwners(address account) external view returns (address[] memory ownersArray) { + // get the owners from the linked list + (ownersArray,) = owners.getEntriesPaginated(account, SENTINEL, MAX_OWNERS); + } + + /*////////////////////////////////////////////////////////////////////////// + MODULE LOGIC + //////////////////////////////////////////////////////////////////////////*/ + + /** + * Validates a user operation + * + * @param userOp PackedUserOperation struct containing the UserOperation + * @param userOpHash bytes32 hash of the UserOperation + * + * @return ValidationData the UserOperation validation result + */ + function validateUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash + ) + external + view + override + returns (ValidationData) + { + // validate the signature with the config + bool isValid = _validateSignatureWithConfig(userOp.sender, userOpHash, userOp.signature); + + // return the result + if (isValid) { + return VALIDATION_SUCCESS; + } + return VALIDATION_FAILED; + } + + /** + * Validates an ERC-1271 signature with the sender + * + * @param hash bytes32 hash of the data + * @param data bytes data containing the signatures + * + * @return bytes4 EIP1271_SUCCESS if the signature is valid, EIP1271_FAILED otherwise + */ + function isValidSignatureWithSender( + address, + bytes32 hash, + bytes calldata data + ) + external + view + override + returns (bytes4) + { + // validate the signature with the config + bool isValid = _validateSignatureWithConfig(msg.sender, hash, data); + + // return the result + if (isValid) { + return EIP1271_SUCCESS; + } + return EIP1271_FAILED; + } + + /** + * Validates a signature with the data (stateless validation) + * + * @param hash bytes32 hash of the data + * @param signature bytes data containing the signatures + * @param data bytes data containing the data + * + * @return bool true if the signature is valid, false otherwise + */ + function validateSignatureWithData( + bytes32 hash, + bytes calldata signature, + bytes calldata data + ) + external + view + returns (bool) + { + // decode the threshold and owners + (uint256 _threshold, address[] memory _owners) = abi.decode(data, (uint256, address[])); + + // check that owners are sorted and uniquified + if (!_owners.isSortedAndUniquified()) { + return false; + } + + // check that threshold is set + if (_threshold == 0) { + return false; + } + + // recover the signers from the signatures + address[] memory signers = + CheckSignatures.recoverNSignatures(ECDSA.toEthSignedMessageHash(hash), signature, _threshold); + + // sort and uniquify the signers to make sure a signer is not reused + signers.sort(); + signers.uniquifySorted(); + + // check if the signers are owners + uint256 validSigners; + uint256 signersLength = signers.length; + for (uint256 i = 0; i < signersLength; i++) { + (bool found,) = _owners.searchSorted(signers[i]); + if (found) { + validSigners++; + } + } + + // check if the threshold is met and return the result + if (validSigners >= _threshold) { + // if the threshold is met, return true + return true; + } + // if the threshold is not met, false + return false; + } + + /*////////////////////////////////////////////////////////////////////////// + INTERNAL + //////////////////////////////////////////////////////////////////////////*/ + + function _validateSignatureWithConfig( + address account, + bytes32 hash, + bytes calldata data + ) + internal + view + returns (bool) + { + // get the threshold and check that its set + uint256 _threshold = threshold[account]; + if (_threshold == 0) { + return false; + } + + // recover the signers from the signatures + address[] memory signers = + CheckSignatures.recoverNSignatures(ECDSA.toEthSignedMessageHash(hash), data, _threshold); + + // sort and uniquify the signers to make sure a signer is not reused + signers.sort(); + signers.uniquifySorted(); + + // check if the signers are owners + uint256 validSigners; + uint256 signersLength = signers.length; + for (uint256 i = 0; i < signersLength; i++) { + if (owners.contains(account, signers[i])) { + validSigners++; + } + } + + // check if the threshold is met and return the result + if (validSigners >= _threshold) { + // if the threshold is met, return true + return true; + } + // if the threshold is not met, return false + return false; + } + + /*////////////////////////////////////////////////////////////////////////// + METADATA + //////////////////////////////////////////////////////////////////////////*/ + + /** + * Returns the type of the module + * + * @param typeID type of the module + * + * @return true if the type is a module type, false otherwise + */ + function isModuleType(uint256 typeID) external pure override returns (bool) { + return typeID == TYPE_VALIDATOR || typeID == TYPE_STATELESS_VALIDATOR; + } + + /** + * Returns the name of the module + * + * @return name of the module + */ + function name() external pure virtual returns (string memory) { + return "OwnableValidator"; + } + + /** + * Returns the version of the module + * + * @return version of the module + */ + function version() external pure virtual returns (string memory) { + return "1.0.0"; + } +} diff --git a/test/unit/MultiKeySigner.t.sol b/test/unit/MultiKeySigner.t.sol new file mode 100644 index 00000000..cc419f33 --- /dev/null +++ b/test/unit/MultiKeySigner.t.sol @@ -0,0 +1,586 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Test } from "forge-std/Test.sol"; +import { ECDSA } from "solady/utils/ECDSA.sol"; + +import { SubModuleLib } from "contracts/lib/SubModuleLib.sol"; +import { MultiKeySigner, ISessionValidator } from "contracts/external/wc-cosigner/MultiKeySigner.sol"; +import { SignerEncode, Signer, SignerType } from "contracts/external/wc-cosigner/libs/SignerEncode.sol"; +import { PasskeyHelper, WebAuthnValidatorData } from "contracts/external/wc-cosigner/libs/PasskeyHelper.sol"; +import { ERC7579_MODULE_TYPE_STATELESS_VALIDATOR } from "contracts/DataTypes.sol"; + +import { WebAuthn } from "webauthn-sol/WebAuthn.sol"; +import { Base64Url } from "FreshCryptoLib/utils/Base64Url.sol"; + +contract MultiKeySignerTest is Test { + using PasskeyHelper for *; + using SubModuleLib for *; + using SignerEncode for *; + + MultiKeySigner internal mkSigner; + + // EOA accounts + uint256 internal alicePk = 0xA11CE; + address internal aliceAddr = vm.addr(alicePk); + uint256 internal bobPk = 0xB0B; + address internal bobAddr = vm.addr(bobPk); + + // Passkey data (values adapted from user's WebAuthnTest.sol - chrome test) + WebAuthnValidatorData internal passkeyDefaultData; + bytes internal passkeyDefaultAuthenticatorData; + string internal passkeyClientDataJSONTemplate = '{"type":"webauthn.get","challenge":"",' + '"origin":"http://localhost:3005","crossOrigin":false}'; + uint256 internal passkeyDefaultR; + uint256 internal passkeyDefaultS; + uint256 internal passkeyChallengeIndex = 23; + uint256 internal passkeyTypeIndex = 1; + + // Default challenge for which the passkeyDefaultR, passkeyDefaultS are valid. + // From user's WebAuthnTest.sol (challenge value used below) + bytes32 internal constant DEFAULT_USER_OP_HASH_FOR_PASSKEY = + 0xf631058a3ba1116acce12396fad0a125b5041c43f8e15723709f81aa8d5f4ccf; + + function setUp() public { + // Deploy MultiKeySigner + mkSigner = new MultiKeySigner(); + + // Initialize Passkey Test Data (values from user's WebAuthnTest.sol - chrome test) + passkeyDefaultData = WebAuthnValidatorData({ + pubKeyX: 28_573_233_055_232_466_711_029_625_910_063_034_642_429_572_463_461_595_413_086_259_353_299_906_450_061, + pubKeyY: 39_367_742_072_897_599_771_788_408_398_752_356_480_431_855_827_262_528_811_857_788_332_151_452_825_281 + }); + passkeyDefaultAuthenticatorData = + hex"49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763050000010a"; + passkeyDefaultR = + 29_739_767_516_584_490_820_047_863_506_833_955_097_567_272_713_519_339_793_744_591_468_032_609_909_569; + passkeyDefaultS = + 45_947_455_641_742_997_809_691_064_512_762_075_989_493_430_661_170_736_817_032_030_660_832_793_108_102; + } + + // --- Helper Functions --- + + function _encodeSignerArray(Signer[] memory signers) internal pure returns (bytes memory) { + // Directly use the public encodeSigners from the MultiKeySigner contract if possible, + // or the library function for testing encoding logic. + // For preparing `data` for `validateSignatureWithData`, we use `mkSigner.encodeSigners` + // or `SignerEncode.encodeSignersInternal` + return SignerEncode.encodeSignersInternal(signers); + } + + // Helper functions to bridge bytes memory to bytes calldata for SignerEncode.decodeSigners + function _decodeSignersViaCalldata(bytes memory encodedData) internal view returns (Signer[] memory) { + return this.externalCallDecode(encodedData); + } + + function externalCallDecode(bytes calldata data) external pure returns (Signer[] memory) { + return data.decodeSigners(); + } + + function _encodeSignatures(bytes[] memory sigs) internal pure returns (bytes memory) { + return abi.encode(sigs); + } + + function _getEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) { + return ECDSA.toEthSignedMessageHash(hash); + } + + function _signWithEoa(bytes32 hash, uint256 privateKey) internal pure returns (bytes memory) { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, hash); + return abi.encodePacked(r, s, v); + } + + // Helper to create the passkey signature blob, now structured as WebAuthn.WebAuthnAuth + function _createPasskeySignatureBlob(bytes32 userOpHashOrChallenge) internal view returns (bytes memory) { + // The challenge in clientDataJSON must be the base64url encoding of the raw challenge bytes. + // In WebAuthn.sol, verify() takes `bytes memory challenge` (raw bytes). + // Here, userOpHashOrChallenge is the raw challenge (often a bytes32 hash). + string memory actualChallengeBase64URL = Base64Url.encode( + abi.encode(userOpHashOrChallenge) // abi.encode to get bytes from bytes32 + ); + + string memory clientDataJSON = + _replace(passkeyClientDataJSONTemplate, "", actualChallengeBase64URL); + + WebAuthn.WebAuthnAuth memory auth = WebAuthn.WebAuthnAuth({ + authenticatorData: passkeyDefaultAuthenticatorData, + clientDataJSON: clientDataJSON, + challengeIndex: passkeyChallengeIndex, + typeIndex: passkeyTypeIndex, + r: passkeyDefaultR, + s: passkeyDefaultS + }); + + return abi.encode(auth); + } + + function substring(string memory str, uint256 startIndex, uint256 endIndex) internal pure returns (string memory) { + bytes memory strBytes = bytes(str); + bytes memory result = new bytes(endIndex - startIndex); + for (uint256 i = startIndex; i < endIndex; i++) { + result[i - startIndex] = strBytes[i]; + } + return string(result); + } + + // String replace helper (basic) + function _replace(string memory str, string memory from, string memory to) internal pure returns (string memory) { + bytes memory strBytes = bytes(str); + bytes memory fromBytes = bytes(from); + bytes memory toBytes = bytes(to); + + if (fromBytes.length == 0) return str; + if (strBytes.length < fromBytes.length) return str; // Cannot find 'from' if 'str' is shorter + + // Calculate the length of the result string + // This part is a bit tricky due to potential multiple occurrences and different lengths of 'from' and 'to' + // For simplicity in this refactor, we'll build an intermediate parts array. + // A more gas-efficient way for on-chain use would pre-calculate the exact length. + + bytes memory resultBuilder; // Using a dynamic bytes array to build the result + + uint256 k = 0; // Current position in strBytes + while (k < strBytes.length) { + bool isMatch = true; + if (k + fromBytes.length <= strBytes.length) { + for (uint256 j = 0; j < fromBytes.length; j++) { + if (strBytes[k + j] != fromBytes[j]) { + isMatch = false; + break; + } + } + } else { + isMatch = false; + } + + if (isMatch) { + resultBuilder = abi.encodePacked(resultBuilder, toBytes); + k += fromBytes.length; + } else { + resultBuilder = abi.encodePacked(resultBuilder, strBytes[k]); + k++; + } + } + return string(resultBuilder); + } + + // --- Basic Module Information Tests --- + + function test_onInstall_emptyData_succeeds() public { + mkSigner.onInstall(""); + // No revert expected, implicit pass + } + + function test_RevertWhen_onInstall_withData() public { + vm.expectRevert(MultiKeySigner.InstallationDataNotSupported.selector); + mkSigner.onInstall(hex"0123"); + } + + function test_onUninstall_doesNotRevert() public { + mkSigner.onUninstall(""); // Should not revert + mkSigner.onUninstall(hex"0123"); // Should not revert + // Implicit pass if no revert + } + + function test_isModuleType_withValidatorType_returnsTrue() public view { + assertTrue(mkSigner.isModuleType(ERC7579_MODULE_TYPE_STATELESS_VALIDATOR), "Should be stateless validator type"); + } + + function test_isModuleType_withOtherType_returnsFalse() public view { + assertFalse(mkSigner.isModuleType(0), "Should not be validator type for 0"); + assertFalse(mkSigner.isModuleType(12_345), "Should not be validator type for 12345"); + } + + function test_supportsInterface_withISessionValidator_returnsTrue() public view { + assertTrue( + mkSigner.supportsInterface(type(ISessionValidator).interfaceId), + "Should support ISessionValidator interface" + ); + } + + function test_supportsInterface_withRandomInterface_returnsFalse() public view { + assertFalse( + mkSigner.supportsInterface(bytes4(keccak256("randomInterface()"))), "Should not support random interface" + ); + assertFalse(mkSigner.supportsInterface(0xffffffff), "Should not support 0xffffffff interface"); + } + + function test_isInitialized_alwaysReturnsTrue() public view { + assertTrue( + mkSigner.isInitialized(address(0)), "isInitialized should always return true for stateless validator" + ); + assertTrue(mkSigner.isInitialized(aliceAddr), "isInitialized should always return true, regardless of address"); + } + + // --- SignerEncode Tests (via MultiKeySigner.encodeSigners and direct if needed) --- + + function test_encodeSigners_withEmptyArray_succeeds() public view { + Signer[] memory signers = new Signer[](0); + bytes memory encodedData = mkSigner.encodeSigners(signers); + assertEq(encodedData.length, 1, "Encoded empty signers length"); + assertEq(uint8(encodedData[0]), 0, "Encoded empty signers byte content"); + + Signer[] memory decodedSigners = _decodeSignersViaCalldata(encodedData); + assertEq(decodedSigners.length, 0, "Decoded empty signers length"); + } + + function test_encodeDecode_withSingleEoa_succeeds() public view { + Signer[] memory signers = new Signer[](1); + signers[0] = Signer({ signerType: SignerType.EOA, data: abi.encodePacked(aliceAddr) }); + + bytes memory encodedData = mkSigner.encodeSigners(signers); + assertEq(encodedData.length, 1 + 1 + 20, "Encoded single EOA length"); + assertEq(uint8(encodedData[0]), 1, "Signer array length in encoded data"); + assertEq(uint8(encodedData[1]), uint8(SignerType.EOA), "Signer type EOA in encoded data"); + + (Signer[] memory decodedSigners) = _decodeSignersViaCalldata(encodedData); + assertEq(decodedSigners.length, 1, "Decoded single EOA length"); + assertEq(uint8(decodedSigners[0].signerType), uint8(SignerType.EOA), "Decoded EOA signer type"); + assertEq(SignerEncode.decodeEOA(decodedSigners[0]), aliceAddr, "Decoded EOA address"); + } + + function test_encodeDecode_withSinglePasskey_succeeds() public view { + Signer[] memory signers = new Signer[](1); + // Create dummy WebAuthnValidatorData for encoding test; contents don't matter for encoding structure + WebAuthnValidatorData memory pkData = WebAuthnValidatorData({ pubKeyX: 123, pubKeyY: 456 }); + signers[0] = Signer({ + signerType: SignerType.PASSKEY, + data: abi.encode(pkData) // WebAuthnValidatorData is a struct, abi.encode it + }); + + bytes memory encodedData = mkSigner.encodeSigners(signers); + assertEq(encodedData.length, 1 + 1 + 64, "Encoded single Passkey length"); + assertEq(uint8(encodedData[0]), 1, "Signer array length for Passkey"); + assertEq(uint8(encodedData[1]), uint8(SignerType.PASSKEY), "Signer type Passkey in encoded data"); + + (Signer[] memory decodedSigners) = _decodeSignersViaCalldata(encodedData); + assertEq(decodedSigners.length, 1, "Decoded single Passkey length"); + assertEq(uint8(decodedSigners[0].signerType), uint8(SignerType.PASSKEY), "Decoded Passkey signer type"); + WebAuthnValidatorData memory decodedPkData = SignerEncode.decodePasskey(decodedSigners[0]); + assertEq(decodedPkData.pubKeyX, 123, "Decoded Passkey pubKeyX"); + assertEq(decodedPkData.pubKeyY, 456, "Decoded Passkey pubKeyY"); + } + + function test_encodeDecode_withMultipleSigners_succeeds() public view { + Signer[] memory signers = new Signer[](2); + WebAuthnValidatorData memory pkData = WebAuthnValidatorData({ pubKeyX: 789, pubKeyY: 101 }); + signers[0] = Signer({ signerType: SignerType.EOA, data: abi.encodePacked(aliceAddr) }); + signers[1] = Signer({ signerType: SignerType.PASSKEY, data: abi.encode(pkData) }); + + bytes memory encodedData = mkSigner.encodeSigners(signers); + assertEq(encodedData.length, 1 + (1 + 20) + (1 + 64), "Encoded multiple signers length"); + assertEq(uint8(encodedData[0]), 2, "Signer array length for multiple signers"); + + (Signer[] memory decodedSigners) = _decodeSignersViaCalldata(encodedData); + assertEq(decodedSigners.length, 2, "Decoded multiple signers length"); + assertEq(uint8(decodedSigners[0].signerType), uint8(SignerType.EOA), "Decoded signer 0 type (EOA)"); + assertEq(SignerEncode.decodeEOA(decodedSigners[0]), aliceAddr, "Decoded signer 0 EOA address"); + assertEq(uint8(decodedSigners[1].signerType), uint8(SignerType.PASSKEY), "Decoded signer 1 type (Passkey)"); + WebAuthnValidatorData memory decodedPkData = SignerEncode.decodePasskey(decodedSigners[1]); + assertEq(decodedPkData.pubKeyX, 789, "Decoded signer 1 Passkey pubKeyX"); + assertEq(decodedPkData.pubKeyY, 101, "Decoded signer 1 Passkey pubKeyY"); + } + + function test_RevertWhen_decodeSigners_withInvalidType() public { + bytes memory malformedData = abi.encodePacked(uint8(1), uint8(99), bytes20(0)); + vm.expectRevert(SignerEncode.UnknownSignerType.selector); + _decodeSignersViaCalldata(malformedData); + } + + // --- validateSignatureWithData Tests - EOA --- + + function test_validateSignatureWithData_withSingleValidEoaSignature_returnsTrue() public view { + bytes32 userOpHash = keccak256("test_user_op_hash_eoa_valid"); + bytes32 ethSignedHash = _getEthSignedMessageHash(userOpHash); + + Signer[] memory signers = new Signer[](1); + signers[0] = Signer({ signerType: SignerType.EOA, data: abi.encodePacked(aliceAddr) }); + bytes memory signerData = _encodeSignerArray(signers); + + bytes[] memory signatures = new bytes[](1); + signatures[0] = _signWithEoa(ethSignedHash, alicePk); + bytes memory sigData = _encodeSignatures(signatures); + + bool result = mkSigner.validateSignatureWithData(userOpHash, sigData, signerData); + assertEq(result, true, "Valid single EOA signature should pass"); + } + + function test_validateSignatureWithData_withSingleEoa_invalidSignatureWrongSigner_returnsFalse() public view { + bytes32 userOpHash = keccak256("test_user_op_hash_eoa_invalid_signer"); + bytes32 ethSignedHash = _getEthSignedMessageHash(userOpHash); + + Signer[] memory signers = new Signer[](1); + // Expecting Alice to sign, but Bob actually signs + signers[0] = Signer({ signerType: SignerType.EOA, data: abi.encodePacked(aliceAddr) }); + bytes memory signerData = _encodeSignerArray(signers); + + bytes[] memory signatures = new bytes[](1); + signatures[0] = _signWithEoa(ethSignedHash, bobPk); // Bob signs + bytes memory sigData = _encodeSignatures(signatures); + + bool result = mkSigner.validateSignatureWithData(userOpHash, sigData, signerData); + assertEq(result, false, "Invalid EOA signature (wrong signer) should fail"); + } + + function test_validateSignatureWithData_withSingleEoa_invalidSignatureDataPointsToWrongSigner_returnsFalse() + public + view + { + bytes32 userOpHash = keccak256("test_user_op_hash_eoa_data_wrong_signer"); + bytes32 ethSignedHash = _getEthSignedMessageHash(userOpHash); + + Signer[] memory signers = new Signer[](1); + // Data says Bob should sign, but Alice signs + signers[0] = Signer({ signerType: SignerType.EOA, data: abi.encodePacked(bobAddr) }); + bytes memory signerData = _encodeSignerArray(signers); + + bytes[] memory signatures = new bytes[](1); + signatures[0] = _signWithEoa(ethSignedHash, alicePk); // Alice signs + bytes memory sigData = _encodeSignatures(signatures); + + bool result = mkSigner.validateSignatureWithData(userOpHash, sigData, signerData); + assertEq(result, false, "Invalid EOA signature (signerData points to wrong EOA) should fail"); + } + + function test_validateSignatureWithData_withSingleEoa_invalidSignatureWrongHash_returnsFalse() public view { + bytes32 userOpHash = keccak256("test_user_op_hash_eoa_wrong_hash_1"); + bytes32 wrongUserOpHash = keccak256("test_user_op_hash_eoa_wrong_hash_2"); + bytes32 ethSignedHashForSigning = _getEthSignedMessageHash(wrongUserOpHash); + + Signer[] memory signers = new Signer[](1); + signers[0] = Signer({ signerType: SignerType.EOA, data: abi.encodePacked(aliceAddr) }); + bytes memory signerData = _encodeSignerArray(signers); + + bytes[] memory signatures = new bytes[](1); + // Sign the wrong hash + signatures[0] = _signWithEoa(ethSignedHashForSigning, alicePk); + bytes memory sigData = _encodeSignatures(signatures); + + // Validate against the original userOpHash + bool result = mkSigner.validateSignatureWithData(userOpHash, sigData, signerData); + assertEq(result, false, "Invalid EOA signature (signed wrong hash) should fail"); + } + + // --- validateSignatureWithData Tests - Passkey --- + + function test_validateSignatureWithData_withSingleValidPasskeySignature_returnsTrue() public view { + // Use the default userOpHash/challenge for which the default passkey signature is valid. + bytes32 userOpHash = DEFAULT_USER_OP_HASH_FOR_PASSKEY; + + Signer[] memory signers = new Signer[](1); + signers[0] = Signer({ signerType: SignerType.PASSKEY, data: abi.encode(passkeyDefaultData) }); + bytes memory signerData = _encodeSignerArray(signers); + + // Create signature blob for the userOpHash + bytes memory passkeySigBlob = _createPasskeySignatureBlob(userOpHash); + + bytes[] memory signatures = new bytes[](1); + signatures[0] = passkeySigBlob; + bytes memory sigData = _encodeSignatures(signatures); + + bool result = mkSigner.validateSignatureWithData(userOpHash, sigData, signerData); + assertEq(result, true, "Valid Passkey signature should pass"); + } + + function test_validateSignatureWithData_withSinglePasskey_invalidSignatureTampered_returnsFalse() public view { + bytes32 userOpHash = DEFAULT_USER_OP_HASH_FOR_PASSKEY; + + Signer[] memory signers = new Signer[](1); + signers[0] = Signer({ signerType: SignerType.PASSKEY, data: abi.encode(passkeyDefaultData) }); + bytes memory signerData = _encodeSignerArray(signers); + + // Create original blob + bytes memory originalPasskeySigBlob = _createPasskeySignatureBlob(userOpHash); + + // Decode, tamper, and re-encode the WebAuthn.WebAuthnAuth struct + WebAuthn.WebAuthnAuth memory authStruct = abi.decode(originalPasskeySigBlob, (WebAuthn.WebAuthnAuth)); + + // Tamper one of the signature components, e.g., r + authStruct.r = authStruct.r + 1; + + bytes memory tamperedPasskeySigBlob = abi.encode(authStruct); + + bytes[] memory signatures = new bytes[](1); + signatures[0] = tamperedPasskeySigBlob; + bytes memory sigData = _encodeSignatures(signatures); + + bool result = mkSigner.validateSignatureWithData(userOpHash, sigData, signerData); + assertEq(result, false, "Tampered Passkey signature should fail"); + } + + function test_validateSignatureWithData_withSinglePasskey_wrongPublicKeyInSignerData_returnsFalse() public view { + bytes32 userOpHash = DEFAULT_USER_OP_HASH_FOR_PASSKEY; + + WebAuthnValidatorData memory wrongPasskeyData = WebAuthnValidatorData({ pubKeyX: 1, pubKeyY: 2 }); + + Signer[] memory signers = new Signer[](1); + signers[0] = Signer({ signerType: SignerType.PASSKEY, data: abi.encode(wrongPasskeyData) }); + bytes memory signerData = _encodeSignerArray(signers); + + // Signature is for the default public key, generated with DEFAULT_USER_OP_HASH_FOR_PASSKEY + bytes memory passkeySigBlob = _createPasskeySignatureBlob(userOpHash); + + bytes[] memory signatures = new bytes[](1); + signatures[0] = passkeySigBlob; + bytes memory sigData = _encodeSignatures(signatures); + + bool result = mkSigner.validateSignatureWithData(userOpHash, sigData, signerData); + assertEq(result, false, "Passkey sig with wrong pubkey in signer data should fail"); + } + + function test_validateSignatureWithData_withSinglePasskey_challengeMismatch_returnsFalse() public view { + // This is the hash that validateSignatureWithData will use as the expected challenge. + bytes32 userOpHashForValidation = keccak256(abi.encodePacked("passkey_challenge_mismatch_validation_hash_new")); + + // The signature blob, however, is created for `DEFAULT_USER_OP_HASH_FOR_PASSKEY`. + bytes32 challengeForBlobCreation = DEFAULT_USER_OP_HASH_FOR_PASSKEY; + + Signer[] memory signers = new Signer[](1); + signers[0] = Signer({ signerType: SignerType.PASSKEY, data: abi.encode(passkeyDefaultData) }); + bytes memory signerData = _encodeSignerArray(signers); + + // Create signature blob with clientDataJSON referring to `challengeForBlobCreation` + bytes memory passkeySigBlob = _createPasskeySignatureBlob(challengeForBlobCreation); + + bytes[] memory signatures = new bytes[](1); + signatures[0] = passkeySigBlob; + bytes memory sigData = _encodeSignatures(signatures); + + // Validate against `userOpHashForValidation`. This should fail because the challenge + // embedded in the blob's clientDataJSON (derived from `challengeForBlobCreation`) + // will not match `userOpHashForValidation`. + bool result = mkSigner.validateSignatureWithData(userOpHashForValidation, sigData, signerData); + assertEq(result, false, "Passkey sig with challenge mismatch should fail"); + } + + // --- validateSignatureWithData Tests - Mixed Signers --- + + function test_validateSignatureWithData_withMixedSigners_eoaValid_passkeyValid_returnsTrue() public view { + // Use DEFAULT_USER_OP_HASH_FOR_PASSKEY for the Passkey part's challenge. + bytes32 userOpHash = DEFAULT_USER_OP_HASH_FOR_PASSKEY; + // EOA part will sign the ethSignedMessageHash of this userOpHash. + bytes32 ethSignedHash = _getEthSignedMessageHash(userOpHash); + + Signer[] memory signers = new Signer[](2); + signers[0] = Signer({ signerType: SignerType.EOA, data: abi.encodePacked(aliceAddr) }); + signers[1] = Signer({ signerType: SignerType.PASSKEY, data: abi.encode(passkeyDefaultData) }); + bytes memory signerData = _encodeSignerArray(signers); + + bytes[] memory signatures = new bytes[](2); + signatures[0] = _signWithEoa(ethSignedHash, alicePk); + signatures[1] = _createPasskeySignatureBlob(userOpHash); // Passkey blob created with raw userOpHash challenge + bytes memory sigData = _encodeSignatures(signatures); + + bool result = mkSigner.validateSignatureWithData(userOpHash, sigData, signerData); + assertEq(result, true, "Mixed (Valid EOA + Valid Passkey) should succeed"); + } + + function test_validateSignatureWithData_withMixedSigners_eoaValid_passkeyInvalid_returnsFalse() public view { + bytes32 userOpHash = DEFAULT_USER_OP_HASH_FOR_PASSKEY; + bytes32 ethSignedHash = _getEthSignedMessageHash(userOpHash); + + Signer[] memory signers = new Signer[](2); + signers[0] = Signer({ signerType: SignerType.EOA, data: abi.encodePacked(aliceAddr) }); + signers[1] = Signer({ signerType: SignerType.PASSKEY, data: abi.encode(passkeyDefaultData) }); + bytes memory signerData = _encodeSignerArray(signers); + + bytes memory validPasskeyBlob = _createPasskeySignatureBlob(userOpHash); + WebAuthn.WebAuthnAuth memory authStruct = abi.decode(validPasskeyBlob, (WebAuthn.WebAuthnAuth)); + authStruct.s = authStruct.s - 1; // Tamper passkey sig component + bytes memory invalidPasskeyBlob = abi.encode(authStruct); + + bytes[] memory signatures = new bytes[](2); + signatures[0] = _signWithEoa(ethSignedHash, alicePk); + signatures[1] = invalidPasskeyBlob; + bytes memory sigData = _encodeSignatures(signatures); + + bool result = mkSigner.validateSignatureWithData(userOpHash, sigData, signerData); + assertEq(result, false, "Mixed (Valid EOA + Invalid Passkey) should fail"); + } + + function test_validateSignatureWithData_withMixedSigners_eoaInvalid_passkeyValid_returnsFalse() public view { + bytes32 userOpHash = DEFAULT_USER_OP_HASH_FOR_PASSKEY; + bytes32 ethSignedHash = _getEthSignedMessageHash(userOpHash); + + Signer[] memory signers = new Signer[](2); + signers[0] = Signer({ signerType: SignerType.EOA, data: abi.encodePacked(aliceAddr) }); // Alice is expected + signers[1] = Signer({ signerType: SignerType.PASSKEY, data: abi.encode(passkeyDefaultData) }); + bytes memory signerData = _encodeSignerArray(signers); + + bytes[] memory signatures = new bytes[](2); + signatures[0] = _signWithEoa(ethSignedHash, bobPk); // Invalid EOA sig (signed by Bob) + signatures[1] = _createPasskeySignatureBlob(userOpHash); // Valid passkey blob + bytes memory sigData = _encodeSignatures(signatures); + + bool result = mkSigner.validateSignatureWithData(userOpHash, sigData, signerData); + assertEq(result, false, "Mixed (Invalid EOA + Valid Passkey) should fail"); + } + + // --- validateSignatureWithData Tests - Edge Cases --- + + function test_validateSignatureWithData_withEmptySignersAndSignatures_returnsTrue() public view { + bytes32 userOpHash = keccak256(abi.encodePacked("empty_signers_test")); + + Signer[] memory signers = new Signer[](0); + bytes memory signerData = _encodeSignerArray(signers); + + bytes[] memory signatures = new bytes[](0); + bytes memory sigData = _encodeSignatures(signatures); + + bool result = mkSigner.validateSignatureWithData(userOpHash, sigData, signerData); + assertEq(result, true, "Empty signers and signatures should succeed (validate nothing)"); + } + + function test_RevertWhen_validateSignatureWithData_lengthMismatch_signatureArrayShorter() public { + bytes32 userOpHash = keccak256(abi.encodePacked("length_mismatch_shorter_sig")); + + Signer[] memory signers = new Signer[](1); + signers[0] = Signer({ signerType: SignerType.EOA, data: abi.encodePacked(aliceAddr) }); + bytes memory signerData = _encodeSignerArray(signers); + + bytes[] memory signatures = new bytes[](0); // Sig array is shorter + bytes memory sigData = _encodeSignatures(signatures); + + vm.expectRevert(MultiKeySigner.InvalidSignatureLength.selector); + mkSigner.validateSignatureWithData(userOpHash, sigData, signerData); + } + + function test_RevertWhen_validateSignatureWithData_lengthMismatch_signatureArrayLonger() public { + bytes32 userOpHash = keccak256(abi.encodePacked("length_mismatch_longer_sig")); + bytes32 ethSignedHash = _getEthSignedMessageHash(userOpHash); + + Signer[] memory signers = new Signer[](1); + signers[0] = Signer({ signerType: SignerType.EOA, data: abi.encodePacked(aliceAddr) }); + bytes memory signerData = _encodeSignerArray(signers); + + bytes[] memory signatures = new bytes[](2); + signatures[0] = _signWithEoa(ethSignedHash, alicePk); + signatures[1] = _signWithEoa(ethSignedHash, bobPk); // Extra signature + bytes memory sigData = _encodeSignatures(signatures); + + vm.expectRevert(MultiKeySigner.InvalidSignatureLength.selector); + mkSigner.validateSignatureWithData(userOpHash, sigData, signerData); + } + + function test_RevertWhen_validateSignatureWithData_unknownSignerTypeInSignerData() public { + bytes32 userOpHash = keccak256(abi.encodePacked("unknown_signer_type_test")); + + // Manually craft signer data with an invalid type + // Signer array of length 1 + // SignerType = 99 (invalid) + // Data = 20 bytes of zero (doesn't matter as type check fails first) + bytes memory malformedSignerData = abi.encodePacked(uint8(1), uint8(99), bytes20(0)); + + bytes[] memory signatures = new bytes[](1); + signatures[0] = hex"00"; // Dummy signature, won't be reached + bytes memory sigData = _encodeSignatures(signatures); + + vm.expectRevert(SignerEncode.UnknownSignerType.selector); + // This revert comes from SignerEncode.decodeSigners directly when it encounters an unknown type, + // as it cannot determine the length of the data to skip. + // MultiKeySigner.validateSignatureWithData's InvalidSignatureType would only be hit if decoding somehow + // succeeded + // but the type was still not one of the recognized SignerType enums. + mkSigner.validateSignatureWithData(userOpHash, sigData, malformedSignerData); + } +} diff --git a/test/unit/OwnableValidatorIntegration.t.sol b/test/unit/OwnableValidatorIntegration.t.sol new file mode 100644 index 00000000..2920be37 --- /dev/null +++ b/test/unit/OwnableValidatorIntegration.t.sol @@ -0,0 +1,408 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import "../Base.t.sol"; +import { OwnableValidator } from "test/unit/FlattenedOwnableValidator.sol"; +import "solady/utils/ECDSA.sol"; +import { PackedUserOperation } from "modulekit/ModuleKit.sol"; + +/** + * @title OwnableValidatorIntegration + * @dev Comprehensive test demonstrating OwnableValidator usage as a stateless validator in Smart Sessions + * Tests the complete flow from session creation to transaction execution with edge cases + */ +contract OwnableValidatorIntegrationTest is BaseTest { + using ModuleKitHelpers for *; + using ModuleKitUserOp for *; + using EncodeLib for PermissionId; + + /*////////////////////////////////////////////////////////////////////////// + CONTRACTS + //////////////////////////////////////////////////////////////////////////*/ + + OwnableValidator internal ownableValidator; + + /*////////////////////////////////////////////////////////////////////////// + VARIABLES + //////////////////////////////////////////////////////////////////////////*/ + + // Test signers (DApp + Backend) - properly sorted for OwnableValidator + address[] internal owners; + uint256[] internal ownerPks; + uint256 internal threshold; + + // Session salt + bytes32 internal constant SESSION_SALT = 0x3100000000000000000000000000000000000000000000000000000000000000; + + /*////////////////////////////////////////////////////////////////////////// + SETUP + //////////////////////////////////////////////////////////////////////////*/ + + function setUp() public virtual override { + super.setUp(); + + // Deploy OwnableValidator + ownableValidator = new OwnableValidator(); + + // Create properly sorted signers for multisig + _setupSortedOwners(); + + // Require all signers to sign (DApp + Backend) + threshold = owners.length; + } + + /*////////////////////////////////////////////////////////////////////////// + HELPER FUNCTIONS + //////////////////////////////////////////////////////////////////////////*/ + + function _setupSortedOwners() internal { + owners = new address[](2); + ownerPks = new uint256[](2); + + (address owner1, uint256 owner1Pk) = makeAddrAndKey("dappSigner"); + (address owner2, uint256 owner2Pk) = makeAddrAndKey("backendSigner"); + + // Ensure proper sorting (OwnableValidator requires sorted owners) + if (uint160(owner1) < uint160(owner2)) { + owners[0] = owner1; + ownerPks[0] = owner1Pk; + owners[1] = owner2; + ownerPks[1] = owner2Pk; + } else { + owners[0] = owner2; + ownerPks[0] = owner2Pk; + owners[1] = owner1; + ownerPks[1] = owner1Pk; + } + } + + function _createSession(uint256 _threshold, address[] memory _owners) internal returns (Session memory) { + bytes memory ownableValidatorInitData = abi.encode(_threshold, _owners); + + return Session({ + sessionValidator: ISessionValidator(address(ownableValidator)), + salt: SESSION_SALT, + sessionValidatorInitData: ownableValidatorInitData, + userOpPolicies: _getEmptyPolicyDatas(address(sudoPolicy)), + erc7739Policies: _getEmptyERC7739Data("0", new PolicyData[](0)), + actions: _getContractCallActions(), + permitERC4337Paymaster: true + }); + } + + function _enableSession(Session memory session) internal returns (PermissionId) { + Session[] memory sessions = new Session[](1); + sessions[0] = session; + + vm.prank(instance.account); + PermissionId[] memory permissionIds = smartSession.enableSessions(sessions); + return permissionIds[0]; + } + + function _executeWithSession(PermissionId permissionId, uint256 value) internal returns (UserOpData memory) { + UserOpData memory userOpData = instance.getExecOps({ + target: address(target), + value: 0, + callData: abi.encodeCall(MockTarget.setValue, (value)), + txValidator: address(smartSession) + }); + + userOpData.userOp.signature = + EncodeLib.encodeUse({ permissionId: permissionId, sig: _createOwnableSignatures(userOpData.userOp) }); + + userOpData.execUserOps(); + return userOpData; + } + + function _getContractCallActions() internal view returns (ActionData[] memory) { + ActionData[] memory actions = new ActionData[](1); + actions[0] = ActionData({ + actionTarget: address(target), + actionTargetSelector: MockTarget.setValue.selector, + actionPolicies: _getEmptyPolicyDatas(address(sudoPolicy)) + }); + return actions; + } + + function _createOwnableSignatures(PackedUserOperation memory userOp) internal view returns (bytes memory) { + bytes32 userOpHash = instance.aux.entrypoint.getUserOpHash(userOp); + + // OwnableValidator calls ECDSA.toEthSignedMessageHash(hash) internally + bytes32 ethHash = ECDSA.toEthSignedMessageHash(userOpHash); + (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(ownerPks[0], ethHash); + (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(ownerPks[1], ethHash); + + // Pack signatures in the format expected by OwnableValidator: {r}{s}{v} concatenated + return abi.encodePacked(r1, s1, v1, r2, s2, v2); + } + + function _createInsufficientSignatures(PackedUserOperation memory userOp) internal view returns (bytes memory) { + bytes32 userOpHash = instance.aux.entrypoint.getUserOpHash(userOp); + bytes32 ethHash = ECDSA.toEthSignedMessageHash(userOpHash); + (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(ownerPks[0], ethHash); + + // Only one signature (insufficient for threshold of 2) + return abi.encodePacked(r1, s1, v1); + } + + /*////////////////////////////////////////////////////////////////////////// + CORE TESTS + //////////////////////////////////////////////////////////////////////////*/ + + /** + * @dev Test complete OwnableValidator session flow with Smart Sessions + */ + function test_ownableValidatorSessionFlow() public { + // Create and enable session + Session memory session = _createSession(threshold, owners); + PermissionId permissionId = _enableSession(session); + + // Verify the session is enabled + assertTrue(smartSession.isPermissionEnabled(permissionId, instance.account)); + + // Execute first transaction + _executeWithSession(permissionId, 1337); + assertEq(target.value(), 1337); + + // Execute second transaction using same session + _executeWithSession(permissionId, 1338); + assertEq(target.value(), 1338); + } + + /** + * @dev Test OwnableValidator stateless validation directly + */ + function test_ownableValidatorStatelessValidation() public { + bytes32 testHash = keccak256("test message"); + bytes32 ethHash = ECDSA.toEthSignedMessageHash(testHash); + + // Create signatures from both owners (in sorted order) + (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(ownerPks[0], ethHash); + (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(ownerPks[1], ethHash); + + bytes memory combinedSignature = abi.encodePacked(r1, s1, v1, r2, s2, v2); + bytes memory validatorData = abi.encode(threshold, owners); + + // Test direct stateless validation + bool isValid = ownableValidator.validateSignatureWithData(testHash, combinedSignature, validatorData); + + assertTrue(isValid, "Signature should be valid"); + } + + /*////////////////////////////////////////////////////////////////////////// + THRESHOLD TESTS + //////////////////////////////////////////////////////////////////////////*/ + + /** + * @dev Test session creation with different threshold configurations + */ + function test_ownableValidatorDifferentThresholds() public { + // Test with threshold of 1 (any signer can authorize) + uint256 relaxedThreshold = 1; + Session memory session = _createSession(relaxedThreshold, owners); + PermissionId permissionId = _enableSession(session); + + // Verify session is enabled + assertTrue(smartSession.isPermissionEnabled(permissionId, instance.account)); + } + + /** + * @dev Test that threshold of 0 fails validation + */ + function test_ownableValidatorThresholdZeroFails() public { + bytes32 testHash = keccak256("test message"); + bytes memory validatorData = abi.encode(0, owners); // threshold = 0 + bytes memory signature = abi.encodePacked(bytes32(0), bytes32(0), uint8(0)); + + bool isValid = ownableValidator.validateSignatureWithData(testHash, signature, validatorData); + + assertFalse(isValid, "Threshold of 0 should fail"); + } + + /*////////////////////////////////////////////////////////////////////////// + ERROR TESTS + //////////////////////////////////////////////////////////////////////////*/ + + /** + * @dev Test that insufficient signatures fail validation + */ + function test_ownableValidatorInsufficientSignatures() public { + bytes32 testHash = keccak256("test message"); + bytes32 ethHash = ECDSA.toEthSignedMessageHash(testHash); + + // Only one signature (insufficient for threshold of 2) + (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(ownerPks[0], ethHash); + bytes memory insufficientSignature = abi.encodePacked(r1, s1, v1); + bytes memory validatorData = abi.encode(threshold, owners); + + // Should revert when insufficient signatures provided + vm.expectRevert(); + ownableValidator.validateSignatureWithData(testHash, insufficientSignature, validatorData); + } + + /** + * @dev Test that unsorted owners fail validation + */ + function test_ownableValidatorUnsortedOwnersFail() public { + bytes32 testHash = keccak256("test message"); + + // Create unsorted owners array + address[] memory unsortedOwners = new address[](2); + unsortedOwners[0] = owners[1]; // Swap order + unsortedOwners[1] = owners[0]; + + bytes memory validatorData = abi.encode(threshold, unsortedOwners); + bytes memory signature = abi.encodePacked(bytes32(0), bytes32(0), uint8(0)); + + bool isValid = ownableValidator.validateSignatureWithData(testHash, signature, validatorData); + + assertFalse(isValid, "Unsorted owners should fail"); + } + + /** + * @dev Test that wrong signer fails validation + */ + function test_ownableValidatorWrongSignerFails() public { + bytes32 testHash = keccak256("test message"); + bytes32 ethHash = ECDSA.toEthSignedMessageHash(testHash); + + // Create signature with wrong signer + (address wrongSigner, uint256 wrongPk) = makeAddrAndKey("wrongSigner"); + (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(wrongPk, ethHash); + (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(ownerPks[1], ethHash); + + bytes memory wrongSignature = abi.encodePacked(r1, s1, v1, r2, s2, v2); + bytes memory validatorData = abi.encode(threshold, owners); + + bool isValid = ownableValidator.validateSignatureWithData(testHash, wrongSignature, validatorData); + + assertFalse(isValid, "Wrong signer should fail"); + } + + /** + * @dev Test session execution with insufficient signatures fails + */ + function test_sessionExecutionWithInsufficientSignaturesFails() public { + Session memory session = _createSession(threshold, owners); + PermissionId permissionId = _enableSession(session); + + UserOpData memory userOpData = instance.getExecOps({ + target: address(target), + value: 0, + callData: abi.encodeCall(MockTarget.setValue, (1337)), + txValidator: address(smartSession) + }); + + // Use insufficient signatures + userOpData.userOp.signature = + EncodeLib.encodeUse({ permissionId: permissionId, sig: _createInsufficientSignatures(userOpData.userOp) }); + + // Should fail during execution + vm.expectRevert(); + userOpData.execUserOps(); + } + + /*////////////////////////////////////////////////////////////////////////// + EDGE CASE TESTS + //////////////////////////////////////////////////////////////////////////*/ + + /** + * @dev Test with maximum number of owners + */ + function test_ownableValidatorManyOwners() public { + // Create session with 5 owners, threshold 3 + address[] memory manyOwners = new address[](5); + uint256[] memory manyOwnerPks = new uint256[](5); + + for (uint256 i = 0; i < 5; i++) { + (manyOwners[i], manyOwnerPks[i]) = makeAddrAndKey(string(abi.encodePacked("owner", i))); + } + + // Sort owners (simple bubble sort for test) + for (uint256 i = 0; i < manyOwners.length - 1; i++) { + for (uint256 j = 0; j < manyOwners.length - i - 1; j++) { + if (uint160(manyOwners[j]) > uint160(manyOwners[j + 1])) { + // Swap addresses + address tempAddr = manyOwners[j]; + manyOwners[j] = manyOwners[j + 1]; + manyOwners[j + 1] = tempAddr; + + // Swap private keys + uint256 tempPk = manyOwnerPks[j]; + manyOwnerPks[j] = manyOwnerPks[j + 1]; + manyOwnerPks[j + 1] = tempPk; + } + } + } + + uint256 manyThreshold = 3; + Session memory session = _createSession(manyThreshold, manyOwners); + PermissionId permissionId = _enableSession(session); + + assertTrue(smartSession.isPermissionEnabled(permissionId, instance.account)); + } + + /** + * @dev Test empty owners array fails + */ + function test_ownableValidatorEmptyOwnersFails() public { + address[] memory emptyOwners = new address[](0); + bytes32 testHash = keccak256("test message"); + bytes memory validatorData = abi.encode(1, emptyOwners); + bytes memory signature = ""; + + // Should revert with InvalidSignature for empty owners + vm.expectRevert(); + ownableValidator.validateSignatureWithData(testHash, signature, validatorData); + } + + /*////////////////////////////////////////////////////////////////////////// + INTEGRATION TESTS + //////////////////////////////////////////////////////////////////////////*/ + + /** + * @dev Test session reuse across multiple transactions + */ + function test_sessionReuseAcrossMultipleTransactions() public { + Session memory session = _createSession(threshold, owners); + PermissionId permissionId = _enableSession(session); + + // Execute multiple transactions with same session + uint256[] memory values = new uint256[](5); + values[0] = 100; + values[1] = 200; + values[2] = 300; + values[3] = 400; + values[4] = 500; + + for (uint256 i = 0; i < values.length; i++) { + _executeWithSession(permissionId, values[i]); + assertEq(target.value(), values[i]); + } + } + + /** + * @dev Test multiple sessions with different configurations + */ + function test_multipleSessionsWithDifferentConfigurations() public { + // Session 1: Threshold 2 + Session memory session1 = _createSession(2, owners); + PermissionId permissionId1 = _enableSession(session1); + + // Session 2: Threshold 1 + Session memory session2 = _createSession(1, owners); + session2.salt = bytes32(uint256(SESSION_SALT) + 1); // Different salt + PermissionId permissionId2 = _enableSession(session2); + + // Both sessions should be enabled + assertTrue(smartSession.isPermissionEnabled(permissionId1, instance.account)); + assertTrue(smartSession.isPermissionEnabled(permissionId2, instance.account)); + + // Both should work + _executeWithSession(permissionId1, 1000); + assertEq(target.value(), 1000); + + _executeWithSession(permissionId2, 2000); + assertEq(target.value(), 2000); + } +} diff --git a/test/utils/WebAuthnUtils.sol b/test/utils/WebAuthnUtils.sol new file mode 100644 index 00000000..19f382cf --- /dev/null +++ b/test/utils/WebAuthnUtils.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { FCL_Elliptic_ZZ } from "freshcryptolib/FCL_elliptic.sol"; +import { Base64Url } from "freshcryptolib/utils/Base64Url.sol"; + +struct WebAuthnInfo { + bytes authenticatorData; + string clientDataJSON; + bytes32 messageHash; +} + +library Utils { + uint256 constant P256_N_DIV_2 = FCL_Elliptic_ZZ.n / 2; + + function getWebAuthnStruct(bytes32 challenge) public pure returns (WebAuthnInfo memory) { + string memory challengeb64url = Base64Url.encode(abi.encode(challenge)); + string memory clientDataJSON = string( + abi.encodePacked( + '{"type":"webauthn.get","challenge":"', + challengeb64url, + '","origin":"https://sign.coinbase.com","crossOrigin":false}' + ) + ); + + // Authenticator data for Chrome Profile touchID signature + bytes memory authenticatorData = hex"49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000"; + + bytes32 clientDataJSONHash = sha256(bytes(clientDataJSON)); + bytes32 messageHash = sha256(abi.encodePacked(authenticatorData, clientDataJSONHash)); + + return WebAuthnInfo(authenticatorData, clientDataJSON, messageHash); + } + + /// @dev normalizes the s value from a p256r1 signature so that + /// it will pass malleability checks. + function normalizeS(uint256 s) public pure returns (uint256) { + if (s > P256_N_DIV_2) { + return FCL_Elliptic_ZZ.n - s; + } + + return s; + } +}