@vbyte/btc-dev
Version:
Batteries-included toolset for plebian bitcoin development
96 lines (95 loc) • 4.04 kB
JavaScript
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));
}