UNPKG

ox

Version:

Ethereum Standard Library

770 lines (711 loc) 25.4 kB
import { Bytes, Cbor, Hash, Hex, PublicKey } from 'ox' import { describe, expect, test } from 'vp/test' describe('encode', () => { test.each([ ['0', [0], 0], ['1', [1], 1], ['10', [10], 10], ['23', [23], 23], ['24', [24, 24], 24], ['25', [24, 25], 25], ['100', [24, 100], 100], ['1000', [25, 3, 232], 1000], ['1000000', [26, 0, 15, 66, 64], 1000000], ['-1', [32], -1], ['-10', [41], -10], ['-24', [55], -24], ['-25', [56, 24], -25], ['-26', [56, 25], -26], ['-100', [56, 99], -100], ['-1000', [57, 3, 231], -1000], ['-1000000', [58, 0, 15, 66, 63], -1000000], ["''", [96], ''], ["'a'", [97, 97], 'a'], ["'IETF'", [100, 73, 69, 84, 70], 'IETF'], [`'"\\'`, [98, 34, 92], `"\\`], ["'\u00fc'", [98, 195, 188], '\u00fc'], ["'\u6c34'", [99, 230, 176, 180], '\u6c34'], ["'\ud800\udd51'", [100, 240, 144, 133, 145], '\ud800\udd51'], ['[]', [128], []], [ "['a', {'b': 'c'}]", [130, 97, 97, 161, 97, 98, 97, 99], ['a', { b: 'c' }], ], ['[1,2,3]', [131, 1, 2, 3], [1, 2, 3]], [ '[1, [2, 3], [4, 5]]', [131, 1, 130, 2, 3, 130, 4, 5], [1, [2, 3], [4, 5]], ], [ '[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]', [ 152, 25, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 24, 24, 25, ], [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, ], ], ['{}', [160], {}], [ "{'a': 1, 'b': [2, 3]}", [162, 97, 97, 1, 97, 98, 130, 2, 3], { a: 1, b: [2, 3] }, ], [ '25-length object', [ 184, 26, 97, 48, 97, 97, 97, 49, 97, 97, 97, 50, 97, 97, 97, 51, 97, 97, 97, 52, 97, 97, 97, 53, 97, 97, 97, 54, 97, 97, 97, 55, 97, 97, 97, 56, 97, 97, 97, 57, 97, 97, 98, 49, 48, 97, 97, 98, 49, 49, 97, 97, 98, 49, 50, 97, 97, 98, 49, 51, 97, 97, 98, 49, 52, 97, 97, 98, 49, 53, 97, 97, 98, 49, 54, 97, 97, 98, 49, 55, 97, 97, 98, 49, 56, 97, 97, 98, 49, 57, 97, 97, 98, 50, 48, 97, 97, 98, 50, 49, 97, 97, 98, 50, 50, 97, 97, 98, 50, 51, 97, 97, 98, 50, 52, 97, 97, 98, 50, 53, 97, 97, ], { '0': 'a', '1': 'a', '2': 'a', '3': 'a', '4': 'a', '5': 'a', '6': 'a', '7': 'a', '8': 'a', '9': 'a', '10': 'a', '11': 'a', '12': 'a', '13': 'a', '14': 'a', '15': 'a', '16': 'a', '17': 'a', '18': 'a', '19': 'a', '20': 'a', '21': 'a', '22': 'a', '23': 'a', '24': 'a', '25': 'a', }, ], [ "{'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D', 'e': 'E'}", [ 165, 97, 97, 97, 65, 97, 98, 97, 66, 97, 99, 97, 67, 97, 100, 97, 68, 97, 101, 97, 69, ], { a: 'A', b: 'B', c: 'C', d: 'D', e: 'E' }, ], [ "{ 'Fun': true, 'Amt': -2}", [162, 99, 70, 117, 110, 245, 99, 65, 109, 116, 33], { Fun: true, Amt: -2 }, ], ['false', [244], false], ['true', [245], true], ['null', [246], null], ['undefined', [247], undefined], ['+Infinity', [250, 127, 128, 0, 0], Infinity], ['NaN', [250, 127, 192, 0, 0], NaN], ['-Infinity', [250, 255, 128, 0, 0], -Infinity], ['0.5', [250, 63, 0, 0, 0], 0.5], ['9007199254740994', [251, 67, 64, 0, 0, 0, 0, 0, 1], 9007199254740994], ['1.0e+300', [251, 126, 55, 228, 60, 136, 0, 117, 156], 1e300], ['-9007199254740994', [251, 195, 64, 0, 0, 0, 0, 0, 1], -9007199254740994], ["h''", [0x40], new Uint8Array()], ["h'01020304'", [68, 1, 2, 3, 4], new Uint8Array([1, 2, 3, 4])], ])('encode: %s', (_, output, input) => { expect(Cbor.encode(input)).toStrictEqual(Hex.from(output)) expect(Cbor.encode(input, { as: 'Bytes' })).toStrictEqual( Bytes.from(output), ) }) test('large string (> 0xffff)', () => { const str = 'a'.repeat(0x10000) const encoded = Cbor.encode(str) const decoded = Cbor.decode(encoded) expect(decoded).toBe(str) }) test('large array (> 0xff)', () => { const arr = Array.from({ length: 256 }, (_, i) => i) const encoded = Cbor.encode(arr) const decoded = Cbor.decode(encoded) expect(decoded).toStrictEqual(arr) }) test('large array (> 0xffff)', () => { const arr = Array.from({ length: 0x10000 }, (_, i) => i % 256) const encoded = Cbor.encode(arr) const decoded = Cbor.decode(encoded) expect(decoded).toStrictEqual(arr) }) test('large object (> 0xff)', () => { const obj: Record<string, number> = {} for (let i = 0; i < 256; i++) { obj[`key${i}`] = i } const encoded = Cbor.encode(obj) const decoded = Cbor.decode(encoded) expect(decoded).toStrictEqual(obj) }) test('large byte string (> 0xff)', () => { const bytes = new Uint8Array(256).fill(42) const encoded = Cbor.encode(bytes) const decoded = Cbor.decode(encoded) expect(decoded).toStrictEqual(bytes) }) test('large byte string (> 0xffff)', () => { const bytes = new Uint8Array(0x10000).fill(42) const encoded = Cbor.encode(bytes) const decoded = Cbor.decode(encoded) expect(decoded).toStrictEqual(bytes) }) test('ArrayBuffer encoding', () => { const buffer = new ArrayBuffer(4) const view = new Uint8Array(buffer) view.set([1, 2, 3, 4]) const encoded = Cbor.encode(buffer) const decoded = Cbor.decode(encoded) expect(decoded).toStrictEqual(new Uint8Array([1, 2, 3, 4])) }) test('Uint16Array encoding', () => { const arr = new Uint16Array([1, 2, 3, 4]) const encoded = Cbor.encode(arr) const decoded = Cbor.decode(encoded) expect(decoded).toStrictEqual(new Uint8Array([1, 0, 2, 0, 3, 0, 4, 0])) }) test('Int8Array encoding', () => { const arr = new Int8Array([-1, -2, 3, 4]) const encoded = Cbor.encode(arr) const decoded = Cbor.decode(encoded) expect(decoded).toStrictEqual(new Uint8Array([255, 254, 3, 4])) }) test('Float32Array encoding', () => { const arr = new Float32Array([1.5, 2.5]) const encoded = Cbor.encode(arr) const decoded = Cbor.decode(encoded) as Uint8Array expect(decoded).toBeInstanceOf(Uint8Array) expect(decoded.length).toBe(8) }) test('DataView encoding', () => { const buffer = new ArrayBuffer(2) const view = new DataView(buffer) view.setUint8(0, 1) view.setUint8(1, 2) const encoded = Cbor.encode(view) const decoded = Cbor.decode(encoded) expect(decoded).toStrictEqual(new Uint8Array([1, 2])) }) test('number too large throws', () => { const num = 0xfffffffffffff expect(() => Cbor.encode(num)).toThrow(Cbor.NumberTooLargeError) }) test('negative number too large throws', () => { const num = -0xfffffffffffff expect(() => Cbor.encode(num)).toThrow(Cbor.NumberTooLargeError) }) test('bigint throws', () => { expect(() => Cbor.encode(BigInt(42))).toThrow(Cbor.UnsupportedBigIntError) }) test('unsupported type throws', () => { const symbol = Symbol('test') expect(() => Cbor.encode(symbol)).toThrow(Cbor.UnexpectedTokenError) }) test('float64 encoding when value !== float32', () => { // A value that can't be represented exactly as float32 const value = 1e100 const encoded = Cbor.encode(value) const decoded = Cbor.decode(encoded) expect(decoded).toBe(value) }) test('Map with integer keys', () => { const map = new Map<number, unknown>([ [1, 2], [3, -7], ]) const encoded = Cbor.encode(map) // keys should be CBOR integers, not text strings // {1: 2, 3: -7} = a2 01 02 03 26 expect(encoded).toBe(Hex.from([0xa2, 0x01, 0x02, 0x03, 0x26])) }) test('Map with negative integer keys', () => { const map = new Map<number, unknown>([ [-1, 1], [-2, new Uint8Array([0xaa, 0xbb])], ]) const encoded = Cbor.encode(map) // -1 = 0x20, -2 = 0x21 const decoded = Cbor.decode<Record<string, unknown>>(encoded) expect(decoded).toStrictEqual({ '-1': 1, '-2': new Uint8Array([0xaa, 0xbb]), }) }) test('Map with mixed key types', () => { const map = new Map<number | string, unknown>([ [1, 'hello'], ['foo', 42], ]) const encoded = Cbor.encode(map) const decoded = Cbor.decode<Record<string, unknown>>(encoded) expect(decoded).toStrictEqual({ '1': 'hello', foo: 42 }) }) test('Map roundtrip for COSE_Key structure', () => { const x = new Uint8Array(32).fill(0xaa) const y = new Uint8Array(32).fill(0xbb) const coseKey = new Map<number, unknown>([ [1, 2], [3, -7], [-1, 1], [-2, x], [-3, y], ]) const encoded = Cbor.encode(coseKey) const decoded = Cbor.decode<Record<string, unknown>>(encoded) expect(decoded['1']).toBe(2) expect(decoded['3']).toBe(-7) expect(decoded['-1']).toBe(1) expect(decoded['-2']).toStrictEqual(x) expect(decoded['-3']).toStrictEqual(y) }) }) describe('decode', () => { test.each([ ['0', [0], 0], ['1', [1], 1], ['1', [1], 1], ['10', [10], 10], ['23', [23], 23], ['24', [24, 24], 24], ['25', [24, 25], 25], ['100', [24, 100], 100], ['1000', [25, 3, 232], 1000], ['1000000', [26, 0, 15, 66, 64], 1000000], ['-1', [32], -1], ['-10', [41], -10], ['-24', [55], -24], ['-25', [56, 24], -25], ['-26', [56, 25], -26], ['-100', [56, 99], -100], ['-1000', [57, 3, 231], -1000], ['-1000000', [58, 0, 15, 66, 63], -1000000], ["''", [96], ''], ["'a'", [97, 97], 'a'], ["'IETF'", [100, 73, 69, 84, 70], 'IETF'], [`'"\\'`, [98, 34, 92], `"\\`], ["'\u00fc'", [98, 195, 188], '\u00fc'], ["'\u6c34'", [99, 230, 176, 180], '\u6c34'], ["'\ud800\udd51'", [100, 240, 144, 133, 145], '\ud800\udd51'], [ "'strea'+'ming'", [127, 101, 115, 116, 114, 101, 97, 100, 109, 105, 110, 103, 255], 'streaming', ], [ '255-length string', [ 120, 255, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, 97, 98, 99, 100, 101, ], 'abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde', ], ['[]', [128], []], [ "['a', {'b': 'c'}]", [130, 97, 97, 161, 97, 98, 97, 99], ['a', { b: 'c' }], ], [ "['a, {_ 'b': 'c'}]", [130, 97, 97, 191, 97, 98, 97, 99, 255], ['a', { b: 'c' }], ], ['[1,2,3]', [131, 1, 2, 3], [1, 2, 3]], [ '[1, [2, 3], [4, 5]]', [131, 1, 130, 2, 3, 130, 4, 5], [1, [2, 3], [4, 5]], ], [ '[1, [2, 3], [_ 4, 5]]', [131, 1, 130, 2, 3, 159, 4, 5, 255], [1, [2, 3], [4, 5]], ], [ '[1, [_ 2, 3], [4, 5]]', [131, 1, 159, 2, 3, 255, 130, 4, 5], [1, [2, 3], [4, 5]], ], [ '[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]', [ 152, 25, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 24, 24, 25, ], [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, ], ], [ '[_ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]', [ 159, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 24, 24, 25, 255, ], [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, ], ], [ '[_ 1, [2, 3], [4, 5]]', [159, 1, 130, 2, 3, 130, 4, 5, 255], [1, [2, 3], [4, 5]], ], [ '[_ 1, [2, 3], [_ 4, 5]]', [159, 1, 130, 2, 3, 159, 4, 5, 255, 255], [1, [2, 3], [4, 5]], ], ['[_ ]', [159, 255], []], ['{}', [160], {}], [ "{'a': 1, 'b': [2, 3]}", [162, 97, 97, 1, 97, 98, 130, 2, 3], { a: 1, b: [2, 3] }, ], [ '25-length object', [ 184, 26, 97, 48, 97, 97, 97, 49, 97, 97, 97, 50, 97, 97, 97, 51, 97, 97, 97, 52, 97, 97, 97, 53, 97, 97, 97, 54, 97, 97, 97, 55, 97, 97, 97, 56, 97, 97, 97, 57, 97, 97, 98, 49, 48, 97, 97, 98, 49, 49, 97, 97, 98, 49, 50, 97, 97, 98, 49, 51, 97, 97, 98, 49, 52, 97, 97, 98, 49, 53, 97, 97, 98, 49, 54, 97, 97, 98, 49, 55, 97, 97, 98, 49, 56, 97, 97, 98, 49, 57, 97, 97, 98, 50, 48, 97, 97, 98, 50, 49, 97, 97, 98, 50, 50, 97, 97, 98, 50, 51, 97, 97, 98, 50, 52, 97, 97, 98, 50, 53, 97, 97, ], { '0': 'a', '1': 'a', '2': 'a', '3': 'a', '4': 'a', '5': 'a', '6': 'a', '7': 'a', '8': 'a', '9': 'a', '10': 'a', '11': 'a', '12': 'a', '13': 'a', '14': 'a', '15': 'a', '16': 'a', '17': 'a', '18': 'a', '19': 'a', '20': 'a', '21': 'a', '22': 'a', '23': 'a', '24': 'a', '25': 'a', }, ], [ "{'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D', 'e': 'E'}", [ 165, 97, 97, 97, 65, 97, 98, 97, 66, 97, 99, 97, 67, 97, 100, 97, 68, 97, 101, 97, 69, ], { a: 'A', b: 'B', c: 'C', d: 'D', e: 'E' }, ], [ "{_ 'a': 1, 'b': [_ 2, 3]}", [191, 97, 97, 1, 97, 98, 159, 2, 3, 255, 255], { a: 1, b: [2, 3] }, ], [ "{_ 'Fun': true, 'Amt': -2}", [191, 99, 70, 117, 110, 245, 99, 65, 109, 116, 33, 255], { Fun: true, Amt: -2 }, ], ['false', [244], false], ['true', [245], true], ['null', [246], null], ['undefined', [247], undefined], ['Simple value 255', [248, 255], undefined], ['+Infinity', [250, 127, 128, 0, 0], Infinity], ['NaN', [250, 127, 192, 0, 0], NaN], ['-Infinity', [250, 255, 128, 0, 0], -Infinity], ['0.5', [250, 63, 0, 0, 0], 0.5], ['9007199254740994', [251, 67, 64, 0, 0, 0, 0, 0, 1], 9007199254740994], ['1.0e+300', [251, 126, 55, 228, 60, 136, 0, 117, 156], 1e300], ['-9007199254740994', [251, 195, 64, 0, 0, 0, 0, 0, 1], -9007199254740994], ["h''", [0x40], new Uint8Array()], ["h'01020304'", [68, 1, 2, 3, 4], new Uint8Array([1, 2, 3, 4])], [ "(_ h'0102', h'030405')", [95, 66, 1, 2, 67, 3, 4, 5, 255], new Uint8Array([1, 2, 3, 4, 5]), ], ])('decode: %s', (_, input, output) => { const decoded = Cbor.decode(Hex.from(input)) expect(decoded).toStrictEqual(output) }) test('from Bytes', () => { const bytes = Bytes.from([0x18, 0x2a]) const decoded = Cbor.decode(bytes) expect(decoded).toBe(42) }) test('invalid hex length throws', () => { expect(() => Cbor.decode('0x123')).toThrow() }) test('large string (> 0xffff)', () => { const str = 'a'.repeat(0x10000) const encoded = Cbor.encode(str) const decoded = Cbor.decode(encoded) expect(decoded).toBe(str) }) test('large array (> 0xffff)', () => { const arr = Array.from({ length: 0x10000 }, (_, i) => i % 256) const encoded = Cbor.encode(arr) const decoded = Cbor.decode(encoded) expect(decoded).toStrictEqual(arr) }) test('large byte string (> 0xffff)', () => { const bytes = new Uint8Array(0x10000).fill(42) const encoded = Cbor.encode(bytes) const decoded = Cbor.decode(encoded) expect(decoded).toStrictEqual(bytes) }) test('float16: zero', () => { // Float16 zero: 0x0000 const encoded = Hex.from([0xf9, 0x00, 0x00]) const decoded = Cbor.decode(encoded) expect(decoded).toBe(0) }) test('float16: negative zero', () => { // Float16 negative zero: 0x8000 const encoded = Hex.from([0xf9, 0x80, 0x00]) const decoded = Cbor.decode(encoded) expect(Object.is(decoded, -0)).toBe(true) }) test('float16: infinity', () => { // Float16 infinity: 0x7c00 const encoded = Hex.from([0xf9, 0x7c, 0x00]) const decoded = Cbor.decode(encoded) expect(decoded).toBe(Infinity) }) test('float16: negative infinity', () => { // Float16 negative infinity: 0xfc00 const encoded = Hex.from([0xf9, 0xfc, 0x00]) const decoded = Cbor.decode(encoded) expect(decoded).toBe(-Infinity) }) test('float16: NaN', () => { // Float16 NaN: 0x7e00 const encoded = Hex.from([0xf9, 0x7e, 0x00]) const decoded = Cbor.decode(encoded) expect(Number.isNaN(decoded)).toBe(true) }) test('float16: subnormal', () => { // Float16 smallest subnormal: 0x0001 const encoded = Hex.from([0xf9, 0x00, 0x01]) const decoded = Cbor.decode(encoded) // Smallest positive subnormal float16 value expect(decoded).toBeGreaterThan(0) expect(decoded).toBeLessThan(1e-7) }) test('float16: normal number', () => { // Float16 1.0: 0x3c00 const encoded = Hex.from([0xf9, 0x3c, 0x00]) const decoded = Cbor.decode(encoded) expect(decoded).toBe(1) }) test('invalid major type throws', () => { // Actually, let's create a truly invalid major type const invalid = new Uint8Array([0xe0]) // 11100000 = major type 7, but invalid additional info expect(() => Cbor.decode(invalid)).toThrow() }) test('unsupported 64-bit integer throws', () => { // Additional info 27 = 64-bit integer const encoded = Hex.from([ 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]) expect(() => Cbor.decode(encoded)).toThrow( Cbor.Unsupported64BitIntegerError, ) }) test('unsupported tag throws', () => { // Major type 6 = tag const encoded = Hex.from([0xc0, 0x01]) expect(() => Cbor.decode(encoded)).toThrow(Cbor.UnsupportedTagError) }) test('invalid additional info throws', () => { // Additional info 28-30 are invalid const encoded = Hex.from([0x1c]) // 00011100 = major type 0, additional info 28 expect(() => Cbor.decode(encoded)).toThrow(Cbor.InvalidAdditionalInfoError) }) test('map with integer key converts to string', () => { // Map with integer key (converted to string for COSE_Key compatibility) const encoded = Hex.from([0xa1, 0x01, 0x02]) // {1: 2} const decoded = Cbor.decode<Record<string, number>>(encoded) expect(decoded).toEqual({ '1': 2 }) }) test('invalid simple value throws', () => { // Simple value < 32 (reserved) const encoded = Hex.from([0xf8, 0x10]) // Simple value 16 expect(() => Cbor.decode(encoded)).toThrow(Cbor.InvalidSimpleValueError) }) test('invalid indefinite length byte string chunk throws', () => { // Indefinite-length byte string with non-byte-string chunk const encoded = Hex.from([0x5f, 0x61, 0x61, 0xff]) // (_ 'a') expect(() => Cbor.decode(encoded)).toThrow( Cbor.InvalidIndefiniteLengthChunkError, ) }) test('invalid indefinite length text string chunk throws', () => { // Indefinite-length text string with non-text-string chunk const encoded = Hex.from([0x7f, 0x40, 0xff]) // (_ h'') expect(() => Cbor.decode(encoded)).toThrow( Cbor.InvalidIndefiniteLengthChunkError, ) }) test('readLength: additional info < 24', () => { const encoded = Hex.from([0x15]) // 21 directly encoded const decoded = Cbor.decode(encoded) expect(decoded).toBe(21) }) test('readLength: additional info 24 (uint8)', () => { const encoded = Hex.from([0x18, 0xff]) // 255 const decoded = Cbor.decode(encoded) expect(decoded).toBe(255) }) test('readLength: additional info 25 (uint16)', () => { const encoded = Hex.from([0x19, 0x01, 0x00]) // 256 const decoded = Cbor.decode(encoded) expect(decoded).toBe(256) }) test('readLength: additional info 26 (uint32)', () => { const encoded = Hex.from([0x1a, 0x00, 0x01, 0x00, 0x00]) // 65536 const decoded = Cbor.decode(encoded) expect(decoded).toBe(65536) }) }) describe('roundtrip', () => { test.each([ ['number: 0', 0], ['number: 42', 42], ['number: 1000', 1000], ['number: -1', -1], ['number: -100', -100], ['float: 0.5', 0.5], ['float: Infinity', Infinity], ['float: -Infinity', -Infinity], ['string: empty', ''], ['string: hello', 'hello'], ['string: unicode', '\u6c34'], ['boolean: true', true], ['boolean: false', false], ['null', null], ['undefined', undefined], ['array: empty', []], ['array: numbers', [1, 2, 3]], ['array: nested', [1, [2, 3], [4, 5]]], ['object: empty', {}], ['object: simple', { a: 1, b: 2 }], ['object: nested', { a: 1, b: [2, 3] }], ['complex', { a: 'hello', b: [1, 2, { c: true }], d: null }], ])('roundtrip: %s', (_, value) => { const encoded = Cbor.encode(value) const decoded = Cbor.decode(encoded) if (typeof value === 'number' && Number.isNaN(value)) { expect(Number.isNaN(decoded)).toBe(true) } else { expect(decoded).toStrictEqual(value) } }) test('roundtrip: byte string', () => { const value = new Uint8Array([1, 2, 3, 4]) const encoded = Cbor.encode(value) const decoded = Cbor.decode(encoded) expect(decoded).toStrictEqual(value) }) }) describe('webauthn', () => { const rpId = 'localhost' const attestationObject = '0xa363666d74646e6f6e656761747453746d74a0686175746844617461589849960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97635d00000000fbfc3007154e4ecc8c0b6e020557d7bd0014f55a2aef110f9c55f4343dc5e73ab1d291ae72cca5010203262001215820eee72c4fc66e2670b0f606fcb54ce453b8ae09d0661303dd4ad8d4063be212872258202093aff8b065c247c742e6833937ba987fb0f871e61fcfc6763fd9144611210f' const publicKey = PublicKey.from({ x: '0xeee72c4fc66e2670b0f606fcb54ce453b8ae09d0661303dd4ad8d4063be21287', y: '0x2093aff8b065c247c742e6833937ba987fb0f871e61fcfc6763fd9144611210f', }) test('behavior: verify authData', () => { const decoded = Cbor.decode<{ fmt: string; authData: Bytes.Bytes }>( attestationObject, ) const authDataBytes = decoded.authData const authData = Bytes.toHex(authDataBytes) // Extract rpIdHash (bytes 0-31) const rpIdHash_expected = Hex.slice(authData, 0, 32) const rpIdHash_actual = Hash.sha256(Hex.fromString(rpId)) expect(rpIdHash_actual).toBe(rpIdHash_expected) // Extract flags (byte 32) const flags = authDataBytes[32]! expect(flags & 0x01).toBe(0x01) // UP bit should be set expect(flags & 0x40).toBe(0x40) // AT bit should be set (attested credential data present) // Extract signCount (bytes 33-36) const signCount = authDataBytes.slice(33, 37) expect(signCount).toStrictEqual(Bytes.from([0, 0, 0, 0])) // Extract attested credential data (when AT bit is set) // Structure: aaguid (16 bytes) + credentialIdLength (2 bytes) + credentialId + credentialPublicKey const aaguid = authDataBytes.slice(37, 53) expect(aaguid.length).toBe(16) const credentialIdLength = (authDataBytes[53]! << 8) | authDataBytes[54]! const credentialId = authDataBytes.slice(55, 55 + credentialIdLength) expect(credentialId.length).toBe(credentialIdLength) // Extract credentialPublicKey (starts after credentialId) // The credentialPublicKey is CBOR-encoded as a COSE_Key map const credentialPublicKeyStart = 55 + credentialIdLength const credentialPublicKeyCbor = authDataBytes.slice( credentialPublicKeyStart, ) const credentialPublicKey = Cbor.decode<{ 1: number // kty 3: number // alg '-1': number // crv '-2': Bytes.Bytes // x '-3': Bytes.Bytes // y }>(credentialPublicKeyCbor) expect(credentialPublicKey['3']).toBe(-7) expect(credentialPublicKey['-1']).toBe(1) const x = Hex.fromBytes(credentialPublicKey['-2'], { size: 32 }) const y = Hex.fromBytes(credentialPublicKey['-3'], { size: 32 }) expect(PublicKey.from({ x, y })).toStrictEqual(publicKey) }) })