{"version":3,"sources":["../../src/shared/verifySignature.ts"],"sourcesContent":["import {\n  hashTypedData,\n  recoverAddress,\n  isAddressEqual,\n  getAddress,\n  parseErc6492Signature,\n} from \"viem\";\nimport type { TypedDataDomain } from \"viem\";\nimport type { FacilitatorEvmSigner } from \"../signer\";\n\n/**\n * Parsed ERC-6492 classification for a payer address.\n *\n * `isCounterfactual` is true when the payment comes from an undeployed smart wallet\n * (ERC-6492 wrapper present, no bytecode at the payer address yet). In this case\n * pre-verification of the signature is deferred to on-chain simulation or settle.\n */\nexport type Erc6492Classification = {\n  isCounterfactual: boolean;\n  isDeployedAtPayer: boolean;\n  hasDeploymentInfo: boolean;\n  innerSignature: `0x${string}`;\n  eip6492Deployment?: { factoryAddress: `0x${string}`; factoryCalldata: `0x${string}` };\n};\n\nconst ZERO_ADDRESS = \"0x0000000000000000000000000000000000000000\" as const;\n\n/**\n * Classify an ERC-6492 payer in one RPC round-trip: parse the sig wrapper, fetch code,\n * and determine counterfactual vs deployed state.\n *\n * @param signer - Facilitator signer used to call `eth_getCode` on the payer address.\n * @param signature - The full signature, which may be an ERC-6492 wrapper.\n * @param payerAddress - The address whose bytecode is fetched to detect deployment state.\n * @returns Classification result including counterfactual flag, deployment state, and inner signature.\n */\nexport async function classifyErc6492Payer(\n  signer: FacilitatorEvmSigner,\n  signature: `0x${string}`,\n  payerAddress: `0x${string}`,\n): Promise<Erc6492Classification> {\n  const erc6492Data = parseErc6492Signature(signature);\n  const hasDeploymentInfo = !!(\n    erc6492Data.address &&\n    erc6492Data.data &&\n    !isAddressEqual(erc6492Data.address, ZERO_ADDRESS)\n  );\n  const innerSignature = hasDeploymentInfo ? erc6492Data.signature : signature;\n  const eip6492Deployment = hasDeploymentInfo\n    ? { factoryAddress: erc6492Data.address!, factoryCalldata: erc6492Data.data! }\n    : undefined;\n\n  let code: `0x${string}` | undefined;\n  try {\n    code = await signer.getCode({ address: payerAddress });\n  } catch {\n    code = undefined;\n  }\n  const isDeployedAtPayer = !!(code && code !== \"0x\");\n  const isCounterfactual = hasDeploymentInfo && !isDeployedAtPayer;\n\n  return {\n    isCounterfactual,\n    isDeployedAtPayer,\n    hasDeploymentInfo,\n    innerSignature,\n    eip6492Deployment,\n  };\n}\n\n/**\n * Strict signature verification primitive that mirrors on-chain SignatureChecker\n * semantics exactly:\n *\n *   if signer.code.length == 0:\n *     ecrecover(digest, sig) == signer\n *   else:\n *     IERC1271(signer).isValidSignature(digest, sig) == 0x1626ba7e\n *\n * This matches:\n *   - Permit2 (libraries/SignatureVerification.sol)\n *   - USDC v2.2 (Circle's util/SignatureChecker.sol)\n *   - x402BatchSettlement (uses OpenZeppelin SignatureChecker)\n *\n * It deliberately does NOT fall back to ECDSA when EIP-1271 returns failure.\n * That fallback (which viem's `publicClient.verifyTypedData` performs) makes\n * pre-verify accept signatures that on-chain rejects — most visibly for\n * ERC-7702 delegated EOAs whose delegate's `isValidSignature` does not accept\n * raw owner ECDSA. With this primitive, pre-verify outcome == on-chain outcome.\n *\n * Plain EOAs (no code) take the ecrecover path and behave identically to before.\n * 7702-delegated EOAs take the EIP-1271 path because they have code; the delegate\n * decides — same as on-chain.\n */\nconst ERC1271_MAGIC_VALUE = \"0x1626ba7e\" as const;\n\nconst ERC1271_ABI = [\n  {\n    name: \"isValidSignature\",\n    type: \"function\",\n    stateMutability: \"view\",\n    inputs: [\n      { name: \"hash\", type: \"bytes32\" },\n      { name: \"signature\", type: \"bytes\" },\n    ],\n    outputs: [{ name: \"\", type: \"bytes4\" }],\n  },\n] as const;\n\n/**\n * Verify a typed-data signature using strict on-chain SignatureChecker semantics.\n *\n * @param signer - Facilitator signer used for `eth_getCode` and `isValidSignature` calls.\n * @param params - Typed-data verification parameters.\n * @param params.address - The address that is expected to have signed the data.\n * @param params.domain - EIP-712 domain.\n * @param params.types - EIP-712 type definitions.\n * @param params.primaryType - The primary type to hash.\n * @param params.message - The typed-data message.\n * @param params.signature - The signature to verify.\n * @returns `true` if the signature is valid, `false` otherwise.\n */\nexport async function verifyTypedDataSignature(\n  signer: FacilitatorEvmSigner,\n  params: {\n    address: `0x${string}`;\n    domain: TypedDataDomain;\n    types: Record<string, readonly { name: string; type: string }[]>;\n    primaryType: string;\n    message: Record<string, unknown>;\n    signature: `0x${string}`;\n  },\n): Promise<boolean> {\n  let digest: `0x${string}`;\n  try {\n    digest = hashTypedData({\n      domain: params.domain,\n      types: params.types,\n      primaryType: params.primaryType,\n      message: params.message,\n    });\n  } catch {\n    // Malformed typed data (e.g. non-checksummed address that viem rejects).\n    // Treat as an invalid signature rather than propagating the error.\n    return false;\n  }\n  return verifyHashSignature(signer, params.address, digest, params.signature);\n}\n\n/**\n * Lower-level variant of {@link verifyTypedDataSignature} for callers that already have the digest.\n *\n * @param signer - Facilitator signer used for `eth_getCode` and `isValidSignature` calls.\n * @param address - The address that is expected to have produced the signature.\n * @param digest - The EIP-191 / EIP-712 message hash to verify against.\n * @param signature - The signature to verify.\n * @returns `true` if the signature is valid, `false` otherwise.\n */\nexport async function verifyHashSignature(\n  signer: FacilitatorEvmSigner,\n  address: `0x${string}`,\n  digest: `0x${string}`,\n  signature: `0x${string}`,\n): Promise<boolean> {\n  // getCode must be guarded: a transient RPC error should return false (invalid sig\n  // semantics) rather than propagating as an unhandled rejection. All callers rely on\n  // this function returning a boolean, never throwing.\n  let code: `0x${string}` | undefined;\n  try {\n    code = await signer.getCode({ address });\n  } catch {\n    return false;\n  }\n  return verifyHashSignatureWithCode(signer, address, code, digest, signature);\n}\n\n/**\n * Like {@link verifyHashSignature} but accepts pre-fetched bytecode to avoid a\n * redundant `eth_getCode` RPC when the caller already has it (e.g. after the\n * ERC-6492 counterfactual check in {@link classifyErc6492Payer}).\n *\n * Pass `undefined` or `\"0x\"` for `code` to take the EOA (ecrecover) path.\n *\n * @param signer - Facilitator signer used for `isValidSignature` calls on deployed contracts.\n * @param address - The address that is expected to have produced the signature.\n * @param code - Pre-fetched bytecode at `address`; `undefined` or `\"0x\"` takes the ECDSA path.\n * @param digest - The message hash to verify against.\n * @param signature - The signature to verify.\n * @returns `true` if the signature is valid, `false` otherwise.\n */\nexport function verifyHashSignatureWithCode(\n  signer: FacilitatorEvmSigner,\n  address: `0x${string}`,\n  code: `0x${string}` | undefined,\n  digest: `0x${string}`,\n  signature: `0x${string}`,\n): Promise<boolean> {\n  if (!code || code === \"0x\") {\n    return verifyECDSA(address, digest, signature);\n  }\n  return verifyERC1271(signer, address, digest, signature);\n}\n\n/**\n * ecrecover path — used when the address has no code.\n *\n * @param address - The address expected to be recovered from the signature.\n * @param digest - The message hash to recover from.\n * @param signature - The compact 65-byte ECDSA signature.\n * @returns `true` if ecrecover produces `address`, `false` otherwise.\n */\nexport async function verifyECDSA(\n  address: `0x${string}`,\n  digest: `0x${string}`,\n  signature: `0x${string}`,\n): Promise<boolean> {\n  const sigHex = signature.startsWith(\"0x\") ? signature.slice(2) : signature;\n  if (sigHex.length !== 130) return false;\n  try {\n    const recovered = await recoverAddress({ hash: digest, signature });\n    return isAddressEqual(getAddress(recovered), getAddress(address));\n  } catch {\n    return false;\n  }\n}\n\n/**\n * Strict EIP-1271 path — returns false on revert or non-magic return, never falls back to ECDSA.\n *\n * @param signer - Facilitator signer used to call `isValidSignature` on the contract.\n * @param address - The contract address whose `isValidSignature` is called.\n * @param digest - The hash passed as the first argument to `isValidSignature`.\n * @param signature - The signature bytes passed as the second argument.\n * @returns `true` if `isValidSignature` returns the ERC-1271 magic value, `false` otherwise.\n */\nexport async function verifyERC1271(\n  signer: FacilitatorEvmSigner,\n  address: `0x${string}`,\n  digest: `0x${string}`,\n  signature: `0x${string}`,\n): Promise<boolean> {\n  try {\n    const result = (await signer.readContract({\n      address,\n      abi: ERC1271_ABI,\n      functionName: \"isValidSignature\",\n      args: [digest, signature],\n    })) as `0x${string}` | undefined;\n    if (typeof result !== \"string\") return false;\n    return result.toLowerCase().startsWith(ERC1271_MAGIC_VALUE);\n  } catch {\n    return false;\n  }\n}\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAmBP,IAAM,eAAe;AAWrB,eAAsB,qBACpB,QACA,WACA,cACgC;AAChC,QAAM,cAAc,sBAAsB,SAAS;AACnD,QAAM,oBAAoB,CAAC,EACzB,YAAY,WACZ,YAAY,QACZ,CAAC,eAAe,YAAY,SAAS,YAAY;AAEnD,QAAM,iBAAiB,oBAAoB,YAAY,YAAY;AACnE,QAAM,oBAAoB,oBACtB,EAAE,gBAAgB,YAAY,SAAU,iBAAiB,YAAY,KAAM,IAC3E;AAEJ,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,OAAO,QAAQ,EAAE,SAAS,aAAa,CAAC;AAAA,EACvD,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,oBAAoB,CAAC,EAAE,QAAQ,SAAS;AAC9C,QAAM,mBAAmB,qBAAqB,CAAC;AAE/C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AA0BA,IAAM,sBAAsB;AAE5B,IAAM,cAAc;AAAA,EAClB;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,iBAAiB;AAAA,IACjB,QAAQ;AAAA,MACN,EAAE,MAAM,QAAQ,MAAM,UAAU;AAAA,MAChC,EAAE,MAAM,aAAa,MAAM,QAAQ;AAAA,IACrC;AAAA,IACA,SAAS,CAAC,EAAE,MAAM,IAAI,MAAM,SAAS,CAAC;AAAA,EACxC;AACF;AAeA,eAAsB,yBACpB,QACA,QAQkB;AAClB,MAAI;AACJ,MAAI;AACF,aAAS,cAAc;AAAA,MACrB,QAAQ,OAAO;AAAA,MACf,OAAO,OAAO;AAAA,MACd,aAAa,OAAO;AAAA,MACpB,SAAS,OAAO;AAAA,IAClB,CAAC;AAAA,EACH,QAAQ;AAGN,WAAO;AAAA,EACT;AACA,SAAO,oBAAoB,QAAQ,OAAO,SAAS,QAAQ,OAAO,SAAS;AAC7E;AAWA,eAAsB,oBACpB,QACA,SACA,QACA,WACkB;AAIlB,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,OAAO,QAAQ,EAAE,QAAQ,CAAC;AAAA,EACzC,QAAQ;AACN,WAAO;AAAA,EACT;AACA,SAAO,4BAA4B,QAAQ,SAAS,MAAM,QAAQ,SAAS;AAC7E;AAgBO,SAAS,4BACd,QACA,SACA,MACA,QACA,WACkB;AAClB,MAAI,CAAC,QAAQ,SAAS,MAAM;AAC1B,WAAO,YAAY,SAAS,QAAQ,SAAS;AAAA,EAC/C;AACA,SAAO,cAAc,QAAQ,SAAS,QAAQ,SAAS;AACzD;AAUA,eAAsB,YACpB,SACA,QACA,WACkB;AAClB,QAAM,SAAS,UAAU,WAAW,IAAI,IAAI,UAAU,MAAM,CAAC,IAAI;AACjE,MAAI,OAAO,WAAW,IAAK,QAAO;AAClC,MAAI;AACF,UAAM,YAAY,MAAM,eAAe,EAAE,MAAM,QAAQ,UAAU,CAAC;AAClE,WAAO,eAAe,WAAW,SAAS,GAAG,WAAW,OAAO,CAAC;AAAA,EAClE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAWA,eAAsB,cACpB,QACA,SACA,QACA,WACkB;AAClB,MAAI;AACF,UAAM,SAAU,MAAM,OAAO,aAAa;AAAA,MACxC;AAAA,MACA,KAAK;AAAA,MACL,cAAc;AAAA,MACd,MAAM,CAAC,QAAQ,SAAS;AAAA,IAC1B,CAAC;AACD,QAAI,OAAO,WAAW,SAAU,QAAO;AACvC,WAAO,OAAO,YAAY,EAAE,WAAW,mBAAmB;AAAA,EAC5D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}