UNPKG

micro-ordinals

Version:

Manage ordinals, inscriptions and runes using scure-btc-signer

268 lines 10.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CBOR = void 0; const btc_signer_1 = require("@scure/btc-signer"); const P = require("micro-packed"); // Binary JSON-like encoding: [RFC 7049](https://www.rfc-editor.org/rfc/rfc7049) // And partially [RFC 8949](https://www.rfc-editor.org/rfc/rfc8949.html): without tagged values. // Used for metadata encoding in ordinals and passkeys. Complex, but efficient encoding. const isNegZero = (x) => x === 0 && 1 / x < 0; // Float16Array is not available in JS as per Apr 2024. // For now, we implement it using RFC 8949 like technique, // while preserving Infinity and NaN. f32 rounding would be too slow. // https://github.com/tc39/proposal-float16array const F16BE = P.wrap({ encodeStream(w, value) { // We simple encode popular values as bytes if (value === Infinity) return w.bytes(new Uint8Array([0x7c, 0x00])); if (value === -Infinity) return w.bytes(new Uint8Array([0xfc, 0x00])); if (Number.isNaN(value)) return w.bytes(new Uint8Array([0x7e, 0x00])); if (isNegZero(value)) return w.bytes(new Uint8Array([0x80, 0x00])); throw w.err('f16: not implemented'); }, decodeStream: (r) => { // decode_half from RFC 8949 const half = P.U16BE.decodeStream(r); const exp = (half & 0x7c00) >> 10; const mant = half & 0x03ff; let val; if (exp === 0) val = 6.103515625e-5 * (mant / 1024); else if (exp !== 31) val = Math.pow(2, exp - 15) * (1 + mant / 1024); else val = mant ? NaN : Infinity; return half & 0x8000 ? -val : val; }, }); const INFO = P.bits(5); // additional info const U64LEN = P.apply(P.U64BE, P.coders.numberBigint); // Number/lengths limits const CBOR_LIMITS = { 24: [2 ** 8 - 1, P.U8, P.U8], 25: [2 ** 16 - 1, P.U16BE, P.U16BE], 26: [2 ** 32 - 1, P.U32BE, P.U32BE], 27: [2n ** 64n - 1n, P.U64BE, U64LEN], }; const cborUint = P.wrap({ encodeStream(w, value) { if (value < 24) return INFO.encodeStream(w, typeof value === 'bigint' ? Number(value) : value); for (const ai in CBOR_LIMITS) { const [limit, intCoder, _] = CBOR_LIMITS[ai]; if (value > limit) continue; INFO.encodeStream(w, Number(ai)); return intCoder.encodeStream(w, value); } throw w.err(`cbor/uint: wrong value=${value}`); }, decodeStream(r) { const ai = INFO.decodeStream(r); if (ai < 24) return ai; const intCoder = CBOR_LIMITS[ai][1]; if (!intCoder) throw r.err(`cbor/uint wrong additional information=${ai}`); return intCoder.decodeStream(r); }, }); const cborNegint = P.wrap({ encodeStream: (w, v) => cborUint.encodeStream(w, typeof v === 'bigint' ? -(v + 1n) : -(v + 1)), decodeStream(r) { const v = cborUint.decodeStream(r); return typeof v === 'bigint' ? -1n - v : -1 - v; }, }); const cborArrLength = (inner) => P.wrap({ encodeStream(w, value) { if (value.length < 24) { INFO.encodeStream(w, value.length); P.array(value.length, inner).encodeStream(w, value); return; } for (const ai in CBOR_LIMITS) { const [limit, _, lenCoder] = CBOR_LIMITS[ai]; if (value.length < limit) { INFO.encodeStream(w, Number(ai)); P.array(lenCoder, inner).encodeStream(w, value); return; } } throw w.err(`cbor/lengthArray: wrong value=${value}`); }, decodeStream(r) { const ai = INFO.decodeStream(r); if (ai < 24) return P.array(ai, inner).decodeStream(r); // array can have indefinite-length if (ai === 31) return P.array(new Uint8Array([0xff]), inner).decodeStream(r); const lenCoder = CBOR_LIMITS[ai][2]; if (!lenCoder) throw r.err(`cbor/lengthArray wrong length=${ai}`); return P.array(lenCoder, inner).decodeStream(r); }, }); // for strings/bytestrings const cborLength = (fn, // Indefinity-length strings accept other elements with different types, we validate that later def) => P.wrap({ encodeStream(w, value) { if (Array.isArray(value)) throw new Error('cbor/length: encoding indefinite-length strings not supported'); const bytes = fn(null).encode(value); if (bytes.length < 24) { INFO.encodeStream(w, bytes.length); w.bytes(bytes); return; } for (const ai in CBOR_LIMITS) { const [limit, _, lenCoder] = CBOR_LIMITS[ai]; if (bytes.length < limit) { INFO.encodeStream(w, Number(ai)); lenCoder.encodeStream(w, bytes.length); w.bytes(bytes); return; } } throw w.err(`cbor/lengthArray: wrong value=${value}`); }, decodeStream(r) { const ai = INFO.decodeStream(r); if (ai < 24) return fn(ai).decodeStream(r); if (ai === 31) return P.array(new Uint8Array([0xff]), def).decodeStream(r); const lenCoder = CBOR_LIMITS[ai][2]; if (!lenCoder) throw r.err(`cbor/length wrong length=${ai}`); return fn(lenCoder).decodeStream(r); }, }); const cborSimple = P.wrap({ encodeStream(w, value) { if (value === false) return INFO.encodeStream(w, 20); if (value === true) return INFO.encodeStream(w, 21); if (value === null) return INFO.encodeStream(w, 22); if (value === undefined) return INFO.encodeStream(w, 23); if (typeof value !== 'number') throw w.err(`cbor/simple: wrong value type=${typeof value}`); // Basic values encoded as f16 if (isNegZero(value) || Number.isNaN(value) || value === Infinity || value === -Infinity) { INFO.encodeStream(w, 25); return F16BE.encodeStream(w, value); } // If can be encoded as F32 without rounding if (Math.fround(value) === value) { INFO.encodeStream(w, 26); return P.F32BE.encodeStream(w, value); } INFO.encodeStream(w, 27); return P.F64BE.encodeStream(w, value); }, decodeStream(r) { const ai = INFO.decodeStream(r); if (ai === 20) return false; if (ai === 21) return true; if (ai === 22) return null; if (ai === 23) return undefined; // ai === 24 is P.U8 with simple, reserved if (ai === 25) return F16BE.decodeStream(r); if (ai === 26) return P.F32BE.decodeStream(r); if (ai === 27) return P.F64BE.decodeStream(r); throw r.err('cbor/simple: unassigned'); }, }); const cborValue = P.mappedTag(P.bits(3), { uint: [0, cborUint], // An unsigned integer in the range 0..264-1 inclusive. negint: [1, cborNegint], // A negative integer in the range -264..-1 inclusive bytes: [2, P.lazy(() => cborLength(P.bytes, cborValue))], // A byte string. string: [3, P.lazy(() => cborLength(P.string, cborValue))], // A text string (utf8) array: [4, cborArrLength(P.lazy(() => cborValue))], // An array of data items map: [5, P.lazy(() => cborArrLength(P.tuple([cborValue, cborValue])))], // A map of pairs of data items tag: [6, P.tuple([cborUint, P.lazy(() => cborValue)])], // A tagged data item ("tag") whose tag number simple: [7, cborSimple], // Floating-point numbers and simple values, as well as the "break" stop code }); exports.CBOR = P.apply(cborValue, { encode(from) { let value = from.data; if (from.TAG === 'bytes') { if (btc_signer_1.utils.isBytes(value)) return value; const chunks = []; if (!Array.isArray(value)) throw new Error(`CBOR: wrong indefinite-length bytestring=${value}`); for (const c of value) { if (c.TAG !== 'bytes' || !btc_signer_1.utils.isBytes(c.data)) throw new Error(`CBOR: wrong indefinite-length bytestring=${c}`); chunks.push(c.data); } return btc_signer_1.utils.concatBytes(...chunks); } if (from.TAG === 'string') { if (typeof value === 'string') return value; if (!Array.isArray(value)) throw new Error(`CBOR: wrong indefinite-length string=${value}`); let res = ''; for (const c of value) { if (c.TAG !== 'string' || typeof c.data !== 'string') throw new Error(`CBOR: wrong indefinite-length string=${c}`); res += c.data; } return res; } if (from.TAG === 'array' && Array.isArray(value)) value = value.map((i) => this.encode(i)); if (from.TAG === 'map' && typeof value === 'object' && value !== null) { return Object.fromEntries(from.data.map(([k, v]) => [this.encode(k), this.encode(v)])); } if (from.TAG === 'tag') throw new Error('not implemented'); return value; }, decode(data) { if (typeof data === 'bigint') { return data < 0n ? { TAG: 'negint', data } : { TAG: 'uint', data }; } if (typeof data === 'string') return { TAG: 'string', data }; if (btc_signer_1.utils.isBytes(data)) return { TAG: 'bytes', data }; if (Array.isArray(data)) return { TAG: 'array', data: data.map((i) => this.decode(i)) }; if (typeof data === 'number' && Number.isSafeInteger(data) && !isNegZero(data)) { return data < 0 ? { TAG: 'negint', data } : { TAG: 'uint', data }; } if (typeof data === 'boolean' || typeof data === 'number' || data === null || data === undefined) { return { TAG: 'simple', data: data }; } if (typeof data === 'object') { return { TAG: 'map', data: Object.entries(data).map((kv) => kv.map((i) => this.decode(i))), }; } throw new Error('unknown type'); }, }); //# sourceMappingURL=cbor.js.map