Skip to content

Commit

Permalink
[#312] Benchmark deployment, verification cost for proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
akshay-ap committed Apr 8, 2024
1 parent 67e090f commit 45439c5
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 2 deletions.
6 changes: 4 additions & 2 deletions modules/passkey/contracts/SafeWebAuthnSignerProxyFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {IP256Verifier} from "./interfaces/IP256Verifier.sol";
import {ERC1271} from "./libraries/ERC1271.sol";
import {WebAuthn} from "./libraries/WebAuthn.sol";
import {SafeWebAuthnSignerProxy} from "./SafeWebAuthnSignerProxy.sol";

import {SafeWebAuthnSignerSingleton} from "./SafeWebAuthnSignerSingleton.sol";
/**
* @title WebAuthnSignerFactory
* @dev A factory contract for creating and managing WebAuthn signers.
Expand All @@ -16,7 +16,7 @@ contract SafeWebAuthnSignerProxyFactory is ICustomECDSASignerProxyFactory {
* @inheritdoc ICustomECDSASignerProxyFactory
*/
function getSigner(address singleton, uint256 x, uint256 y, address verifier) public view override returns (address signer) {
bytes32 codeHash = keccak256(abi.encodePacked(type(SafeWebAuthnSignerProxy).creationCode, singleton));
bytes32 codeHash = keccak256(abi.encodePacked(type(SafeWebAuthnSignerProxy).creationCode, uint256(uint160(singleton))));
bytes32 salt = keccak256(abi.encodePacked(x, y, verifier));
signer = address(uint160(uint256(keccak256(abi.encodePacked(hex"ff", address(this), salt, codeHash)))));
}
Expand All @@ -26,9 +26,11 @@ contract SafeWebAuthnSignerProxyFactory is ICustomECDSASignerProxyFactory {
*/
function createSigner(address singleton, uint256 x, uint256 y, address verifier) external returns (address signer) {
signer = getSigner(singleton, x, y, verifier);

bytes32 salt = keccak256(abi.encodePacked(x, y, verifier));
if (_hasNoCode(signer)) {
SafeWebAuthnSignerProxy created = new SafeWebAuthnSignerProxy{salt: salt}(singleton);
SafeWebAuthnSignerSingleton(address(created)).setup(x, y, verifier);
require(address(created) == signer);
}
}
Expand Down
14 changes: 14 additions & 0 deletions modules/passkey/src/deploy/webauthn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ const deploy: DeployFunction = async ({ deployments, getNamedAccounts }) => {
log: true,
deterministicDeployment: true,
})

await deploy('SafeWebAuthnSignerProxyFactory', {
from: deployer,
args: [],
log: true,
deterministicDeployment: true,
})

await deploy('SafeWebAuthnSignerSingleton', {
from: deployer,
args: [],
log: true,
deterministicDeployment: true,
})
}

export default deploy
99 changes: 99 additions & 0 deletions modules/passkey/test/GasBenchmarkingProxy.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { expect } from 'chai'
import { deployments, ethers } from 'hardhat'

import { WebAuthnCredentials, decodePublicKey, encodeWebAuthnSignature } from './utils/webauthn'
import { IP256Verifier } from '../typechain-types'

describe('Gas Benchmarking Proxy [@bench]', function () {
const navigator = {
credentials: new WebAuthnCredentials(),
}
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 setupTests = deployments.createFixture(async ({ deployments }) => {
const { DaimoP256Verifier, FCLP256Verifier, SafeWebAuthnSignerProxyFactory, SafeWebAuthnSignerSingleton } = await deployments.fixture()

const Benchmarker = await ethers.getContractFactory('Benchmarker')
const benchmarker = await Benchmarker.deploy()

const proxyFactory = await ethers.getContractAt('SafeWebAuthnSignerProxyFactory', SafeWebAuthnSignerProxyFactory.address)

const DummyP256Verifier = await ethers.getContractFactory('DummyP256Verifier')
const verifiers = {
fcl: await ethers.getContractAt('IP256Verifier', FCLP256Verifier.address),
daimo: await ethers.getContractAt('IP256Verifier', DaimoP256Verifier.address),
dummy: await DummyP256Verifier.deploy(),
} as Record<string, IP256Verifier>

const singleton = await ethers.getContractAt('SafeWebAuthnSignerSingleton', SafeWebAuthnSignerSingleton.address)
return { benchmarker, proxyFactory, verifiers, singleton }
})

describe('SafeWebAuthnSigner', () => {
it(`Benchmark signer deployment cost`, async function () {
const { benchmarker, proxyFactory, singleton } = await setupTests()

const { x, y } = decodePublicKey(credential.response)
const verifier = `0x${'ee'.repeat(20)}`

const [gas] = await benchmarker.call.staticCall(
proxyFactory,
proxyFactory.interface.encodeFunctionData('createSigner', [singleton.target, x, y, verifier]),
)

console.log(` ⛽ deployment: ${gas}`)
})

for (const [name, key] of [
['FreshCryptoLib', 'fcl'],
['daimo-eth', 'daimo'],
['Dummy', 'dummy'],
]) {
it(`Benchmark signer verification cost with ${name} verifier`, async function () {
const { benchmarker, verifiers, proxyFactory, singleton } = await setupTests()

const challenge = ethers.id('hello world')
const assertion = navigator.credentials.get({
publicKey: {
challenge: ethers.getBytes(challenge),
rpId: 'safe.global',
allowCredentials: [{ type: 'public-key', id: new Uint8Array(credential.rawId) }],
userVerification: 'required',
},
})

const { x, y } = decodePublicKey(credential.response)
const verifier = verifiers[key]

const isValidSignatureInterface = new ethers.Interface(['function isValidSignature(bytes32,bytes) external view returns (bytes4)'])
await proxyFactory.createSigner(singleton, x, y, verifier)
const signerProxy = await ethers.getContractAt('SafeWebAuthnSignerProxy', await proxyFactory.getSigner(singleton, x, y, verifier))
const signature = encodeWebAuthnSignature(assertion.response)

const [gas, returnData] = await benchmarker.call.staticCall(
signerProxy,
isValidSignatureInterface.encodeFunctionData('isValidSignature(bytes32,bytes)', [challenge, signature]),
)

const [magicValue] = ethers.AbiCoder.defaultAbiCoder().decode(['bytes4'], returnData)
expect(magicValue).to.equal('0x1626ba7e')

console.log(` ⛽ verification (${name}): ${gas}`)
})
}
})
})

0 comments on commit 45439c5

Please sign in to comment.