ox 0.12.2 → 0.12.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/core/Cbor.ts CHANGED
@@ -254,6 +254,9 @@ function getEncodable(value: unknown): Encodable {
254
254
  new Uint8Array(value.buffer, value.byteOffset, value.byteLength),
255
255
  )
256
256
 
257
+ if (value instanceof Map)
258
+ return getEncodable.map(value as Map<unknown, unknown>)
259
+
257
260
  if (typeof value === 'object')
258
261
  return getEncodable.object(value as Record<string, unknown>)
259
262
 
@@ -549,6 +552,67 @@ namespace getEncodable {
549
552
  }
550
553
  throw new ObjectTooLargeError({ size })
551
554
  }
555
+
556
+ /** @internal */
557
+ export function map(value: Map<unknown, unknown>): Encodable {
558
+ const entries: { key: Encodable; value: Encodable }[] = []
559
+ for (const [k, v] of value)
560
+ entries.push({ key: getEncodable(k), value: getEncodable(v) })
561
+ const bodyLength = entries.reduce(
562
+ (acc, entry) => acc + entry.key.length + entry.value.length,
563
+ 0,
564
+ )
565
+ const size = value.size
566
+
567
+ if (size <= 0x17)
568
+ return {
569
+ length: 1 + bodyLength,
570
+ encode(cursor) {
571
+ cursor.pushUint8(0xa0 + size)
572
+ for (const entry of entries) {
573
+ entry.key.encode(cursor)
574
+ entry.value.encode(cursor)
575
+ }
576
+ },
577
+ }
578
+ if (size <= 0xff)
579
+ return {
580
+ length: 2 + bodyLength,
581
+ encode(cursor) {
582
+ cursor.pushUint8(0xb8)
583
+ cursor.pushUint8(size)
584
+ for (const entry of entries) {
585
+ entry.key.encode(cursor)
586
+ entry.value.encode(cursor)
587
+ }
588
+ },
589
+ }
590
+ if (size <= 0xffff)
591
+ return {
592
+ length: 3 + bodyLength,
593
+ encode(cursor) {
594
+ cursor.pushUint8(0xb9)
595
+ cursor.pushUint16(size)
596
+ for (const entry of entries) {
597
+ entry.key.encode(cursor)
598
+ entry.value.encode(cursor)
599
+ }
600
+ },
601
+ }
602
+ if (size <= 0xffffffff)
603
+ return {
604
+ length: 5 + bodyLength,
605
+ encode(cursor) {
606
+ cursor.pushUint8(0xba)
607
+ cursor.pushUint32(size)
608
+ for (const entry of entries) {
609
+ entry.key.encode(cursor)
610
+ entry.value.encode(cursor)
611
+ }
612
+ },
613
+ }
614
+ throw new ObjectTooLargeError({ size })
615
+ }
552
616
  }
553
617
 
554
618
  /** @internal */
@@ -0,0 +1,93 @@
1
+ import * as Cbor from './Cbor.js'
2
+ import * as Errors from './Errors.js'
3
+ import type * as Hex from './Hex.js'
4
+ import * as PublicKey from './PublicKey.js'
5
+
6
+ /**
7
+ * Converts a P256 {@link ox#PublicKey.PublicKey} to a CBOR-encoded COSE_Key.
8
+ *
9
+ * The COSE_Key uses integer map keys per [RFC 9053](https://datatracker.ietf.org/doc/html/rfc9053):
10
+ * - `1` (kty): `2` (EC2)
11
+ * - `3` (alg): `-7` (ES256)
12
+ * - `-1` (crv): `1` (P-256)
13
+ * - `-2` (x): x coordinate bytes
14
+ * - `-3` (y): y coordinate bytes
15
+ *
16
+ * @example
17
+ * ```ts twoslash
18
+ * import { CoseKey, P256 } from 'ox'
19
+ *
20
+ * const { publicKey } = P256.createKeyPair()
21
+ *
22
+ * const coseKey = CoseKey.fromPublicKey(publicKey)
23
+ * ```
24
+ *
25
+ * @param publicKey - The P256 public key to convert.
26
+ * @returns The CBOR-encoded COSE_Key as a Hex string.
27
+ */
28
+ export function fromPublicKey(publicKey: PublicKey.PublicKey): Hex.Hex {
29
+ const pkBytes = PublicKey.toBytes(publicKey)
30
+ const x = pkBytes.slice(1, 33)
31
+ const y = pkBytes.slice(33, 65)
32
+ return Cbor.encode(
33
+ new Map<number, unknown>([
34
+ [1, 2], // kty: EC2
35
+ [3, -7], // alg: ES256
36
+ [-1, 1], // crv: P-256
37
+ [-2, x], // x coordinate
38
+ [-3, y], // y coordinate
39
+ ]),
40
+ )
41
+ }
42
+
43
+ export declare namespace fromPublicKey {
44
+ type ErrorType =
45
+ | PublicKey.toBytes.ErrorType
46
+ | Cbor.encode.ErrorType
47
+ | Errors.GlobalErrorType
48
+ }
49
+
50
+ /**
51
+ * Converts a CBOR-encoded COSE_Key to a P256 {@link ox#PublicKey.PublicKey}.
52
+ *
53
+ * @example
54
+ * ```ts twoslash
55
+ * import { CoseKey, P256 } from 'ox'
56
+ *
57
+ * const { publicKey } = P256.createKeyPair()
58
+ * const coseKey = CoseKey.fromPublicKey(publicKey)
59
+ *
60
+ * const publicKey2 = CoseKey.toPublicKey(coseKey)
61
+ * ```
62
+ *
63
+ * @param coseKey - The CBOR-encoded COSE_Key.
64
+ * @returns The P256 public key.
65
+ */
66
+ export function toPublicKey(coseKey: Hex.Hex): PublicKey.PublicKey {
67
+ const decoded = Cbor.decode<Record<string, unknown>>(coseKey)
68
+
69
+ const x = decoded['-2']
70
+ const y = decoded['-3']
71
+
72
+ if (!(x instanceof Uint8Array) || !(y instanceof Uint8Array))
73
+ throw new InvalidCoseKeyError()
74
+
75
+ return PublicKey.from(new Uint8Array([0x04, ...x, ...y]))
76
+ }
77
+
78
+ export declare namespace toPublicKey {
79
+ type ErrorType =
80
+ | Cbor.decode.ErrorType
81
+ | PublicKey.from.ErrorType
82
+ | InvalidCoseKeyError
83
+ | Errors.GlobalErrorType
84
+ }
85
+
86
+ /** Thrown when a COSE_Key does not contain valid P256 public key coordinates. */
87
+ export class InvalidCoseKeyError extends Errors.BaseError {
88
+ override readonly name = 'CoseKey.InvalidCoseKeyError'
89
+
90
+ constructor() {
91
+ super('COSE_Key does not contain valid P256 public key coordinates.')
92
+ }
93
+ }
@@ -1,5 +1,7 @@
1
1
  import * as Base64 from './Base64.js'
2
2
  import * as Bytes from './Bytes.js'
3
+ import * as Cbor from './Cbor.js'
4
+ import * as CoseKey from './CoseKey.js'
3
5
  import * as Errors from './Errors.js'
4
6
  import * as Hash from './Hash.js'
5
7
  import * as Hex from './Hex.js'
@@ -128,21 +130,69 @@ export declare namespace createCredential {
128
130
  * // @log: "0xa379a6f6eeafb9a55e378c118034e2751e682fab9f2d30ab13d2125586ce194705000001a4"
129
131
  * ```
130
132
  *
133
+ * @example
134
+ * ### With Attested Credential Data
135
+ *
136
+ * Include a credential ID and public key in the authenticator data (for registration responses):
137
+ *
138
+ * ```ts twoslash
139
+ * import { P256, WebAuthnP256 } from 'ox'
140
+ *
141
+ * const { publicKey } = P256.createKeyPair()
142
+ *
143
+ * const authenticatorData = WebAuthnP256.getAuthenticatorData({
144
+ * rpId: 'example.com',
145
+ * flag: 0x41, // UP + AT
146
+ * credential: {
147
+ * id: new Uint8Array(32),
148
+ * publicKey,
149
+ * },
150
+ * })
151
+ * ```
152
+ *
131
153
  * @param options - Options to construct the authenticator data.
132
154
  * @returns The authenticator data.
133
155
  */
134
156
  export function getAuthenticatorData(
135
157
  options: getAuthenticatorData.Options = {},
136
158
  ): Hex.Hex {
137
- const { flag = 5, rpId = window.location.hostname, signCount = 0 } = options
159
+ const {
160
+ credential,
161
+ flag = 5,
162
+ rpId = window.location.hostname,
163
+ signCount = 0,
164
+ } = options
138
165
  const rpIdHash = Hash.sha256(Hex.fromString(rpId))
139
166
  const flag_bytes = Hex.fromNumber(flag, { size: 1 })
140
167
  const signCount_bytes = Hex.fromNumber(signCount, { size: 4 })
141
- return Hex.concat(rpIdHash, flag_bytes, signCount_bytes)
168
+ const base = Hex.concat(rpIdHash, flag_bytes, signCount_bytes)
169
+
170
+ if (!credential) return base
171
+
172
+ // AAGUID (16 bytes of zeros)
173
+ const aaguid = Hex.fromBytes(new Uint8Array(16))
174
+
175
+ // Credential ID
176
+ const credentialId = Hex.fromBytes(credential.id)
177
+ const credIdLen = Hex.fromNumber(credential.id.length, { size: 2 })
178
+
179
+ // COSE public key
180
+ const coseKey = CoseKey.fromPublicKey(credential.publicKey)
181
+
182
+ return Hex.concat(base, aaguid, credIdLen, credentialId, coseKey)
142
183
  }
143
184
 
144
185
  export declare namespace getAuthenticatorData {
145
186
  type Options = {
187
+ /** Attested credential data to include (credential ID + public key). When set, the AT flag (0x40) should also be set. */
188
+ credential?:
189
+ | {
190
+ /** The credential ID as raw bytes. */
191
+ id: Uint8Array
192
+ /** The P256 public key associated with the credential. */
193
+ publicKey: PublicKey.PublicKey
194
+ }
195
+ | undefined
146
196
  /** A bitfield that indicates various attributes that were asserted by the authenticator. [Read more](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API/Authenticator_data#flags) */
147
197
  flag?: number | undefined
148
198
  /** The [Relying Party ID](https://w3c.github.io/webauthn/#relying-party-identifier) that the credential is scoped to. */
@@ -187,10 +237,11 @@ export function getClientDataJSON(options: getClientDataJSON.Options): string {
187
237
  crossOrigin = false,
188
238
  extraClientData,
189
239
  origin = window.location.origin,
240
+ type = 'webauthn.get',
190
241
  } = options
191
242
 
192
243
  return JSON.stringify({
193
- type: 'webauthn.get',
244
+ type,
194
245
  challenge: Base64.fromHex(challenge, { url: true, pad: false }),
195
246
  origin,
196
247
  crossOrigin,
@@ -208,11 +259,66 @@ export declare namespace getClientDataJSON {
208
259
  extraClientData?: Record<string, unknown> | undefined
209
260
  /** The fully qualified origin of the relying party which has been given by the client/browser to the authenticator. */
210
261
  origin?: string | undefined
262
+ /** The WebAuthn ceremony type. @default 'webauthn.get' */
263
+ type?: 'webauthn.create' | 'webauthn.get' | undefined
211
264
  }
212
265
 
213
266
  type ErrorType = Errors.GlobalErrorType
214
267
  }
215
268
 
269
+ /**
270
+ * Constructs a CBOR-encoded attestation object for testing WebAuthn registration
271
+ * verification. Combines the authenticator data with an attestation statement.
272
+ *
273
+ * :::warning
274
+ *
275
+ * This function is mainly for testing purposes. In production, the attestation
276
+ * object is returned by the authenticator during `navigator.credentials.create()`.
277
+ *
278
+ * :::
279
+ *
280
+ * @example
281
+ * ```ts twoslash
282
+ * import { P256, WebAuthnP256 } from 'ox'
283
+ *
284
+ * const { publicKey } = P256.createKeyPair()
285
+ *
286
+ * const attestationObject = WebAuthnP256.getAttestationObject({
287
+ * authData: WebAuthnP256.getAuthenticatorData({
288
+ * rpId: 'example.com',
289
+ * flag: 0x41,
290
+ * credential: { id: new Uint8Array(32), publicKey },
291
+ * }),
292
+ * })
293
+ * ```
294
+ *
295
+ * @param options - Options to construct the attestation object.
296
+ * @returns The CBOR-encoded attestation object as a Hex string.
297
+ */
298
+ export function getAttestationObject(
299
+ options: getAttestationObject.Options,
300
+ ): Hex.Hex {
301
+ const { attStmt = {}, authData, fmt = 'none' } = options
302
+ return Cbor.encode({
303
+ fmt,
304
+ attStmt,
305
+ authData: Hex.toBytes(authData),
306
+ })
307
+ }
308
+
309
+ export declare namespace getAttestationObject {
310
+ type Options = {
311
+ /** Attestation statement. */
312
+ attStmt?: Record<string, unknown> | undefined
313
+ /** Authenticator data as a Hex string (from {@link ox#WebAuthnP256.(getAuthenticatorData:function)}). */
314
+ authData: Hex.Hex
315
+ /** Attestation format. @default 'none' */
316
+ fmt?: string | undefined
317
+ }
318
+
319
+ type ErrorType = Cbor.encode.ErrorType | Errors.GlobalErrorType
320
+ }
321
+
216
322
  /**
217
323
  * Returns the creation options for a P256 WebAuthn Credential to be used with
218
324
  * the Web Authentication API.
@@ -716,7 +822,10 @@ export function verify(options: verify.Options): boolean {
716
822
  // Check that response is for an authentication assertion (if typeIndex is provided)
717
823
  if (typeIndex !== undefined) {
718
824
  const type = '"type":"webauthn.get"'
719
- if (type !== clientDataJSON.slice(Number(typeIndex), type.length + 1))
825
+ if (
826
+ type !==
827
+ clientDataJSON.slice(Number(typeIndex), Number(typeIndex) + type.length)
828
+ )
720
829
  return false
721
830
  }
722
831
 
package/index.ts CHANGED
@@ -1330,7 +1330,6 @@ export * as Caches from './core/Caches.js'
1330
1330
  * @category Data
1331
1331
  */
1332
1332
  export * as Cbor from './core/Cbor.js'
1333
-
1334
1333
  /**
1335
1334
  * Utility functions for computing Contract Addresses.
1336
1335
  *
@@ -1368,6 +1367,38 @@ export * as Cbor from './core/Cbor.js'
1368
1367
  * @category Addresses
1369
1368
  */
1370
1369
  export * as ContractAddress from './core/ContractAddress.js'
1370
+ /**
1371
+ * Utility functions for converting between COSE_Key and P256 public keys.
1372
+ *
1373
+ * COSE_Key is the key format used in WebAuthn attestation objects, as defined in
1374
+ * [RFC 9053](https://datatracker.ietf.org/doc/html/rfc9053).
1375
+ *
1376
+ * @example
1377
+ * ### Encoding a Public Key to COSE_Key
1378
+ *
1379
+ * ```ts twoslash
1380
+ * import { CoseKey, P256 } from 'ox'
1381
+ *
1382
+ * const { publicKey } = P256.createKeyPair()
1383
+ *
1384
+ * const coseKey = CoseKey.fromPublicKey(publicKey)
1385
+ * ```
1386
+ *
1387
+ * @example
1388
+ * ### Decoding a COSE_Key to Public Key
1389
+ *
1390
+ * ```ts twoslash
1391
+ * import { CoseKey, P256 } from 'ox'
1392
+ *
1393
+ * const { publicKey } = P256.createKeyPair()
1394
+ * const coseKey = CoseKey.fromPublicKey(publicKey)
1395
+ *
1396
+ * const publicKey2 = CoseKey.toPublicKey(coseKey)
1397
+ * ```
1398
+ *
1399
+ * @category Crypto
1400
+ */
1401
+ export * as CoseKey from './core/CoseKey.js'
1371
1402
 
1372
1403
  /**
1373
1404
  * Utilities for working with Ed25519 signatures and key pairs.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ox",
3
3
  "description": "Ethereum Standard Library",
4
- "version": "0.12.2",
4
+ "version": "0.12.3",
5
5
  "main": "./_cjs/index.js",
6
6
  "module": "./_esm/index.js",
7
7
  "types": "./_types/index.d.ts",
@@ -178,6 +178,11 @@
178
178
  "import": "./_esm/core/ContractAddress.js",
179
179
  "default": "./_cjs/core/ContractAddress.js"
180
180
  },
181
+ "./CoseKey": {
182
+ "types": "./_types/core/CoseKey.d.ts",
183
+ "import": "./_esm/core/CoseKey.js",
184
+ "default": "./_cjs/core/CoseKey.js"
185
+ },
181
186
  "./Ed25519": {
182
187
  "types": "./_types/core/Ed25519.d.ts",
183
188
  "import": "./_esm/core/Ed25519.js",
package/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** @internal */
2
- export const version = '0.12.2'
2
+ export const version = '0.12.3'