UNPKG

@scrow/tapscript

Version:

A development build of tapscript. Built for escrow.

249 lines (221 loc) 7.29 kB
import { Buff } from '@cmdcode/buff' import { parse_tx } from '../../tx/index.js' import { encode_script } from '../../script/encode.js' import { encode_tapscript } from '../../tap/encode.js' import { parse_txinput } from '../utils.js' import TxEncoder from '../../tx/encode.js' import { SigHashOptions, ScriptData, TxBytes, TxData, TxInput, TxOutput } from '../../../types/index.js' import * as assert from '../../../assert.js' const { encode_idx, encode_locktime, encode_sequence, encode_txid, encode_value, encode_version } = TxEncoder const VALID_HASH_TYPES = [ 0x00, 0x01, 0x02, 0x03, 0x81, 0x82, 0x83 ] export function hash_tx ( template : TxBytes | TxData, config : SigHashOptions = {} ) : Buff { // Unpack configuration. const { script, txindex, sigflag = 0x00, extflag = 0x00, key_version = 0x00, separator_pos = 0xFFFFFFFF } = config // Normalize the txdata object. const tx = parse_tx(template) // Unpack the txdata object. const { version, vin: input, vout: output, locktime } = tx // Parse the input we are signing from the config. const txinput = parse_txinput(tx, config) // Unpack the txinput object. const { txid, vout, sequence, witness = [] } = txinput // Check if we are using a valid hash type. if (!VALID_HASH_TYPES.includes(sigflag)) { // If the sigflag is an invalid type, throw error. throw new Error('Invalid hash type: ' + String(sigflag)) } if (extflag < 0 || extflag > 127) { // If the extflag is out of range, throw error. throw new Error('Extention flag out of range: ' + String(extflag)) } let { extension } = config if (script !== undefined) { extension = encode_tapscript(script) } // Define the parameters of the transaction. 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 hashtag = Buff.str('TapSighash').digest // Begin building our preimage. const preimage = [ hashtag, // Buffer input with hashtag, // 2x hashed strings. Buff.num(0x00, 1), // Add zero-byte. Buff.num(sigflag, 1), // Commit to signature flag. encode_version(version), // Commit to tx version. encode_locktime(locktime) // Commit to tx locktime. ] if (!is_anypay) { // If flag ANYONE_CAN_PAY is not set, // then commit to all inputs. const prevouts = input.map(e => get_prevout(e)) preimage.push( hash_outpoints(input), // Commit to txid/vout for each input. hash_amounts(prevouts), // Commit to prevout amount for each input. hash_scripts(prevouts), // Commit to prevout script for each input. hash_sequence(input) // Commit to sequence value for each input. ) } if ((sigflag & 0x03) < 2 || (sigflag & 0x03) > 3) { // If neither SINGLE or NONE flags are set, // include a commitment to all outputs. preimage.push(hash_outputs(output)) } // At this step, we include the spend type. preimage.push(Buff.num(spendType, 1)) if (is_anypay) { // If ANYONE_CAN_PAY flag is set, then we will // provide a commitment to the input being signed. const { value, scriptPubKey } = get_prevout(txinput) preimage.push( encode_txid(txid), // Commit to the input txid. encode_idx(vout), // Commit to the input vout index. encode_value(value), // Commit to the input's prevout value. encode_script(scriptPubKey, true), // Commit to the input's prevout script. encode_sequence(sequence) // Commit to the input's sequence value. ) } else { // Otherwise, we must have already included a commitment // to all inputs in the tx, so simply add a commitment to // the index of the input we are signing for. assert.ok(typeof txindex === 'number') preimage.push(Buff.num(txindex, 4).reverse()) } if (annex !== undefined) { // If an annex has been set, include it here. preimage.push(annex) } if ((sigflag & 0x03) === 0x03) { // If the SINGLE flag is set, then include a // commitment to the output which is adjacent // to the input that we are signing for. assert.ok(typeof txindex === 'number') preimage.push(hash_output(output[txindex])) } if (extension !== undefined) { // If we are extending this signature to include // other commitments (such as a tapleaf), then we // will add it to the preimage here. preimage.push( Buff.bytes(extension), // Extention data (in bytes). Buff.num(key_version), // Key version (reserved for future upgrades). Buff.num(separator_pos, 4) // If OP_CODESEPARATOR is used, this must be set. ) } // Useful for debugging the preimage stack. // console.log(preimage.map(e => Buff.raw(e).hex)) return Buff.join(preimage).digest } export function hash_outpoints ( vin : TxInput[] ) : Buff { const stack = [] for (const { txid, vout } of vin) { stack.push(encode_txid(txid)) stack.push(encode_idx(vout)) } return Buff.join(stack).digest } export function hash_sequence ( vin : TxInput[] ) : Buff { const stack = [] for (const { sequence } of vin) { stack.push(encode_sequence(sequence)) } return Buff.join(stack).digest } export function hash_amounts ( prevouts : TxOutput[] ) : Buff { const stack = [] for (const { value } of prevouts) { stack.push(encode_value(value)) } return Buff.join(stack).digest } export function hash_scripts ( prevouts : TxOutput[] ) : Buff { const stack = [] for (const { scriptPubKey } of prevouts) { stack.push(encode_script(scriptPubKey, true)) } return Buff.join(stack).digest } export function hash_outputs ( vout : TxOutput[] ) : Buff { const stack = [] for (const { value, scriptPubKey } of vout) { stack.push(encode_value(value)) stack.push(encode_script(scriptPubKey, true)) } return Buff.join(stack).digest } export function hash_output ( vout : TxOutput ) : Buff { return Buff.join([ encode_value(vout.value), encode_script(vout.scriptPubKey, true) ]).digest } function get_annex_data ( witness ?: ScriptData[] ) : Buff | undefined { // If no witness exists, return undefined. if (witness === undefined) return // If there are less than two elements, return undefined. if (witness.length < 2) return // Define the last element as the annex. let annex = witness.at(-1) // If element is a string, if (typeof annex === 'string') { // convert to bytes. annex = Buff.hex(annex) } // If first byte is annex flag, if ( annex instanceof Uint8Array && annex[0] === 0x50 ) { // return a digest of the annex. return Buff.raw(annex).add_varint('be').digest } // Else, return undefined. return undefined } function get_prevout (vin : TxInput) : TxOutput { if (vin.prevout === undefined) { throw new Error('Prevout data missing for input: ' + String(vin.txid)) } return vin.prevout }