Skip to content

Commit

Permalink
Update dependencies to use cbor-web instead of cbor in `@safe-glo…
Browse files Browse the repository at this point in the history
…bal/safe-passkey` and update imports in the example app (#488)

When trying to solve
#395, I looked at the
original error that triggered the discussion of adding an ESM build to
the packages. I thought that the error was due to a bug in the bundler's
ESM interoperability mechanism, but I realised that actually the issue
was in the `cbor` package and compatibility with the browser
environment.

When you import a js module, the code will get evaluated regardless of
it use:
https://www.perplexity.ai/search/does-javascript-evaluate-an-im-y4Zv1HKiQNmqxnHd1HJ1SA#0
(we may not use any of the functionality that's actually incompatible
but because the code gets evaluated and it may imported a package that's
unavailable in browser)

This PR:
- Fixes compatibility of the `@safe-global/safe-passkey` with the
browser environment by switching from `cbor` to `cbor-web` package as
recommended in the package [readme](https://github.com/hildjj/node-cbor)
- Use the `@safe-global/safe-passkey` code in the example app
- Updates the upstream bundler image TAG because it seems like they
switched branch names
  • Loading branch information
mmv08 authored Aug 29, 2024
1 parent 120220b commit e09f7fc
Show file tree
Hide file tree
Showing 10 changed files with 308 additions and 364 deletions.
2 changes: 1 addition & 1 deletion examples/4337-passkeys/.env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Get projectId at https://cloud.walletconnect.com
VITE_WC_CLOUD_PROJECT_ID=
// 4337 Bundler URL. We recommend https://www.pimlico.io/
// 4337 Bundler URL. We recommend https://www.pimlico.io/. Should use the same network as the app.
VITE_WC_4337_BUNDLER_URL=
2 changes: 1 addition & 1 deletion examples/4337-passkeys/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"@safe-global/safe-contracts": "1.4.1-build.0",
"@safe-global/safe-deployments": "^1.37.0",
"@safe-global/safe-modules-deployments": "^2.2.0",
"@safe-global/safe-passkey": "0.2.0",
"@safe-global/safe-passkey": "workspace:^0.2.1-1",
"@web3modal/ethers": "^4.1.11",
"ethers": "^6.13.1",
"react": "^18.3.1",
Expand Down
70 changes: 1 addition & 69 deletions examples/4337-passkeys/src/logic/userOp.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ethers } from 'ethers'
import { abi as EntryPointAbi } from '@account-abstraction/contracts/artifacts/EntryPoint.json'
import { IEntryPoint } from '@safe-global/safe-4337/dist/typechain-types'
import { getSignatureBytes, DUMMY_AUTHENTICATOR_DATA, DUMMY_CLIENT_DATA_FIELDS } from '@safe-global/safe-passkey/dist/src/utils/webauthn'
import { getExecuteUserOpData, getValidateUserOpData } from './safe'
import { APP_CHAIN_ID, ENTRYPOINT_ADDRESS, SAFE_4337_MODULE_ADDRESS, SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS } from '../config'
import { PasskeyLocalStorageFormat, signWithPasskey } from './passkeys'
Expand All @@ -15,75 +16,6 @@ type UserOperation = UserOperationOgType & { sender: string }
type PackedUserOperation = PackedUserOperationOgType & { sender: string }
type UnsignedPackedUserOperation = Omit<PackedUserOperation, 'signature'>

/**
* Dummy client data JSON fields. This can be used for gas estimations, as it pads the fields enough
* to account for variations in WebAuthn implementations.
*/
export const DUMMY_CLIENT_DATA_FIELDS = [
`"origin":"http://safe.global"`,
`"padding":"This pads the clientDataJSON so that we can leave room for additional implementation specific fields for a more accurate 'preVerificationGas' estimate."`,
].join(',')

/**
* Dummy authenticator data. This can be used for gas estimations, as it ensures that the correct
* authenticator flags are set.
*/
export const DUMMY_AUTHENTICATOR_DATA = new Uint8Array(37)
// Authenticator data is the concatenation of:
// - 32 byte SHA-256 hash of the relying party ID
// - 1 byte for the user verification flag
// - 4 bytes for the signature count
// We fill it all with `0xfe` and set the appropriate user verification flag.
DUMMY_AUTHENTICATOR_DATA.fill(0xfe)
DUMMY_AUTHENTICATOR_DATA[32] = 0x04

/**
* Encodes the given WebAuthn signature into a string. This computes the ABI-encoded signature parameters:
* ```solidity
* abi.encode(authenticatorData, clientDataFields, r, s);
* ```
*
* @param authenticatorData - The authenticator data as a Uint8Array.
* @param clientDataFields - The client data fields as a string.
* @param r - The value of r as a bigint.
* @param s - The value of s as a bigint.
* @returns The encoded string.
*/
export function getSignatureBytes({
authenticatorData,
clientDataFields,
r,
s,
}: {
authenticatorData: Uint8Array
clientDataFields: string
r: bigint
s: bigint
}): string {
// Helper functions
// Convert a number to a 64-byte hex string with padded upto Hex string with 32 bytes
const encodeUint256 = (x: bigint | number) => x.toString(16).padStart(64, '0')
// Calculate the byte size of the dynamic data along with the length parameter alligned to 32 bytes
const byteSize = (data: Uint8Array) => 32 * (Math.ceil(data.length / 32) + 1) // +1 is for the length parameter
// Encode dynamic data padded with zeros if necessary in 32 bytes chunks
const encodeBytes = (data: Uint8Array) => `${encodeUint256(data.length)}${ethers.hexlify(data).slice(2)}`.padEnd(byteSize(data) * 2, '0')

// authenticatorData starts after the first four words.
const authenticatorDataOffset = 32 * 4
// clientDataFields starts immediately after the authenticator data.
const clientDataFieldsOffset = authenticatorDataOffset + byteSize(authenticatorData)

return (
'0x' +
encodeUint256(authenticatorDataOffset) +
encodeUint256(clientDataFieldsOffset) +
encodeUint256(r) +
encodeUint256(s) +
encodeBytes(authenticatorData) +
encodeBytes(new TextEncoder().encode(clientDataFields))
)
}

/**
* Generates a dummy signature for a user operation.
*
Expand Down
4 changes: 2 additions & 2 deletions modules/passkey/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@safe-global/safe-passkey",
"version": "0.2.1",
"version": "0.2.1-1",
"author": "@safe-global",
"description": "Safe Passkey Owner",
"homepage": "https://github.com/safe-global/safe-modules/tree/main/modules/passkey",
Expand Down Expand Up @@ -68,6 +68,6 @@
"dependencies": {
"@account-abstraction/contracts": "0.7.0",
"@openzeppelin/contracts": "5.0.0",
"cbor": "^9.0.2"
"cbor-web": "^9.0.2"
}
}
6 changes: 3 additions & 3 deletions modules/passkey/src/utils/webauthn.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import CBOR from 'cbor'
import { decode as cborDecode } from 'cbor-web'

/**
* Returns the flag for the user verification requirement.
Expand Down Expand Up @@ -40,15 +40,15 @@ export function base64UrlEncode(data: string | Uint8Array | ArrayBuffer): string
* @returns The x and y coordinates of the public key.
*/
export function decodePublicKey(response: Pick<AuthenticatorAttestationResponse, 'attestationObject'>): { x: bigint; y: bigint } {
const attestationObject = CBOR.decode(response.attestationObject)
const attestationObject = cborDecode(response.attestationObject)
const authData = new DataView(
attestationObject.authData.buffer,
attestationObject.authData.byteOffset,
attestationObject.authData.byteLength,
)
const credentialIdLength = authData.getUint16(53)
const cosePublicKey = attestationObject.authData.slice(55 + credentialIdLength)
const key: Map<number, unknown> = CBOR.decode(cosePublicKey)
const key: Map<number, unknown> = cborDecode(cosePublicKey)
const bn = (bytes: Uint8Array) => BigInt('0x' + toHexString(bytes))
return {
x: bn(key.get(-2) as Uint8Array),
Expand Down
6 changes: 3 additions & 3 deletions modules/passkey/test/utils/webauthnShim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import { p256 } from '@noble/curves/p256'
import { ethers } from 'ethers'
import type { BytesLike } from 'ethers'
import CBOR from 'cbor'
import { encode as cborEncode } from 'cbor-web'
import { base64UrlEncode, userVerificationFlag } from '../../src/utils/webauthn'

function b2ab(buf: Uint8Array): ArrayBuffer {
Expand Down Expand Up @@ -135,7 +135,7 @@ class Credential {
key.set(1, 2) // kty = EC2
key.set(3, -7) // alg = ES256 (Elliptic curve signature with SHA-256)

return ethers.hexlify(CBOR.encode(key))
return ethers.hexlify(cborEncode(key))
}
}

Expand Down Expand Up @@ -191,7 +191,7 @@ export class WebAuthnCredentials {
rawId: credential.rawId.slice(),
response: {
clientDataJSON: b2ab(ethers.toUtf8Bytes(JSON.stringify(clientData))),
attestationObject: b2ab(CBOR.encode(attestationObject)),
attestationObject: b2ab(cborEncode(attestationObject)),
},
type: 'public-key',
}
Expand Down
4 changes: 2 additions & 2 deletions modules/passkey/test/webauthn/WebAuthnShim.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
verifyRegistrationResponse,
} from '@simplewebauthn/server'
import { expect } from 'chai'
import CBOR from 'cbor'
import { decode as cborDecode } from 'cbor-web'
import { ethers } from 'ethers'
import { WebAuthnCredentials } from '../../test/utils/webauthnShim'
import { base64UrlEncode } from '../../src/utils/webauthn'
Expand Down Expand Up @@ -90,7 +90,7 @@ describe('WebAuthn Shim', () => {
},
})

const attestationObject = CBOR.decode(credential.response.attestationObject)
const attestationObject = cborDecode(credential.response.attestationObject)
const authData = new DataView(
attestationObject.authData.buffer,
attestationObject.authData.byteOffset,
Expand Down
2 changes: 1 addition & 1 deletion modules/passkey/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
"skipLibCheck": true,
"resolveJsonModule": true
},
"include": ["bin", "src/**/*.ts", "hardhat.config.ts", "test", "typechain-types"]
"include": ["bin", "src/**/*.ts", "hardhat.config.ts", "test/**/*.ts", "typechain-types"]
}
2 changes: 1 addition & 1 deletion packages/4337-local-bundler/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ services:
context: .
dockerfile: docker/bundler/Dockerfile
args:
TAG: main
TAG: master
restart: always
command: ['--auto', '--network=http://geth:8545']
environment:
Expand Down
Loading

0 comments on commit e09f7fc

Please sign in to comment.