-
Notifications
You must be signed in to change notification settings - Fork 73
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add User Story For Determinstic Deployment
This PR adds an additional user story demonstrating how the passkey signer is deployed to a deterministic address that can be computed off-chain.
- Loading branch information
Showing
1 changed file
with
74 additions
and
0 deletions.
There are no files selected for viewing
74 changes: 74 additions & 0 deletions
74
modules/passkey/test/userstories/DeterministicPasskeySignerAddress.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { expect } from 'chai' | ||
import { deployments, ethers } from 'hardhat' | ||
import { WebAuthnCredentials } from '../../test/utils/webauthnShim' | ||
import { decodePublicKey } from '../../src/utils/webauthn' | ||
|
||
/** | ||
* User story: Deterministic passkey signer address | ||
* This user story test documents how the {SafeWebAuthnSignerProxy} can be pre-computed so that it | ||
* can be used for counterfactual deployments. | ||
*/ | ||
describe('Deterministic passkey signer address [@userstory]', () => { | ||
const setupTests = deployments.createFixture(async ({ deployments }) => { | ||
const { FCLP256Verifier, SafeWebAuthnSignerFactory } = await deployments.run() | ||
|
||
const verifier = await ethers.getContractAt('IP256Verifier', FCLP256Verifier.address) | ||
const signerFactory = await ethers.getContractAt('SafeWebAuthnSignerFactory', SafeWebAuthnSignerFactory.address) | ||
|
||
const navigator = { | ||
credentials: new WebAuthnCredentials(), | ||
} | ||
|
||
return { | ||
module, | ||
signerFactory, | ||
navigator, | ||
verifier, | ||
} | ||
}) | ||
|
||
it('should execute a userOp with replaced WebAuthn signer as Safe owner', async () => { | ||
const { verifier, signerFactory, navigator } = await setupTests() | ||
|
||
// Step 1: Create the WebAuthn credential for the passkey signer | ||
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 }], | ||
}, | ||
}) | ||
|
||
// Step 2: Compute signer configuration | ||
const publicKey = decodePublicKey(credential.response) | ||
const p256Precompile = ethers.ZeroAddress // precompile disabled | ||
const verifiers = BigInt(ethers.solidityPacked(['uint16', 'address'], [p256Precompile, await verifier.getAddress()])) | ||
|
||
// Step 3: Compute the deterministic address for the passkey signer | ||
const SafeWebAuthnSignerProxy = await ethers.getContractFactory('SafeWebAuthnSignerProxy') | ||
const { data } = await SafeWebAuthnSignerProxy.getDeployTransaction( | ||
await signerFactory.SINGLETON(), | ||
publicKey.x, | ||
publicKey.y, | ||
verifiers, | ||
) | ||
const signerAddress = ethers.getCreate2Address(await signerFactory.getAddress(), ethers.ZeroHash, ethers.keccak256(data)) | ||
|
||
// Check: | ||
// 1. the computed address matches the address reported by the signer factory | ||
// 2. there is no code deployed at the signer address | ||
// 3. that the signer factory deploys a signer at that address | ||
expect(await signerFactory.getSigner(publicKey.x, publicKey.y, verifiers)).to.equal(signerAddress) | ||
expect(await ethers.provider.getCode(signerAddress)).to.equal('0x') | ||
await expect(signerFactory.createSigner(publicKey.x, publicKey.y, verifiers)).to.not.be.reverted | ||
expect(await ethers.provider.getCode(signerAddress)).to.not.equal('0x') | ||
}) | ||
}) |