Skip to content

Commit

Permalink
Remove libs
Browse files Browse the repository at this point in the history
  • Loading branch information
yagopv committed Nov 18, 2024
1 parent 56d5955 commit e813a3f
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 44 deletions.
2 changes: 0 additions & 2 deletions packages/protocol-kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@
},
"dependencies": {
"@noble/curves": "^1.6.0",
"@noble/hashes": "^1.3.3",
"@peculiar/asn1-schema": "^2.3.13",
"@safe-global/safe-deployments": "^1.37.14",
"@safe-global/safe-modules-deployments": "^2.2.4",
"@safe-global/types-kit": "^1.0.0",
Expand Down
135 changes: 93 additions & 42 deletions packages/protocol-kit/src/utils/passkeys/extractPasskeyData.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,57 @@
import { p256 } from '@noble/curves/p256'
import { Buffer } from 'buffer'
import { getFCLP256VerifierDeployment } from '@safe-global/safe-modules-deployments'
import { PasskeyArgType, PasskeyCoordinates } from '@safe-global/protocol-kit/types'

/**
* Extracts and returns the passkey data (coordinates and rawId) from a given passkey Credential.
* Converts a Base64 URL-encoded string to a Uint8Array.
*
* @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
* This function handles Base64 URL variants by replacing URL-safe characters
* with standard Base64 characters, decodes the Base64 string into a binary string,
* and then converts it into a Uint8Array.
*
* @param {string} base64 - The Base64 URL-encoded string to convert.
* @returns {Uint8Array} The resulting Uint8Array from the decoded Base64 string.
*/
export async function extractPasskeyData(passkeyCredential: Credential): Promise<PasskeyArgType> {
const passkey = passkeyCredential as PublicKeyCredential
const attestationResponse = passkey.response as AuthenticatorAttestationResponse

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

return {
rawId,
coordinates
}
}

function base64ToUint8Array(base64: string): Uint8Array {
const base64Fixed = base64.replace(/-/g, '+').replace(/_/g, '/')
const binaryString = atob(base64Fixed)
const bytes = new Uint8Array(binaryString.length)
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i)
}
return bytes
const binaryBuffer = Buffer.from(base64Fixed, 'base64')

return new Uint8Array(binaryBuffer)
}

/**
* Ensures the elliptic curve public key is in the correct uncompressed format.
*
* Elliptic curve operations often require the public key to be in an uncompressed format,
* which starts with a `0x04` byte, followed by the x and y coordinates. This function
* checks the key length and prepends `0x04` if necessary.
*
* @param {Uint8Array} publicKey - The public key to format.
* @returns {Uint8Array} The formatted public key in uncompressed format.
*/
function ensureCorrectFormat(publicKey: Uint8Array): Uint8Array {
if (publicKey.length === 64) {
const uncompressedKey = new Uint8Array(65)
uncompressedKey[0] = 0x04
uncompressedKey.set(publicKey, 1)

return uncompressedKey
}

return publicKey
}

/**
* Decodes a Base64-encoded ECDSA public key for React Native and extracts the x and y coordinates.
*
* This function decodes a Base64-encoded public key, ensures it is in the correct uncompressed format,
* and extracts the x and y coordinates using the `@noble/curves` library. The coordinates are returned
* as hexadecimal strings prefixed with '0x'.
*
* @param {string} publicKey - The Base64-encoded public key to decode.
* @returns {PasskeyCoordinates} An object containing the x and y coordinates of the public key.
* @throws {Error} Throws an error if the key coordinates cannot be extracted.
*/
function decodePublicKeyForReactNative(publicKey: string): PasskeyCoordinates {
const publicKeyUint8Array = base64ToUint8Array(publicKey)

Expand All @@ -53,6 +60,7 @@ function decodePublicKeyForReactNative(publicKey: string): PasskeyCoordinates {
}

const formattedKey = ensureCorrectFormat(publicKeyUint8Array)

const point = p256.ProjectivePoint.fromHex(formattedKey)

const x = point.x.toString(16).padStart(64, '0')
Expand All @@ -64,6 +72,18 @@ function decodePublicKeyForReactNative(publicKey: string): PasskeyCoordinates {
}
}

/**
* Decodes an ECDSA public key for the web platform and extracts the x and y coordinates.
*
* This function uses the Web Crypto API to import a public key in SPKI format and then
* exports it to a JWK format to retrieve the x and y coordinates. The coordinates are
* returned as hexadecimal strings prefixed with '0x'.
*
* @param {ArrayBuffer} publicKey - The public key in SPKI format to decode.
* @returns {Promise<PasskeyCoordinates>} A promise that resolves to an object containing
* the x and y coordinates of the public key.
* @throws {Error} Throws an error if the key coordinates cannot be extracted.
*/
async function decodePublicKeyForWeb(publicKey: ArrayBuffer): Promise<PasskeyCoordinates> {
const algorithm = {
name: 'ECDSA',
Expand Down Expand Up @@ -94,7 +114,7 @@ async function decodePublicKeyForWeb(publicKey: ArrayBuffer): Promise<PasskeyCoo
* @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 async function decodePublicKey(
async function decodePublicKey(
response: AuthenticatorAttestationResponse
): Promise<PasskeyCoordinates> {
const publicKey = response.getPublicKey()
Expand All @@ -103,24 +123,55 @@ export async function decodePublicKey(
throw new Error('Failed to generate passkey coordinates. getPublicKey() failed')
}

try {
if (typeof publicKey === 'string') {
// Public key is base64 encoded
// React Native platform uses base64 encoded strings
return decodePublicKeyForReactNative(publicKey)
} else if (publicKey instanceof ArrayBuffer) {
// Public key is an ArrayBuffer
// Web platform uses ArrayBuffer
return await decodePublicKeyForWeb(publicKey)
} else {
throw new Error('Unsupported public key format.')
}
} catch (error) {
console.error('Error decoding public key:', error)
throw error
if (typeof publicKey === 'string') {
// Public key is base64 encoded
// - React Native platform uses base64 encoded strings
return decodePublicKeyForReactNative(publicKey)
}

if (publicKey instanceof ArrayBuffer) {
// Public key is an ArrayBuffer
// - Web platform uses ArrayBuffer
return await decodePublicKeyForWeb(publicKey)
}

throw new Error('Unsupported public key format.')
}

/**
* 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()` 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 rawId = Buffer.from(passkey.rawId).toString('hex')
const coordinates = await decodePublicKey(attestationResponse)

return {
rawId,
coordinates
}
}

/**
* Retrieves the default FCLP256 Verifier address for a given blockchain network.
*
* This function fetches the deployment information for the FCLP256 Verifier and
* returns the verifier address associated with the specified chain ID. It ensures
* that the correct version and release status are used.
*
* @param {string} chainId - The ID of the blockchain network to retrieve the verifier address for.
* @returns {string} The FCLP256 Verifier address for the specified chain ID.
* @throws {Error} Throws an error if the deployment information or address cannot be found.
*/

export function getDefaultFCLP256VerifierAddress(chainId: string): string {
const FCLP256VerifierDeployment = getFCLP256VerifierDeployment({
version: '0.2.1',
Expand Down

0 comments on commit e813a3f

Please sign in to comment.