diff --git a/package-lock.json b/package-lock.json index c35505a..47b36a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,15 +9,16 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "@scure/base": "~1.1.6", - "@scure/btc-signer": "~1.3.1", + "@scure/base": "~1.1.5", + "@scure/btc-signer": "~1.3.2", "enquirer": "2.4.1", - "micro-packed": "~0.5.3" + "micro-packed": "~0.6.2" }, "bin": { "ord": "lib/esm/cli.js" }, "devDependencies": { + "@noble/curves": "^1.4.0", "@paulmillr/jsbt": "0.1.0", "@types/node": "20.12.7", "micro-bmark": "0.3.1", @@ -66,14 +67,14 @@ } }, "node_modules/@scure/btc-signer": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@scure/btc-signer/-/btc-signer-1.3.1.tgz", - "integrity": "sha512-PMXk3PRfqzdM33dDejtmfBGK6fIr24hHq4x7x0R24rBkRLUac5UyC31BglAddLiv409+feT8oH0rgu5Tfvch9w==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@scure/btc-signer/-/btc-signer-1.3.2.tgz", + "integrity": "sha512-BmcQHvxaaShKwgbFC0vDk0xzqbMhNtNmgXm6u7cz07FNtGsVItUuHow6NbgHmc+oJSBZJRym5dz8+Uu0JoEJhQ==", "dependencies": { "@noble/curves": "~1.4.0", "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6", - "micro-packed": "~0.5.3" + "micro-packed": "~0.6.2" }, "funding": { "url": "https://paulmillr.com/funding/" @@ -123,9 +124,9 @@ "dev": true }, "node_modules/micro-packed": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/micro-packed/-/micro-packed-0.5.3.tgz", - "integrity": "sha512-zWRoH+qUb/ZMp9gVZhexvRGCENDM5HEQF4sflqpdilUHWK2/zKR7/MT8GBctnTwbhNJwy1iuk5q6+TYP7/twYA==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/micro-packed/-/micro-packed-0.6.2.tgz", + "integrity": "sha512-a1C2m0WdjZSMleMylPihztSwQSt3pd97lbk6inQgYJ+AVWJIzNfufOQo3faBztJAdvr0h9gt190H9xp+sKKJGA==", "dependencies": { "@scure/base": "~1.1.5" }, diff --git a/package.json b/package.json index d114c00..13bc008 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,7 @@ "name": "micro-ordinals", "version": "0.1.0", "description": "Manage ordinals, inscriptions and runes using scure-btc-signer", - "files": [ - "lib", - "src" - ], + "files": ["lib", "src"], "bin": { "ord": "lib/esm/cli.js" }, @@ -13,12 +10,13 @@ "module": "lib/esm/index.js", "types": "lib/index.d.ts", "dependencies": { - "@scure/base": "~1.1.6", - "@scure/btc-signer": "~1.3.1", + "@scure/base": "~1.1.5", + "@scure/btc-signer": "~1.3.2", "enquirer": "2.4.1", - "micro-packed": "~0.5.3" + "micro-packed": "~0.6.2" }, "devDependencies": { + "@noble/curves": "^1.4.0", "@paulmillr/jsbt": "0.1.0", "@types/node": "20.12.7", "micro-bmark": "0.3.1", diff --git a/src/cbor.ts b/src/cbor.ts index b35ed33..ce80ae1 100644 --- a/src/cbor.ts +++ b/src/cbor.ts @@ -35,35 +35,8 @@ const F16BE = P.wrap({ }, }); -const createView = (arr: Uint8Array) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength); - -const f32 = (le?: boolean) => - P.wrap({ - encodeStream(w, value: number) { - if (Math.fround(value) !== value) throw w.err(`f32: wrong value=${value}`); - const buf = new Uint8Array(4); - createView(buf).setFloat32(0, value, le); - w.bytes(buf); - }, - decodeStream: (r) => createView(r.bytes(4)).getFloat32(0, le), - }); - -const f64 = (le?: boolean) => - P.wrap({ - encodeStream(w, value: number) { - // no validation, any JS number can be encoded as float64 - const buf = new Uint8Array(8); - createView(buf).setFloat64(0, value, le); - w.bytes(buf); - }, - decodeStream: (r) => createView(r.bytes(8)).getFloat64(0, le), - }); - -const F32BE = /* @__PURE__ */ f32(false); // const F32LE = /* @__PURE__ */ f32(true); -const F64BE = /* @__PURE__ */ f64(false); // const F64LE = /* @__PURE__ */ f64(true); - const INFO = P.bits(5); // additional info -const U64LEN = P.apply(P.U64BE, P.coders.number); +const U64LEN = P.apply(P.U64BE, P.coders.numberBigint); // Number/lengths limits const CBOR_LIMITS: Record< @@ -186,10 +159,10 @@ const cborSimple: P.CoderType = P.wrap({ // If can be encoded as F32 without rounding if (Math.fround(value) === value) { INFO.encodeStream(w, 26); - return F32BE.encodeStream(w, value); + return P.F32BE.encodeStream(w, value); } INFO.encodeStream(w, 27); - return F64BE.encodeStream(w, value); + return P.F64BE.encodeStream(w, value); }, decodeStream(r) { const ai = INFO.decodeStream(r); @@ -199,8 +172,8 @@ const cborSimple: P.CoderType = P.wrap({ if (ai === 23) return undefined; // ai === 24 is P.U8 with simple, reserved if (ai === 25) return F16BE.decodeStream(r); - if (ai === 26) return F32BE.decodeStream(r); - if (ai === 27) return F64BE.decodeStream(r); + if (ai === 26) return P.F32BE.decodeStream(r); + if (ai === 27) return P.F64BE.decodeStream(r); throw r.err('cbor/simple: unassigned'); }, }); @@ -280,7 +253,10 @@ export const CBOR = P.apply(cborValue, { return { TAG: 'simple', data: data }; } if (typeof data === 'object') { - return { TAG: 'map', data: Object.entries(data).map((kv) => kv.map((i) => this.decode(i))) }; + return { + TAG: 'map', + data: Object.entries(data).map((kv) => kv.map((i) => this.decode(i))), + }; } throw new Error('unknown type'); }, diff --git a/src/index.ts b/src/index.ts index bcb063f..dfe3457 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ -import { Coder, hex, utf8 } from '@scure/base'; -import * as P from 'micro-packed'; +import { Coder, hex, utf8 } from "@scure/base"; +import * as P from "micro-packed"; import { Script, ScriptType, @@ -7,11 +7,11 @@ import { CustomScript, MAX_SCRIPT_BYTE_LENGTH, utils, -} from '@scure/btc-signer'; -import { CBOR } from './cbor.js'; +} from "@scure/btc-signer"; +import { CBOR } from "./cbor.js"; type Bytes = Uint8Array; -const PROTOCOL_ID = /* @__PURE__ */ utf8.decode('ord'); +const PROTOCOL_ID = /* @__PURE__ */ utf8.decode("ord"); function splitChunks(buf: Bytes): Bytes[] { const res = []; @@ -22,13 +22,14 @@ function splitChunks(buf: Bytes): Bytes[] { const RawInscriptionId = /* @__PURE__ */ P.tuple([ P.bytes(32, true), - P.apply(P.bigint(4, true, false, false), P.coders.number), + P.apply(P.bigint(4, true, false, false), P.coders.numberBigint), ] as const); export const InscriptionId: P.Coder = { encode(data: string) { - const [txId, index] = data.split('i', 2); - if (`${+index}` !== index) throw new Error(`InscriptionId wrong index: ${index}`); + const [txId, index] = data.split("i", 2); + if (`${+index}` !== index) + throw new Error(`InscriptionId wrong index: ${index}`); return RawInscriptionId.encode([hex.decode(txId), +index]); }, decode(data: Bytes) { @@ -96,28 +97,34 @@ const TagCoder: P.Coder = { const res: Partial = {}; if (unknown.length) res.unknown = unknown; for (const field in tmp) { - if (field === 'parent' && tmp[field].length > 1) { - res[field as TagName] = tmp[field].map((i) => TagCoders.parent.decode(i)); + if (field === "parent" && tmp[field].length > 1) { + res[field as TagName] = tmp[field].map((i) => + TagCoders.parent.decode(i), + ); continue; } - res[field as TagName] = TagCoders[field as TagName].decode(utils.concatBytes(...tmp[field])); + res[field as TagName] = TagCoders[field as TagName].decode( + utils.concatBytes(...tmp[field]), + ); } return res as Tags; }, decode(to: Tags): TagRaw[] { const res: TagRaw[] = []; for (const field in to) { - if (field === 'unknown') continue; + if (field === "unknown") continue; const tagName = TagCoderInternal.encode(field); - if (field === 'parent' && Array.isArray(to.parent)) { - for (const p of to.parent) res.push({ tag: tagName, data: TagCoders.parent.encode(p) }); + if (field === "parent" && Array.isArray(to.parent)) { + for (const p of to.parent) + res.push({ tag: tagName, data: TagCoders.parent.encode(p) }); continue; } const bytes = TagCoders[field as TagName].encode(to[field as TagName]); for (const data of splitChunks(bytes)) res.push({ tag: tagName, data }); } if (to.unknown) { - if (!Array.isArray(to.unknown)) throw new Error('ordinals/TagCoder: unknown should be array'); + if (!Array.isArray(to.unknown)) + throw new Error("ordinals/TagCoder: unknown should be array"); for (const [tag, data] of to.unknown) res.push({ tag, data }); } return res; @@ -126,24 +133,28 @@ const TagCoder: P.Coder = { export type Inscription = { tags: Tags; body: Bytes; cursed?: boolean }; type OutOrdinalRevealType = { - type: 'tr_ord_reveal'; + type: "tr_ord_reveal"; pubkey: Bytes; inscriptions: Inscription[]; }; const parseEnvelopes = (script: ScriptType, pos = 0) => { - if (!Number.isSafeInteger(pos)) throw new Error(`parseInscription: wrong pos=${typeof pos}`); + if (!Number.isSafeInteger(pos)) + throw new Error(`parseInscription: wrong pos=${typeof pos}`); const envelopes = []; // Inscriptions with broken parsing are called 'cursed' (stutter or pushnum) let stutter = false; main: for (; pos < script.length; pos++) { const instr = script[pos]; if (instr !== 0) continue; - if (script[pos + 1] !== 'IF') { + if (script[pos + 1] !== "IF") { if (script[pos + 1] === 0) stutter = true; continue main; } - if (!utils.isBytes(script[pos + 2]) || !P.equalBytes(script[pos + 2] as any, PROTOCOL_ID)) { + if ( + !utils.isBytes(script[pos + 2]) || + !P.utils.equalBytes(script[pos + 2] as any, PROTOCOL_ID) + ) { if (script[pos + 2] === 0) stutter = true; continue main; } @@ -152,17 +163,17 @@ const parseEnvelopes = (script: ScriptType, pos = 0) => { for (let j = pos + 3; j < script.length; j++) { const op = script[j]; // done - if (op === 'ENDIF') { + if (op === "ENDIF") { envelopes.push({ start: pos + 3, end: j, pushnum, payload, stutter }); pos = j; break; } - if (op === '1NEGATE') { + if (op === "1NEGATE") { pushnum = true; payload.push(new Uint8Array([0x81])); continue; } - if (typeof op === 'number' && 1 <= op && op <= 16) { + if (typeof op === "number" && 1 <= op && op <= 16) { pushnum = true; payload.push(new Uint8Array([op])); continue; @@ -179,9 +190,12 @@ const parseEnvelopes = (script: ScriptType, pos = 0) => { }; // Additional API for parsing inscriptions -export function parseInscriptions(script: ScriptType, strict = false): Inscription[] | undefined { +export function parseInscriptions( + script: ScriptType, + strict = false, +): Inscription[] | undefined { if (strict && (!utils.isBytes(script[0]) || script[0].length !== 32)) return; - if (strict && script[1] !== 'CHECKSIG') return; + if (strict && script[1] !== "CHECKSIG") return; const envelopes = parseEnvelopes(script); const inscriptions: Inscription[] = []; @@ -196,8 +210,10 @@ export function parseInscriptions(script: ScriptType, strict = false): Inscripti for (; i < payload.length && payload[i] !== 0; i += 2) { const tag = payload[i]; const data = payload[i + 1]; - if (!utils.isBytes(tag)) throw new Error('parseInscription: non-bytes tag'); - if (!utils.isBytes(data)) throw new Error('parseInscription: non-bytes tag data'); + if (!utils.isBytes(tag)) + throw new Error("parseInscription: non-bytes tag"); + if (!utils.isBytes(data)) + throw new Error("parseInscription: non-bytes tag data"); tags.push({ tag, data }); } while (payload[i] === 0 && i < payload.length) i++; @@ -222,15 +238,19 @@ export function parseInscriptions(script: ScriptType, strict = false): Inscripti * Parse inscriptions from reveal tx input witness (tx.inputs[0].finalScriptWitness) */ export function parseWitness(witness: Bytes[]): Inscription[] | undefined { - if (witness.length !== 3) throw new Error('Wrong witness'); + if (witness.length !== 3) throw new Error("Wrong witness"); // We don't validate other parts of witness here since we want to parse // as much stuff as possible. When creating inscription, it is done more strictly return parseInscriptions(Script.decode(witness[1])); } -export const OutOrdinalReveal: Coder & CustomScript = { +export const OutOrdinalReveal: Coder< + OptScript, + OutOrdinalRevealType | undefined +> & + CustomScript = { encode(from: ScriptType): OutOrdinalRevealType | undefined { - const res: Partial = { type: 'tr_ord_reveal' }; + const res: Partial = { type: "tr_ord_reveal" }; try { res.inscriptions = parseInscriptions(from, true); res.pubkey = from[0] as Bytes; @@ -240,23 +260,24 @@ export const OutOrdinalReveal: Coder { - if (to.type !== 'tr_ord_reveal') return; - const out: ScriptType = [to.pubkey, 'CHECKSIG']; + if (to.type !== "tr_ord_reveal") return; + const out: ScriptType = [to.pubkey, "CHECKSIG"]; for (const { tags, body } of to.inscriptions) { - out.push(0, 'IF', PROTOCOL_ID); + out.push(0, "IF", PROTOCOL_ID); const rawTags = TagCoder.decode(tags); for (const tag of rawTags) out.push(tag.tag, tag.data); // Body out.push(0); for (const c of splitChunks(body)) out.push(c); - out.push('ENDIF'); + out.push("ENDIF"); } return out as any; }, finalizeTaproot: (script: any, parsed: any, signatures: any) => { - if (signatures.length !== 1) throw new Error('tr_ord_reveal/finalize: wrong signatures array'); + if (signatures.length !== 1) + throw new Error("tr_ord_reveal/finalize: wrong signatures array"); const [{ pubKey }, sig] = signatures[0]; - if (!P.equalBytes(pubKey, parsed.pubkey)) return; + if (!P.utils.equalBytes(pubKey, parsed.pubkey)) return; return [sig, script]; }, }; @@ -267,9 +288,9 @@ export const OutOrdinalReveal: Coder