UNPKG

@vbyte/btc-dev

Version:

Batteries-included toolset for plebian bitcoin development

96 lines (95 loc) 4.04 kB
import { Buff } from '@vbyte/buff'; import { Assert } from '@vbyte/micro-lib'; import { hash340, sha256 } from '@vbyte/micro-lib/hash'; import { encode_tapscript } from '../../lib/taproot/encode.js'; import { parse_tx } from '../../lib/tx/parse.js'; import * as CONST from '../../const.js'; import { parse_txinput, get_annex_data, get_prevout } from './util.js'; import { encode_txin_vout, encode_tx_locktime, encode_txin_sequence, encode_txin_txid, encode_vout_value, encode_tx_version, encode_script_data } from '../../lib/tx/encode.js'; export function hash_taproot_tx(template, config = {}) { const preimage = get_taproot_tx_preimage(template, config); return hash340('TapSighash', preimage); } export function get_taproot_tx_preimage(template, config = {}) { const { script, txindex, sigflag = 0x00, extflag = 0x00, key_version = 0x00, separator_pos = 0xFFFFFFFF } = config; const tx = parse_tx(template); const { version, vin: input, vout: output, locktime } = tx; const txinput = parse_txinput(tx, config); const { txid, vout, sequence, witness = [] } = txinput; if (!CONST.SIGHASH_TAPROOT.includes(sigflag)) { throw new Error('Invalid hash type: ' + String(sigflag)); } if (extflag < 0 || extflag > 127) { throw new Error('Extention flag out of range: ' + String(extflag)); } let { extension } = config; if (script !== undefined) { extension = encode_tapscript(script).hex; } const is_anypay = (sigflag & 0x80) === 0x80; const annex = get_annex_data(witness); const annexBit = (annex !== undefined) ? 1 : 0; const extendBit = (extension !== undefined) ? 1 : 0; const spendType = ((extflag + extendBit) * 2) + annexBit; const preimage = [ Buff.num(0x00, 1), Buff.num(sigflag, 1), encode_tx_version(version), encode_tx_locktime(locktime) ]; if (!is_anypay) { const prevouts = input.map(e => get_prevout(e)); preimage.push(bip341_hash_outpoints(input), bip341_hash_amounts(prevouts), bip341_hash_scripts(prevouts), bip341_hash_sequence(input)); } if ((sigflag & 0x03) < 2 || (sigflag & 0x03) > 3) { preimage.push(bip341_hash_outputs(output)); } preimage.push(Buff.num(spendType, 1)); if (is_anypay) { const { value, script_pk } = get_prevout(txinput); preimage.push(encode_txin_txid(txid), encode_txin_vout(vout), encode_vout_value(value), encode_script_data(script_pk), encode_txin_sequence(sequence)); } else { Assert.ok(typeof txindex === 'number'); preimage.push(Buff.num(txindex, 4).reverse()); } if (annex !== undefined) { preimage.push(annex); } if ((sigflag & 0x03) === 0x03) { Assert.ok(typeof txindex === 'number'); preimage.push(bip341_hash_output(output[txindex])); } if (extension !== undefined) { preimage.push(Buff.bytes(extension), Buff.num(key_version), Buff.num(separator_pos, 4, 'le')); } return Buff.join(preimage); } export function bip341_hash_outpoints(vin) { const stack = []; for (const { txid, vout } of vin) { stack.push(encode_txin_txid(txid)); stack.push(encode_txin_vout(vout)); } return sha256(Buff.join(stack)); } export function bip341_hash_sequence(vin) { return sha256(...vin.map(vin => encode_txin_sequence(vin.sequence))); } export function bip341_hash_amounts(prevouts) { return sha256(...prevouts.map(prevout => encode_vout_value(prevout.value))); } export function bip341_hash_scripts(prevouts) { return sha256(...prevouts.map(prevout => encode_script_data(prevout.script_pk))); } export function bip341_hash_outputs(vout) { const stack = []; for (const { value, script_pk } of vout) { stack.push(encode_vout_value(value)); stack.push(encode_script_data(script_pk)); } return sha256(...stack); } export function bip341_hash_output(vout) { return sha256(encode_vout_value(vout.value), encode_script_data(vout.script_pk)); }