@vbyte/btc-dev
Version:
Batteries-included toolset for plebian bitcoin development
153 lines (134 loc) • 4.09 kB
text/typescript
import { Buff } from '@vbyte/buff'
import { hash160, hash256 } from '@vbyte/micro-lib/hash'
import { Assert } from '@vbyte/micro-lib'
import { parse_txinput } from './util.js'
import {
prefix_script_size,
decode_script
} from '@/lib/script/index.js'
import {
encode_txin_vout,
encode_tx_locktime,
encode_txin_sequence,
encode_txin_txid,
encode_vout_value,
encode_tx_version,
parse_tx
} from '@/lib/tx/index.js'
import {
SigHashOptions,
TxData,
TxInput,
TxOutput
} from '@/types/index.js'
import * as CONST from '@/const.js'
export function hash_segwit_tx (
txdata : TxData,
options : SigHashOptions = {}
) : Buff {
// Unpack the sigflag from our config object.
const { sigflag = 0x01, txindex } = options
// Normalize the tx into JSON format.
const tx = parse_tx(txdata)
// Check if the ANYONECANPAY flag is set.
const is_anypay = (sigflag & 0x80) === 0x80
// Save a normalized version of the sigflag.
const flag = sigflag % 0x80
// Check if the sigflag exists as a valid type.
if (!CONST.SIGHASH_SEGWIT.includes(flag)) {
throw new Error('Invalid hash type: ' + String(sigflag))
}
// Unpack the tx object.
const { version, vin, vout, locktime } = tx
// Parse the input we are signing from the config.
const txinput = parse_txinput(tx, options)
// Unpack the chosen input for signing.
const { txid, vout: prevIdx, prevout, sequence } = txinput
// Unpack the prevout for the chosen input.
const { value } = prevout ?? {}
// Check if a prevout value is provided.
if (value === undefined) {
throw new Error('Prevout value is empty!')
}
// Initialize our script variable from the config.
let { pubkey, script } = options
// Check if a pubkey is provided (instead of a script).
if (script === undefined && pubkey !== undefined) {
const pkhash = hash160(pubkey).hex
script = `76a914${String(pkhash)}88ac`
}
// Make sure that some form of script has been provided.
if (script === undefined) {
throw new Error('No pubkey / script has been set!')
}
// Throw if OP_CODESEPARATOR is used in a script.
if (decode_script(script).includes('OP_CODESEPARATOR')) {
throw new Error('This library does not currently support the use of OP_CODESEPARATOR in segwit scripts.')
}
const sighash = [
encode_tx_version(version),
bip143_hash_prevouts(vin, is_anypay),
bip143_hash_sequence(vin, flag, is_anypay),
encode_txin_txid(txid),
encode_txin_vout(prevIdx),
prefix_script_size(script),
encode_vout_value(value),
encode_txin_sequence(sequence),
bip143_hash_outputs(vout, flag, txindex),
encode_tx_locktime(locktime),
Buff.num(sigflag, 4).reverse()
]
return hash256(Buff.join(sighash))
}
export function bip143_hash_prevouts (
vin : TxInput[],
isAnypay ?: boolean
) : Uint8Array {
if (isAnypay === true) {
return Buff.num(0, 32)
}
const stack = []
for (const { txid, vout } of vin) {
stack.push(encode_txin_txid(txid))
stack.push(encode_txin_vout(vout))
}
return hash256(Buff.join(stack))
}
export function bip143_hash_sequence (
vin : TxInput[],
sigflag : number,
isAnyPay : boolean
) : Uint8Array {
if (isAnyPay || sigflag !== 0x01) {
return Buff.num(0, 32)
}
const stack = []
for (const { sequence } of vin) {
stack.push(encode_txin_sequence(sequence))
}
return hash256(Buff.join(stack))
}
export function bip143_hash_outputs (
vout : TxOutput[],
sigflag : number,
idx ?: number
) : Uint8Array {
const stack = []
if (sigflag === 0x01) {
for (const { value, script_pk } of vout) {
stack.push(encode_vout_value(value))
stack.push(prefix_script_size(script_pk))
}
return hash256(Buff.join(stack))
}
if (sigflag === 0x03) {
Assert.ok(idx !== undefined)
if (idx < vout.length) {
const { value, script_pk } = vout[idx]
stack.push(encode_vout_value(value))
stack.push(prefix_script_size(script_pk))
return hash256(Buff.join(stack))
}
}
return Buff.num(0, 32)
}