UNPKG

ox

Version:

Ethereum Standard Library

171 lines 5.57 kB
import * as Bytes from './Bytes.js'; import * as Cbor from './Cbor.js'; import * as Errors from './Errors.js'; import * as Cursor from './internal/cursor.js'; import * as PublicKey from './PublicKey.js'; /** * Converts a P256 {@link ox#PublicKey.PublicKey} to a CBOR-encoded COSE_Key. * * The COSE_Key uses integer map keys per [RFC 9053](https://datatracker.ietf.org/doc/html/rfc9053): * - `1` (kty): `2` (EC2) * - `3` (alg): `-7` (ES256) * - `-1` (crv): `1` (P-256) * - `-2` (x): x coordinate bytes * - `-3` (y): y coordinate bytes * * @example * ```ts twoslash * import { CoseKey, P256 } from 'ox' * * const { publicKey } = P256.createKeyPair() * * const coseKey = CoseKey.fromPublicKey(publicKey) * ``` * * @param publicKey - The P256 public key to convert. * @returns The CBOR-encoded COSE_Key as a Hex string. */ export function fromPublicKey(publicKey) { const pkBytes = PublicKey.toBytes(publicKey); const x = pkBytes.slice(1, 33); const y = pkBytes.slice(33, 65); return Cbor.encode(new Map([ [1, 2], // kty: EC2 [3, -7], // alg: ES256 [-1, 1], // crv: P-256 [-2, x], // x coordinate [-3, y], // y coordinate ])); } /** * Converts a CBOR-encoded COSE_Key to a P256 {@link ox#PublicKey.PublicKey}. * * Accepts the COSE key as either a hex string or raw bytes. When the * `returnByteLength` or `returnDecoded` option is set, the function returns an * object containing the public key plus the consumed byte length and/or the * decoded CBOR map. This is useful for parsing CBOR streams (such as WebAuthn * `authenticatorData`) where the COSE key is followed by trailing data. * * @example * ```ts twoslash * import { CoseKey, P256 } from 'ox' * * const { publicKey } = P256.createKeyPair() * const coseKey = CoseKey.fromPublicKey(publicKey) * * const publicKey2 = CoseKey.toPublicKey(coseKey) * ``` * * @example * ### With Byte Length * * ```ts twoslash * import { CoseKey, P256 } from 'ox' * * const { publicKey } = P256.createKeyPair() * const coseKey = CoseKey.fromPublicKey(publicKey) * * const { publicKey: pk, byteLength } = CoseKey.toPublicKey( * coseKey, * { * returnByteLength: true * } * ) * ``` * * @param coseKey - The CBOR-encoded COSE_Key as hex or bytes. * @param options - Decoding options. * @returns The P256 public key, optionally with byte length and decoded CBOR. */ export function toPublicKey(coseKey, options = {}) { const bytes = typeof coseKey === 'string' ? Bytes.fromHex(coseKey) : coseKey; const cursor = Cursor.create(bytes); const decoded = Cbor.decode(bytes); // After Cbor.decode, the cursor's position would advance, but Cbor.decode // creates its own cursor internally. Walk the bytes here to compute consumed // byte length without re-encoding (which loses optional COSE members). cursor.position = 0; cborSkip(cursor); const byteLength = cursor.position; // Validate COSE_Key header per RFC 9053 (kty=2 EC2, alg=-7 ES256, crv=1 P-256). if (decoded['1'] !== 2 || decoded['3'] !== -7 || decoded['-1'] !== 1) throw new InvalidCoseKeyError(); const x = decoded['-2']; const y = decoded['-3']; if (!(x instanceof Uint8Array) || x.length !== 32 || !(y instanceof Uint8Array) || y.length !== 32) throw new InvalidCoseKeyError(); const raw = new Uint8Array(65); raw[0] = 0x04; raw.set(x, 1); raw.set(y, 33); const publicKey = PublicKey.from(raw); if (!options.returnByteLength && !options.returnDecoded) return publicKey; const result = { publicKey }; if (options.returnByteLength) result.byteLength = byteLength; if (options.returnDecoded) result.decoded = decoded; return result; } /** * Walks one CBOR element on the cursor, advancing `cursor.position` past it. * Supports the major types used by COSE keys: unsigned int (0), negative int * (1), byte string (2), text string (3), array (4), map (5). * * @internal */ function cborSkip(cursor) { const initialByte = cursor.readUint8(); const majorType = initialByte >> 5; const additionalInfo = initialByte & 0b00011111; const length = cborReadLength(cursor, additionalInfo); switch (majorType) { case 0: case 1: // value already consumed by `cborReadLength`. return; case 2: case 3: // skip `length` bytes of byte/text string content cursor.readBytes(length); return; case 4: for (let i = 0; i < length; i++) cborSkip(cursor); return; case 5: for (let i = 0; i < length; i++) { cborSkip(cursor); cborSkip(cursor); } return; case 7: return; default: throw new InvalidCoseKeyError(); } } /** @internal */ function cborReadLength(cursor, ai) { if (ai < 24) return ai; if (ai === 24) return cursor.readUint8(); if (ai === 25) return cursor.readUint16(); if (ai === 26) return cursor.readUint32(); throw new InvalidCoseKeyError(); } /** Thrown when a COSE_Key does not contain valid P256 public key coordinates. */ export class InvalidCoseKeyError extends Errors.BaseError { name = 'CoseKey.InvalidCoseKeyError'; constructor() { super('COSE_Key does not contain valid P256 public key coordinates.'); } } //# sourceMappingURL=CoseKey.js.map