Skip to content

Commit

Permalink
Verify Multiple P-256 Verifiers Are Supported
Browse files Browse the repository at this point in the history
  • Loading branch information
nlordell committed Mar 6, 2024
1 parent f03be94 commit f5680bb
Show file tree
Hide file tree
Showing 13 changed files with 514 additions and 586 deletions.
4 changes: 1 addition & 3 deletions modules/4337/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,13 @@
},
"devDependencies": {
"@account-abstraction/contracts": "^0.7.0",
"@noble/curves": "^1.3.0",
"@nomicfoundation/hardhat-toolbox": "^4.0.0",
"@openzeppelin/contracts": "^5.0.2",
"@simplewebauthn/server": "9.0.0",
"@safe-global/safe-passkey": "^0.1.0",
"@types/chai": "^4.3.11",
"@types/mocha": "^10.0.6",
"@types/node": "^20.11.18",
"@types/yargs": "^17.0.32",
"cbor": "^9.0.2",
"debug": "^4.3.4",
"dotenv": "^16.4.4",
"ethers": "^6.11.1",
Expand Down
23 changes: 3 additions & 20 deletions modules/4337/test/e2e/WebAuthnSigner.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import { WebAuthnCredentials, extractPublicKey, extractSignature } from '@safe-global/safe-passkey/test/utils/webauthn'
import { expect } from 'chai'
import { deployments, ethers, network } from 'hardhat'
import { bundlerRpc, prepareAccounts, waitForUserOp } from '../utils/e2e'
import { chainId } from '../utils/encoding'
import {
UserVerificationRequirement,
WebAuthnCredentials,
extractClientDataFields,
extractPublicKey,
extractSignature,
} from '../utils/webauthn'
import { packGasParameters, unpackUserOperation } from '../../src/utils/userOp'

describe('E2E - WebAuthn Signers', () => {
Expand Down Expand Up @@ -187,23 +181,12 @@ describe('E2E - WebAuthn Signers', () => {
challenge: ethers.getBytes(safeInitOpHash),
rpId: 'safe.global',
allowCredentials: [{ type: 'public-key', id: new Uint8Array(credential.rawId) }],
userVerification: UserVerificationRequirement.required,
userVerification: 'required',
},
})
const signature = ethers.solidityPacked(
['uint48', 'uint48', 'bytes'],
[
safeInitOp.validAfter,
safeInitOp.validUntil,
ethers.AbiCoder.defaultAbiCoder().encode(
['bytes', 'bytes', 'uint256[2]'],
[
new Uint8Array(assertion.response.authenticatorData),
extractClientDataFields(assertion.response),
extractSignature(assertion.response),
],
),
],
[safeInitOp.validAfter, safeInitOp.validUntil, extractSignature(assertion.response)],
)

await user.sendTransaction({ to: safe, value: ethers.parseEther('1') }).then((tx) => tx.wait())
Expand Down
19 changes: 3 additions & 16 deletions modules/4337/test/e2e/WebAuthnSingletonSigner.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import { WebAuthnCredentials, extractPublicKey, extractSignature } from '@safe-global/safe-passkey/test/utils/webauthn'
import { expect } from 'chai'
import { deployments, ethers, network } from 'hardhat'
import { bundlerRpc, encodeMultiSendTransactions, prepareAccounts, waitForUserOp } from '../utils/e2e'
import {
UserVerificationRequirement,
WebAuthnCredentials,
extractClientDataFields,
extractPublicKey,
extractSignature,
} from '../utils/webauthn'
import {
buildSafeUserOpTransaction,
buildPackedUserOperationFromSafeUserOperation,
Expand Down Expand Up @@ -153,20 +147,13 @@ describe('E2E - WebAuthn Singleton Signers', () => {
challenge: ethers.getBytes(opHash),
rpId: 'safe.global',
allowCredentials: [{ type: 'public-key', id: new Uint8Array(credential.rawId) }],
userVerification: UserVerificationRequirement.required,
userVerification: 'required',
},
})
const signature = buildSignatureBytes([
{
signer: signer.target as string,
data: ethers.AbiCoder.defaultAbiCoder().encode(
['bytes', 'bytes', 'uint256[2]'],
[
new Uint8Array(assertion.response.authenticatorData),
extractClientDataFields(assertion.response),
extractSignature(assertion.response),
],
),
data: extractSignature(assertion.response),
dynamic: true,
},
])
Expand Down
34 changes: 5 additions & 29 deletions modules/4337/test/erc4337/ERC4337WebAuthn.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { WebAuthnCredentials, extractPublicKey, extractSignature } from '@safe-global/safe-passkey/test/utils/webauthn'
import { expect } from 'chai'
import { deployments, ethers } from 'hardhat'
import { getEntryPoint } from '../utils/setup'
Expand All @@ -9,13 +10,6 @@ import {
packGasParameters,
} from '../../src/utils/userOp'
import { chainId } from '../utils/encoding'
import {
UserVerificationRequirement,
WebAuthnCredentials,
extractClientDataFields,
extractPublicKey,
extractSignature,
} from '../utils/webauthn'
import { Safe4337 } from '../../src/utils/safe'

describe('Safe4337Module - WebAuthn Owner', () => {
Expand Down Expand Up @@ -187,23 +181,12 @@ describe('Safe4337Module - WebAuthn Owner', () => {
challenge: ethers.getBytes(safeInitOpHash),
rpId: 'safe.global',
allowCredentials: [{ type: 'public-key', id: new Uint8Array(credential.rawId) }],
userVerification: UserVerificationRequirement.required,
userVerification: 'required',
},
})
const signature = ethers.solidityPacked(
['uint48', 'uint48', 'bytes'],
[
safeInitOp.validAfter,
safeInitOp.validUntil,
ethers.AbiCoder.defaultAbiCoder().encode(
['bytes', 'bytes', 'uint256[2]'],
[
new Uint8Array(assertion.response.authenticatorData),
extractClientDataFields(assertion.response),
extractSignature(assertion.response),
],
),
],
[safeInitOp.validAfter, safeInitOp.validUntil, extractSignature(assertion.response)],
)

await user.sendTransaction({ to: safe, value: ethers.parseEther('1') }).then((tx) => tx.wait())
Expand Down Expand Up @@ -278,20 +261,13 @@ describe('Safe4337Module - WebAuthn Owner', () => {
challenge: ethers.getBytes(safeOpHash),
rpId: 'safe.global',
allowCredentials: [{ type: 'public-key', id: new Uint8Array(credential.rawId) }],
userVerification: UserVerificationRequirement.required,
userVerification: 'required',
},
})
const signature = buildSignatureBytes([
{
signer: signer.target as string,
data: ethers.AbiCoder.defaultAbiCoder().encode(
['bytes', 'bytes', 'uint256[2]'],
[
new Uint8Array(assertion.response.authenticatorData),
extractClientDataFields(assertion.response),
extractSignature(assertion.response),
],
),
data: extractSignature(assertion.response),
dynamic: true,
},
])
Expand Down
1 change: 0 additions & 1 deletion modules/passkey/contracts/test/TestP256VerifierLib.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// SPDX-License-Identifier: LGPL-3.0-only
/* solhint-disable payable-fallback */
pragma solidity ^0.8.0;

import {IP256Verifier, P256VerifierLib} from "../verifiers/IP256Verifier.sol";
Expand Down
86 changes: 86 additions & 0 deletions modules/passkey/contracts/test/TestWebAuthnSignerFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: LGPL-3.0-only
/* solhint-disable one-contract-per-file */
pragma solidity ^0.8.0;

import {IP256Verifier, P256VerifierLib} from "../verifiers/IP256Verifier.sol";

contract TestWebAuthnSignerFactory {
function createSigner(address verifier, uint256 x, uint256 y) external returns (TestWebAuthnSigner signer) {
signer = new TestWebAuthnSigner{salt: 0}(verifier, x, y);
}
}

contract TestWebAuthnSigner {
using P256VerifierLib for IP256Verifier;

struct SignatureData {
bytes authenticatorData;
bytes clientDataFields;
uint256 r;
uint256 s;
}

string private constant _BASE64URL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";

address private immutable _VERIFIER;
uint256 private immutable _X;
uint256 private immutable _Y;

constructor(address verifier, uint256 x, uint256 y) {
_VERIFIER = verifier;
_X = x;
_Y = y;
}

function isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4 magicValue) {
SignatureData calldata sig;
// solhint-disable-next-line no-inline-assembly
assembly ("memory-safe") {
sig := signature.offset
}

if (
IP256Verifier(_VERIFIER).verifySignatureAllowMalleability(
_signingMessage(hash, sig.authenticatorData, sig.clientDataFields),
sig.r,
sig.s,
_X,
_Y
)
) {
magicValue = this.isValidSignature.selector;
}
}

function _signingMessage(
bytes32 challenge,
bytes calldata authenticatorData,
bytes calldata clientDataFields
) internal pure returns (bytes32 message) {
/* solhint-disable quotes */
bytes memory clientDataJson = abi.encodePacked(
'{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",',
clientDataFields,
"}"
);
/* solhint-enable quotes */

string memory alphabet = _BASE64URL;
// solhint-disable-next-line no-inline-assembly
assembly ("memory-safe") {
let lut := add(alphabet, 1)
let ptr := add(clientDataJson, 68)

for {
let i := 0
} lt(i, 42) {
i := add(i, 1)
} {
mstore8(add(ptr, i), mload(add(lut, and(0x3f, shr(sub(250, mul(6, i)), challenge)))))
}
mstore8(add(ptr, 42), mload(add(lut, and(0x3f, shl(2, challenge)))))
}

message = sha256(abi.encodePacked(authenticatorData, sha256(clientDataJson)));
}
}
2 changes: 2 additions & 0 deletions modules/passkey/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
"devDependencies": {
"@noble/curves": "^1.3.0",
"@nomicfoundation/hardhat-toolbox": "^4.0.0",
"@simplewebauthn/server": "^9.0.3",
"cbor": "^9.0.2",
"hardhat": "^2.20.1",
"hardhat-deploy": "^0.12.1",
"solhint": "^4.1.1"
Expand Down
10 changes: 10 additions & 0 deletions modules/passkey/src/deploy/verifiers.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import type { DeployFunction } from 'hardhat-deploy/types'

import DaimoP256Verifier from '../vendor/daimo-eth/P256Verifier.json'

const deploy: DeployFunction = async ({ deployments, getNamedAccounts }) => {
const { deployer } = await getNamedAccounts()
const { deploy } = deployments

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

await deploy('FCLP256Verifier', {
from: deployer,
args: [],
Expand Down
Loading

0 comments on commit f5680bb

Please sign in to comment.