UNPKG

@bsv/overlay

Version:
85 lines 2.98 kB
import { createHash } from 'node:crypto'; export const BASM_ZERO_HASH = '0000000000000000000000000000000000000000000000000000000000000000'; function sha256d(buffer) { const first = createHash('sha256').update(buffer).digest(); return createHash('sha256').update(first).digest(); } function assertHashHex(hash, label) { if (!/^[0-9a-fA-F]{64}$/.test(hash)) { throw new Error(`${label} must be 32 bytes of hex`); } } function displayHexToInternal(hash) { assertHashHex(hash, 'hash'); return Buffer.from(hash, 'hex').reverse(); } function internalToDisplayHex(hash) { return Buffer.from(hash).reverse().toString('hex'); } function normalizeAdmittedTxids(admitted) { return admitted .map((item, originalIndex) => { if (typeof item === 'string') { return { txid: item, blockIndex: originalIndex }; } return item; }) .sort((a, b) => a.blockIndex - b.blockIndex) .map(item => item.txid.toLowerCase()); } /** * Computes a BRC-136 BASM root from admitted topic txids. * * The API accepts normal display-order txid hex because that is what the BSV * TypeScript stack exposes at its public boundaries. Hashing is performed on * internal byte order as required by the BRC, and the returned root is display * order for JSON/wire compatibility. */ export function computeBasmRoot(admitted) { const txids = normalizeAdmittedTxids(admitted); if (txids.length === 0) { return BASM_ZERO_HASH; } let layer = txids.map(txid => displayHexToInternal(txid)); if (layer.length === 1) { return internalToDisplayHex(layer[0]); } while (layer.length > 1) { const next = []; for (let i = 0; i < layer.length; i += 2) { const left = layer[i]; const right = i + 1 < layer.length ? layer[i + 1] : left; next.push(sha256d(Buffer.concat([left, right]))); } layer = next; } return internalToDisplayHex(layer[0]); } /** * Computes the BRC-136 cumulative Topic Anchor Chain hash: * SHA256d(prevTac || blockHash || basmRoot), with all inputs reversed to * internal byte order before hashing and the output returned as display hex. */ export function computeTac(prevTac, blockHash, basmRoot) { const input = Buffer.concat([ displayHexToInternal(prevTac.toLowerCase()), displayHexToInternal(blockHash.toLowerCase()), displayHexToInternal(basmRoot.toLowerCase()) ]); return internalToDisplayHex(sha256d(input)); } export function extractMerkleProofMetadata(txid, proof) { if (proof === undefined) { return undefined; } const leaf = proof.path[0]?.find(candidate => candidate.hash === txid); if (leaf === undefined) { return undefined; } return { blockHeight: proof.blockHeight, blockIndex: leaf.offset, merkleRoot: proof.computeRoot(txid) }; } //# sourceMappingURL=BASM.js.map