micro-ordinals
Version:
Manage ordinals, inscriptions and runes using scure-btc-signer
268 lines • 10.6 kB
JavaScript
;
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