// bitcoin.mjs (quantum exposure report, C12): the Bitcoin ADDRESS and SCRIPT codec, the TRANSACTION parser and the extraction of // on-chain EVIDENCE from inputs and outputs. Pure functions over bytes; nothing here touches the network. // // Address forms (checked, never trusted from a label): // base58check (BIP-13 / Bitcoin Core base58.cpp): 4-byte SHA-256d checksum; version 0x00 P2PKH, 0x05 P2SH on mainnet, 0x6f / 0xc4 on // testnet and signet (regtest shares them) // bech32 (BIP-173) for witness version 0 and bech32m (BIP-350) for versions 1..16: hrp bc / tb / bcrt, program 20 or 32 bytes for // version 0, 2..40 bytes for the others; a version-0 address with the bech32m constant (or a version-1 with the bech32 one) is REFUSED // Script types are derived from the scriptPubKey BYTES (BIP-16, BIP-141, BIP-341; Bitcoin Core script/solver.cpp): // p2pk <33|65-byte key> OP_CHECKSIG the public key IS the script // p2pkh OP_DUP OP_HASH160 <20> OP_EQUALVERIFY OP_CHECKSIG // p2sh OP_HASH160 <20> OP_EQUAL // p2ms OP_m OP_n OP_CHECKMULTISIG bare multisig: the keys are the script // p2wpkh OP_0 <20>; p2wsh OP_0 <32>; p2tr OP_1 <32> (the output key, i.e. the tweaked key, is the script) // Evidence of a REVEALED key or script, from a spending input (BIP-141 witness or scriptSig): a 33/65-byte push whose HASH160 equals // the address's hash (p2pkh, p2wpkh, p2sh-p2wpkh), a redeem script whose HASH160 equals the p2sh hash, a witness script whose SHA-256 // equals the p2wsh program, the control block of a taproot script-path spend (its internal key). Every match is a byte equality // computed here, not a label read from an index. A push counts as a KEY only when it decodes as a point on secp256k1 (isKey); in a // revealed script of no standard form it counts only when it is also the operand of a signature check (OP_CHECKSIG / OP_CHECKSIGVERIFY / // OP_CHECKSIGADD, or the key run of an OP_CHECKMULTISIG); a push that merely looks like a key is reported as such, never as evidence. import crypto from 'node:crypto'; import { ripemd160, secp256k1 } from './noble.mjs'; export const sha256 = (b) => crypto.createHash('sha256').update(b).digest(); export const sha256d = (b) => sha256(sha256(b)); export const hash160 = (b) => Buffer.from(ripemd160(sha256(b))); export const hex = (b) => Buffer.from(b).toString('hex'); export const dinHex = (s) => { const t = String(s || '').replace(/^0x/, ''); if (!/^(?:[0-9a-fA-F]{2})*$/.test(t)) throw new Error('not hex'); return Buffer.from(t, 'hex'); }; // ---------------------------------------------------------------- base58check const B58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; export function base58Decode(s) { if (!/^[1-9A-HJ-NP-Za-km-z]+$/.test(s)) return null; let n = 0n; for (const c of s) n = n * 58n + BigInt(B58.indexOf(c)); let h = n.toString(16); if (h.length % 2) h = '0' + h; let b = n === 0n ? Buffer.alloc(0) : Buffer.from(h, 'hex'); let zeros = 0; for (const c of s) { if (c !== '1') break; zeros++; } return Buffer.concat([Buffer.alloc(zeros), b]); } export function base58checkDecode(s) { const b = base58Decode(s); if (!b || b.length < 5) return null; const body = b.subarray(0, b.length - 4), sum = b.subarray(b.length - 4); if (!sha256d(body).subarray(0, 4).equals(sum)) return null; return body; } /** body (version byte + payload) -> base58check string (used to NAME the sibling scripts of an address in the report) */ export function base58checkEncode(body) { const d = Buffer.concat([Buffer.from(body), sha256d(body).subarray(0, 4)]); let n = BigInt('0x' + (d.toString('hex') || '0')); let s = ''; while (n > 0n) { s = B58[Number(n % 58n)] + s; n /= 58n; } for (const x of d) { if (x === 0) s = '1' + s; else break; } return s; } // ---------------------------------------------------------------- bech32 / bech32m (BIP-173, BIP-350) const B32 = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; const GEN = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; function polymod(v) { let c = 1; for (const x of v) { const c0 = c >>> 25; c = ((c & 0x1ffffff) << 5) ^ x; for (let i = 0; i < 5; i++) if ((c0 >> i) & 1) c ^= GEN[i]; } return c >>> 0; } const hrpExpand = (hrp) => [...hrp].map((c) => c.charCodeAt(0) >> 5).concat([0], [...hrp].map((c) => c.charCodeAt(0) & 31)); export const BECH32 = 1, BECH32M = 0x2bc830a3; /** -> { hrp, data (5-bit values), constant } or null; the string must be all-lower or all-upper (BIP-173) */ export function bech32Decode(s) { if (s !== s.toLowerCase() && s !== s.toUpperCase()) return null; const t = s.toLowerCase(); const p = t.lastIndexOf('1'); if (p < 1 || p + 7 > t.length || t.length > 90) return null; const hrp = t.slice(0, p); const dataS = t.slice(p + 1); if ([...hrp].some((c) => c.charCodeAt(0) < 33 || c.charCodeAt(0) > 126)) return null; const data = [...dataS].map((c) => B32.indexOf(c)); if (data.includes(-1)) return null; const c = polymod(hrpExpand(hrp).concat(data)); if (c !== BECH32 && c !== BECH32M) return null; return { hrp, data: data.slice(0, -6), constant: c }; } function convertBits(data, from, to, pad) { let acc = 0, bits = 0; const out = []; const max = (1 << to) - 1; for (const v of data) { if (v < 0 || v >> from) return null; acc = (acc << from) | v; bits += from; while (bits >= to) { bits -= to; out.push((acc >> bits) & max); } } if (pad) { if (bits) out.push((acc << (to - bits)) & max); } else if (bits >= from || ((acc << (to - bits)) & max)) return null; return out; } /** BIP-173/350 segwit address -> { hrp, version, program } or null (checksum, constant/version pairing, program length checked) */ export function segwitDecode(s) { const d = bech32Decode(s); if (!d || d.data.length < 1) return null; const version = d.data[0]; if (version > 16) return null; const prog = convertBits(d.data.slice(1), 5, 8, false); if (!prog || prog.length < 2 || prog.length > 40) return null; if (version === 0 && prog.length !== 20 && prog.length !== 32) return null; if (version === 0 && d.constant !== BECH32) return null; if (version !== 0 && d.constant !== BECH32M) return null; return { hrp: d.hrp, version, program: Buffer.from(prog) }; } /** hrp, witness version, program -> the BIP-173 (version 0) or BIP-350 (version 1+) address (used to NAME sibling scripts in the report) */ export function segwitEncode(hrp, version, program) { const data = [version, ...convertBits([...program], 8, 5, true)]; const c = polymod(hrpExpand(hrp).concat(data, [0, 0, 0, 0, 0, 0])) ^ (version === 0 ? BECH32 : BECH32M); const sum = []; for (let i = 0; i < 6; i++) sum.push((c >>> (5 * (5 - i))) & 31); return hrp + '1' + [...data, ...sum].map((v) => B32[v]).join(''); } // ---------------------------------------------------------------- script pushes and types export const OP = { 0: 0x00, PUSHDATA1: 0x4c, PUSHDATA2: 0x4d, PUSHDATA4: 0x4e, 1: 0x51, 16: 0x60, RETURN: 0x6a, DUP: 0x76, EQUAL: 0x87, EQUALVERIFY: 0x88, HASH160: 0xa9, CHECKSIG: 0xac, CHECKMULTISIG: 0xae }; /** the script as a list of { op } or { data } (pushes); null when a push runs past the end */ export function parseScript(b) { const out = []; let i = 0; while (i < b.length) { const op = b[i++]; let n = -1; if (op >= 1 && op <= 75) n = op; else if (op === OP.PUSHDATA1) { if (i + 1 > b.length) return null; n = b[i]; i += 1; } else if (op === OP.PUSHDATA2) { if (i + 2 > b.length) return null; n = b.readUInt16LE(i); i += 2; } else if (op === OP.PUSHDATA4) { if (i + 4 > b.length) return null; n = b.readUInt32LE(i); i += 4; } if (n >= 0) { if (i + n > b.length) return null; out.push({ data: Buffer.from(b.subarray(i, i + n)) }); i += n; } else out.push({ op }); } return out; } const smallNum = (op) => (op === OP[0] ? 0 : op >= OP[1] && op <= OP[16] ? op - OP[1] + 1 : null); /** the FORM of a public key: 33 bytes with prefix 02/03, or 65 bytes with prefix 04, or the HYBRID 65-byte form 06/07 (x, y, and the * parity of y repeated in the prefix), which consensus accepts in a signature check (Bitcoin Core pubkey.h GetLen, libsecp256k1 eckey_impl.h) */ export const isKeyForm = (d) => (d.length === 33 && (d[0] === 2 || d[0] === 3)) || (d.length === 65 && (d[0] === 4 || d[0] === 6 || d[0] === 7)); /** a public key: the form AND a point on secp256k1 (decoded here; a 33-byte string that is not an x-coordinate is not a key). A hybrid key * is checked as its 04 form, and its prefix must state the parity of y (06 even, 07 odd), as libsecp256k1 requires */ export const isKey = (d) => { if (!isKeyForm(d)) return false; try { if (d[0] === 6 || d[0] === 7) { if ((d[64] & 1) !== (d[0] & 1)) return false; secp256k1.ProjectivePoint.fromHex(Buffer.concat([Buffer.from([4]), Buffer.from(d).subarray(1)])); return true; } secp256k1.ProjectivePoint.fromHex(Buffer.from(d)); return true; } catch { return false; } }; const CHECKSIG_OPS = new Set([0xac, 0xad, 0xba]); // OP_CHECKSIG, OP_CHECKSIGVERIFY, OP_CHECKSIGADD const CHECKMULTISIG_OPS = new Set([0xae, 0xaf]); // OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY /** among the pushes of a parsed script, the public keys BOUND to a signature check: the operand of a CHECKSIG op, or a key inside the * run of pushes that ends with OP_CHECKMULTISIG; a valid point pushed anywhere else (data, a hash lock, a drop) does not count */ export function cheiLegate(p) { let n = 0; for (let i = 0; i < p.length; i++) { if (!p[i].data || !isKey(p[i].data)) continue; const next = p[i + 1]; if (next && next.op !== undefined && CHECKSIG_OPS.has(next.op)) { n++; continue; } let j = i + 1; while (p[j] && p[j].data && isKey(p[j].data)) j++; if (p[j] && p[j].op !== undefined && smallNum(p[j].op) !== null && p[j + 1] && p[j + 1].op !== undefined && CHECKMULTISIG_OPS.has(p[j + 1].op)) n++; } return n; } /** { type, hash|program|key|m|n|keys } from scriptPubKey bytes; type 'nonstandard' when nothing matches */ export function tipScript(spk) { const b = Buffer.from(spk); if (b.length === 25 && b[0] === OP.DUP && b[1] === OP.HASH160 && b[2] === 20 && b[23] === OP.EQUALVERIFY && b[24] === OP.CHECKSIG) return { type: 'p2pkh', hash: Buffer.from(b.subarray(3, 23)) }; if (b.length === 23 && b[0] === OP.HASH160 && b[1] === 20 && b[22] === OP.EQUAL) return { type: 'p2sh', hash: Buffer.from(b.subarray(2, 22)) }; if (b.length === 22 && b[0] === OP[0] && b[1] === 20) return { type: 'p2wpkh', version: 0, program: Buffer.from(b.subarray(2)) }; if (b.length === 34 && b[0] === OP[0] && b[1] === 32) return { type: 'p2wsh', version: 0, program: Buffer.from(b.subarray(2)) }; if (b.length === 34 && b[0] === OP[1] && b[1] === 32) return { type: 'p2tr', version: 1, program: Buffer.from(b.subarray(2)) }; if (b.length >= 4 && b[0] >= OP[1] && b[0] <= OP[16] && b[1] >= 2 && b[1] <= 40) { const v = b[0] - OP[1] + 1; const n = b.length - 2; if (n >= 2 && n <= 40 && b[1] === n) return { type: 'witness-unknown', version: v, program: Buffer.from(b.subarray(2)) }; } const p = parseScript(b); if (p && p.length === 2 && p[0].data && isKey(p[0].data) && p[1].op === OP.CHECKSIG) return { type: 'p2pk', key: p[0].data }; if (p && p.length >= 4 && p[p.length - 1].op === OP.CHECKMULTISIG) { const m = smallNum(p[0].op ?? -1), n = smallNum(p[p.length - 2].op ?? -1); const keys = p.slice(1, -2); if (m !== null && n !== null && keys.length === n && m >= 1 && m <= n && keys.every((k) => k.data && isKey(k.data))) return { type: 'p2ms', m, n, keys: keys.map((k) => k.data) }; } if (b.length >= 1 && b[0] === OP.RETURN) return { type: 'op_return' }; return { type: 'nonstandard' }; } /** describe a redeem / witness script (what a spend revealed) */ export function tipScriptIntern(script) { const t = tipScript(script); if (t.type === 'p2ms') return { kind: 'multisig', m: t.m, n: t.n, keys: t.keys }; if (t.type === 'p2wpkh') return { kind: 'p2wpkh', program: t.program }; if (t.type === 'p2wsh') return { kind: 'p2wsh', program: t.program }; if (t.type === 'p2pk') return { kind: 'p2pk', key: t.key }; if (t.type === 'p2pkh') return { kind: 'p2pkh', hash: t.hash }; const p = parseScript(script) || []; // keyShapedPushes: pushes with the FORM of a key (not verified); keysBound: valid points that are the operand of a signature check return { kind: 'other', keyShapedPushes: p.filter((x) => x.data && isKeyForm(x.data)).length, keysBound: cheiLegate(p) }; } // ---------------------------------------------------------------- addresses -> scripts const VERS = { 0x00: ['p2pkh', 'mainnet'], 0x05: ['p2sh', 'mainnet'], 0x6f: ['p2pkh', 'testnet'], 0xc4: ['p2sh', 'testnet'] }; const HRP = { bc: 'mainnet', tb: 'testnet', bcrt: 'regtest' }; /** * one input line -> { input, form, network, scriptPubKey, ...tipScript } or { input, error } * forms: a base58check address, a bech32/bech32m address, `script:` (a raw scriptPubKey), `p2pk:<33|65-byte key hex>` */ export function decodeazaIntrareBitcoin(line) { const s = String(line).trim(); if (/^script:/i.test(s)) { let spk; try { spk = dinHex(s.slice(7)); } catch { return { input: s, error: 'script: is not hex' }; } if (!spk.length) return { input: s, error: 'script: is empty' }; return { input: s, form: 'script', network: 'unknown', scriptPubKey: spk, ...tipScript(spk) }; } if (/^p2pk:/i.test(s)) { let k; try { k = dinHex(s.slice(5)); } catch { return { input: s, error: 'p2pk: is not hex' }; } if (!isKey(k)) return { input: s, error: 'p2pk: needs a 33-byte (02/03) or 65-byte (04, or hybrid 06/07) secp256k1 public key (a point on the curve)' }; const spk = Buffer.concat([Buffer.from([k.length]), k, Buffer.from([OP.CHECKSIG])]); return { input: s, form: 'p2pk', network: 'unknown', scriptPubKey: spk, ...tipScript(spk) }; } const sw = segwitDecode(s); if (sw) { const net = HRP[sw.hrp]; if (!net) return { input: s, error: 'bech32 address with an unknown prefix ' + sw.hrp }; const spk = Buffer.concat([Buffer.from([sw.version === 0 ? OP[0] : OP[1] + sw.version - 1, sw.program.length]), sw.program]); return { input: s, form: sw.version === 0 ? 'bech32' : 'bech32m', network: net, scriptPubKey: spk, ...tipScript(spk) }; } if (/^[1-9A-HJ-NP-Za-km-z]{26,35}$/.test(s)) { const b = base58checkDecode(s); if (!b) return { input: s, error: 'base58check address with a wrong checksum (or not an address)' }; if (b.length !== 21 || !VERS[b[0]]) return { input: s, error: 'base58check payload is not a known address version' }; const [type, net] = VERS[b[0]]; const h = b.subarray(1); const spk = type === 'p2pkh' ? Buffer.concat([Buffer.from([OP.DUP, OP.HASH160, 20]), h, Buffer.from([OP.EQUALVERIFY, OP.CHECKSIG])]) : Buffer.concat([Buffer.from([OP.HASH160, 20]), h, Buffer.from([OP.EQUAL])]); return { input: s, form: 'base58check', network: net, scriptPubKey: spk, ...tipScript(spk) }; } const lower = s.toLowerCase(); if (/^(bc|tb|bcrt)1[02-9ac-hj-np-z]+$/.test(lower)) return { input: s, error: 'bech32 address with a wrong checksum, mixed case, or a wrong version/encoding pair' }; return { input: s, error: 'not a Bitcoin address, script: or p2pk: line' }; } /** the Electrum scripthash of a scriptPubKey: sha256 of the script, byte-reversed, hex */ export const scripthash = (spk) => hex(Buffer.from(sha256(spk)).reverse()); // ---------------------------------------------------------------- sibling scripts (the same key under another script type) const VER_P2PKH = { mainnet: 0x00, testnet: 0x6f, regtest: 0x6f }, VER_P2SH = { mainnet: 0x05, testnet: 0xc4, regtest: 0xc4 }, HRP_RETEA = { mainnet: 'bc', testnet: 'tb', regtest: 'bcrt' }; /** the three encodings of a secp256k1 point that consensus accepts in a signature check (Bitcoin Core pubkey.h; libsecp256k1 eckey_impl.h): * compressed (02/03), uncompressed (04) and hybrid (06/07: x, y, and the parity of y repeated in the prefix) -> { P, compressed, uncompressed, hybrid } */ export function codificariCheie(k) { const d = Buffer.from(k); const P = secp256k1.ProjectivePoint.fromHex(d[0] === 6 || d[0] === 7 ? Buffer.concat([Buffer.from([4]), d.subarray(1)]) : d); const uncompressed = Buffer.from(P.toRawBytes(false)); const hybrid = Buffer.from(uncompressed); hybrid[0] = 6 | (uncompressed[64] & 1); return { P, compressed: Buffer.from(P.toRawBytes(true)), uncompressed, hybrid }; } /** BIP-86 (BIP-341 with no script tree): the 32-byte x-only OUTPUT key of the internal key P, Q = P' + t*G, P' the point of x(P) with an * even y (BIP-340 lift_x), t = int(tagged_hash("TapTweak", x(P'))); null in the case BIP-341 rejects (t not below the group order) */ export function cheieIesireBip86(P) { const Pe = P.toAffine().y % 2n === 0n ? P : P.negate(); const x = Buffer.from(Pe.toRawBytes(true)).subarray(1); const tag = sha256(Buffer.from('TapTweak', 'utf8')); const tw = BigInt('0x' + hex(sha256(Buffer.concat([tag, tag, x])))); if (tw === 0n || tw >= secp256k1.CURVE.n) return null; return Buffer.from(Pe.add(secp256k1.ProjectivePoint.BASE.multiply(tw)).toRawBytes(true)).subarray(1); } const scriptP2pkh = (h) => Buffer.concat([Buffer.from([OP.DUP, OP.HASH160, 20]), h, Buffer.from([OP.EQUALVERIFY, OP.CHECKSIG])]); const scriptP2pk = (k) => Buffer.concat([Buffer.from([k.length]), k, Buffer.from([OP.CHECKSIG])]); /** * the SIBLING scripts of an input: the other scripts that the same key determines (project document, N8: the same key reused under another * script type), each with its RELATION to the input (`relatie`): * 'hash160' the same 20-byte HASH160 of a key under P2PKH, P2WPKH and P2SH-P2WPKH (whose redeem script is 0x0014 || hash, so its own * hash is HASH160(0x0014 || hash)). For a p2pkh or p2wpkh input the hash is the address's (the key itself is not known, so * nothing else is derived); for a p2pk input it is HASH160 of the key in the encoding given. * 'codificare' for a p2pk input: the same point in its other encodings (compressed, uncompressed, hybrid), each under P2PK and under the * three scripts of its own HASH160 (`codificare` names the encoding). * 'bip86' for a p2pk input: the P2TR output of the key used as an internal key with no script tree (BIP-86). * The input's own script is left out. What the key may sit in that the input does not determine (a P2TR output with a script tree, a P2SH * or P2WSH script, a bare multisig with other keys) is not derived, and neither is any sibling of a P2SH, P2WSH, P2TR, bare-multisig or * non-standard input: the hash of a script does not give the keys in it. -> [{ type, relatie, codificare?, input, scriptPubKey }], `input` * an address in the input's network (only a NAME for the report: the adapters read a sibling by its script, never by that string), or * `script:` when the network is unknown (p2pk:, script:) or the script has no address form (P2PK) */ export function scripturiSurori(t) { let h; if (t.type === 'p2pkh') h = t.hash; else if (t.type === 'p2wpkh') h = t.program; else if (t.type === 'p2pk') h = hash160(t.key); else return []; const net = VER_P2PKH[t.network] !== undefined ? t.network : null; const formeHash = (hh, relatie, codificare) => { const p2wpkh = Buffer.concat([Buffer.from([OP[0], 20]), hh]); const nume = (tip, spk) => (!net ? 'script:' + hex(spk) : tip === 'p2pkh' ? base58checkEncode(Buffer.concat([Buffer.from([VER_P2PKH[net]]), hh])) : tip === 'p2wpkh' ? segwitEncode(HRP_RETEA[net], 0, hh) : base58checkEncode(Buffer.concat([Buffer.from([VER_P2SH[net]]), hash160(p2wpkh)]))); return [['p2pkh', scriptP2pkh(hh)], ['p2wpkh', p2wpkh], ['p2sh-p2wpkh', Buffer.concat([Buffer.from([OP.HASH160, 20]), hash160(p2wpkh), Buffer.from([OP.EQUAL])])]] .map(([tip, spk]) => ({ type: tip, relatie, ...(codificare ? { codificare } : {}), input: nume(tip, spk), scriptPubKey: spk })); }; const lista = formeHash(h, 'hash160'); if (t.type === 'p2pk') { // the other encodings of the same point, and its BIP-86 output: derived from the key the input gives const c = codificariCheie(t.key); for (const cod of ['compressed', 'uncompressed', 'hybrid']) { const k = c[cod]; if (k.equals(Buffer.from(t.key))) continue; const spk = scriptP2pk(k); lista.push({ type: 'p2pk', relatie: 'codificare', codificare: cod, input: 'script:' + hex(spk), scriptPubKey: spk }, ...formeHash(hash160(k), 'codificare', cod)); } const q = cheieIesireBip86(c.P); if (q) { const spk = Buffer.concat([Buffer.from([OP[1], 32]), q]); lista.push({ type: 'p2tr', relatie: 'bip86', input: net ? segwitEncode(HRP_RETEA[net], 1, q) : 'script:' + hex(spk), scriptPubKey: spk }); } } return lista.filter((s) => !s.scriptPubKey.equals(t.scriptPubKey)); } /** a sibling as a target the adapters read BY ITS SCRIPT (form 'script': Esplora asks /scripthash/, Electrum the scripthash), named by its input */ export const tintaSora = (s, network = 'unknown') => ({ input: s.input, form: 'script', network, scriptPubKey: s.scriptPubKey, ...tipScript(s.scriptPubKey) }); // ---------------------------------------------------------------- block headers (the tip a target serves) /** the target a compact `bits` field states (Bitcoin Core arith_uint256::SetCompact), or null when it states none (negative, zero, overflow) */ export function tintaDinBits(bits) { const exp = bits >>> 24; const mant = bits & 0x007fffff; if ((bits & 0x00800000) || mant === 0 || exp > 32) return null; return exp <= 3 ? BigInt(mant >>> (8 * (3 - exp))) : BigInt(mant) << BigInt(8 * (exp - 3)); } /** * 80 header bytes as hex -> { hash, time, bits } when they are a block header: the hash (SHA-256d, byte-reversed) meets the target that * the header's own `bits` field states, as every block header does. This alone does NOT say that the header is on Bitcoin mainnet (a * regtest or signet header meets its own easy target): which chain a header can belong to is judged with RETELE_BTC (its genesis block, * its proof-of-work limit and, on mainnet, a checkpoint: clasifica.mjs), and whether it is on the chain with the most work is not judged * at all. It says the bytes * are a header and not some other string, so that the time read from them (offset 68) is a header's time. -> { eroare } otherwise */ export function antetBloc(hexAntet) { const s = String(hexAntet ?? ''); if (!/^[0-9a-fA-F]{160}$/.test(s)) return { eroare: 'not 80 bytes of hex' }; const b = Buffer.from(s, 'hex'); const hash = hex(Buffer.from(sha256d(b)).reverse()); const bits = b.readUInt32LE(72); const tinta = tintaDinBits(bits); if (tinta === null) return { eroare: 'its bits field states no valid target' }; if (BigInt('0x' + hash) > tinta) return { eroare: 'its hash does not meet the proof of work its bits field states' }; return { hash, time: b.readUInt32LE(68), bits }; } /** * the CHECKPOINTS this tool holds for Bitcoin mainnet: a chain that split from Bitcoin after its genesis block shares that block and the * proof-of-work limit (Bitcoin Cash from height 478559; Bitcoin SV and eCash split later from Bitcoin Cash and keep its blocks), so * neither tells it apart; the block at a height after the split does. These are the two pins of the shield's Bitcoin header follower (btc/antete.mjs, * PUNCTE_PINATE: hash and 80 header bytes read from two public block explorers of different operators, mempool.space and * blockstream.info, on 2026-10-11, identical on both; that agreement is their whole provenance). The test requires each to equal that * pin and its header to hash to its hash and meet its proof of work. A chain that split from Bitcoin AFTER the checkpoint used is not told * apart (none is known on the date written here), and a tip below the lowest one cannot be checked at all. */ const PUNCTE_MAINNET = Object.freeze([ Object.freeze({ height: 967680, hash: '0000000000000000000161354016172f2a377610d52dd46d0f68a919333458cd', header: '00000030410ccb1cba623e26f2e02f3fcb0e7f06f66998b68cb5010000000000000000003d16ae2558e8ec7b8dd369ee29bb2733fbd39bcc1058c681970a36a7f1a2bdea1135ae6ac51e0217480c3581' }), Object.freeze({ height: 969696, hash: '000000000000000000001e39df127cbab82a824ac43cfcdbf62e59f6a08b9f0b', header: '00e00020ea7409145abed3693b6aa5e269dea055406e0962804301000000000000000000a8f40cc468f3aa4518fdc368e37fc7aff01eb12b293885f847517602a96e3a93d4abc06af01e021779d3a409' }), ]); /** * the Bitcoin networks a target may serve: the hash of each genesis block (Bitcoin Core kernel/chainparams.cpp; the test recomputes each * from the genesis header's fields) and each chain's proof-of-work LIMIT (consensus.powLimit: CheckProofOfWork refuses a header whose bits * state a target above it); mainnet also carries its checkpoints (above). Signet here is the default signet; a custom signet shares its * genesis block and states its own limit. The test networks hold no checkpoint: a chain that shares one of their genesis blocks is not * told apart from it */ export const RETELE_BTC = Object.freeze({ mainnet: Object.freeze({ nume: 'Bitcoin mainnet', geneza: '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f', limita: 0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffn, puncte: PUNCTE_MAINNET }), testnet3: Object.freeze({ nume: 'Bitcoin testnet3', geneza: '000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943', limita: 0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffn }), testnet4: Object.freeze({ nume: 'Bitcoin testnet4', geneza: '00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043', limita: 0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffn }), signet: Object.freeze({ nume: 'Bitcoin signet', geneza: '00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6', limita: 0x00000377ae000000000000000000000000000000000000000000000000000000n }), regtest: Object.freeze({ nume: 'Bitcoin regtest', geneza: '0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206', limita: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn }), }); /** the network whose genesis block has this hash, or null (none of the five) */ export const reteaDinGeneza = (hash) => Object.keys(RETELE_BTC).find((n) => RETELE_BTC[n].geneza === String(hash)) || null; /** the checkpoint a target's chain is judged against: null when the network holds none; { sub: } when the chain the * target serves ends below the lowest one; else the HIGHEST checkpoint at or below that height (the later the split it tells apart) */ export function punctDeControl(retea, inaltime) { const P = retea && RETELE_BTC[retea] && RETELE_BTC[retea].puncte; if (!P || !P.length) return null; const sub = P.filter((p) => p.height <= inaltime); return sub.length ? sub[sub.length - 1] : { sub: P[0].height }; } /** the networks an input can belong to: its address form names them (base58check 0x6f / 0xc4 is shared by testnet3, testnet4, signet and * regtest; bech32 `tb` by the first three, `bcrt` is regtest); a `p2pk:` or `script:` line names none and is read on mainnet only */ export const reteleIntrarii = (t) => (t.network === 'mainnet' ? ['mainnet'] : t.network === 'regtest' ? ['regtest'] : t.network === 'testnet' ? (t.form === 'base58check' ? ['testnet3', 'testnet4', 'signet', 'regtest'] : ['testnet3', 'testnet4', 'signet']) : ['mainnet']); // ---------------------------------------------------------------- transactions (BIP-141 serialization) function readVarint(b, i) { const f = b[i]; if (f === undefined) throw new Error('varint past the end'); if (f < 0xfd) return [f, i + 1]; if (f === 0xfd) return [b.readUInt16LE(i + 1), i + 3]; if (f === 0xfe) return [b.readUInt32LE(i + 1), i + 5]; return [Number(b.readBigUInt64LE(i + 1)), i + 9]; } export function varint(n) { if (n < 0xfd) return Buffer.from([n]); if (n <= 0xffff) { const b = Buffer.alloc(3); b[0] = 0xfd; b.writeUInt16LE(n, 1); return b; } if (n <= 0xffffffff) { const b = Buffer.alloc(5); b[0] = 0xfe; b.writeUInt32LE(n, 1); return b; } const b = Buffer.alloc(9); b[0] = 0xff; b.writeBigUInt64LE(BigInt(n), 1); return b; } /** raw tx bytes -> { txid, version, vin: [{txid, vout, scriptSig, sequence, witness: [Buffer]}], vout: [{value (bigint), scriptPubKey}], locktime, segwit } */ export function parseTx(raw) { const b = Buffer.from(raw); let i = 0; if (b.length < 10) throw new Error('transaction too short'); const version = b.readInt32LE(0); i = 4; let segwit = false; if (b[i] === 0 && b[i + 1] === 1) { segwit = true; i += 2; } let nIn; [nIn, i] = readVarint(b, i); if (nIn === 0) throw new Error('transaction with no inputs'); const vin = []; for (let k = 0; k < nIn; k++) { const txid = hex(Buffer.from(b.subarray(i, i + 32)).reverse()); i += 32; const vout = b.readUInt32LE(i); i += 4; let l; [l, i] = readVarint(b, i); const scriptSig = Buffer.from(b.subarray(i, i + l)); i += l; if (scriptSig.length !== l) throw new Error('scriptSig past the end'); const sequence = b.readUInt32LE(i); i += 4; vin.push({ txid, vout, scriptSig, sequence, witness: [] }); } let nOut; [nOut, i] = readVarint(b, i); const vout = []; for (let k = 0; k < nOut; k++) { const value = b.readBigUInt64LE(i); i += 8; let l; [l, i] = readVarint(b, i); const spk = Buffer.from(b.subarray(i, i + l)); i += l; if (spk.length !== l) throw new Error('scriptPubKey past the end'); vout.push({ value, scriptPubKey: spk }); } const endOutputs = i; if (segwit) { for (let k = 0; k < nIn; k++) { let n; [n, i] = readVarint(b, i); for (let j = 0; j < n; j++) { let l; [l, i] = readVarint(b, i); const it = Buffer.from(b.subarray(i, i + l)); if (it.length !== l) throw new Error('witness past the end'); i += l; vin[k].witness.push(it); } } } if (i + 4 !== b.length) throw new Error('trailing bytes after the transaction (' + (b.length - i - 4) + ')'); const locktime = b.readUInt32LE(i); // txid: the serialization WITHOUT marker, flag and witness const strip = segwit ? Buffer.concat([b.subarray(0, 4), b.subarray(6, endOutputs), b.subarray(b.length - 4)]) : b; return { txid: hex(Buffer.from(sha256d(strip)).reverse()), version, vin, vout, locktime, segwit, size: b.length }; } /** serialize a tx of the parsed form (used by tests to build synthetic transactions) */ export function serializeTx(tx) { const segwit = tx.vin.some((v) => v.witness && v.witness.length); const p = [Buffer.alloc(4)]; p[0].writeInt32LE(tx.version); if (segwit) p.push(Buffer.from([0, 1])); p.push(varint(tx.vin.length)); for (const v of tx.vin) { const q = Buffer.alloc(4); q.writeUInt32LE(v.sequence >>> 0); const s = Buffer.alloc(4); s.writeUInt32LE(v.vout); p.push(Buffer.from(dinHex(v.txid)).reverse(), s, varint(v.scriptSig.length), v.scriptSig, q); } p.push(varint(tx.vout.length)); for (const o of tx.vout) { const q = Buffer.alloc(8); q.writeBigUInt64LE(BigInt(o.value)); p.push(q, varint(o.scriptPubKey.length), o.scriptPubKey); } if (segwit) for (const v of tx.vin) { p.push(varint((v.witness || []).length)); for (const w of v.witness || []) p.push(varint(w.length), w); } const lt = Buffer.alloc(4); lt.writeUInt32LE(tx.locktime >>> 0); p.push(lt); return Buffer.concat(p); } // ---------------------------------------------------------------- evidence from inputs /** * what a spending input REVEALS about the target script `t` (the output of tipScript / decodeazaIntrareBitcoin). * input = { scriptSig: Buffer, witness: [Buffer] }; spendsTarget = the caller established (from the previous output the input names) * that this input spends an output paying the target. Returns a list of { what, detail } byte-level findings, empty when nothing in * this input ties to the target. For hash-type targets a finding is emitted only when a hash computed here equals the target's * hash/program (that equality is the proof, whatever output the input spends); for p2tr / p2pk / p2ms only when spendsTarget. */ export function dovezileIntrarii(t, input, spendsTarget = false) { const out = []; const ss = parseScript(input.scriptSig || Buffer.alloc(0)) || []; const w = input.witness || []; const kHash = (k) => hash160(k); // every item pushed in this input (scriptSig pushes and witness elements): a script that commits to the HASH of a key (a P2PKH-shaped // redeem or witness script, a hash-locked contract) receives the key as a separate push in the same input const pushuri = () => [...ss.filter((x) => x.data).map((x) => x.data), ...w]; const scriptDezvaluit = (script, where) => { const r = tipScriptIntern(script); if (r.kind === 'multisig') { out.push({ what: 'keys-revealed-in-script', detail: `${where}: ${r.m}-of-${r.n} multisig, ${r.n} public keys revealed` }); return r; } if (r.kind === 'p2pk') { out.push({ what: 'keys-revealed-in-script', detail: `${where}: P2PK form, the public key is in the script` }); return r; } const hashuri = (parseScript(script) || []).filter((x) => x.data && x.data.length === 20).map((x) => x.data); const chei = pushuri().filter((d) => isKey(d) && hashuri.some((h) => h.equals(kHash(d)))); if (chei.length) { out.push({ what: 'public-key-revealed', detail: `${where}: the script commits to HASH160 of a key, and ${chei.length} public key(s) with that hash are pushed in the same input` }); return r; } if (r.kind === 'other' && r.keysBound > 0) { out.push({ what: 'keys-revealed-in-script', detail: `${where}: script revealed, carrying ${r.keysBound} public key(s), each a point on secp256k1 that is the operand of a signature check in the script` }); return r; } if (r.kind === 'other' && r.keyShapedPushes > 0) { out.push({ what: 'script-revealed-unclassified', detail: `${where}: script revealed, carrying ${r.keyShapedPushes} public-key-shaped push(es) not verified as keys bound to a signature check (a point on the curve that OP_CHECKSIG / OP_CHECKMULTISIG consumes); its form is not classified by this tool` }); return r; } out.push({ what: 'script-revealed-unclassified', detail: `${where}: script revealed (${r.kind}), with no public-key-shaped push and no key hash matched by a push of this input; its form is not classified by this tool` }); return r; }; // P2PKH / P2WPKH: EVERY push of the scriptSig (a legacy scriptSig need not end with the key: ` OP_NOP` is valid) and every // witness element is compared with the hash; a push whose HASH160 equals it is the preimage, and it counts as a key only when it is a // point on the curve (compressed, uncompressed or hybrid); an equal hash on bytes this tool does not decode as a key is named, not dropped const preimagine = (lista, h, unde) => { const egale = lista.filter((d) => kHash(d).equals(h)); if (egale.some((d) => isKey(d))) out.push({ what: 'public-key-revealed', detail: `public key revealed in the ${unde}` }); else if (egale.length) out.push({ what: 'script-revealed-unclassified', detail: `the ${unde} carries a push whose HASH160 equals the hash, not decoded as a public key by this tool` }); }; if (t.type === 'p2pkh') { preimagine(ss.filter((x) => x.data).map((x) => x.data), t.hash, 'scriptSig (HASH160 equals the address hash)'); } else if (t.type === 'p2wpkh') { preimagine(w, t.program, 'witness (HASH160 equals the witness program)'); } else if (t.type === 'p2sh') { const last = ss.length ? ss[ss.length - 1] : null; if (last && last.data && kHash(last.data).equals(t.hash)) { const r = tipScriptIntern(last.data); if (r.kind === 'p2wpkh') { const k = w.length >= 2 ? w[w.length - 1] : null; if (k && isKey(k) && kHash(k).equals(r.program)) out.push({ what: 'public-key-revealed', detail: 'P2SH-P2WPKH: redeem script revealed in the scriptSig and public key revealed in the witness' }); else out.push({ what: 'script-revealed', detail: 'P2SH-P2WPKH: redeem script revealed in the scriptSig (the key would be in the witness; not found there)' }); } else if (r.kind === 'p2wsh') { const ws = w.length ? w[w.length - 1] : null; if (ws && sha256(ws).equals(r.program)) scriptDezvaluit(ws, 'P2SH-P2WSH witness script'); else out.push({ what: 'script-revealed-unclassified', detail: 'P2SH-P2WSH: redeem script revealed in the scriptSig, but no witness script hashing to its program was found in the witness' }); } else scriptDezvaluit(last.data, 'P2SH redeem script (HASH160 equals the address hash)'); } } else if (t.type === 'p2wsh') { const ws = w.length ? w[w.length - 1] : null; if (ws && sha256(ws).equals(t.program)) scriptDezvaluit(ws, 'P2WSH witness script (SHA-256 equals the witness program)'); } else if (t.type === 'p2tr' && spendsTarget) { // only an input that spends THIS output says anything: the witness alone does not name the output it spends // BIP-341: key path = a single 64/65-byte signature; script path = [.., script, control block], control block = 1 + 32 (internal key) + 32k // the annex exists only when the witness has at least TWO elements (BIP-341); a lone 64-byte signature starting with 0x50 is a signature const items = w.length >= 2 && w[w.length - 1].length > 0 && w[w.length - 1][0] === 0x50 ? w.slice(0, -1) : w; if (items.length === 1 && (items[0].length === 64 || items[0].length === 65)) out.push({ what: 'taproot-key-path-spend', detail: 'spent on the key path: a Schnorr signature under the output key is in the witness' }); else if (items.length >= 2) { const cb = items[items.length - 1]; if (cb.length >= 33 && (cb.length - 33) % 32 === 0) out.push({ what: 'taproot-internal-key-revealed', detail: 'spent on the script path: the control block in the witness carries the internal public key' }); } } else if ((t.type === 'p2pk' || t.type === 'p2ms') && spendsTarget) { if (ss.length >= 1 || w.length >= 1) out.push({ what: 'spend-seen', detail: 'a spend of this output is on chain (the key was already in the output script)' }); } return out; } /** the findings that mean a public key (or the script holding the keys) is readable from the input */ export const DOVEZI_REVELATOARE = Object.freeze(new Set(['public-key-revealed', 'keys-revealed-in-script', 'taproot-internal-key-revealed'])); /** does this scriptPubKey pay the target script exactly (byte equality)? */ export const platesteTinta = (t, spk) => Buffer.from(spk).equals(t.scriptPubKey);