Skip to content

Commit

Permalink
Remove asn and keep web decoding as it was
Browse files Browse the repository at this point in the history
  • Loading branch information
yagopv committed Nov 15, 2024
1 parent 9779f9e commit 37115b4
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 60 deletions.
11 changes: 7 additions & 4 deletions packages/protocol-kit/src/Safe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import {
SwapOwnerTxParams,
SafeModulesPaginated,
RemovePasskeyOwnerTxParams,
PasskeyCoordinates
PasskeyArgType
} from './types'
import {
EthSafeSignature,
Expand Down Expand Up @@ -1701,9 +1701,12 @@ class Safe {
return getContractInfo(contractAddress)
}

static createPasskeySigner = async (
credentials: Credential
): Promise<{ rawId: string; coordinates: PasskeyCoordinates }> => {
/**
* This method creates a signer to be used with the init method
* @param {Credential} credentials - The credentials to be used to create the signer. Can be generated in the web with navigator.credentials.create
* @returns {PasskeyArgType} - The signer to be used with the init method
*/
static createPasskeySigner = async (credentials: Credential): Promise<PasskeyArgType> => {
return extractPasskeyData(credentials)
}
}
Expand Down
102 changes: 46 additions & 56 deletions packages/protocol-kit/src/utils/passkeys/extractPasskeyData.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,30 @@
import { p256 } from '@noble/curves/p256'
import { AsnProp, AsnPropTypes, AsnType, AsnTypeTypes, AsnParser } from '@peculiar/asn1-schema'
import { Buffer } from 'buffer'
import { getFCLP256VerifierDeployment } from '@safe-global/safe-modules-deployments'
import { PasskeyArgType, PasskeyCoordinates } from '@safe-global/protocol-kit/types'

@AsnType({ type: AsnTypeTypes.Sequence })
class AlgorithmIdentifier {
@AsnProp({ type: AsnPropTypes.ObjectIdentifier })
public id: string = ''

@AsnProp({ type: AsnPropTypes.ObjectIdentifier, optional: true })
public curve: string = ''
}

@AsnType({ type: AsnTypeTypes.Sequence })
class ECPublicKey {
@AsnProp({ type: AlgorithmIdentifier })
public algorithm = new AlgorithmIdentifier()

@AsnProp({ type: AsnPropTypes.BitString })
public publicKey: ArrayBuffer = new ArrayBuffer(0)
}

/**
* Extracts and returns the passkey data (coordinates and rawId) from a given passkey Credential.
*
* @param {Credential} passkeyCredential - The passkey credential generated via `navigator.credentials.create()` using correct parameters.
* @param {Credential} passkeyCredential - The passkey credential generated via `navigator.credentials.create()` or other method in another platforms.
* @returns {Promise<PasskeyArgType>} A promise that resolves to an object containing the coordinates and the rawId derived from the passkey.
* This is the important information in the Safe account context and should be stored securely as it is used to verify the passkey and to instantiate the SDK
* as a signer (`Safe.init())
* @throws {Error} Throws an error if the coordinates could not be extracted
*/
export async function extractPasskeyData(passkeyCredential: Credential): Promise<PasskeyArgType> {
const passkey = passkeyCredential as PublicKeyCredential
const attestationResponse = passkey.response as AuthenticatorAttestationResponse

const coordinates = decodePublicKey(attestationResponse)
const rawId = Buffer.from(passkey.rawId).toString('hex')
const coordinates = await decodePublicKey(attestationResponse)

return {
rawId,
coordinates
}
}

// function decodeBase64(base64: string): Uint8Array {
// const base64Fixed = base64.replace(/-/g, '+').replace(/_/g, '/')
// const binaryString = Buffer.from(base64Fixed, 'base64')
// return new Uint8Array(binaryString)
// }

function base64ToUint8Array(base64: string): Uint8Array {
const base64Fixed = base64.replace(/-/g, '+').replace(/_/g, '/')
const binaryString = atob(base64Fixed)
Expand All @@ -70,59 +47,72 @@ function ensureCorrectFormat(publicKey: Uint8Array): Uint8Array {

/**
* Decodes the x and y coordinates of the public key from a created public key credential response.
* Inspired from <https://webauthn.guide/#registration>.
*
* @param {Pick<AuthenticatorAttestationResponse, 'attestationObject'>} response
* @returns {PasskeyCoordinates} Object containing the coordinates derived from the public key of the passkey.
* @throws {Error} Throws an error if the coordinates could not be extracted via `p256.ProjectivePoint.fromHex`
*/
export function decodePublicKey(response: AuthenticatorAttestationResponse): PasskeyCoordinates {
export async function decodePublicKey(
response: AuthenticatorAttestationResponse
): Promise<PasskeyCoordinates> {
const publicKey = response.getPublicKey()

if (!publicKey) {
throw new Error('Failed to generate passkey coordinates. getPublicKey() failed')
}

console.log('Public Key:', publicKey)

try {
let publicKeyUint8Array: Uint8Array

if (typeof publicKey === 'string') {
console.log('Public Key is Base64')
// Public key is base64 encoded
// React Native platform uses base64 encoded strings
publicKeyUint8Array = base64ToUint8Array(publicKey)
} else if (publicKey instanceof ArrayBuffer) {
console.log('Public Key is ArrayBuffer')
publicKeyUint8Array = new Uint8Array(publicKey)
// Parse the DER-encoded public key using the ASN.1 schema
const decodedKey = AsnParser.parse(publicKeyUint8Array.buffer, ECPublicKey)

// Extract the actual public key bytes
publicKeyUint8Array = new Uint8Array(decodedKey.publicKey)
} else {
throw new Error('Unsupported public key format.')
}
if (publicKeyUint8Array.length === 0) {
throw new Error('Decoded public key is empty.')
}

console.log('Decoded Public Key Uint8Array:', publicKeyUint8Array)
const formattedKey = ensureCorrectFormat(publicKeyUint8Array)

if (publicKeyUint8Array.length === 0) {
throw new Error('Decoded public key is empty.')
}
// Parse the public key bytes into a point on the curve
const point = p256.ProjectivePoint.fromHex(formattedKey)

const formattedKey = ensureCorrectFormat(publicKeyUint8Array)
console.log('Elliptic Curve Point:', point)

// Parse the public key bytes into a point on the curve
const point = p256.ProjectivePoint.fromHex(formattedKey)
// Extract x and y coordinates
const x = point.x.toString(16).padStart(64, '0')
const y = point.y.toString(16).padStart(64, '0')

return {
x: '0x' + x,
y: '0x' + y
}
} else if (publicKey instanceof ArrayBuffer) {
// Public key is an ArrayBuffer
// Web platform uses ArrayBuffer
const algorithm = {
name: 'ECDSA',
namedCurve: 'P-256',
hash: { name: 'SHA-256' }
}

console.log('Elliptic Curve Point:', point)
const key = await crypto.subtle.importKey('spki', publicKey, algorithm, true, ['verify'])

// Extract x and y coordinates
const x = point.x.toString(16).padStart(64, '0')
const y = point.y.toString(16).padStart(64, '0')
const { x, y } = await crypto.subtle.exportKey('jwk', key)

return {
x: '0x' + x,
y: '0x' + y
const isValidCoordinates = !!x && !!y

if (!isValidCoordinates) {
throw new Error('Failed to generate passkey Coordinates. crypto.subtle.exportKey() failed')
}

return {
x: '0x' + Buffer.from(x, 'base64').toString('hex'),
y: '0x' + Buffer.from(y, 'base64').toString('hex')
}
} else {
throw new Error('Unsupported public key format.')
}
} catch (error) {
console.error('Error decoding public key:', error)
Expand Down

0 comments on commit 37115b4

Please sign in to comment.