'use strict'
Object.defineProperty(exports, '__esModule', { value: true })
const bufferutils = require('@oipwg/bitcoinjs-lib/src/bufferutils')
const bcrypto = require('@oipwg/bitcoinjs-lib/src/crypto')
const bscript = require('@oipwg/bitcoinjs-lib/src/script')
const script = require('@oipwg/bitcoinjs-lib/src/script')
const types = require('@oipwg/bitcoinjs-lib/src/types')
const typeforce = require('typeforce')
const varuint = require('varuint-bitcoin')
const bip174 = require('bip174')
const bip174Utils = require('bip174/src/lib/utils')
const addressLib = require('@oipwg/bitcoinjs-lib/src/address')
const crypto = require('@oipwg/bitcoinjs-lib/src/crypto')
const ecpair = require('@oipwg/bitcoinjs-lib/src/ecpair')
const networks = require('@oipwg/bitcoinjs-lib/src/networks')
const payments = require('@oipwg/bitcoinjs-lib/src/payments')
const transaction = require('@oipwg/bitcoinjs-lib/src/transaction')
function varSliceSize (someScript) {
const length = someScript.length
return varuint.encodingLength(length) + length
}
function vectorSize (someVector) {
const length = someVector.length
return (
varuint.encodingLength(length) +
someVector.reduce((sum, witness) => {
return sum + varSliceSize(witness)
}, 0)
)
}
const EMPTY_SCRIPT = Buffer.allocUnsafe(0)
const EMPTY_WITNESS = []
const ZERO = Buffer.from(
'0000000000000000000000000000000000000000000000000000000000000000',
'hex'
)
const ONE = Buffer.from(
'0000000000000000000000000000000000000000000000000000000000000001',
'hex'
)
const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex')
const BLANK_OUTPUT = {
script: EMPTY_SCRIPT,
valueBuffer: VALUE_UINT64_MAX
}
function isOutput (out) {
return out.value !== undefined
}
class FloTransaction {
constructor () {
this.version = 1
this.locktime = 0
this.ins = []
this.outs = []
this.floData = Buffer.allocUnsafe(0)
}
static fromBuffer (buffer, _NO_STRICT) {
const bufferReader = new bufferutils.BufferReader(buffer)
const tx = new FloTransaction()
tx.version = bufferReader.readInt32()
const marker = bufferReader.readUInt8()
const flag = bufferReader.readUInt8()
let hasWitnesses = false
if (
marker === FloTransaction.ADVANCED_TRANSACTION_MARKER &&
flag === FloTransaction.ADVANCED_TRANSACTION_FLAG
) {
hasWitnesses = true
} else {
bufferReader.offset -= 2
}
const vinLen = bufferReader.readVarInt()
for (let i = 0; i < vinLen; ++i) {
tx.ins.push({
hash: bufferReader.readSlice(32),
index: bufferReader.readUInt32(),
script: bufferReader.readVarSlice(),
sequence: bufferReader.readUInt32(),
witness: EMPTY_WITNESS
})
}
const voutLen = bufferReader.readVarInt()
for (let i = 0; i < voutLen; ++i) {
tx.outs.push({
value: bufferReader.readUInt64(),
script: bufferReader.readVarSlice()
})
}
if (hasWitnesses) {
for (let i = 0; i < vinLen; ++i) {
tx.ins[i].witness = bufferReader.readVector()
}
// was this pointless?
if (!tx.hasWitnesses()) { throw new Error('FloTransaction has superfluous witness data') }
}
tx.locktime = bufferReader.readUInt32()
if (tx.version >= 2) { tx.floData = bufferReader.readVarSlice() }
if (_NO_STRICT) return tx
if (bufferReader.offset !== buffer.length) { throw new Error('FloTransaction has unexpected data') }
return tx
}
static fromHex (hex) {
return FloTransaction.fromBuffer(Buffer.from(hex, 'hex'), false)
}
static isCoinbaseHash (buffer) {
typeforce(types.Hash256bit, buffer)
for (let i = 0; i < 32; ++i) {
if (buffer[i] !== 0) return false
}
return true
}
isCoinbase () {
return (
this.ins.length === 1 && FloTransaction.isCoinbaseHash(this.ins[0].hash)
)
}
addInput (hash, index, sequence, scriptSig) {
typeforce(
types.tuple(
types.Hash256bit,
types.UInt32,
types.maybe(types.UInt32),
types.maybe(types.Buffer)
),
arguments
)
if (types.Null(sequence)) {
sequence = FloTransaction.DEFAULT_SEQUENCE
}
// Add the input and return the input's index
return (
this.ins.push({
hash,
index,
script: scriptSig || EMPTY_SCRIPT,
sequence: sequence,
witness: EMPTY_WITNESS
}) - 1
)
}
addOutput (scriptPubKey, value) {
typeforce(types.tuple(types.Buffer, types.Satoshi), arguments)
// Add the output and return the output's index
return (
this.outs.push({
script: scriptPubKey,
value
}) - 1
)
}
hasWitnesses () {
return this.ins.some(x => {
return x.witness.length !== 0
})
}
weight () {
const base = this.byteLength(false)
const total = this.byteLength(true)
return base * 3 + total
}
virtualSize () {
return Math.ceil(this.weight() / 4)
}
byteLength (_ALLOW_WITNESS = true) {
const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses()
return (
(hasWitnesses ? 10 : 8) +
(this.version >= 2 ? varSliceSize(this.floData) : 0) +
varuint.encodingLength(this.ins.length) +
varuint.encodingLength(this.outs.length) +
this.ins.reduce((sum, input) => {
return sum + 40 + varSliceSize(input.script)
}, 0) +
this.outs.reduce((sum, output) => {
return sum + 8 + varSliceSize(output.script)
}, 0) +
(hasWitnesses
? this.ins.reduce((sum, input) => {
return sum + vectorSize(input.witness)
}, 0)
: 0)
)
}
clone () {
const newTx = new FloTransaction()
newTx.version = this.version
newTx.locktime = this.locktime
newTx.floData = this.floData
newTx.ins = this.ins.map(txIn => {
return {
hash: txIn.hash,
index: txIn.index,
script: txIn.script,
sequence: txIn.sequence,
witness: txIn.witness
}
})
newTx.outs = this.outs.map(txOut => {
return {
script: txOut.script,
value: txOut.value
}
})
return newTx
}
/**
* Hash floTransaction for signing a specific input.
*
* Bitcoin uses a different hash for each signed floTransaction input.
* This method copies the floTransaction, makes the necessary changes based on the
* hashType, and then hashes the result.
* This hash can then be used to sign the provided floTransaction input.
*/
hashForSignature (inIndex, prevOutScript, hashType) {
typeforce(
types.tuple(types.UInt32, types.Buffer, /* types.UInt8 */ types.Number),
arguments
)
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29
if (inIndex >= this.ins.length) return ONE
// ignore OP_CODESEPARATOR
const ourScript = bscript.compile(
bscript.decompile(prevOutScript).filter(x => {
return x !== script.OPS.OP_CODESEPARATOR
})
)
const txTmp = this.clone()
// SIGHASH_NONE: ignore all outputs? (wildcard payee)
if ((hashType & 0x1f) === FloTransaction.SIGHASH_NONE) {
txTmp.outs = []
// ignore sequence numbers (except at inIndex)
txTmp.ins.forEach((input, i) => {
if (i === inIndex) return
input.sequence = 0
})
// SIGHASH_SINGLE: ignore all outputs, except at the same index?
} else if ((hashType & 0x1f) === FloTransaction.SIGHASH_SINGLE) {
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60
if (inIndex >= this.outs.length) return ONE
// truncate outputs after
txTmp.outs.length = inIndex + 1
// "blank" outputs before
for (let i = 0; i < inIndex; i++) {
txTmp.outs[i] = BLANK_OUTPUT
}
// ignore sequence numbers (except at inIndex)
txTmp.ins.forEach((input, y) => {
if (y === inIndex) return
input.sequence = 0
})
}
// SIGHASH_ANYONECANPAY: ignore inputs entirely?
if (hashType & FloTransaction.SIGHASH_ANYONECANPAY) {
txTmp.ins = [txTmp.ins[inIndex]]
txTmp.ins[0].script = ourScript
// SIGHASH_ALL: only ignore input scripts
} else {
// "blank" others input scripts
txTmp.ins.forEach(input => {
input.script = EMPTY_SCRIPT
})
txTmp.ins[inIndex].script = ourScript
}
// serialize and hash
const buffer = Buffer.allocUnsafe(txTmp.byteLength(false) + 4)
buffer.writeInt32LE(hashType, buffer.length - 4)
txTmp.__toBuffer(buffer, 0, false)
return bcrypto.hash256(buffer)
}
hashForWitnessV0 (inIndex, prevOutScript, value, hashType) {
typeforce(
types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32),
arguments
)
let tbuffer = Buffer.from([])
let bufferWriter
let hashOutputs = ZERO
let hashPrevouts = ZERO
let hashSequence = ZERO
if (!(hashType & FloTransaction.SIGHASH_ANYONECANPAY)) {
tbuffer = Buffer.allocUnsafe(36 * this.ins.length)
bufferWriter = new bufferutils.BufferWriter(tbuffer, 0)
this.ins.forEach(txIn => {
bufferWriter.writeSlice(txIn.hash)
bufferWriter.writeUInt32(txIn.index)
})
hashPrevouts = bcrypto.hash256(tbuffer)
}
if (
!(hashType & FloTransaction.SIGHASH_ANYONECANPAY) &&
(hashType & 0x1f) !== FloTransaction.SIGHASH_SINGLE &&
(hashType & 0x1f) !== FloTransaction.SIGHASH_NONE
) {
tbuffer = Buffer.allocUnsafe(4 * this.ins.length)
bufferWriter = new bufferutils.BufferWriter(tbuffer, 0)
this.ins.forEach(txIn => {
bufferWriter.writeUInt32(txIn.sequence)
})
hashSequence = bcrypto.hash256(tbuffer)
}
if (
(hashType & 0x1f) !== FloTransaction.SIGHASH_SINGLE &&
(hashType & 0x1f) !== FloTransaction.SIGHASH_NONE
) {
const txOutsSize = this.outs.reduce((sum, output) => {
return sum + 8 + varSliceSize(output.script)
}, 0)
tbuffer = Buffer.allocUnsafe(txOutsSize)
bufferWriter = new bufferutils.BufferWriter(tbuffer, 0)
this.outs.forEach(out => {
bufferWriter.writeUInt64(out.value)
bufferWriter.writeVarSlice(out.script)
})
hashOutputs = bcrypto.hash256(tbuffer)
} else if (
(hashType & 0x1f) === FloTransaction.SIGHASH_SINGLE &&
inIndex < this.outs.length
) {
const output = this.outs[inIndex]
tbuffer = Buffer.allocUnsafe(8 + varSliceSize(output.script))
bufferWriter = new bufferutils.BufferWriter(tbuffer, 0)
bufferWriter.writeUInt64(output.value)
bufferWriter.writeVarSlice(output.script)
hashOutputs = bcrypto.hash256(tbuffer)
}
tbuffer = Buffer.allocUnsafe(156 + varSliceSize(prevOutScript))
bufferWriter = new bufferutils.BufferWriter(tbuffer, 0)
const input = this.ins[inIndex]
bufferWriter.writeUInt32(this.version)
bufferWriter.writeSlice(hashPrevouts)
bufferWriter.writeSlice(hashSequence)
bufferWriter.writeSlice(input.hash)
bufferWriter.writeUInt32(input.index)
bufferWriter.writeVarSlice(prevOutScript)
bufferWriter.writeUInt64(value)
bufferWriter.writeUInt32(input.sequence)
bufferWriter.writeSlice(hashOutputs)
bufferWriter.writeUInt32(this.locktime)
if (this.version >= 2) { bufferWriter.writeVarSlice(this.floData) }
bufferWriter.writeUInt32(hashType)
return bcrypto.hash256(tbuffer)
}
getHash (forWitness) {
// wtxid for coinbase is always 32 bytes of 0x00
if (forWitness && this.isCoinbase()) return Buffer.alloc(32, 0)
return bcrypto.hash256(this.__toBuffer(undefined, undefined, forWitness))
}
getId () {
// floTransaction hash's are displayed in reverse order
return bufferutils.reverseBuffer(this.getHash(false)).toString('hex')
}
toBuffer (buffer, initialOffset) {
return this.__toBuffer(buffer, initialOffset, true)
}
toHex () {
return this.toBuffer(undefined, undefined).toString('hex')
}
setInputScript (index, scriptSig) {
typeforce(types.tuple(types.Number, types.Buffer), arguments)
this.ins[index].script = scriptSig
}
setWitness (index, witness) {
typeforce(types.tuple(types.Number, [types.Buffer]), arguments)
this.ins[index].witness = witness
}
__toBuffer (buffer, initialOffset, _ALLOW_WITNESS = false) {
if (!buffer) buffer = Buffer.allocUnsafe(this.byteLength(_ALLOW_WITNESS))
const bufferWriter = new bufferutils.BufferWriter(
buffer,
initialOffset || 0
)
bufferWriter.writeInt32(this.version)
const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses()
if (hasWitnesses) {
bufferWriter.writeUInt8(FloTransaction.ADVANCED_TRANSACTION_MARKER)
bufferWriter.writeUInt8(FloTransaction.ADVANCED_TRANSACTION_FLAG)
}
bufferWriter.writeVarInt(this.ins.length)
this.ins.forEach(txIn => {
bufferWriter.writeSlice(txIn.hash)
bufferWriter.writeUInt32(txIn.index)
bufferWriter.writeVarSlice(txIn.script)
bufferWriter.writeUInt32(txIn.sequence)
})
bufferWriter.writeVarInt(this.outs.length)
this.outs.forEach(txOut => {
if (isOutput(txOut)) {
bufferWriter.writeUInt64(txOut.value)
} else {
bufferWriter.writeSlice(txOut.valueBuffer)
}
bufferWriter.writeVarSlice(txOut.script)
})
if (hasWitnesses) {
this.ins.forEach(input => {
bufferWriter.writeVector(input.witness)
})
}
bufferWriter.writeUInt32(this.locktime)
if (this.version >= 2) { bufferWriter.writeVarSlice(this.floData) }
// avoid slicing unless necessary
if (initialOffset !== undefined) { return buffer.slice(initialOffset, bufferWriter.offset) }
return buffer
}
}
FloTransaction.DEFAULT_SEQUENCE = 0xffffffff
FloTransaction.SIGHASH_ALL = 0x01
FloTransaction.SIGHASH_NONE = 0x02
FloTransaction.SIGHASH_SINGLE = 0x03
FloTransaction.SIGHASH_ANYONECANPAY = 0x80
FloTransaction.ADVANCED_TRANSACTION_MARKER = 0x00
FloTransaction.ADVANCED_TRANSACTION_FLAG = 0x01
/**
* These are the default arguments for a Psbt instance.
*/
const DEFAULT_OPTS = {
/**
* A bitcoinjs Network object. This is only used if you pass an `address`
* parameter to addOutput. Otherwise it is not needed and can be left default.
*/
network: networks.bitcoin,
/**
* When extractTransaction is called, the fee rate is checked.
* THIS IS NOT TO BE RELIED ON.
* It is only here as a last ditch effort to prevent sending a 500 BTC fee etc.
*/
maximumFeeRate: 5000
}
/**
* Psbt class can parse and generate a PSBT binary based off of the BIP174.
* There are 6 roles that this class fulfills. (Explained in BIP174)
*
* Creator: This can be done with `new Psbt()`
* Updater: This can be done with `psbt.addInput(input)`, `psbt.addInputs(inputs)`,
* `psbt.addOutput(output)`, `psbt.addOutputs(outputs)` when you are looking to
* add new inputs and outputs to the PSBT, and `psbt.updateGlobal(itemObject)`,
* `psbt.updateInput(itemObject)`, `psbt.updateOutput(itemObject)`
* addInput requires hash: Buffer | string; and index: number; as attributes
* and can also include any attributes that are used in updateInput method.
* addOutput requires script: Buffer; and value: number; and likewise can include
* data for updateOutput.
* For a list of what attributes should be what types. Check the bip174 library.
* Also, check the integration tests for some examples of usage.
* Signer: There are a few methods. signAllInputs and signAllInputsAsync, which will search all input
* information for your pubkey or pubkeyhash, and only sign inputs where it finds
* your info. Or you can explicitly sign a specific input with signInput and
* signInputAsync. For the async methods you can create a SignerAsync object
* and use something like a hardware wallet to sign with. (You must implement this)
* Combiner: psbts can be combined easily with `psbt.combine(psbt2, psbt3, psbt4 ...)`
* the psbt calling combine will always have precedence when a conflict occurs.
* Combine checks if the internal bitcoin transaction is the same, so be sure that
* all sequences, version, locktime, etc. are the same before combining.
* Input Finalizer: This role is fairly important. Not only does it need to construct
* the input scriptSigs and witnesses, but it SHOULD verify the signatures etc.
* Before running `psbt.finalizeAllInputs()` please run `psbt.validateSignaturesOfAllInputs()`
* Running any finalize method will delete any data in the input(s) that are no longer
* needed due to the finalized scripts containing the information.
* Transaction Extractor: This role will perform some checks before returning a
* Transaction object. Such as fee rate not being larger than maximumFeeRate etc.
*/
class FloPsbt {
constructor (opts = {}, data = new bip174.Psbt(new FloPsbtTransaction())) {
this.data = data
// set defaults
this.opts = Object.assign({}, DEFAULT_OPTS, opts)
this.__CACHE = {
__NON_WITNESS_UTXO_TX_CACHE: [],
__NON_WITNESS_UTXO_BUF_CACHE: [],
__TX_IN_CACHE: {},
__TX: this.data.globalMap.unsignedTx.tx
}
if (this.data.inputs.length === 0) this.setVersion(2)
// Make data hidden when enumerating
const dpew = (obj, attr, enumerable, writable) =>
Object.defineProperty(obj, attr, {
enumerable,
writable
})
dpew(this, '__CACHE', false, true)
dpew(this, 'opts', false, true)
}
static fromBase64 (data, opts = {}) {
const buffer = Buffer.from(data, 'base64')
return this.fromBuffer(buffer, opts)
}
static fromHex (data, opts = {}) {
const buffer = Buffer.from(data, 'hex')
return this.fromBuffer(buffer, opts)
}
static fromBuffer (buffer, opts = {}) {
const psbtBase = bip174.Psbt.fromBuffer(buffer, transactionFromBuffer)
const psbt = new FloPsbt(opts, psbtBase)
checkTxForDupeIns(psbt.__CACHE.__TX, psbt.__CACHE)
return psbt
}
get inputCount () {
return this.data.inputs.length
}
combine (...those) {
this.data.combine(...those.map(o => o.data))
return this
}
clone () {
// TODO: more efficient cloning
const res = FloPsbt.fromBuffer(this.data.toBuffer())
res.opts = JSON.parse(JSON.stringify(this.opts))
return res
}
setMaximumFeeRate (satoshiPerByte) {
check32Bit(satoshiPerByte) // 42.9 BTC per byte IS excessive... so throw
this.opts.maximumFeeRate = satoshiPerByte
}
setVersion (version) {
check32Bit(version)
checkInputsForPartialSig(this.data.inputs, 'setVersion')
const c = this.__CACHE
c.__TX.version = version
c.__EXTRACTED_TX = undefined
return this
}
setLocktime (locktime) {
check32Bit(locktime)
checkInputsForPartialSig(this.data.inputs, 'setLocktime')
const c = this.__CACHE
c.__TX.locktime = locktime
c.__EXTRACTED_TX = undefined
return this
}
setFloData (flodata) {
// check32Bit(locktime);
checkInputsForPartialSig(this.data.inputs, 'setFloData')
const c = this.__CACHE
c.__TX.floData = flodata
c.__EXTRACTED_TX = undefined
return this
}
setInputSequence (inputIndex, sequence) {
check32Bit(sequence)
checkInputsForPartialSig(this.data.inputs, 'setInputSequence')
const c = this.__CACHE
if (c.__TX.ins.length <= inputIndex) {
throw new Error('Input index too high')
}
c.__TX.ins[inputIndex].sequence = sequence
c.__EXTRACTED_TX = undefined
return this
}
addInputs (inputDatas) {
inputDatas.forEach(inputData => this.addInput(inputData))
return this
}
addInput (inputData) {
if (
arguments.length > 1 ||
!inputData ||
inputData.hash === undefined ||
inputData.index === undefined
) {
throw new Error(
'Invalid arguments for Psbt.addInput. ' +
'Requires single object with at least [hash] and [index]'
)
}
checkInputsForPartialSig(this.data.inputs, 'addInput')
const c = this.__CACHE
this.data.addInput(inputData)
const txIn = c.__TX.ins[c.__TX.ins.length - 1]
checkTxInputCache(c, txIn)
const inputIndex = this.data.inputs.length - 1
const input = this.data.inputs[inputIndex]
if (input.nonWitnessUtxo) {
addNonWitnessTxCache(this.__CACHE, input, inputIndex)
}
c.__FEE = undefined
c.__FEE_RATE = undefined
c.__EXTRACTED_TX = undefined
return this
}
addOutputs (outputDatas) {
outputDatas.forEach(outputData => this.addOutput(outputData))
return this
}
addOutput (outputData) {
if (
arguments.length > 1 ||
!outputData ||
outputData.value === undefined ||
(outputData.address === undefined && outputData.script === undefined)
) {
throw new Error(
'Invalid arguments for Psbt.addOutput. ' +
'Requires single object with at least [script or address] and [value]'
)
}
checkInputsForPartialSig(this.data.inputs, 'addOutput')
const { address } = outputData
if (typeof address === 'string') {
const { network } = this.opts
const script = addressLib.toOutputScript(address, network)
outputData = Object.assign(outputData, { script })
}
const c = this.__CACHE
this.data.addOutput(outputData)
c.__FEE = undefined
c.__FEE_RATE = undefined
c.__EXTRACTED_TX = undefined
return this
}
extractTransaction (disableFeeCheck) {
if (!this.data.inputs.every(isFinalized)) throw new Error('Not finalized')
const c = this.__CACHE
if (!disableFeeCheck) {
checkFees(this, c, this.opts)
}
if (c.__EXTRACTED_TX) return c.__EXTRACTED_TX
const tx = c.__TX.clone()
inputFinalizeGetAmts(this.data.inputs, tx, c, true)
return tx
}
getFeeRate () {
return getTxCacheValue(
'__FEE_RATE',
'fee rate',
this.data.inputs,
this.__CACHE
)
}
getFee () {
return getTxCacheValue('__FEE', 'fee', this.data.inputs, this.__CACHE)
}
finalizeAllInputs () {
bip174Utils.checkForInput(this.data.inputs, 0) // making sure we have at least one
range(this.data.inputs.length).forEach(idx => this.finalizeInput(idx))
return this
}
finalizeInput (inputIndex, finalScriptsFunc = getFinalScripts) {
const input = bip174Utils.checkForInput(this.data.inputs, inputIndex)
const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput(
inputIndex,
input,
this.__CACHE
)
if (!script) throw new Error(`No script found for input #${inputIndex}`)
checkPartialSigSighashes(input)
const { finalScriptSig, finalScriptWitness } = finalScriptsFunc(
inputIndex,
input,
script,
isSegwit,
isP2SH,
isP2WSH
)
if (finalScriptSig) this.data.updateInput(inputIndex, { finalScriptSig })
if (finalScriptWitness) { this.data.updateInput(inputIndex, { finalScriptWitness }) }
if (!finalScriptSig && !finalScriptWitness) { throw new Error(`Unknown error finalizing input #${inputIndex}`) }
this.data.clearFinalizedInput(inputIndex)
return this
}
validateSignaturesOfAllInputs () {
bip174Utils.checkForInput(this.data.inputs, 0) // making sure we have at least one
const results = range(this.data.inputs.length).map(idx =>
this.validateSignaturesOfInput(idx)
)
return results.reduce((final, res) => res === true && final, true)
}
validateSignaturesOfInput (inputIndex, pubkey) {
const input = this.data.inputs[inputIndex]
const partialSig = (input || {}).partialSig
if (!input || !partialSig || partialSig.length < 1) { throw new Error('No signatures to validate') }
const mySigs = pubkey
? partialSig.filter(sig => sig.pubkey.equals(pubkey))
: partialSig
if (mySigs.length < 1) throw new Error('No signatures for this pubkey')
const results = []
let hashCache
let scriptCache
let sighashCache
for (const pSig of mySigs) {
const sig = bscript.signature.decode(pSig.signature)
const { hash, script } =
sighashCache !== sig.hashType
? getHashForSig(
inputIndex,
Object.assign({}, input, { sighashType: sig.hashType }),
this.__CACHE
)
: { hash: hashCache, script: scriptCache }
sighashCache = sig.hashType
hashCache = hash
scriptCache = script
checkScriptForPubkey(pSig.pubkey, script, 'verify')
const keypair = ecpair.fromPublicKey(pSig.pubkey)
results.push(keypair.verify(hash, sig.signature))
}
return results.every(res => res === true)
}
signAllInputsHD (
hdKeyPair,
sighashTypes = [transaction.Transaction.SIGHASH_ALL]
) {
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
throw new Error('Need HDSigner to sign input')
}
const results = []
for (const i of range(this.data.inputs.length)) {
try {
this.signInputHD(i, hdKeyPair, sighashTypes)
results.push(true)
} catch (err) {
results.push(false)
}
}
if (results.every(v => v === false)) {
throw new Error('No inputs were signed')
}
return this
}
signAllInputsHDAsync (
hdKeyPair,
sighashTypes = [transaction.Transaction.SIGHASH_ALL]
) {
return new Promise((resolve, reject) => {
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
return reject(new Error('Need HDSigner to sign input'))
}
const results = []
const promises = []
for (const i of range(this.data.inputs.length)) {
promises.push(
this.signInputHDAsync(i, hdKeyPair, sighashTypes).then(
() => {
results.push(true)
},
() => {
results.push(false)
}
)
)
}
return Promise.all(promises).then(() => {
if (results.every(v => v === false)) {
return reject(new Error('No inputs were signed'))
}
resolve()
})
})
}
signInputHD (
inputIndex,
hdKeyPair,
sighashTypes = [transaction.Transaction.SIGHASH_ALL]
) {
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
throw new Error('Need HDSigner to sign input')
}
const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair)
signers.forEach(signer => this.signInput(inputIndex, signer, sighashTypes))
return this
}
signInputHDAsync (
inputIndex,
hdKeyPair,
sighashTypes = [transaction.Transaction.SIGHASH_ALL]
) {
return new Promise((resolve, reject) => {
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
return reject(new Error('Need HDSigner to sign input'))
}
const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair)
const promises = signers.map(signer =>
this.signInputAsync(inputIndex, signer, sighashTypes)
)
return Promise.all(promises)
.then(() => {
resolve()
})
.catch(reject)
})
}
signAllInputs (
keyPair,
sighashTypes = [transaction.Transaction.SIGHASH_ALL]
) {
if (!keyPair || !keyPair.publicKey) { throw new Error('Need Signer to sign input') }
// TODO: Add a pubkey/pubkeyhash cache to each input
// as input information is added, then eventually
// optimize this method.
const results = []
for (const i of range(this.data.inputs.length)) {
try {
this.signInput(i, keyPair, sighashTypes)
results.push(true)
} catch (err) {
console.error(err)
results.push(false)
}
}
if (results.every(v => v === false)) {
throw new Error('No inputs were signed')
}
return this
}
signAllInputsAsync (
keyPair,
sighashTypes = [transaction.Transaction.SIGHASH_ALL]
) {
return new Promise((resolve, reject) => {
if (!keyPair || !keyPair.publicKey) { return reject(new Error('Need Signer to sign input')) }
// TODO: Add a pubkey/pubkeyhash cache to each input
// as input information is added, then eventually
// optimize this method.
const results = []
const promises = []
for (const [i] of this.data.inputs.entries()) {
promises.push(
this.signInputAsync(i, keyPair, sighashTypes).then(
() => {
results.push(true)
},
() => {
results.push(false)
}
)
)
}
return Promise.all(promises).then(() => {
if (results.every(v => v === false)) {
return reject(new Error('No inputs were signed'))
}
resolve()
})
})
}
signInput (
inputIndex,
keyPair,
sighashTypes = [transaction.Transaction.SIGHASH_ALL]
) {
if (!keyPair || !keyPair.publicKey) { throw new Error('Need Signer to sign input') }
const { hash, sighashType } = getHashAndSighashType(
this.data.inputs,
inputIndex,
keyPair.publicKey,
this.__CACHE,
sighashTypes
)
const partialSig = [
{
pubkey: keyPair.publicKey,
signature: bscript.signature.encode(keyPair.sign(hash), sighashType)
}
]
this.data.updateInput(inputIndex, { partialSig })
return this
}
signInputAsync (
inputIndex,
keyPair,
sighashTypes = [transaction.Transaction.SIGHASH_ALL]
) {
return new Promise((resolve, reject) => {
if (!keyPair || !keyPair.publicKey) { return reject(new Error('Need Signer to sign input')) }
const { hash, sighashType } = getHashAndSighashType(
this.data.inputs,
inputIndex,
keyPair.publicKey,
this.__CACHE,
sighashTypes
)
Promise.resolve(keyPair.sign(hash)).then(signature => {
const partialSig = [
{
pubkey: keyPair.publicKey,
signature: bscript.signature.encode(signature, sighashType)
}
]
this.data.updateInput(inputIndex, { partialSig })
resolve()
})
})
}
toBuffer () {
return this.data.toBuffer()
}
toHex () {
return this.data.toHex()
}
toBase64 () {
return this.data.toBase64()
}
updateGlobal (updateData) {
this.data.updateGlobal(updateData)
return this
}
updateInput (inputIndex, updateData) {
this.data.updateInput(inputIndex, updateData)
if (updateData.nonWitnessUtxo) {
addNonWitnessTxCache(
this.__CACHE,
this.data.inputs[inputIndex],
inputIndex
)
}
return this
}
updateOutput (outputIndex, updateData) {
this.data.updateOutput(outputIndex, updateData)
return this
}
addUnknownKeyValToGlobal (keyVal) {
this.data.addUnknownKeyValToGlobal(keyVal)
return this
}
addUnknownKeyValToInput (inputIndex, keyVal) {
this.data.addUnknownKeyValToInput(inputIndex, keyVal)
return this
}
addUnknownKeyValToOutput (outputIndex, keyVal) {
this.data.addUnknownKeyValToOutput(outputIndex, keyVal)
return this
}
clearFinalizedInput (inputIndex) {
this.data.clearFinalizedInput(inputIndex)
return this
}
}
/**
* This function is needed to pass to the bip174 base class's fromBuffer.
* It takes the "transaction buffer" portion of the psbt buffer and returns a
* Transaction (From the bip174 library) interface.
*/
const transactionFromBuffer = buffer => new FloPsbtTransaction(buffer)
/**
* This class implements the Transaction interface from bip174 library.
* It contains a @oipwg/bitcoinjs-lib Transaction object.
*/
class FloPsbtTransaction {
constructor (buffer = Buffer.from([2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])) {
this.tx = FloTransaction.fromBuffer(buffer)
checkTxEmpty(this.tx)
Object.defineProperty(this, 'tx', {
enumerable: false,
writable: true
})
}
getInputOutputCounts () {
return {
inputCount: this.tx.ins.length,
outputCount: this.tx.outs.length
}
}
addInput (input) {
if (
input.hash === undefined ||
input.index === undefined ||
(!Buffer.isBuffer(input.hash) && typeof input.hash !== 'string') ||
typeof input.index !== 'number'
) {
throw new Error('Error adding input.')
}
const hash =
typeof input.hash === 'string'
? bufferutils.reverseBuffer(Buffer.from(input.hash, 'hex'))
: input.hash
this.tx.addInput(hash, input.index, input.sequence)
}
addOutput (output) {
if (
output.script === undefined ||
output.value === undefined ||
!Buffer.isBuffer(output.script) ||
typeof output.value !== 'number'
) {
throw new Error('Error adding output.')
}
this.tx.addOutput(output.script, output.value)
}
toBuffer () {
return this.tx.toBuffer()
}
}
function canFinalize (input, script, scriptType) {
switch (scriptType) {
case 'pubkey':
case 'pubkeyhash':
case 'witnesspubkeyhash':
return hasSigs(1, input.partialSig)
case 'multisig': {
const p2ms = payments.p2ms({ output: script })
return hasSigs(p2ms.m, input.partialSig, p2ms.pubkeys)
}
default:
return false
}
}
function hasSigs (neededSigs, partialSig, pubkeys) {
if (!partialSig) return false
let sigs
if (pubkeys) {
sigs = pubkeys
.map(pkey => {
const pubkey = ecpair.fromPublicKey(pkey, { compressed: true })
.publicKey
return partialSig.find(pSig => pSig.pubkey.equals(pubkey))
})
.filter(v => !!v)
} else {
sigs = partialSig
}
if (sigs.length > neededSigs) throw new Error('Too many signatures')
return sigs.length === neededSigs
}
function isFinalized (input) {
return !!input.finalScriptSig || !!input.finalScriptWitness
}
function isPaymentFactory (payment) {
return script => {
try {
payment({ output: script })
return true
} catch (err) {
return false
}
}
}
const isP2MS = isPaymentFactory(payments.p2ms)
const isP2PK = isPaymentFactory(payments.p2pk)
const isP2PKH = isPaymentFactory(payments.p2pkh)
const isP2WPKH = isPaymentFactory(payments.p2wpkh)
const isP2WSHScript = isPaymentFactory(payments.p2wsh)
function check32Bit (num) {
if (
typeof num !== 'number' ||
num !== Math.floor(num) ||
num > 0xffffffff ||
num < 0
) {
throw new Error('Invalid 32 bit integer')
}
}
function checkFees (psbt, cache, opts) {
const feeRate = cache.__FEE_RATE || psbt.getFeeRate()
const vsize = cache.__EXTRACTED_TX.virtualSize()
const satoshis = feeRate * vsize
if (feeRate >= opts.maximumFeeRate) {
throw new Error(
`Warning: You are paying around ${(satoshis / 1e8).toFixed(8)} in ` +
`fees, which is ${feeRate} satoshi per byte for a transaction ` +
`with a VSize of ${vsize} bytes (segwit counted as 0.25 byte per ` +
'byte). Use setMaximumFeeRate method to raise your threshold, or ' +
'pass true to the first arg of extractTransaction.'
)
}
}
function checkInputsForPartialSig (inputs, action) {
inputs.forEach(input => {
let throws = false
let pSigs = []
if ((input.partialSig || []).length === 0) {
if (!input.finalScriptSig && !input.finalScriptWitness) return
pSigs = getPsigsFromInputFinalScripts(input)
} else {
pSigs = input.partialSig
}
pSigs.forEach(pSig => {
const { hashType } = bscript.signature.decode(pSig.signature)
const whitelist = []
const isAnyoneCanPay =
hashType & FloTransaction.SIGHASH_ANYONECANPAY
if (isAnyoneCanPay) whitelist.push('addInput')
const hashMod = hashType & 0x1f
switch (hashMod) {
case FloTransaction.SIGHASH_ALL:
break
case FloTransaction.SIGHASH_SINGLE:
case FloTransaction.SIGHASH_NONE:
whitelist.push('addOutput')
whitelist.push('setInputSequence')
break
}
if (whitelist.indexOf(action) === -1) {
throws = true
}
})
if (throws) {
throw new Error('Can not modify transaction, signatures exist.')
}
})
}
function checkPartialSigSighashes (input) {
if (!input.sighashType || !input.partialSig) return
const { partialSig, sighashType } = input
partialSig.forEach(pSig => {
const { hashType } = bscript.signature.decode(pSig.signature)
if (sighashType !== hashType) {
throw new Error('Signature sighash does not match input sighash type')
}
})
}
function checkScriptForPubkey (pubkey, script, action) {
const pubkeyHash = crypto.hash160(pubkey)
const decompiled = bscript.decompile(script)
if (decompiled === null) throw new Error('Unknown script error')
const hasKey = decompiled.some(element => {
if (typeof element === 'number') return false
return element.equals(pubkey) || element.equals(pubkeyHash)
})
if (!hasKey) {
throw new Error(
`Can not ${action} for this input with the key ${pubkey.toString('hex')}`
)
}
}
function checkTxEmpty (tx) {
const isEmpty = tx.ins.every(
input =>
input.script &&
input.script.length === 0 &&
input.witness &&
input.witness.length === 0
)
if (!isEmpty) {
throw new Error('Format Error: Transaction ScriptSigs are not empty')
}
}
function checkTxForDupeIns (tx, cache) {
tx.ins.forEach(input => {
checkTxInputCache(cache, input)
})
}
function checkTxInputCache (cache, input) {
const key =
bufferutils.reverseBuffer(Buffer.from(input.hash)).toString('hex') +
':' +
input.index
if (cache.__TX_IN_CACHE[key]) throw new Error('Duplicate input detected.')
cache.__TX_IN_CACHE[key] = 1
}
function scriptCheckerFactory (payment, paymentScriptName) {
return (inputIndex, scriptPubKey, redeemScript) => {
const redeemScriptOutput = payment({
redeem: { output: redeemScript }
}).output
if (!scriptPubKey.equals(redeemScriptOutput)) {
throw new Error(
`${paymentScriptName} for input #${inputIndex} doesn't match the scriptPubKey in the prevout`
)
}
}
}
const checkRedeemScript = scriptCheckerFactory(payments.p2sh, 'Redeem script')
const checkWitnessScript = scriptCheckerFactory(
payments.p2wsh,
'Witness script'
)
function getTxCacheValue (key, name, inputs, c) {
if (!inputs.every(isFinalized)) { throw new Error(`PSBT must be finalized to calculate ${name}`) }
if (key === '__FEE_RATE' && c.__FEE_RATE) return c.__FEE_RATE
if (key === '__FEE' && c.__FEE) return c.__FEE
let tx
let mustFinalize = true
if (c.__EXTRACTED_TX) {
tx = c.__EXTRACTED_TX
mustFinalize = false
} else {
tx = c.__TX.clone()
}
inputFinalizeGetAmts(inputs, tx, c, mustFinalize)
if (key === '__FEE_RATE') return c.__FEE_RATE
else if (key === '__FEE') return c.__FEE
}
function getFinalScripts (inputIndex, input, script, isSegwit, isP2SH, isP2WSH) {
const scriptType = classifyScript(script)
if (!canFinalize(input, script, scriptType)) { throw new Error(`Can not finalize input #${inputIndex}`) }
return prepareFinalScripts(
script,
scriptType,
input.partialSig,
isSegwit,
isP2SH,
isP2WSH
)
}
function prepareFinalScripts (
script,
scriptType,
partialSig,
isSegwit,
isP2SH,
isP2WSH
) {
let finalScriptSig
let finalScriptWitness
// Wow, the payments API is very handy
const payment = getPayment(script, scriptType, partialSig)
const p2wsh = !isP2WSH ? null : payments.p2wsh({ redeem: payment })
const p2sh = !isP2SH ? null : payments.p2sh({ redeem: p2wsh || payment })
if (isSegwit) {
if (p2wsh) {
finalScriptWitness = witnessStackToScriptWitness(p2wsh.witness)
} else {
finalScriptWitness = witnessStackToScriptWitness(payment.witness)
}
if (p2sh) {
finalScriptSig = p2sh.input
}
} else {
if (p2sh) {
finalScriptSig = p2sh.input
} else {
finalScriptSig = payment.input
}
}
return {
finalScriptSig,
finalScriptWitness
}
}
function getHashAndSighashType (
inputs,
inputIndex,
pubkey,
cache,
sighashTypes
) {
const input = bip174Utils.checkForInput(inputs, inputIndex)
const { hash, sighashType, script } = getHashForSig(
inputIndex,
input,
cache,
sighashTypes
)
checkScriptForPubkey(pubkey, script, 'sign')
return {
hash,
sighashType
}
}
function getHashForSig (inputIndex, input, cache, sighashTypes) {
const unsignedTx = cache.__TX
const sighashType =
input.sighashType || FloTransaction.SIGHASH_ALL
if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) {
const str = sighashTypeToString(sighashType)
throw new Error(
'Sighash type is not allowed. Retry the sign method passing the ' +
`sighashTypes array of whitelisted types. Sighash type: ${str}`
)
}
let hash
let script
if (input.nonWitnessUtxo) {
const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(
cache,
input,
inputIndex
)
const prevoutHash = unsignedTx.ins[inputIndex].hash
const utxoHash = nonWitnessUtxoTx.getHash()
// If a non-witness UTXO is provided, its hash must match the hash specified in the prevout
if (!prevoutHash.equals(utxoHash)) {
throw new Error(
`Non-witness UTXO hash for input #${inputIndex} doesn't match the hash specified in the prevout`
)
}
const prevoutIndex = unsignedTx.ins[inputIndex].index
const prevout = nonWitnessUtxoTx.outs[prevoutIndex]
if (input.redeemScript) {
// If a redeemScript is provided, the scriptPubKey must be for that redeemScript
checkRedeemScript(inputIndex, prevout.script, input.redeemScript)
script = input.redeemScript
} else {
script = prevout.script
}
if (isP2WSHScript(script)) {
if (!input.witnessScript) { throw new Error('Segwit input needs witnessScript if not P2WPKH') }
checkWitnessScript(inputIndex, script, input.witnessScript)
hash = unsignedTx.hashForWitnessV0(
inputIndex,
input.witnessScript,
prevout.value,
sighashType
)
script = input.witnessScript
} else if (isP2WPKH(script)) {
// P2WPKH uses the P2PKH template for prevoutScript when signing
const signingScript = payments.p2pkh({ hash: script.slice(2) }).output
hash = unsignedTx.hashForWitnessV0(
inputIndex,
signingScript,
prevout.value,
sighashType
)
} else {
hash = unsignedTx.hashForSignature(inputIndex, script, sighashType)
}
} else if (input.witnessUtxo) {
let _script // so we don't shadow the `let script` above
if (input.redeemScript) {
// If a redeemScript is provided, the scriptPubKey must be for that redeemScript
checkRedeemScript(
inputIndex,
input.witnessUtxo.script,
input.redeemScript
)
_script = input.redeemScript
} else {
_script = input.witnessUtxo.script
}
if (isP2WPKH(_script)) {
// P2WPKH uses the P2PKH template for prevoutScript when signing
const signingScript = payments.p2pkh({ hash: _script.slice(2) }).output
hash = unsignedTx.hashForWitnessV0(
inputIndex,
signingScript,
input.witnessUtxo.value,
sighashType
)
script = _script
} else if (isP2WSHScript(_script)) {
if (!input.witnessScript) { throw new Error('Segwit input needs witnessScript if not P2WPKH') }
checkWitnessScript(inputIndex, _script, input.witnessScript)
hash = unsignedTx.hashForWitnessV0(
inputIndex,
input.witnessScript,
input.witnessUtxo.value,
sighashType
)
// want to make sure the script we return is the actual meaningful script
script = input.witnessScript
} else {
throw new Error(
`Input #${inputIndex} has witnessUtxo but non-segwit script: ` +
`${_script.toString('hex')}`
)
}
} else {
throw new Error('Need a Utxo input item for signing')
}
return {
script,
sighashType,
hash
}
}
function getPayment (script, scriptType, partialSig) {
let payment
switch (scriptType) {
case 'multisig': {
const sigs = getSortedSigs(script, partialSig)
payment = payments.p2ms({
output: script,
signatures: sigs
})
break
}
case 'pubkey':
payment = payments.p2pk({
output: script,
signature: partialSig[0].signature
})
break
case 'pubkeyhash':
payment = payments.p2pkh({
output: script,
pubkey: partialSig[0].pubkey,
signature: partialSig[0].signature
})
break
case 'witnesspubkeyhash':
payment = payments.p2wpkh({
output: script,
pubkey: partialSig[0].pubkey,
signature: partialSig[0].signature
})
break
}
return payment
}
function getPsigsFromInputFinalScripts (input) {
const scriptItems = !input.finalScriptSig
? []
: bscript.decompile(input.finalScriptSig) || []
const witnessItems = !input.finalScriptWitness
? []
: bscript.decompile(input.finalScriptWitness) || []
return scriptItems
.concat(witnessItems)
.filter(item => {
return Buffer.isBuffer(item) && bscript.isCanonicalScriptSignature(item)
})
.map(sig => ({ signature: sig }))
}
function getScriptFromInput (inputIndex, input, cache) {
const unsignedTx = cache.__TX
const res = {
script: null,
isSegwit: false,
isP2SH: false,
isP2WSH: false
}
res.isP2SH = !!input.redeemScript
res.isP2WSH = !!input.witnessScript
if (input.witnessScript) {
res.script = input.witnessScript
} else if (input.redeemScript) {
res.script = input.redeemScript
} else {
if (input.nonWitnessUtxo) {
const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(
cache,
input,
inputIndex
)
const prevoutIndex = unsignedTx.ins[inputIndex].index
res.script = nonWitnessUtxoTx.outs[prevoutIndex].script
} else if (input.witnessUtxo) {
res.script = input.witnessUtxo.script
}
}
if (input.witnessScript || isP2WPKH(res.script)) {
res.isSegwit = true
}
return res
}
function getSignersFromHD (inputIndex, inputs, hdKeyPair) {
const input = bip174Utils.checkForInput(inputs, inputIndex)
if (!input.bip32Derivation || input.bip32Derivation.length === 0) {
throw new Error('Need bip32Derivation to sign with HD')
}
const myDerivations = input.bip32Derivation
.map(bipDv => {
if (bipDv.masterFingerprint.equals(hdKeyPair.fingerprint)) {
return bipDv
} else {
}
})
.filter(v => !!v)
if (myDerivations.length === 0) {
throw new Error(
'Need one bip32Derivation masterFingerprint to match the HDSigner fingerprint'
)
}
const signers = myDerivations.map(bipDv => {
const node = hdKeyPair.derivePath(bipDv.path)
if (!bipDv.pubkey.equals(node.publicKey)) {
throw new Error('pubkey did not match bip32Derivation')
}
return node
})
return signers
}
function getSortedSigs (script, partialSig) {
const p2ms = payments.p2ms({ output: script })
// for each pubkey in order of p2ms script
return p2ms.pubkeys
.map(pk => {
// filter partialSig array by pubkey being equal
return (
partialSig.filter(ps => {
return ps.pubkey.equals(pk)
})[0] || {}
).signature
// Any pubkey without a match will return undefined
// this last filter removes all the undefined items in the array.
})
.filter(v => !!v)
}
function scriptWitnessToWitnessStack (buffer) {
let offset = 0
function readSlice (n) {
offset += n
return buffer.slice(offset - n, offset)
}
function readVarInt () {
const vi = varuint.decode(buffer, offset)
offset += varuint.decode.bytes
return vi
}
function readVarSlice () {
return readSlice(readVarInt())
}
function readVector () {
const count = readVarInt()
const vector = []
for (let i = 0; i < count; i++) vector.push(readVarSlice())
return vector
}
return readVector()
}
function sighashTypeToString (sighashType) {
let text =
sighashType & FloTransaction.SIGHASH_ANYONECANPAY
? 'SIGHASH_ANYONECANPAY | '
: ''
const sigMod = sighashType & 0x1f
switch (sigMod) {
case FloTransaction.SIGHASH_ALL:
text += 'SIGHASH_ALL'
break
case FloTransaction.SIGHASH_SINGLE:
text += 'SIGHASH_SINGLE'
break
case FloTransaction.SIGHASH_NONE:
text += 'SIGHASH_NONE'
break
}
return text
}
function witnessStackToScriptWitness (witness) {
let buffer = Buffer.allocUnsafe(0)
function writeSlice (slice) {
buffer = Buffer.concat([buffer, Buffer.from(slice)])
}
function writeVarInt (i) {
const currentLen = buffer.length
const varintLen = varuint.encodingLength(i)
buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)])
varuint.encode(i, buffer, currentLen)
}
function writeVarSlice (slice) {
writeVarInt(slice.length)
writeSlice(slice)
}
function writeVector (vector) {
writeVarInt(vector.length)
vector.forEach(writeVarSlice)
}
writeVector(witness)
return buffer
}
function addNonWitnessTxCache (cache, input, inputIndex) {
cache.__NON_WITNESS_UTXO_BUF_CACHE[inputIndex] = input.nonWitnessUtxo
const tx = FloTransaction.fromBuffer(input.nonWitnessUtxo)
cache.__NON_WITNESS_UTXO_TX_CACHE[inputIndex] = tx
const self = cache
const selfIndex = inputIndex
delete input.nonWitnessUtxo
Object.defineProperty(input, 'nonWitnessUtxo', {
enumerable: true,
get () {
const buf = self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex]
const txCache = self.__NON_WITNESS_UTXO_TX_CACHE[selfIndex]
if (buf !== undefined) {
return buf
} else {
const newBuf = txCache.toBuffer()
self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = newBuf
return newBuf
}
},
set (data) {
self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = data
}
})
}
function inputFinalizeGetAmts (inputs, tx, cache, mustFinalize) {
let inputAmount = 0
inputs.forEach((input, idx) => {
if (mustFinalize && input.finalScriptSig) { tx.ins[idx].script = input.finalScriptSig }
if (mustFinalize && input.finalScriptWitness) {
tx.ins[idx].witness = scriptWitnessToWitnessStack(
input.finalScriptWitness
)
}
if (input.witnessUtxo) {
inputAmount += input.witnessUtxo.value
} else if (input.nonWitnessUtxo) {
const nwTx = nonWitnessUtxoTxFromCache(cache, input, idx)
const vout = tx.ins[idx].index
const out = nwTx.outs[vout]
inputAmount += out.value
}
})
const outputAmount = tx.outs.reduce((total, o) => total + o.value, 0)
const fee = inputAmount - outputAmount
if (fee < 0) {
throw new Error('Outputs are spending more than Inputs')
}
const bytes = tx.virtualSize()
cache.__FEE = fee
cache.__EXTRACTED_TX = tx
cache.__FEE_RATE = Math.floor(fee / bytes)
}
function nonWitnessUtxoTxFromCache (cache, input, inputIndex) {
const c = cache.__NON_WITNESS_UTXO_TX_CACHE
if (!c[inputIndex]) {
addNonWitnessTxCache(cache, input, inputIndex)
}
return c[inputIndex]
}
function classifyScript (script) {
if (isP2WPKH(script)) return 'witnesspubkeyhash'
if (isP2PKH(script)) return 'pubkeyhash'
if (isP2MS(script)) return 'multisig'
if (isP2PK(script)) return 'pubkey'
return 'nonstandard'
}
function range (n) {
return [...Array(n).keys()]
}
exports.FloPsbt = FloPsbt
exports.FloTransaction = FloTransaction
exports.FloPsbtTransaction = FloPsbtTransaction