From 7967b4a865c07d0e3623438c28bcee978e1fc5fc Mon Sep 17 00:00:00 2001 From: Nicholas Rodrigues Lordello Date: Thu, 6 Jun 2024 20:19:31 +0200 Subject: [PATCH] move 4337-passkeys-singleton-signer to 4337-passkeys --- .../.env.example | 0 .../.eslintrc.cjs | 0 .../.gitignore | 0 .../README.md | 4 +- .../index.html | 0 .../package.json | 0 .../public/safe-logo.svg | 0 .../src/components/ConnectButton.tsx | 0 .../src/components/ConnectWallet.tsx | 0 .../components/MissingAccountFundsCard.tsx | 0 .../src/components/SendNativeToken.tsx | 0 .../src/components/SwitchNetwork.tsx | 0 .../src/config.ts | 0 .../src/hooks/UseOutletContext.tsx | 0 .../src/hooks/useCodeAtAddress.ts | 0 .../src/hooks/useEntryPointAccountBalance.ts | 0 .../src/hooks/useEntryPointAccountNonce.ts | 0 .../src/hooks/useFeeData.ts | 0 .../src/hooks/useLocalStorageState.ts | 0 .../src/hooks/useNativeTokenBalance.ts | 0 .../src/hooks/useUserOpGasEstimation.ts | 0 .../src/index.css | 0 .../src/logic/erc721.ts | 0 .../src/logic/passkeys.ts | 0 .../src/logic/safe.ts | 2 +- .../src/logic/safeWalletApp.ts | 0 .../src/logic/storage.ts | 0 .../src/logic/userOp.ts | 0 .../src/logic/wallets.ts | 0 .../src/main.tsx | 0 .../src/routes/CreatePasskey.tsx | 0 .../src/routes/DeploySafe.tsx | 0 .../src/routes/Home.tsx | 0 .../src/routes/Root.tsx | 0 .../src/routes/Safe.tsx | 0 .../src/routes/constants.ts | 0 .../src/utils.ts | 0 .../src/vite-env.d.ts | 0 .../tsconfig.json | 0 .../tsconfig.node.json | 0 .../vite.config.ts | 0 .../4337/{experimental => }/README.md | 0 .../SafeWebAuthnSharedSigner.sol | 6 +- .../test/TestSharedWebAuthnSignerAccessor.sol | 2 +- .../passkey/test/4337/Safe4337Module.spec.ts | 525 ++++++++++++++++++ .../SafeWebAuthnSharedSigner.spec.ts | 256 +-------- .../test/4337/SafeWebAuthnSigner.spec.ts | 300 ---------- .../SafeWebAuthnSharedSigner.spec.ts | 4 +- pnpm-lock.yaml | 4 +- 49 files changed, 539 insertions(+), 564 deletions(-) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/.env.example (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/.eslintrc.cjs (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/.gitignore (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/README.md (56%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/index.html (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/package.json (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/public/safe-logo.svg (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/components/ConnectButton.tsx (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/components/ConnectWallet.tsx (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/components/MissingAccountFundsCard.tsx (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/components/SendNativeToken.tsx (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/components/SwitchNetwork.tsx (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/config.ts (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/hooks/UseOutletContext.tsx (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/hooks/useCodeAtAddress.ts (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/hooks/useEntryPointAccountBalance.ts (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/hooks/useEntryPointAccountNonce.ts (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/hooks/useFeeData.ts (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/hooks/useLocalStorageState.ts (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/hooks/useNativeTokenBalance.ts (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/hooks/useUserOpGasEstimation.ts (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/index.css (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/logic/erc721.ts (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/logic/passkeys.ts (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/logic/safe.ts (98%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/logic/safeWalletApp.ts (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/logic/storage.ts (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/logic/userOp.ts (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/logic/wallets.ts (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/main.tsx (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/routes/CreatePasskey.tsx (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/routes/DeploySafe.tsx (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/routes/Home.tsx (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/routes/Root.tsx (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/routes/Safe.tsx (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/routes/constants.ts (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/utils.ts (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/src/vite-env.d.ts (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/tsconfig.json (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/tsconfig.node.json (100%) rename examples/{4337-passkeys-singleton-signer => 4337-passkeys}/vite.config.ts (100%) rename modules/passkey/contracts/4337/{experimental => }/README.md (100%) rename modules/passkey/contracts/4337/{experimental => }/SafeWebAuthnSharedSigner.sol (97%) create mode 100644 modules/passkey/test/4337/Safe4337Module.spec.ts rename modules/passkey/test/4337/{experimental => }/SafeWebAuthnSharedSigner.spec.ts (53%) delete mode 100644 modules/passkey/test/4337/SafeWebAuthnSigner.spec.ts rename modules/passkey/test/4337/local-bundler/{experimental => }/SafeWebAuthnSharedSigner.spec.ts (99%) diff --git a/examples/4337-passkeys-singleton-signer/.env.example b/examples/4337-passkeys/.env.example similarity index 100% rename from examples/4337-passkeys-singleton-signer/.env.example rename to examples/4337-passkeys/.env.example diff --git a/examples/4337-passkeys-singleton-signer/.eslintrc.cjs b/examples/4337-passkeys/.eslintrc.cjs similarity index 100% rename from examples/4337-passkeys-singleton-signer/.eslintrc.cjs rename to examples/4337-passkeys/.eslintrc.cjs diff --git a/examples/4337-passkeys-singleton-signer/.gitignore b/examples/4337-passkeys/.gitignore similarity index 100% rename from examples/4337-passkeys-singleton-signer/.gitignore rename to examples/4337-passkeys/.gitignore diff --git a/examples/4337-passkeys-singleton-signer/README.md b/examples/4337-passkeys/README.md similarity index 56% rename from examples/4337-passkeys-singleton-signer/README.md rename to examples/4337-passkeys/README.md index 37f52be73..06f36280e 100644 --- a/examples/4337-passkeys-singleton-signer/README.md +++ b/examples/4337-passkeys/README.md @@ -1,6 +1,6 @@ # Safe + 4337 + Passkeys example application -This minimalistic example application demonstrates a Safe{Core} Smart Account deployment leveraging 4337 and Passkeys. It uses experimental and unaudited (at the moment of writing) contracts: [TestWebAuthnSingletonSigner](https://github.com/safe-global/safe-modules/blob/64fda14111b800ec23b48d93a1288324725fd579/modules/passkey/contracts/test/TestWebAuthnSingletonSigner.sol). The `TestWebAuthnSingletonSigner` allows specifying any signature verifier, including the precompile, but the app chooses to use [FreshCryptoLib](https://github.com/rdubois-crypto/FreshCryptoLib/) verifier under the hood. +This minimalistic example application demonstrates a Safe{Core} Smart Account deployment leveraging 4337 and Passkeys. It uses unaudited (at the moment of writing) contracts: [SafeWebAuthnSharedSigner](https://github.com/safe-global/safe-modules/blob/main/modules/passkey/contracts/4337/SafeWebAuthnSharedSigner.sol). The `SafeWebAuthnSharedSigner` allows specifying any signature verifier, including the precompile, but the app chooses to use [FreshCryptoLib](https://github.com/rdubois-crypto/FreshCryptoLib/) verifier under the hood. ## Running the app @@ -33,7 +33,7 @@ Helpful links: ### Run the app in development mode ```bash -pnpm run -F {examples/4337-passkeys-singleton-signer} dev +pnpm run --filter @safe-global/safe-modules-example-4337-passkeys dev ``` ## Config adjustments diff --git a/examples/4337-passkeys-singleton-signer/index.html b/examples/4337-passkeys/index.html similarity index 100% rename from examples/4337-passkeys-singleton-signer/index.html rename to examples/4337-passkeys/index.html diff --git a/examples/4337-passkeys-singleton-signer/package.json b/examples/4337-passkeys/package.json similarity index 100% rename from examples/4337-passkeys-singleton-signer/package.json rename to examples/4337-passkeys/package.json diff --git a/examples/4337-passkeys-singleton-signer/public/safe-logo.svg b/examples/4337-passkeys/public/safe-logo.svg similarity index 100% rename from examples/4337-passkeys-singleton-signer/public/safe-logo.svg rename to examples/4337-passkeys/public/safe-logo.svg diff --git a/examples/4337-passkeys-singleton-signer/src/components/ConnectButton.tsx b/examples/4337-passkeys/src/components/ConnectButton.tsx similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/components/ConnectButton.tsx rename to examples/4337-passkeys/src/components/ConnectButton.tsx diff --git a/examples/4337-passkeys-singleton-signer/src/components/ConnectWallet.tsx b/examples/4337-passkeys/src/components/ConnectWallet.tsx similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/components/ConnectWallet.tsx rename to examples/4337-passkeys/src/components/ConnectWallet.tsx diff --git a/examples/4337-passkeys-singleton-signer/src/components/MissingAccountFundsCard.tsx b/examples/4337-passkeys/src/components/MissingAccountFundsCard.tsx similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/components/MissingAccountFundsCard.tsx rename to examples/4337-passkeys/src/components/MissingAccountFundsCard.tsx diff --git a/examples/4337-passkeys-singleton-signer/src/components/SendNativeToken.tsx b/examples/4337-passkeys/src/components/SendNativeToken.tsx similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/components/SendNativeToken.tsx rename to examples/4337-passkeys/src/components/SendNativeToken.tsx diff --git a/examples/4337-passkeys-singleton-signer/src/components/SwitchNetwork.tsx b/examples/4337-passkeys/src/components/SwitchNetwork.tsx similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/components/SwitchNetwork.tsx rename to examples/4337-passkeys/src/components/SwitchNetwork.tsx diff --git a/examples/4337-passkeys-singleton-signer/src/config.ts b/examples/4337-passkeys/src/config.ts similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/config.ts rename to examples/4337-passkeys/src/config.ts diff --git a/examples/4337-passkeys-singleton-signer/src/hooks/UseOutletContext.tsx b/examples/4337-passkeys/src/hooks/UseOutletContext.tsx similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/hooks/UseOutletContext.tsx rename to examples/4337-passkeys/src/hooks/UseOutletContext.tsx diff --git a/examples/4337-passkeys-singleton-signer/src/hooks/useCodeAtAddress.ts b/examples/4337-passkeys/src/hooks/useCodeAtAddress.ts similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/hooks/useCodeAtAddress.ts rename to examples/4337-passkeys/src/hooks/useCodeAtAddress.ts diff --git a/examples/4337-passkeys-singleton-signer/src/hooks/useEntryPointAccountBalance.ts b/examples/4337-passkeys/src/hooks/useEntryPointAccountBalance.ts similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/hooks/useEntryPointAccountBalance.ts rename to examples/4337-passkeys/src/hooks/useEntryPointAccountBalance.ts diff --git a/examples/4337-passkeys-singleton-signer/src/hooks/useEntryPointAccountNonce.ts b/examples/4337-passkeys/src/hooks/useEntryPointAccountNonce.ts similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/hooks/useEntryPointAccountNonce.ts rename to examples/4337-passkeys/src/hooks/useEntryPointAccountNonce.ts diff --git a/examples/4337-passkeys-singleton-signer/src/hooks/useFeeData.ts b/examples/4337-passkeys/src/hooks/useFeeData.ts similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/hooks/useFeeData.ts rename to examples/4337-passkeys/src/hooks/useFeeData.ts diff --git a/examples/4337-passkeys-singleton-signer/src/hooks/useLocalStorageState.ts b/examples/4337-passkeys/src/hooks/useLocalStorageState.ts similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/hooks/useLocalStorageState.ts rename to examples/4337-passkeys/src/hooks/useLocalStorageState.ts diff --git a/examples/4337-passkeys-singleton-signer/src/hooks/useNativeTokenBalance.ts b/examples/4337-passkeys/src/hooks/useNativeTokenBalance.ts similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/hooks/useNativeTokenBalance.ts rename to examples/4337-passkeys/src/hooks/useNativeTokenBalance.ts diff --git a/examples/4337-passkeys-singleton-signer/src/hooks/useUserOpGasEstimation.ts b/examples/4337-passkeys/src/hooks/useUserOpGasEstimation.ts similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/hooks/useUserOpGasEstimation.ts rename to examples/4337-passkeys/src/hooks/useUserOpGasEstimation.ts diff --git a/examples/4337-passkeys-singleton-signer/src/index.css b/examples/4337-passkeys/src/index.css similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/index.css rename to examples/4337-passkeys/src/index.css diff --git a/examples/4337-passkeys-singleton-signer/src/logic/erc721.ts b/examples/4337-passkeys/src/logic/erc721.ts similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/logic/erc721.ts rename to examples/4337-passkeys/src/logic/erc721.ts diff --git a/examples/4337-passkeys-singleton-signer/src/logic/passkeys.ts b/examples/4337-passkeys/src/logic/passkeys.ts similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/logic/passkeys.ts rename to examples/4337-passkeys/src/logic/passkeys.ts diff --git a/examples/4337-passkeys-singleton-signer/src/logic/safe.ts b/examples/4337-passkeys/src/logic/safe.ts similarity index 98% rename from examples/4337-passkeys-singleton-signer/src/logic/safe.ts rename to examples/4337-passkeys/src/logic/safe.ts index 2edec513c..6d6f1bdf3 100644 --- a/examples/4337-passkeys-singleton-signer/src/logic/safe.ts +++ b/examples/4337-passkeys/src/logic/safe.ts @@ -2,7 +2,7 @@ import { ethers } from 'ethers' import { abi as SetupModuleSetupAbi } from '@safe-global/safe-4337/build/artifacts/contracts/SafeModuleSetup.sol/SafeModuleSetup.json' import { abi as SafeSingletonAbi } from '@safe-global/safe-contracts/build/artifacts/contracts/Safe.sol/Safe.json' import { abi as MultiSendAbi } from '@safe-global/safe-contracts/build/artifacts/contracts/libraries/MultiSend.sol/MultiSend.json' -import { abi as SafeWebAuthnSharedSignerAbi } from '@safe-global/safe-passkey/build/artifacts/contracts/4337/experimental/SafeWebAuthnSharedSigner.sol/SafeWebAuthnSharedSigner.json' +import { abi as SafeWebAuthnSharedSignerAbi } from '@safe-global/safe-passkey/build/artifacts/contracts/4337/SafeWebAuthnSharedSigner.sol/SafeWebAuthnSharedSigner.json' import { abi as Safe4337ModuleAbi } from '@safe-global/safe-4337/build/artifacts/contracts/Safe4337Module.sol/Safe4337Module.json' import { abi as SafeProxyFactoryAbi } from '@safe-global/safe-4337/build/artifacts/@safe-global/safe-contracts/contracts/proxies/SafeProxyFactory.sol/SafeProxyFactory.json' import type { Safe4337Module, SafeModuleSetup, SafeProxyFactory, SafeL2, MultiSend } from '@safe-global/safe-4337/dist/typechain-types/' diff --git a/examples/4337-passkeys-singleton-signer/src/logic/safeWalletApp.ts b/examples/4337-passkeys/src/logic/safeWalletApp.ts similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/logic/safeWalletApp.ts rename to examples/4337-passkeys/src/logic/safeWalletApp.ts diff --git a/examples/4337-passkeys-singleton-signer/src/logic/storage.ts b/examples/4337-passkeys/src/logic/storage.ts similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/logic/storage.ts rename to examples/4337-passkeys/src/logic/storage.ts diff --git a/examples/4337-passkeys-singleton-signer/src/logic/userOp.ts b/examples/4337-passkeys/src/logic/userOp.ts similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/logic/userOp.ts rename to examples/4337-passkeys/src/logic/userOp.ts diff --git a/examples/4337-passkeys-singleton-signer/src/logic/wallets.ts b/examples/4337-passkeys/src/logic/wallets.ts similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/logic/wallets.ts rename to examples/4337-passkeys/src/logic/wallets.ts diff --git a/examples/4337-passkeys-singleton-signer/src/main.tsx b/examples/4337-passkeys/src/main.tsx similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/main.tsx rename to examples/4337-passkeys/src/main.tsx diff --git a/examples/4337-passkeys-singleton-signer/src/routes/CreatePasskey.tsx b/examples/4337-passkeys/src/routes/CreatePasskey.tsx similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/routes/CreatePasskey.tsx rename to examples/4337-passkeys/src/routes/CreatePasskey.tsx diff --git a/examples/4337-passkeys-singleton-signer/src/routes/DeploySafe.tsx b/examples/4337-passkeys/src/routes/DeploySafe.tsx similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/routes/DeploySafe.tsx rename to examples/4337-passkeys/src/routes/DeploySafe.tsx diff --git a/examples/4337-passkeys-singleton-signer/src/routes/Home.tsx b/examples/4337-passkeys/src/routes/Home.tsx similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/routes/Home.tsx rename to examples/4337-passkeys/src/routes/Home.tsx diff --git a/examples/4337-passkeys-singleton-signer/src/routes/Root.tsx b/examples/4337-passkeys/src/routes/Root.tsx similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/routes/Root.tsx rename to examples/4337-passkeys/src/routes/Root.tsx diff --git a/examples/4337-passkeys-singleton-signer/src/routes/Safe.tsx b/examples/4337-passkeys/src/routes/Safe.tsx similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/routes/Safe.tsx rename to examples/4337-passkeys/src/routes/Safe.tsx diff --git a/examples/4337-passkeys-singleton-signer/src/routes/constants.ts b/examples/4337-passkeys/src/routes/constants.ts similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/routes/constants.ts rename to examples/4337-passkeys/src/routes/constants.ts diff --git a/examples/4337-passkeys-singleton-signer/src/utils.ts b/examples/4337-passkeys/src/utils.ts similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/utils.ts rename to examples/4337-passkeys/src/utils.ts diff --git a/examples/4337-passkeys-singleton-signer/src/vite-env.d.ts b/examples/4337-passkeys/src/vite-env.d.ts similarity index 100% rename from examples/4337-passkeys-singleton-signer/src/vite-env.d.ts rename to examples/4337-passkeys/src/vite-env.d.ts diff --git a/examples/4337-passkeys-singleton-signer/tsconfig.json b/examples/4337-passkeys/tsconfig.json similarity index 100% rename from examples/4337-passkeys-singleton-signer/tsconfig.json rename to examples/4337-passkeys/tsconfig.json diff --git a/examples/4337-passkeys-singleton-signer/tsconfig.node.json b/examples/4337-passkeys/tsconfig.node.json similarity index 100% rename from examples/4337-passkeys-singleton-signer/tsconfig.node.json rename to examples/4337-passkeys/tsconfig.node.json diff --git a/examples/4337-passkeys-singleton-signer/vite.config.ts b/examples/4337-passkeys/vite.config.ts similarity index 100% rename from examples/4337-passkeys-singleton-signer/vite.config.ts rename to examples/4337-passkeys/vite.config.ts diff --git a/modules/passkey/contracts/4337/experimental/README.md b/modules/passkey/contracts/4337/README.md similarity index 100% rename from modules/passkey/contracts/4337/experimental/README.md rename to modules/passkey/contracts/4337/README.md diff --git a/modules/passkey/contracts/4337/experimental/SafeWebAuthnSharedSigner.sol b/modules/passkey/contracts/4337/SafeWebAuthnSharedSigner.sol similarity index 97% rename from modules/passkey/contracts/4337/experimental/SafeWebAuthnSharedSigner.sol rename to modules/passkey/contracts/4337/SafeWebAuthnSharedSigner.sol index d4fb68c89..63e4c35c3 100644 --- a/modules/passkey/contracts/4337/experimental/SafeWebAuthnSharedSigner.sol +++ b/modules/passkey/contracts/4337/SafeWebAuthnSharedSigner.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.0; -import {SignatureValidator} from "../../base/SignatureValidator.sol"; -import {ISafe} from "../../interfaces/ISafe.sol"; -import {P256, WebAuthn} from "../../libraries/WebAuthn.sol"; +import {SignatureValidator} from "../base/SignatureValidator.sol"; +import {ISafe} from "../interfaces/ISafe.sol"; +import {P256, WebAuthn} from "../libraries/WebAuthn.sol"; /** * @title Safe WebAuthn Shared Signer diff --git a/modules/passkey/contracts/test/TestSharedWebAuthnSignerAccessor.sol b/modules/passkey/contracts/test/TestSharedWebAuthnSignerAccessor.sol index 1ba8d974b..6af005bf2 100644 --- a/modules/passkey/contracts/test/TestSharedWebAuthnSignerAccessor.sol +++ b/modules/passkey/contracts/test/TestSharedWebAuthnSignerAccessor.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.0; -import {SafeWebAuthnSharedSigner} from "../4337/experimental/SafeWebAuthnSharedSigner.sol"; +import {SafeWebAuthnSharedSigner} from "../4337/SafeWebAuthnSharedSigner.sol"; contract TestSharedWebAuthnSignerAccessor { function getSignerConfiguration(address sharedSigner) external view returns (SafeWebAuthnSharedSigner.Signer memory signer) { diff --git a/modules/passkey/test/4337/Safe4337Module.spec.ts b/modules/passkey/test/4337/Safe4337Module.spec.ts new file mode 100644 index 000000000..99d107e65 --- /dev/null +++ b/modules/passkey/test/4337/Safe4337Module.spec.ts @@ -0,0 +1,525 @@ +import { expect } from 'chai' +import { deployments, ethers } from 'hardhat' +import { buildSignatureBytes, logGas } from '@safe-global/safe-4337/src/utils/execution' +import { + buildSafeUserOpTransaction, + buildPackedUserOperationFromSafeUserOperation, + calculateSafeOperationHash, + signSafeOp, +} from '@safe-global/safe-4337/src/utils/userOp' +import { encodeMultiSendTransactions } from '@safe-global/safe-4337/test/utils/encoding' +import { WebAuthnCredentials } from '../utils/webauthnShim' +import { decodePublicKey, encodeWebAuthnSignature } from '../../src/utils/webauthn' +import { Safe4337 } from '@safe-global/safe-4337/src/utils/safe' + +describe('Safe4337Module', () => { + const setupTests = deployments.createFixture(async ({ deployments }) => { + const { + SafeModuleSetup, + SafeL2, + SafeProxyFactory, + MultiSend, + FCLP256Verifier, + Safe4337Module, + EntryPoint, + SafeWebAuthnSignerFactory, + SafeWebAuthnSharedSigner, + } = await deployments.fixture() + + const { chainId } = await ethers.provider.getNetwork() + const [user] = await ethers.getSigners() + + const entryPoint = await ethers.getContractAt('IEntryPoint', EntryPoint.address) + const module = await ethers.getContractAt(Safe4337Module.abi, Safe4337Module.address) + const proxyFactory = await ethers.getContractAt(SafeProxyFactory.abi, SafeProxyFactory.address) + const multiSend = await ethers.getContractAt('MultiSend', MultiSend.address) + const safeModuleSetup = await ethers.getContractAt(SafeModuleSetup.abi, SafeModuleSetup.address) + const singleton = await ethers.getContractAt(SafeL2.abi, SafeL2.address) + const signerFactory = await ethers.getContractAt('SafeWebAuthnSignerFactory', SafeWebAuthnSignerFactory.address) + const sharedSigner = await ethers.getContractAt('SafeWebAuthnSharedSigner', SafeWebAuthnSharedSigner.address) + + const verifiers = BigInt(FCLP256Verifier.address) + + const navigator = { + credentials: new WebAuthnCredentials(), + } + + return { + user, + chainId, + proxyFactory, + multiSend, + safeModuleSetup, + module, + entryPoint, + singleton, + signerFactory, + sharedSigner, + verifiers, + navigator, + } + }) + + describe('SafeWebAuthnSigner', () => { + describe('executeUserOp - new account', () => { + it('should execute user operation with a pre-deployed signer', async () => { + const { chainId, user, proxyFactory, safeModuleSetup, module, entryPoint, singleton, signerFactory, navigator, verifiers } = + await setupTests() + + const credential = navigator.credentials.create({ + publicKey: { + rp: { + name: 'Safe', + id: 'safe.global', + }, + user: { + id: ethers.getBytes(ethers.id('chucknorris')), + name: 'chucknorris', + displayName: 'Chuck Norris', + }, + challenge: ethers.toBeArray(Date.now()), + pubKeyCredParams: [{ type: 'public-key', alg: -7 }], + }, + }) + const publicKey = decodePublicKey(credential.response) + await signerFactory.createSigner(publicKey.x, publicKey.y, verifiers) + const signer = await ethers.getContractAt( + 'SafeWebAuthnSignerProxy', + await signerFactory.getSigner(publicKey.x, publicKey.y, verifiers), + ) + + const safe = await Safe4337.withSigner(await signer.getAddress(), { + safeSingleton: await singleton.getAddress(), + entryPoint: await entryPoint.getAddress(), + erc4337module: await module.getAddress(), + proxyFactory: await proxyFactory.getAddress(), + safeModuleSetup: await safeModuleSetup.getAddress(), + proxyCreationCode: await proxyFactory.proxyCreationCode(), + chainId: Number(chainId), + }) + + const safeOp = buildSafeUserOpTransaction( + safe.address, + user.address, + ethers.parseEther('0.5'), + '0x', + '0', + await entryPoint.getAddress(), + false, + false, + { + initCode: safe.getInitCode(), + verificationGasLimit: 625000, + }, + ) + const safeOpHash = calculateSafeOperationHash(await module.getAddress(), safeOp, chainId) + const assertion = navigator.credentials.get({ + publicKey: { + challenge: ethers.getBytes(safeOpHash), + rpId: 'safe.global', + allowCredentials: [{ type: 'public-key', id: new Uint8Array(credential.rawId) }], + userVerification: 'required', + }, + }) + const signature = buildSignatureBytes([ + { + signer: signer.target as string, + data: encodeWebAuthnSignature(assertion.response), + dynamic: true, + }, + ]) + + await user.sendTransaction({ to: safe.address, value: ethers.parseEther('1') }).then((tx) => tx.wait()) + expect(await ethers.provider.getCode(safe.address)).to.equal('0x') + expect(await ethers.provider.getBalance(safe.address)).to.equal(ethers.parseEther('1')) + + const userOp = buildPackedUserOperationFromSafeUserOperation({ safeOp, signature }) + await logGas('WebAuthn signer Safe operation', entryPoint.handleOps([userOp], user.address)) + + expect(await ethers.provider.getCode(safe.address)).to.not.equal('0x') + expect(await ethers.provider.getBalance(safe.address)).to.be.lessThanOrEqual(ethers.parseEther('0.5')) + }) + + it('should execute user operation from a 1/2 Safe with a passkey owner', async () => { + const { + chainId, + user, + proxyFactory, + safeModuleSetup, + module, + entryPoint, + singleton, + multiSend, + signerFactory, + navigator, + verifiers, + } = await setupTests() + + const credential = navigator.credentials.create({ + publicKey: { + rp: { + name: 'Safe', + id: 'safe.global', + }, + user: { + id: ethers.getBytes(ethers.id('chucknorris')), + name: 'chucknorris', + displayName: 'Chuck Norris', + }, + challenge: ethers.toBeArray(Date.now()), + pubKeyCredParams: [{ type: 'public-key', alg: -7 }], + }, + }) + const publicKey = decodePublicKey(credential.response) + const signer = await ethers.getContractAt( + 'SafeWebAuthnSignerProxy', + await signerFactory.getSigner(publicKey.x, publicKey.y, verifiers), + ) + + const safe = await Safe4337.withConfigs( + { + signers: [user.address, await signer.getAddress()], + threshold: 1, + nonce: 0, + }, + { + safeSingleton: await singleton.getAddress(), + entryPoint: await entryPoint.getAddress(), + erc4337module: await module.getAddress(), + proxyFactory: await proxyFactory.getAddress(), + safeModuleSetup: await safeModuleSetup.getAddress(), + proxyCreationCode: await proxyFactory.proxyCreationCode(), + chainId: Number(chainId), + }, + ) + + const safeOp = buildSafeUserOpTransaction( + safe.address, + await multiSend.getAddress(), + ethers.parseEther('0.5'), + multiSend.interface.encodeFunctionData('multiSend', [ + encodeMultiSendTransactions([ + { + op: 0, + to: user.address, + value: ethers.parseEther('0.5'), + data: '0x', + }, + { + op: 0, + to: await signerFactory.getAddress(), + value: 0, + data: signerFactory.interface.encodeFunctionData('createSigner', [publicKey.x, publicKey.y, verifiers]), + }, + ]), + ]), + '0', + await entryPoint.getAddress(), + true, // DELEGATECALL + false, + { + initCode: safe.getInitCode(), + }, + ) + const signature = buildSignatureBytes([await signSafeOp(user, await module.getAddress(), safeOp, chainId)]) + + await user.sendTransaction({ to: safe.address, value: ethers.parseEther('1') }).then((tx) => tx.wait()) + expect(await ethers.provider.getCode(safe.address)).to.equal('0x') + expect(await ethers.provider.getCode(await signer.getAddress())).to.equal('0x') + expect(await ethers.provider.getBalance(safe.address)).to.equal(ethers.parseEther('1')) + + const userOp = buildPackedUserOperationFromSafeUserOperation({ safeOp, signature }) + await logGas('WebAuthn signer Safe operation', entryPoint.handleOps([userOp], user.address)) + + expect(await ethers.provider.getCode(safe.address)).to.not.equal('0x') + expect(await ethers.provider.getCode(await signer.getAddress())).to.not.equal('0x') + expect(await ethers.provider.getBalance(safe.address)).to.be.lessThanOrEqual(ethers.parseEther('0.5')) + + const safeInstance = new ethers.Contract(safe.address, singleton.interface, ethers.provider) + expect(await safeInstance.isOwner(await signer.getAddress())).to.be.true + }) + }) + + describe('executeUserOp - existing account', () => { + it('should execute user operation', async () => { + const { chainId, user, proxyFactory, safeModuleSetup, module, entryPoint, singleton, signerFactory, navigator, verifiers } = + await setupTests() + const credential = navigator.credentials.create({ + publicKey: { + rp: { + name: 'Safe', + id: 'safe.global', + }, + user: { + id: ethers.getBytes(ethers.id('chucknorris')), + name: 'chucknorris', + displayName: 'Chuck Norris', + }, + challenge: ethers.toBeArray(Date.now()), + pubKeyCredParams: [{ type: 'public-key', alg: -7 }], + }, + }) + const publicKey = decodePublicKey(credential.response) + await signerFactory.createSigner(publicKey.x, publicKey.y, verifiers) + const signer = await ethers.getContractAt( + 'SafeWebAuthnSignerProxy', + await signerFactory.getSigner(publicKey.x, publicKey.y, verifiers), + ) + + const safe = await Safe4337.withSigner(await signer.getAddress(), { + safeSingleton: await singleton.getAddress(), + entryPoint: await entryPoint.getAddress(), + erc4337module: await module.getAddress(), + proxyFactory: await proxyFactory.getAddress(), + safeModuleSetup: await safeModuleSetup.getAddress(), + proxyCreationCode: await proxyFactory.proxyCreationCode(), + chainId: Number(chainId), + }) + await safe.deploy(user) + + const safeOp = buildSafeUserOpTransaction( + safe.address, + user.address, + ethers.parseEther('0.5'), + '0x', + '0', + await entryPoint.getAddress(), + ) + const safeOpHash = calculateSafeOperationHash(await module.getAddress(), safeOp, chainId) + const assertion = navigator.credentials.get({ + publicKey: { + challenge: ethers.getBytes(safeOpHash), + rpId: 'safe.global', + allowCredentials: [{ type: 'public-key', id: new Uint8Array(credential.rawId) }], + userVerification: 'required', + }, + }) + const signature = buildSignatureBytes([ + { + signer: signer.target as string, + data: encodeWebAuthnSignature(assertion.response), + dynamic: true, + }, + ]) + + await user.sendTransaction({ to: safe.address, value: ethers.parseEther('1') }).then((tx) => tx.wait()) + expect(await ethers.provider.getBalance(safe.address)).to.equal(ethers.parseEther('1')) + + const userOp = buildPackedUserOperationFromSafeUserOperation({ safeOp, signature }) + await logGas('WebAuthn signer Safe operation', entryPoint.handleOps([userOp], user.address)) + + expect(await ethers.provider.getBalance(safe.address)).to.be.lessThanOrEqual(ethers.parseEther('0.5')) + }) + }) + }) + + describe('SafeWebAuthnSharedSigner', () => { + describe('executeUserOp - new account', () => { + it('should execute user operation', async () => { + const { user, proxyFactory, multiSend, safeModuleSetup, module, entryPoint, singleton, sharedSigner, navigator, verifiers } = + await setupTests() + + const credential = navigator.credentials.create({ + publicKey: { + rp: { + name: 'Safe', + id: 'safe.global', + }, + user: { + id: ethers.getBytes(ethers.id('chucknorris')), + name: 'chucknorris', + displayName: 'Chuck Norris', + }, + challenge: ethers.toBeArray(Date.now()), + pubKeyCredParams: [{ type: 'public-key', alg: -7 }], + }, + }) + const publicKey = decodePublicKey(credential.response) + + const initializer = singleton.interface.encodeFunctionData('setup', [ + [sharedSigner.target], + 1, + multiSend.target, + multiSend.interface.encodeFunctionData('multiSend', [ + encodeMultiSendTransactions([ + { + op: 1 as const, + to: safeModuleSetup.target, + data: safeModuleSetup.interface.encodeFunctionData('enableModules', [[module.target]]), + }, + { + op: 1 as const, + to: sharedSigner.target, + data: sharedSigner.interface.encodeFunctionData('configure', [{ ...publicKey, verifiers }]), + }, + ]), + ]), + module.target, + ethers.ZeroAddress, + 0, + ethers.ZeroAddress, + ]) + const safeSalt = Date.now() + const safe = await proxyFactory.createProxyWithNonce.staticCall(singleton.target, initializer, safeSalt) + + const safeOp = buildSafeUserOpTransaction( + safe, + user.address, + ethers.parseEther('0.5'), + '0x', + await entryPoint.getNonce(safe, 0), + await entryPoint.getAddress(), + false, + false, + { + initCode: ethers.solidityPacked( + ['address', 'bytes'], + [ + proxyFactory.target, + proxyFactory.interface.encodeFunctionData('createProxyWithNonce', [singleton.target, initializer, safeSalt]), + ], + ), + verificationGasLimit: 700000, + }, + ) + const safeOpHash = await module.getOperationHash( + buildPackedUserOperationFromSafeUserOperation({ + safeOp, + signature: '0x', + }), + ) + + const assertion = navigator.credentials.get({ + publicKey: { + challenge: ethers.getBytes(safeOpHash), + rpId: 'safe.global', + allowCredentials: [{ type: 'public-key', id: new Uint8Array(credential.rawId) }], + userVerification: 'required', + }, + }) + const signature = buildSignatureBytes([ + { + signer: sharedSigner.target as string, + data: encodeWebAuthnSignature(assertion.response), + dynamic: true, + }, + ]) + + await user.sendTransaction({ to: safe, value: ethers.parseEther('1') }).then((tx) => tx.wait()) + expect(await ethers.provider.getBalance(safe)).to.equal(ethers.parseEther('1')) + expect(await ethers.provider.getCode(safe)).to.equal('0x') + expect(await sharedSigner.getConfiguration(safe)).to.deep.equal([0n, 0n, 0n]) + + await logGas( + 'WebAuthn signer Safe deployment', + entryPoint.handleOps([buildPackedUserOperationFromSafeUserOperation({ safeOp, signature })], user.address), + ) + + expect(await ethers.provider.getBalance(safe)).to.be.lessThanOrEqual(ethers.parseEther('0.5')) + expect(await ethers.provider.getCode(safe)).to.not.equal('0x') + expect(await sharedSigner.getConfiguration(safe)).to.deep.equal([publicKey.x, publicKey.y, verifiers]) + + const [implementation] = ethers.AbiCoder.defaultAbiCoder().decode(['address'], await ethers.provider.getStorage(safe, 0)) + expect(implementation).to.equal(singleton.target) + + const safeInstance = singleton.attach(safe) as typeof singleton + expect(await safeInstance.getOwners()).to.deep.equal([sharedSigner.target]) + }) + }) + + describe('executeUserOp - existing account', () => { + it('should execute user operation', async () => { + const { + user, + chainId, + proxyFactory, + multiSend, + safeModuleSetup, + module, + entryPoint, + singleton, + sharedSigner, + navigator, + verifiers, + } = await setupTests() + + const credential = navigator.credentials.create({ + publicKey: { + rp: { + name: 'Safe', + id: 'safe.global', + }, + user: { + id: ethers.getBytes(ethers.id('chucknorris')), + name: 'chucknorris', + displayName: 'Chuck Norris', + }, + challenge: ethers.toBeArray(Date.now()), + pubKeyCredParams: [{ type: 'public-key', alg: -7 }], + }, + }) + const publicKey = decodePublicKey(credential.response) + + const initializer = singleton.interface.encodeFunctionData('setup', [ + [sharedSigner.target], + 1, + multiSend.target, + multiSend.interface.encodeFunctionData('multiSend', [ + encodeMultiSendTransactions([ + { + op: 1 as const, + to: safeModuleSetup.target, + data: safeModuleSetup.interface.encodeFunctionData('enableModules', [[module.target]]), + }, + { + op: 1 as const, + to: sharedSigner.target, + data: sharedSigner.interface.encodeFunctionData('configure', [{ ...publicKey, verifiers }]), + }, + ]), + ]), + module.target, + ethers.ZeroAddress, + 0, + ethers.ZeroAddress, + ]) + const safeSalt = Date.now() + const safe = await proxyFactory.createProxyWithNonce.staticCall(singleton, initializer, safeSalt) + await proxyFactory.createProxyWithNonce(singleton, initializer, safeSalt) + + const safeOp = buildSafeUserOpTransaction( + safe, + user.address, + ethers.parseEther('0.5'), + '0x', + await entryPoint.getNonce(safe, 0), + await entryPoint.getAddress(), + ) + const safeOpHash = calculateSafeOperationHash(await module.getAddress(), safeOp, chainId) + const assertion = navigator.credentials.get({ + publicKey: { + challenge: ethers.getBytes(safeOpHash), + rpId: 'safe.global', + allowCredentials: [{ type: 'public-key', id: new Uint8Array(credential.rawId) }], + userVerification: 'required', + }, + }) + const signature = buildSignatureBytes([ + { + signer: sharedSigner.target as string, + data: encodeWebAuthnSignature(assertion.response), + dynamic: true, + }, + ]) + + await user.sendTransaction({ to: safe, value: ethers.parseEther('1') }).then((tx) => tx.wait()) + expect(await ethers.provider.getBalance(safe)).to.equal(ethers.parseEther('1')) + + const userOp = buildPackedUserOperationFromSafeUserOperation({ safeOp, signature }) + await logGas('WebAuthn signer Safe operation', entryPoint.handleOps([userOp], user.address)) + + expect(await ethers.provider.getBalance(safe)).to.be.lessThanOrEqual(ethers.parseEther('0.5')) + }) + }) + }) +}) diff --git a/modules/passkey/test/4337/experimental/SafeWebAuthnSharedSigner.spec.ts b/modules/passkey/test/4337/SafeWebAuthnSharedSigner.spec.ts similarity index 53% rename from modules/passkey/test/4337/experimental/SafeWebAuthnSharedSigner.spec.ts rename to modules/passkey/test/4337/SafeWebAuthnSharedSigner.spec.ts index 4cedfa4e8..00186f8aa 100644 --- a/modules/passkey/test/4337/experimental/SafeWebAuthnSharedSigner.spec.ts +++ b/modules/passkey/test/4337/SafeWebAuthnSharedSigner.spec.ts @@ -1,22 +1,9 @@ -import { buildSignatureBytes } from '@safe-global/safe-4337/src/utils/execution' -import { - buildPackedUserOperationFromSafeUserOperation, - buildSafeUserOpTransaction, - calculateSafeOperationHash, -} from '@safe-global/safe-4337/src/utils/userOp' -import { encodeMultiSendTransactions } from '@safe-global/safe-4337/test/utils/encoding' import { expect } from 'chai' import { deployments, ethers } from 'hardhat' -import * as ERC1271 from '../../utils/erc1271' -import { - DUMMY_AUTHENTICATOR_DATA, - base64UrlEncode, - decodePublicKey, - encodeWebAuthnSignature, - getSignatureBytes, -} from '../../../src/utils/webauthn' -import { WebAuthnCredentials, encodeWebAuthnSigningMessage } from '../../utils/webauthnShim' +import * as ERC1271 from '../utils/erc1271' +import { DUMMY_AUTHENTICATOR_DATA, base64UrlEncode, getSignatureBytes } from '../../src/utils/webauthn' +import { encodeWebAuthnSigningMessage } from '../utils/webauthnShim' const SIGNER_MAPPING_SLOT = BigInt(ethers.id('SafeWebAuthnSharedSigner.signer')) - 1n @@ -289,241 +276,4 @@ describe('SafeWebAuthnSharedSigner', () => { expect(await target['isValidSignature(bytes,bytes)'](data, signature)).to.equal('0x00000000') }) }) - - describe('Safe4337Module', () => { - const setupTests = deployments.createFixture(async ({ deployments }) => { - const { - SafeModuleSetup, - SafeL2, - SafeProxyFactory, - MultiSend, - FCLP256Verifier, - Safe4337Module, - EntryPoint, - SafeWebAuthnSharedSigner, - } = await deployments.fixture() - - const [user] = await ethers.getSigners() - const entryPoint = await ethers.getContractAt('IEntryPoint', EntryPoint.address) - const module = await ethers.getContractAt(Safe4337Module.abi, Safe4337Module.address) - const proxyFactory = await ethers.getContractAt(SafeProxyFactory.abi, SafeProxyFactory.address) - const multiSend = await ethers.getContractAt('MultiSend', MultiSend.address) - const safeModuleSetup = await ethers.getContractAt(SafeModuleSetup.abi, SafeModuleSetup.address) - const singleton = await ethers.getContractAt(SafeL2.abi, SafeL2.address) - const sharedSigner = await ethers.getContractAt('SafeWebAuthnSharedSigner', SafeWebAuthnSharedSigner.address) - const verifiers = BigInt(FCLP256Verifier.address) - - const navigator = { - credentials: new WebAuthnCredentials(), - } - - return { - user, - proxyFactory, - multiSend, - safeModuleSetup, - module, - entryPoint, - singleton, - sharedSigner, - verifiers, - navigator, - } - }) - - describe('executeUserOp - new account', () => { - it('should execute user operation', async () => { - const { user, proxyFactory, multiSend, safeModuleSetup, module, entryPoint, singleton, sharedSigner, navigator, verifiers } = - await setupTests() - - const credential = navigator.credentials.create({ - publicKey: { - rp: { - name: 'Safe', - id: 'safe.global', - }, - user: { - id: ethers.getBytes(ethers.id('chucknorris')), - name: 'chucknorris', - displayName: 'Chuck Norris', - }, - challenge: ethers.toBeArray(Date.now()), - pubKeyCredParams: [{ type: 'public-key', alg: -7 }], - }, - }) - const publicKey = decodePublicKey(credential.response) - - const initializer = singleton.interface.encodeFunctionData('setup', [ - [sharedSigner.target], - 1, - multiSend.target, - multiSend.interface.encodeFunctionData('multiSend', [ - encodeMultiSendTransactions([ - { - op: 1 as const, - to: safeModuleSetup.target, - data: safeModuleSetup.interface.encodeFunctionData('enableModules', [[module.target]]), - }, - { - op: 1 as const, - to: sharedSigner.target, - data: sharedSigner.interface.encodeFunctionData('configure', [{ ...publicKey, verifiers }]), - }, - ]), - ]), - module.target, - ethers.ZeroAddress, - 0, - ethers.ZeroAddress, - ]) - const safeSalt = Date.now() - const safe = await proxyFactory.createProxyWithNonce.staticCall(singleton.target, initializer, safeSalt) - - const safeOp = buildSafeUserOpTransaction( - safe, - user.address, - ethers.parseEther('0.5'), - '0x', - await entryPoint.getNonce(safe, 0), - await entryPoint.getAddress(), - false, - false, - { - initCode: ethers.solidityPacked( - ['address', 'bytes'], - [ - proxyFactory.target, - proxyFactory.interface.encodeFunctionData('createProxyWithNonce', [singleton.target, initializer, safeSalt]), - ], - ), - verificationGasLimit: 700000, - }, - ) - const safeOpHash = await module.getOperationHash( - buildPackedUserOperationFromSafeUserOperation({ - safeOp, - signature: '0x', - }), - ) - - const assertion = navigator.credentials.get({ - publicKey: { - challenge: ethers.getBytes(safeOpHash), - rpId: 'safe.global', - allowCredentials: [{ type: 'public-key', id: new Uint8Array(credential.rawId) }], - userVerification: 'required', - }, - }) - const signature = buildSignatureBytes([ - { - signer: sharedSigner.target as string, - data: encodeWebAuthnSignature(assertion.response), - dynamic: true, - }, - ]) - - await user.sendTransaction({ to: safe, value: ethers.parseEther('1') }).then((tx) => tx.wait()) - expect(await ethers.provider.getBalance(safe)).to.equal(ethers.parseEther('1')) - expect(await ethers.provider.getCode(safe)).to.equal('0x') - expect(await sharedSigner.getConfiguration(safe)).to.deep.equal([0n, 0n, 0n]) - - await entryPoint.handleOps([buildPackedUserOperationFromSafeUserOperation({ safeOp, signature })], user.address) - - expect(await ethers.provider.getBalance(safe)).to.be.lessThanOrEqual(ethers.parseEther('0.5')) - expect(await ethers.provider.getCode(safe)).to.not.equal('0x') - expect(await sharedSigner.getConfiguration(safe)).to.deep.equal([publicKey.x, publicKey.y, verifiers]) - - const [implementation] = ethers.AbiCoder.defaultAbiCoder().decode(['address'], await ethers.provider.getStorage(safe, 0)) - expect(implementation).to.equal(singleton.target) - - const safeInstance = singleton.attach(safe) as typeof singleton - expect(await safeInstance.getOwners()).to.deep.equal([sharedSigner.target]) - }) - }) - - describe('executeUserOp - existing account', () => { - it('should execute user operation', async () => { - const { user, proxyFactory, multiSend, safeModuleSetup, module, entryPoint, singleton, sharedSigner, navigator, verifiers } = - await setupTests() - - const credential = navigator.credentials.create({ - publicKey: { - rp: { - name: 'Safe', - id: 'safe.global', - }, - user: { - id: ethers.getBytes(ethers.id('chucknorris')), - name: 'chucknorris', - displayName: 'Chuck Norris', - }, - challenge: ethers.toBeArray(Date.now()), - pubKeyCredParams: [{ type: 'public-key', alg: -7 }], - }, - }) - const publicKey = decodePublicKey(credential.response) - - const initializer = singleton.interface.encodeFunctionData('setup', [ - [sharedSigner.target], - 1, - multiSend.target, - multiSend.interface.encodeFunctionData('multiSend', [ - encodeMultiSendTransactions([ - { - op: 1 as const, - to: safeModuleSetup.target, - data: safeModuleSetup.interface.encodeFunctionData('enableModules', [[module.target]]), - }, - { - op: 1 as const, - to: sharedSigner.target, - data: sharedSigner.interface.encodeFunctionData('configure', [{ ...publicKey, verifiers }]), - }, - ]), - ]), - module.target, - ethers.ZeroAddress, - 0, - ethers.ZeroAddress, - ]) - const safeSalt = Date.now() - const safe = await proxyFactory.createProxyWithNonce.staticCall(singleton, initializer, safeSalt) - await proxyFactory.createProxyWithNonce(singleton, initializer, safeSalt) - - const safeOp = buildSafeUserOpTransaction( - safe, - user.address, - ethers.parseEther('0.5'), - '0x', - await entryPoint.getNonce(safe, 0), - await entryPoint.getAddress(), - ) - const { chainId } = await ethers.provider.getNetwork() - const safeOpHash = calculateSafeOperationHash(await module.getAddress(), safeOp, chainId) - const assertion = navigator.credentials.get({ - publicKey: { - challenge: ethers.getBytes(safeOpHash), - rpId: 'safe.global', - allowCredentials: [{ type: 'public-key', id: new Uint8Array(credential.rawId) }], - userVerification: 'required', - }, - }) - const signature = buildSignatureBytes([ - { - signer: sharedSigner.target as string, - data: encodeWebAuthnSignature(assertion.response), - dynamic: true, - }, - ]) - - await user.sendTransaction({ to: safe, value: ethers.parseEther('1') }).then((tx) => tx.wait()) - expect(await ethers.provider.getBalance(safe)).to.equal(ethers.parseEther('1')) - - const userOp = buildPackedUserOperationFromSafeUserOperation({ safeOp, signature }) - await entryPoint.handleOps([userOp], user.address) - - expect(await ethers.provider.getBalance(safe)).to.be.lessThanOrEqual(ethers.parseEther('0.5')) - }) - }) - }) }) diff --git a/modules/passkey/test/4337/SafeWebAuthnSigner.spec.ts b/modules/passkey/test/4337/SafeWebAuthnSigner.spec.ts deleted file mode 100644 index 887ec5c23..000000000 --- a/modules/passkey/test/4337/SafeWebAuthnSigner.spec.ts +++ /dev/null @@ -1,300 +0,0 @@ -import { expect } from 'chai' -import { deployments, ethers } from 'hardhat' -import { buildSignatureBytes, logGas } from '@safe-global/safe-4337/src/utils/execution' -import { - buildSafeUserOpTransaction, - buildPackedUserOperationFromSafeUserOperation, - calculateSafeOperationHash, - signSafeOp, -} from '@safe-global/safe-4337/src/utils/userOp' -import { encodeMultiSendTransactions } from '@safe-global/safe-4337/test/utils/encoding' -import { Safe4337 } from '@safe-global/safe-4337/src/utils/safe' -import { WebAuthnCredentials } from '../utils/webauthnShim' -import { decodePublicKey, encodeWebAuthnSignature } from '../../src/utils/webauthn' - -describe('SafeWebAuthnSigner', () => { - const setupTests = deployments.createFixture(async ({ deployments }) => { - const { SafeModuleSetup, SafeL2, SafeProxyFactory, MultiSend, FCLP256Verifier, Safe4337Module, EntryPoint, SafeWebAuthnSignerFactory } = - await deployments.fixture() - - const { chainId } = await ethers.provider.getNetwork() - const [user] = await ethers.getSigners() - - const entryPoint = await ethers.getContractAt('IEntryPoint', EntryPoint.address) - const module = await ethers.getContractAt(Safe4337Module.abi, Safe4337Module.address) - const proxyFactory = await ethers.getContractAt(SafeProxyFactory.abi, SafeProxyFactory.address) - const multiSend = await ethers.getContractAt('MultiSend', MultiSend.address) - const safeModuleSetup = await ethers.getContractAt(SafeModuleSetup.abi, SafeModuleSetup.address) - const singleton = await ethers.getContractAt(SafeL2.abi, SafeL2.address) - const signerFactory = await ethers.getContractAt('SafeWebAuthnSignerFactory', SafeWebAuthnSignerFactory.address) - - const verifiers = BigInt(FCLP256Verifier.address) - - const navigator = { - credentials: new WebAuthnCredentials(), - } - - return { - chainId, - user, - proxyFactory, - multiSend, - safeModuleSetup, - module, - entryPoint, - singleton, - signerFactory, - verifiers, - navigator, - } - }) - - describe('executeUserOp - new account', () => { - it('should execute user operation with a pre-deployed signer', async () => { - const { chainId, user, proxyFactory, safeModuleSetup, module, entryPoint, singleton, signerFactory, navigator, verifiers } = - await setupTests() - const credential = navigator.credentials.create({ - publicKey: { - rp: { - name: 'Safe', - id: 'safe.global', - }, - user: { - id: ethers.getBytes(ethers.id('chucknorris')), - name: 'chucknorris', - displayName: 'Chuck Norris', - }, - challenge: ethers.toBeArray(Date.now()), - pubKeyCredParams: [{ type: 'public-key', alg: -7 }], - }, - }) - const publicKey = decodePublicKey(credential.response) - await signerFactory.createSigner(publicKey.x, publicKey.y, verifiers) - const signer = await ethers.getContractAt( - 'SafeWebAuthnSignerProxy', - await signerFactory.getSigner(publicKey.x, publicKey.y, verifiers), - ) - - const safe = await Safe4337.withSigner(await signer.getAddress(), { - safeSingleton: await singleton.getAddress(), - entryPoint: await entryPoint.getAddress(), - erc4337module: await module.getAddress(), - proxyFactory: await proxyFactory.getAddress(), - safeModuleSetup: await safeModuleSetup.getAddress(), - proxyCreationCode: await proxyFactory.proxyCreationCode(), - chainId: Number(chainId), - }) - - const safeOp = buildSafeUserOpTransaction( - safe.address, - user.address, - ethers.parseEther('0.5'), - '0x', - '0', - await entryPoint.getAddress(), - false, - false, - { - initCode: safe.getInitCode(), - verificationGasLimit: 625000, - }, - ) - const safeOpHash = calculateSafeOperationHash(await module.getAddress(), safeOp, chainId) - const assertion = navigator.credentials.get({ - publicKey: { - challenge: ethers.getBytes(safeOpHash), - rpId: 'safe.global', - allowCredentials: [{ type: 'public-key', id: new Uint8Array(credential.rawId) }], - userVerification: 'required', - }, - }) - const signature = buildSignatureBytes([ - { - signer: signer.target as string, - data: encodeWebAuthnSignature(assertion.response), - dynamic: true, - }, - ]) - - await user.sendTransaction({ to: safe.address, value: ethers.parseEther('1') }).then((tx) => tx.wait()) - expect(await ethers.provider.getCode(safe.address)).to.equal('0x') - expect(await ethers.provider.getBalance(safe.address)).to.equal(ethers.parseEther('1')) - - const userOp = buildPackedUserOperationFromSafeUserOperation({ safeOp, signature }) - await logGas('WebAuthn signer Safe operation', entryPoint.handleOps([userOp], user.address)) - - expect(await ethers.provider.getCode(safe.address)).to.not.equal('0x') - expect(await ethers.provider.getBalance(safe.address)).to.be.lessThanOrEqual(ethers.parseEther('0.5')) - }) - - it('should execute user operation from a 1/2 Safe with a passkey owner', async () => { - const { - chainId, - user, - proxyFactory, - safeModuleSetup, - module, - entryPoint, - singleton, - multiSend, - signerFactory, - navigator, - verifiers, - } = await setupTests() - const credential = navigator.credentials.create({ - publicKey: { - rp: { - name: 'Safe', - id: 'safe.global', - }, - user: { - id: ethers.getBytes(ethers.id('chucknorris')), - name: 'chucknorris', - displayName: 'Chuck Norris', - }, - challenge: ethers.toBeArray(Date.now()), - pubKeyCredParams: [{ type: 'public-key', alg: -7 }], - }, - }) - const publicKey = decodePublicKey(credential.response) - const signer = await ethers.getContractAt( - 'SafeWebAuthnSignerProxy', - await signerFactory.getSigner(publicKey.x, publicKey.y, verifiers), - ) - - const safe = await Safe4337.withConfigs( - { - signers: [user.address, await signer.getAddress()], - threshold: 1, - nonce: 0, - }, - { - safeSingleton: await singleton.getAddress(), - entryPoint: await entryPoint.getAddress(), - erc4337module: await module.getAddress(), - proxyFactory: await proxyFactory.getAddress(), - safeModuleSetup: await safeModuleSetup.getAddress(), - proxyCreationCode: await proxyFactory.proxyCreationCode(), - chainId: Number(chainId), - }, - ) - - const safeOp = buildSafeUserOpTransaction( - safe.address, - await multiSend.getAddress(), - ethers.parseEther('0.5'), - multiSend.interface.encodeFunctionData('multiSend', [ - encodeMultiSendTransactions([ - { - op: 0, - to: user.address, - value: ethers.parseEther('0.5'), - data: '0x', - }, - { - op: 0, - to: await signerFactory.getAddress(), - value: 0, - data: signerFactory.interface.encodeFunctionData('createSigner', [publicKey.x, publicKey.y, verifiers]), - }, - ]), - ]), - '0', - await entryPoint.getAddress(), - true, // DELEGATECALL - false, - { - initCode: safe.getInitCode(), - }, - ) - const signature = buildSignatureBytes([await signSafeOp(user, await module.getAddress(), safeOp, chainId)]) - - await user.sendTransaction({ to: safe.address, value: ethers.parseEther('1') }).then((tx) => tx.wait()) - expect(await ethers.provider.getCode(safe.address)).to.equal('0x') - expect(await ethers.provider.getCode(await signer.getAddress())).to.equal('0x') - expect(await ethers.provider.getBalance(safe.address)).to.equal(ethers.parseEther('1')) - - const userOp = buildPackedUserOperationFromSafeUserOperation({ safeOp, signature }) - await logGas('WebAuthn signer Safe operation', entryPoint.handleOps([userOp], user.address)) - - expect(await ethers.provider.getCode(safe.address)).to.not.equal('0x') - expect(await ethers.provider.getCode(await signer.getAddress())).to.not.equal('0x') - expect(await ethers.provider.getBalance(safe.address)).to.be.lessThanOrEqual(ethers.parseEther('0.5')) - - const safeInstance = new ethers.Contract(safe.address, singleton.interface, ethers.provider) - expect(await safeInstance.isOwner(await signer.getAddress())).to.be.true - }) - }) - - describe('executeUserOp - existing account', () => { - it('should execute user operation', async () => { - const { chainId, user, proxyFactory, safeModuleSetup, module, entryPoint, singleton, signerFactory, navigator, verifiers } = - await setupTests() - const credential = navigator.credentials.create({ - publicKey: { - rp: { - name: 'Safe', - id: 'safe.global', - }, - user: { - id: ethers.getBytes(ethers.id('chucknorris')), - name: 'chucknorris', - displayName: 'Chuck Norris', - }, - challenge: ethers.toBeArray(Date.now()), - pubKeyCredParams: [{ type: 'public-key', alg: -7 }], - }, - }) - const publicKey = decodePublicKey(credential.response) - await signerFactory.createSigner(publicKey.x, publicKey.y, verifiers) - const signer = await ethers.getContractAt( - 'SafeWebAuthnSignerProxy', - await signerFactory.getSigner(publicKey.x, publicKey.y, verifiers), - ) - - const safe = await Safe4337.withSigner(await signer.getAddress(), { - safeSingleton: await singleton.getAddress(), - entryPoint: await entryPoint.getAddress(), - erc4337module: await module.getAddress(), - proxyFactory: await proxyFactory.getAddress(), - safeModuleSetup: await safeModuleSetup.getAddress(), - proxyCreationCode: await proxyFactory.proxyCreationCode(), - chainId: Number(chainId), - }) - await safe.deploy(user) - - const safeOp = buildSafeUserOpTransaction( - safe.address, - user.address, - ethers.parseEther('0.5'), - '0x', - '0', - await entryPoint.getAddress(), - ) - const safeOpHash = calculateSafeOperationHash(await module.getAddress(), safeOp, chainId) - const assertion = navigator.credentials.get({ - publicKey: { - challenge: ethers.getBytes(safeOpHash), - rpId: 'safe.global', - allowCredentials: [{ type: 'public-key', id: new Uint8Array(credential.rawId) }], - userVerification: 'required', - }, - }) - const signature = buildSignatureBytes([ - { - signer: signer.target as string, - data: encodeWebAuthnSignature(assertion.response), - dynamic: true, - }, - ]) - - await user.sendTransaction({ to: safe.address, value: ethers.parseEther('1') }).then((tx) => tx.wait()) - expect(await ethers.provider.getBalance(safe.address)).to.equal(ethers.parseEther('1')) - - const userOp = buildPackedUserOperationFromSafeUserOperation({ safeOp, signature }) - await logGas('WebAuthn signer Safe operation', entryPoint.handleOps([userOp], user.address)) - - expect(await ethers.provider.getBalance(safe.address)).to.be.lessThanOrEqual(ethers.parseEther('0.5')) - }) - }) -}) diff --git a/modules/passkey/test/4337/local-bundler/experimental/SafeWebAuthnSharedSigner.spec.ts b/modules/passkey/test/4337/local-bundler/SafeWebAuthnSharedSigner.spec.ts similarity index 99% rename from modules/passkey/test/4337/local-bundler/experimental/SafeWebAuthnSharedSigner.spec.ts rename to modules/passkey/test/4337/local-bundler/SafeWebAuthnSharedSigner.spec.ts index a254d0520..1d95c8ef0 100644 --- a/modules/passkey/test/4337/local-bundler/experimental/SafeWebAuthnSharedSigner.spec.ts +++ b/modules/passkey/test/4337/local-bundler/SafeWebAuthnSharedSigner.spec.ts @@ -8,8 +8,8 @@ import { buildRpcUserOperationFromSafeUserOperation, } from '@safe-global/safe-4337/src/utils/userOp' import { buildSignatureBytes } from '@safe-global/safe-4337/src/utils/execution' -import { WebAuthnCredentials } from '../../../utils/webauthnShim' -import { decodePublicKey, encodeWebAuthnSignature } from '../../../../src/utils/webauthn' +import { WebAuthnCredentials } from '../../utils/webauthnShim' +import { decodePublicKey, encodeWebAuthnSignature } from '../../../src/utils/webauthn' const SENTINEL = ethers.getAddress('0x0000000000000000000000000000000000000001') diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a790e88c2..a0a6177d9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -85,7 +85,7 @@ importers: specifier: ^5.4.5 version: 5.4.5 - examples/4337-passkeys-singleton-signer: + examples/4337-passkeys: dependencies: '@account-abstraction/contracts': specifier: 0.7.0 @@ -6710,7 +6710,7 @@ snapshots: '@walletconnect/safe-json': 1.0.2 '@walletconnect/time': 1.0.2 tslib: 1.14.1 - uint8arrays: 3.1.0 + uint8arrays: 3.1.1 '@walletconnect/safe-json@1.0.2': dependencies: