Skip to content

Commit

Permalink
wip: checkpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
jxom committed Jun 25, 2024
1 parent 1ea646d commit aaaf8b3
Show file tree
Hide file tree
Showing 10 changed files with 102 additions and 109 deletions.
File renamed without changes.
4 changes: 2 additions & 2 deletions src/utils/conversion.ts → src/conversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import {
bytesToHex as bytesToHex_noble,
hexToBytes as hexToBytes_noble,
} from '@noble/hashes/utils'
import type { Hex } from '../types.js'
import type { Hex } from './types.js'

export function bytesToHex(bytes: Uint8Array): Hex {
return `0x${bytesToHex_noble(bytes)}`
}

export function hexToBytes(value: Hex): Uint8Array {
return hexToBytes_noble(`0x${value}`)
return hexToBytes_noble(value.slice(2))
}

export function base64UrlToBytes(base64Url: string): Uint8Array {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { expect, test } from 'vitest'
import { getCredentialCreationOptions } from './getCredentialCreationOptions.js'
import { describe, expect, test } from 'vitest'
import { getCredentialCreationOptions } from './credential.js'

test('default', () => {
expect(
getCredentialCreationOptions({
name: 'Foo',
}),
).toMatchInlineSnapshot(`
describe.todo('createCredential')

describe('getCredentialCreationOptions', () => {
test('default', () => {
expect(
getCredentialCreationOptions({
name: 'Foo',
}),
).toMatchInlineSnapshot(`
{
"publicKey": {
"attestation": "none",
Expand Down Expand Up @@ -85,15 +88,15 @@ test('default', () => {
},
}
`)
})
})

test('args: excludeCredentialIds', () => {
expect(
getCredentialCreationOptions({
excludeCredentialIds: ['pzpQZRhXUkboj-b_srH0X42XJS7Ai2ZXd6-9lnFULig'],
name: 'Foo',
}),
).toMatchInlineSnapshot(`
test('args: excludeCredentialIds', () => {
expect(
getCredentialCreationOptions({
excludeCredentialIds: ['pzpQZRhXUkboj-b_srH0X42XJS7Ai2ZXd6-9lnFULig'],
name: 'Foo',
}),
).toMatchInlineSnapshot(`
{
"publicKey": {
"attestation": "none",
Expand Down Expand Up @@ -211,16 +214,16 @@ test('args: excludeCredentialIds', () => {
},
}
`)
})
})

test('args: user', () => {
expect(
getCredentialCreationOptions({
user: {
name: 'Foo',
},
}),
).toMatchInlineSnapshot(`
test('args: user', () => {
expect(
getCredentialCreationOptions({
user: {
name: 'Foo',
},
}),
).toMatchInlineSnapshot(`
{
"publicKey": {
"attestation": "none",
Expand Down Expand Up @@ -299,4 +302,5 @@ test('args: user', () => {
},
}
`)
})
})
49 changes: 46 additions & 3 deletions ...redential/getCredentialCreationOptions.ts → src/credential.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,52 @@
import { keccak_256 } from '@noble/hashes/sha3'
import { toBytes } from '@noble/hashes/utils'

import type { OneOf } from '../types.js'
import { base64UrlToBytes } from '../utils/conversion.js'
import { createChallenge } from './constants.js'
import { base64UrlToBytes } from './conversion.js'
import { parseCredentialPublicKey } from './publicKey.js'
import type { Hex, OneOf } from './types.js'

// Challenge for credential creation – random 16 bytes.
export const createChallenge = Uint8Array.from([
105, 171, 180, 181, 160, 222, 75, 198, 42, 42, 32, 31, 141, 37, 186, 233,
])

export type CreateCredentialParameters = GetCredentialCreationOptionsParameters

export type CreateCredentialReturnType = {
id: PublicKeyCredential['id']
publicKey: Hex
publicKeyCompressed: Hex
}

/**
* Creates a new credential, which can be stored and later used for signing.
*
* @example
* ```ts
* const credential = await createCredential({ name: 'Example' })
* ```
*/
export async function createCredential(
parameters: CreateCredentialParameters,
): Promise<CreateCredentialReturnType> {
const options = getCredentialCreationOptions(parameters)
try {
const credential = (await window.navigator.credentials.create(
options,
)) as PublicKeyCredential
if (!credential) throw new Error('credential creation failed.')
const publicKey = await parseCredentialPublicKey(
new Uint8Array((credential.response as any).getPublicKey()),
)
return {
id: credential.id,
publicKey,
publicKeyCompressed: `0x${publicKey.slice(4)}`,
}
} catch (error) {
throw new Error('credential creation failed.', { cause: error })
}
}

export type GetCredentialCreationOptionsParameters = {
/**
Expand Down
4 changes: 0 additions & 4 deletions src/credential/constants.ts

This file was deleted.

3 changes: 0 additions & 3 deletions src/credential/createCredential.test.ts

This file was deleted.

43 changes: 0 additions & 43 deletions src/credential/createCredential.ts

This file was deleted.

26 changes: 11 additions & 15 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,3 @@
export {
type CreateCredentialParameters,
type CreateCredentialReturnType,
createCredential,
} from './credential/createCredential.js'
export {
type GetCredentialCreationOptionsParameters,
type GetCredentialCreationOptionsReturnType,
getCredentialCreationOptions,
} from './credential/getCredentialCreationOptions.js'

export type { Hex } from './types.js'

export {
base64ToBase64Url,
base64ToUtf8,
Expand All @@ -22,8 +9,17 @@ export {
cryptoKeyToBytes,
hexToBytes,
utf8ToBase64,
} from './utils/conversion.js'
} from './conversion.js'
export {
type CreateCredentialParameters,
type CreateCredentialReturnType,
createCredential,
type GetCredentialCreationOptionsParameters,
type GetCredentialCreationOptionsReturnType,
getCredentialCreationOptions,
} from './credential.js'
export {
type ParseCredentialPublicKeyOptions,
parseCredentialPublicKey,
} from './utils/publicKey.js'
} from './publicKey.js'
export type { Hex } from './types.js'
16 changes: 7 additions & 9 deletions src/utils/publicKey.test.ts → src/publicKey.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { hexToBytes } from '@noble/hashes/utils'
import { describe, expect, test } from 'vitest'
import { hexToBytes } from './conversion.js'
import { parseCredentialPublicKey, parsePublicKey } from './publicKey.js'

describe('parseCredentialPublicKey', () => {
test('default', async () => {
const cPublicKey = hexToBytes(
'3059301306072a8648ce3d020106082a8648ce3d030107034200041fd0593f9f25ed8ecab174bba6ea6fcf22909c53b3a4e34d5a9f6abd37d6f98cf4954eec64a4b8a39c89e7c4a00b315359e0113fa3fa325ac23cc30ab98a5f21',
'0x3059301306072a8648ce3d020106082a8648ce3d030107034200041fd0593f9f25ed8ecab174bba6ea6fcf22909c53b3a4e34d5a9f6abd37d6f98cf4954eec64a4b8a39c89e7c4a00b315359e0113fa3fa325ac23cc30ab98a5f21',
)
const publicKey = await parseCredentialPublicKey(cPublicKey)
expect(publicKey).toMatchInlineSnapshot(
Expand All @@ -15,7 +15,7 @@ describe('parseCredentialPublicKey', () => {

test('args: compressed', async () => {
const cPublicKey = hexToBytes(
'3059301306072a8648ce3d020106082a8648ce3d030107034200042caa86454963544bbc964f29979ddb953395f1baa9b123b1edb6ed1109bf0cb2ce91893a28a0f9f0c6b85edf44b01e95d46a39eeeab45a0b2583c05cb6414904',
'0x3059301306072a8648ce3d020106082a8648ce3d030107034200042caa86454963544bbc964f29979ddb953395f1baa9b123b1edb6ed1109bf0cb2ce91893a28a0f9f0c6b85edf44b01e95d46a39eeeab45a0b2583c05cb6414904',
)
const publicKey = await parseCredentialPublicKey(cPublicKey, {
compressed: true,
Expand All @@ -28,9 +28,8 @@ describe('parseCredentialPublicKey', () => {

describe('parsePublicKey', () => {
test('default', () => {
const publicKey = hexToBytes(
'2caa86454963544bbc964f29979ddb953395f1baa9b123b1edb6ed1109bf0cb2ce91893a28a0f9f0c6b85edf44b01e95d46a39eeeab45a0b2583c05cb6414904',
)
const publicKey =
'0x2caa86454963544bbc964f29979ddb953395f1baa9b123b1edb6ed1109bf0cb2ce91893a28a0f9f0c6b85edf44b01e95d46a39eeeab45a0b2583c05cb6414904'
const parsed = parsePublicKey(publicKey)
expect(parsed).toMatchInlineSnapshot(`
{
Expand All @@ -41,9 +40,8 @@ describe('parsePublicKey', () => {
})

test('uncompressed', () => {
const publicKey = hexToBytes(
'042caa86454963544bbc964f29979ddb953395f1baa9b123b1edb6ed1109bf0cb2ce91893a28a0f9f0c6b85edf44b01e95d46a39eeeab45a0b2583c05cb6414904',
)
const publicKey =
'0x042caa86454963544bbc964f29979ddb953395f1baa9b123b1edb6ed1109bf0cb2ce91893a28a0f9f0c6b85edf44b01e95d46a39eeeab45a0b2583c05cb6414904'
const parsed = parsePublicKey(publicKey)
expect(parsed).toMatchInlineSnapshot(`
{
Expand Down
12 changes: 7 additions & 5 deletions src/utils/publicKey.ts → src/publicKey.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { Hex } from '../types.js'
import {
base64UrlToBytes,
bytesToBase64Url,
bytesToCryptoKey,
bytesToHex,
cryptoKeyToBytes,
hexToBytes,
} from './conversion.js'
import type { Hex } from './types.js'

export type ParseCredentialPublicKeyOptions = {
compressed?: boolean | undefined
Expand All @@ -32,9 +33,10 @@ type PublicKey = {
y: bigint
}

export function parsePublicKey(publicKey: Uint8Array): PublicKey {
const offset = publicKey[0] === 4 ? 1 : 0
const x = publicKey.slice(offset, 32 + offset)
const y = publicKey.slice(32 + offset, 64 + offset)
export function parsePublicKey(publicKey: Hex): PublicKey {
const bytes = hexToBytes(publicKey)
const offset = bytes[0] === 4 ? 1 : 0
const x = bytes.slice(offset, 32 + offset)
const y = bytes.slice(32 + offset, 64 + offset)
return { x: BigInt(bytesToHex(x)), y: BigInt(bytesToHex(y)) }
}

0 comments on commit aaaf8b3

Please sign in to comment.