@scure/btc-signer 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +151 -12
- package/index.ts +531 -119
- package/{index.js → lib/esm/index.js} +509 -135
- package/lib/esm/package.json +1 -0
- package/{index.d.ts → lib/index.d.ts} +650 -596
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +3035 -0
- package/package.json +26 -18
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { secp256k1 as _secp, schnorr } from '@noble/curves/secp256k1';
|
|
3
3
|
import { sha256 } from '@noble/hashes/sha256';
|
|
4
4
|
import { ripemd160 } from '@noble/hashes/ripemd160';
|
|
5
|
-
import { hex,
|
|
5
|
+
import { hex, createBase58check, bech32, bech32m } from '@scure/base';
|
|
6
6
|
import * as P from 'micro-packed';
|
|
7
7
|
const { ProjectivePoint: ProjPoint, sign: _signECDSA, getPublicKey: _pubECDSA } = _secp;
|
|
8
8
|
const CURVE_ORDER = _secp.CURVE.n;
|
|
@@ -13,7 +13,7 @@ const hash160 = (msg) => ripemd160(sha256(msg));
|
|
|
13
13
|
const sha256x2 = (...msgs) => sha256(sha256(concat(...msgs)));
|
|
14
14
|
const concat = P.concatBytes;
|
|
15
15
|
// Make base58check work
|
|
16
|
-
export const base58check =
|
|
16
|
+
export const base58check = createBase58check(sha256);
|
|
17
17
|
export function cloneDeep(obj) {
|
|
18
18
|
if (Array.isArray(obj))
|
|
19
19
|
return obj.map((i) => cloneDeep(i));
|
|
@@ -476,9 +476,9 @@ export const BTCArray = (t) => P.array(CompactSize, t);
|
|
|
476
476
|
// ui8a of size <CompactSize>
|
|
477
477
|
export const VarBytes = P.bytes(CompactSize);
|
|
478
478
|
export const RawInput = P.struct({
|
|
479
|
-
txid: P.bytes(32, true),
|
|
480
|
-
index: P.U32LE,
|
|
481
|
-
finalScriptSig: VarBytes,
|
|
479
|
+
txid: P.bytes(32, true), // hash(prev_tx),
|
|
480
|
+
index: P.U32LE, // output number of previous tx
|
|
481
|
+
finalScriptSig: VarBytes, // btc merges input and output script, executes it. If ok = tx passes
|
|
482
482
|
sequence: P.U32LE, // ?
|
|
483
483
|
});
|
|
484
484
|
export const RawOutput = P.struct({ amount: P.U64LE, script: VarBytes });
|
|
@@ -517,7 +517,7 @@ const BIP32Der = P.struct({
|
|
|
517
517
|
// Complex structure for PSBT fields
|
|
518
518
|
// <control byte with leaf version and parity bit> <internal key p> <C> <E> <AB>
|
|
519
519
|
const _TaprootControlBlock = P.struct({
|
|
520
|
-
version: P.U8,
|
|
520
|
+
version: P.U8, // With parity :(
|
|
521
521
|
internalKey: P.bytes(32),
|
|
522
522
|
merklePath: P.array(null, P.bytes(32)),
|
|
523
523
|
});
|
|
@@ -554,7 +554,7 @@ const PSBTGlobal = {
|
|
|
554
554
|
fallbackLocktime: [0x03, false, P.U32LE, [], [2], false],
|
|
555
555
|
inputCount: [0x04, false, CompactSizeLen, [2], [2], false],
|
|
556
556
|
outputCount: [0x05, false, CompactSizeLen, [2], [2], false],
|
|
557
|
-
txModifiable: [0x06, false, P.U8, [], [2], false],
|
|
557
|
+
txModifiable: [0x06, false, P.U8, [], [2], false], // TODO: bitfield
|
|
558
558
|
version: [0xfb, false, P.U32LE, [], [0, 2], false],
|
|
559
559
|
proprietary: [0xfc, BytesInf, BytesInf, [], [0, 2], false],
|
|
560
560
|
};
|
|
@@ -1662,6 +1662,115 @@ function validateOpts(opts) {
|
|
|
1662
1662
|
}
|
|
1663
1663
|
return Object.freeze(_opts);
|
|
1664
1664
|
}
|
|
1665
|
+
// Normalizes input
|
|
1666
|
+
function getPrevOut(input) {
|
|
1667
|
+
if (input.nonWitnessUtxo) {
|
|
1668
|
+
if (input.index === undefined)
|
|
1669
|
+
throw new Error('Unknown input index');
|
|
1670
|
+
return input.nonWitnessUtxo.outputs[input.index];
|
|
1671
|
+
}
|
|
1672
|
+
else if (input.witnessUtxo)
|
|
1673
|
+
return input.witnessUtxo;
|
|
1674
|
+
else
|
|
1675
|
+
throw new Error('Cannot find previous output info');
|
|
1676
|
+
}
|
|
1677
|
+
function normalizeInput(i, cur, allowedFields, disableScriptCheck = false) {
|
|
1678
|
+
let { nonWitnessUtxo, txid } = i;
|
|
1679
|
+
// String support for common fields. We usually prefer Uint8Array to avoid errors
|
|
1680
|
+
// like hex looking string accidentally passed, however, in case of nonWitnessUtxo
|
|
1681
|
+
// it is better to expect string, since constructing this complex object will be
|
|
1682
|
+
// difficult for user
|
|
1683
|
+
if (typeof nonWitnessUtxo === 'string')
|
|
1684
|
+
nonWitnessUtxo = hex.decode(nonWitnessUtxo);
|
|
1685
|
+
if (isBytes(nonWitnessUtxo))
|
|
1686
|
+
nonWitnessUtxo = RawTx.decode(nonWitnessUtxo);
|
|
1687
|
+
if (!('nonWitnessUtxo' in i) && nonWitnessUtxo === undefined)
|
|
1688
|
+
nonWitnessUtxo = cur?.nonWitnessUtxo;
|
|
1689
|
+
if (typeof txid === 'string')
|
|
1690
|
+
txid = hex.decode(txid);
|
|
1691
|
+
// TODO: if we have nonWitnessUtxo, we can extract txId from here
|
|
1692
|
+
if (txid === undefined)
|
|
1693
|
+
txid = cur?.txid;
|
|
1694
|
+
let res = { ...cur, ...i, nonWitnessUtxo, txid };
|
|
1695
|
+
if (!('nonWitnessUtxo' in i) && res.nonWitnessUtxo === undefined)
|
|
1696
|
+
delete res.nonWitnessUtxo;
|
|
1697
|
+
if (res.sequence === undefined)
|
|
1698
|
+
res.sequence = DEFAULT_SEQUENCE;
|
|
1699
|
+
if (res.tapMerkleRoot === null)
|
|
1700
|
+
delete res.tapMerkleRoot;
|
|
1701
|
+
res = mergeKeyMap(PSBTInput, res, cur, allowedFields);
|
|
1702
|
+
PSBTInputCoder.encode(res); // Validates that everything is correct at this point
|
|
1703
|
+
let prevOut;
|
|
1704
|
+
if (res.nonWitnessUtxo && res.index !== undefined)
|
|
1705
|
+
prevOut = res.nonWitnessUtxo.outputs[res.index];
|
|
1706
|
+
else if (res.witnessUtxo)
|
|
1707
|
+
prevOut = res.witnessUtxo;
|
|
1708
|
+
if (prevOut && !disableScriptCheck)
|
|
1709
|
+
checkScript(prevOut && prevOut.script, res.redeemScript, res.witnessScript);
|
|
1710
|
+
return res;
|
|
1711
|
+
}
|
|
1712
|
+
function getInputType(input, allowLegacyWitnessUtxo = false) {
|
|
1713
|
+
let txType = 'legacy';
|
|
1714
|
+
let defaultSighash = SignatureHash.ALL;
|
|
1715
|
+
const prevOut = getPrevOut(input);
|
|
1716
|
+
const first = OutScript.decode(prevOut.script);
|
|
1717
|
+
let type = first.type;
|
|
1718
|
+
let cur = first;
|
|
1719
|
+
const stack = [first];
|
|
1720
|
+
if (first.type === 'tr') {
|
|
1721
|
+
defaultSighash = SignatureHash.DEFAULT;
|
|
1722
|
+
return {
|
|
1723
|
+
txType: 'taproot',
|
|
1724
|
+
type: 'tr',
|
|
1725
|
+
last: first,
|
|
1726
|
+
lastScript: prevOut.script,
|
|
1727
|
+
defaultSighash,
|
|
1728
|
+
sighash: input.sighashType || defaultSighash,
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
else {
|
|
1732
|
+
if (first.type === 'wpkh' || first.type === 'wsh')
|
|
1733
|
+
txType = 'segwit';
|
|
1734
|
+
if (first.type === 'sh') {
|
|
1735
|
+
if (!input.redeemScript)
|
|
1736
|
+
throw new Error('inputType: sh without redeemScript');
|
|
1737
|
+
let child = OutScript.decode(input.redeemScript);
|
|
1738
|
+
if (child.type === 'wpkh' || child.type === 'wsh')
|
|
1739
|
+
txType = 'segwit';
|
|
1740
|
+
stack.push(child);
|
|
1741
|
+
cur = child;
|
|
1742
|
+
type += `-${child.type}`;
|
|
1743
|
+
}
|
|
1744
|
+
// wsh can be inside sh
|
|
1745
|
+
if (cur.type === 'wsh') {
|
|
1746
|
+
if (!input.witnessScript)
|
|
1747
|
+
throw new Error('inputType: wsh without witnessScript');
|
|
1748
|
+
let child = OutScript.decode(input.witnessScript);
|
|
1749
|
+
if (child.type === 'wsh')
|
|
1750
|
+
txType = 'segwit';
|
|
1751
|
+
stack.push(child);
|
|
1752
|
+
cur = child;
|
|
1753
|
+
type += `-${child.type}`;
|
|
1754
|
+
}
|
|
1755
|
+
const last = stack[stack.length - 1];
|
|
1756
|
+
if (last.type === 'sh' || last.type === 'wsh')
|
|
1757
|
+
throw new Error('inputType: sh/wsh cannot be terminal type');
|
|
1758
|
+
const lastScript = OutScript.encode(last);
|
|
1759
|
+
const res = {
|
|
1760
|
+
type,
|
|
1761
|
+
txType,
|
|
1762
|
+
last,
|
|
1763
|
+
lastScript,
|
|
1764
|
+
defaultSighash,
|
|
1765
|
+
sighash: input.sighashType || defaultSighash,
|
|
1766
|
+
};
|
|
1767
|
+
if (txType === 'legacy' && !allowLegacyWitnessUtxo && !input.nonWitnessUtxo) {
|
|
1768
|
+
throw new Error(`Transaction/sign: legacy input without nonWitnessUtxo, can result in attack that forces paying higher fees. Pass allowLegacyWitnessUtxo=true, if you sure`);
|
|
1769
|
+
}
|
|
1770
|
+
return res;
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
const toVsize = (weight) => Math.ceil(weight / 4);
|
|
1665
1774
|
export class Transaction {
|
|
1666
1775
|
constructor(opts = {}) {
|
|
1667
1776
|
this.global = {};
|
|
@@ -1816,7 +1925,7 @@ export class Transaction {
|
|
|
1816
1925
|
// We will lose some vectors -> smaller test coverage of preimages (very important!)
|
|
1817
1926
|
inputSighash(idx) {
|
|
1818
1927
|
this.checkInputIdx(idx);
|
|
1819
|
-
const sighash =
|
|
1928
|
+
const sighash = getInputType(this.inputs[idx], this.opts.allowLegacyWitnessUtxo).sighash;
|
|
1820
1929
|
// ALL or DEFAULT -- everything signed
|
|
1821
1930
|
// NONE -- all inputs + no outputs
|
|
1822
1931
|
// SINGLE -- all inputs + output with same index
|
|
@@ -1875,26 +1984,25 @@ export class Transaction {
|
|
|
1875
1984
|
get weight() {
|
|
1876
1985
|
if (!this.isFinal)
|
|
1877
1986
|
throw new Error('Transaction is not finalized');
|
|
1878
|
-
// TODO: Can we find out how much witnesses/script will be used before signing?
|
|
1879
1987
|
let out = 32;
|
|
1988
|
+
// Outputs
|
|
1880
1989
|
const outputs = this.outputs.map(outputBeforeSign);
|
|
1990
|
+
out += 4 * CompactSizeLen.encode(this.outputs.length).length;
|
|
1991
|
+
for (const o of outputs)
|
|
1992
|
+
out += 32 + 4 * VarBytes.encode(o.script).length;
|
|
1993
|
+
// Inputs
|
|
1881
1994
|
if (this.hasWitnesses)
|
|
1882
1995
|
out += 2;
|
|
1883
1996
|
out += 4 * CompactSizeLen.encode(this.inputs.length).length;
|
|
1884
|
-
|
|
1885
|
-
for (const i of this.inputs)
|
|
1997
|
+
for (const i of this.inputs) {
|
|
1886
1998
|
out += 160 + 4 * VarBytes.encode(i.finalScriptSig || P.EMPTY).length;
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
if (this.hasWitnesses) {
|
|
1890
|
-
for (const i of this.inputs)
|
|
1891
|
-
if (i.finalScriptWitness)
|
|
1892
|
-
out += RawWitness.encode(i.finalScriptWitness).length;
|
|
1999
|
+
if (this.hasWitnesses && i.finalScriptWitness)
|
|
2000
|
+
out += RawWitness.encode(i.finalScriptWitness).length;
|
|
1893
2001
|
}
|
|
1894
2002
|
return out;
|
|
1895
2003
|
}
|
|
1896
2004
|
get vsize() {
|
|
1897
|
-
return
|
|
2005
|
+
return toVsize(this.weight);
|
|
1898
2006
|
}
|
|
1899
2007
|
toBytes(withScriptSig = false, withWitness = false) {
|
|
1900
2008
|
return RawTx.encode({
|
|
@@ -1938,42 +2046,10 @@ export class Transaction {
|
|
|
1938
2046
|
return this.inputs.length;
|
|
1939
2047
|
}
|
|
1940
2048
|
// Modification
|
|
1941
|
-
normalizeInput(i, cur, allowedFields) {
|
|
1942
|
-
let { nonWitnessUtxo, txid } = i;
|
|
1943
|
-
// String support for common fields. We usually prefer Uint8Array to avoid errors (like hex looking string accidentally passed),
|
|
1944
|
-
// however in case of nonWitnessUtxo it is better to expect string, since constructing this complex object will be difficult for user
|
|
1945
|
-
if (typeof nonWitnessUtxo === 'string')
|
|
1946
|
-
nonWitnessUtxo = hex.decode(nonWitnessUtxo);
|
|
1947
|
-
if (isBytes(nonWitnessUtxo))
|
|
1948
|
-
nonWitnessUtxo = RawTx.decode(nonWitnessUtxo);
|
|
1949
|
-
if (nonWitnessUtxo === undefined)
|
|
1950
|
-
nonWitnessUtxo = cur?.nonWitnessUtxo;
|
|
1951
|
-
if (typeof txid === 'string')
|
|
1952
|
-
txid = hex.decode(txid);
|
|
1953
|
-
if (txid === undefined)
|
|
1954
|
-
txid = cur?.txid;
|
|
1955
|
-
let res = { ...cur, ...i, nonWitnessUtxo, txid };
|
|
1956
|
-
if (res.nonWitnessUtxo === undefined)
|
|
1957
|
-
delete res.nonWitnessUtxo;
|
|
1958
|
-
if (res.sequence === undefined)
|
|
1959
|
-
res.sequence = DEFAULT_SEQUENCE;
|
|
1960
|
-
if (res.tapMerkleRoot === null)
|
|
1961
|
-
delete res.tapMerkleRoot;
|
|
1962
|
-
res = mergeKeyMap(PSBTInput, res, cur, allowedFields);
|
|
1963
|
-
PSBTInputCoder.encode(res); // Validates that everything is correct at this point
|
|
1964
|
-
let prevOut;
|
|
1965
|
-
if (res.nonWitnessUtxo && res.index !== undefined)
|
|
1966
|
-
prevOut = res.nonWitnessUtxo.outputs[res.index];
|
|
1967
|
-
else if (res.witnessUtxo)
|
|
1968
|
-
prevOut = res.witnessUtxo;
|
|
1969
|
-
if (prevOut && !this.opts.disableScriptCheck)
|
|
1970
|
-
checkScript(prevOut && prevOut.script, res.redeemScript, res.witnessScript);
|
|
1971
|
-
return res;
|
|
1972
|
-
}
|
|
1973
2049
|
addInput(input, _ignoreSignStatus = false) {
|
|
1974
2050
|
if (!_ignoreSignStatus && !this.signStatus().addInput)
|
|
1975
2051
|
throw new Error('Tx has signed inputs, cannot add new one');
|
|
1976
|
-
this.inputs.push(
|
|
2052
|
+
this.inputs.push(normalizeInput(input, undefined, undefined, this.opts.disableScriptCheck));
|
|
1977
2053
|
return this.inputs.length - 1;
|
|
1978
2054
|
}
|
|
1979
2055
|
updateInput(idx, input, _ignoreSignStatus = false) {
|
|
@@ -1984,7 +2060,7 @@ export class Transaction {
|
|
|
1984
2060
|
if (!status.addInput || status.inputs.includes(idx))
|
|
1985
2061
|
allowedFields = PSBTInputUnsignedKeys;
|
|
1986
2062
|
}
|
|
1987
|
-
this.inputs[idx] =
|
|
2063
|
+
this.inputs[idx] = normalizeInput(input, this.inputs[idx], allowedFields, this.opts.disableScriptCheck);
|
|
1988
2064
|
}
|
|
1989
2065
|
// Output stuff
|
|
1990
2066
|
checkOutputIdx(idx) {
|
|
@@ -2016,7 +2092,7 @@ export class Transaction {
|
|
|
2016
2092
|
if (res.script &&
|
|
2017
2093
|
!this.opts.allowUnknownOutputs &&
|
|
2018
2094
|
OutScript.decode(res.script).type === 'unknown') {
|
|
2019
|
-
throw new Error('Transaction/output: unknown output script type, there is a chance that input is unspendable. Pass
|
|
2095
|
+
throw new Error('Transaction/output: unknown output script type, there is a chance that input is unspendable. Pass allowUnknownOutputs=true, if you sure');
|
|
2020
2096
|
}
|
|
2021
2097
|
if (!this.opts.disableScriptCheck)
|
|
2022
2098
|
checkScript(res.script, res.redeemScript, res.witnessScript);
|
|
@@ -2045,7 +2121,7 @@ export class Transaction {
|
|
|
2045
2121
|
get fee() {
|
|
2046
2122
|
let res = 0n;
|
|
2047
2123
|
for (const i of this.inputs) {
|
|
2048
|
-
const prevOut =
|
|
2124
|
+
const prevOut = getPrevOut(i);
|
|
2049
2125
|
if (!prevOut)
|
|
2050
2126
|
throw new Error('Empty input amount');
|
|
2051
2127
|
res += prevOut.amount;
|
|
@@ -2121,7 +2197,7 @@ export class Transaction {
|
|
|
2121
2197
|
throw new Error(`Invalid prevOutScript array=${prevOutScript}`);
|
|
2122
2198
|
const out = [
|
|
2123
2199
|
P.U8.encode(0),
|
|
2124
|
-
P.U8.encode(hashType),
|
|
2200
|
+
P.U8.encode(hashType), // U8 sigHash
|
|
2125
2201
|
P.I32LE.encode(this.version),
|
|
2126
2202
|
P.U32LE.encode(this.lockTime),
|
|
2127
2203
|
];
|
|
@@ -2156,85 +2232,11 @@ export class Transaction {
|
|
|
2156
2232
|
out.push(tapLeafHash(leafScript, leafVer), P.U8.encode(0), P.I32LE.encode(codeSeparator));
|
|
2157
2233
|
return schnorr.utils.taggedHash('TapSighash', ...out);
|
|
2158
2234
|
}
|
|
2159
|
-
// Utils for sign/finalize
|
|
2160
|
-
// Used pretty often, should be fast
|
|
2161
|
-
prevOut(input) {
|
|
2162
|
-
if (input.nonWitnessUtxo) {
|
|
2163
|
-
if (input.index === undefined)
|
|
2164
|
-
throw new Error('Unknown input index');
|
|
2165
|
-
return input.nonWitnessUtxo.outputs[input.index];
|
|
2166
|
-
}
|
|
2167
|
-
else if (input.witnessUtxo)
|
|
2168
|
-
return input.witnessUtxo;
|
|
2169
|
-
else
|
|
2170
|
-
throw new Error('Cannot find previous output info');
|
|
2171
|
-
}
|
|
2172
|
-
inputType(input) {
|
|
2173
|
-
let txType = 'legacy';
|
|
2174
|
-
let defaultSighash = SignatureHash.ALL;
|
|
2175
|
-
const prevOut = this.prevOut(input);
|
|
2176
|
-
const first = OutScript.decode(prevOut.script);
|
|
2177
|
-
let type = first.type;
|
|
2178
|
-
let cur = first;
|
|
2179
|
-
const stack = [first];
|
|
2180
|
-
if (first.type === 'tr') {
|
|
2181
|
-
defaultSighash = SignatureHash.DEFAULT;
|
|
2182
|
-
return {
|
|
2183
|
-
txType: 'taproot',
|
|
2184
|
-
type: 'tr',
|
|
2185
|
-
last: first,
|
|
2186
|
-
lastScript: prevOut.script,
|
|
2187
|
-
defaultSighash,
|
|
2188
|
-
sighash: input.sighashType || defaultSighash,
|
|
2189
|
-
};
|
|
2190
|
-
}
|
|
2191
|
-
else {
|
|
2192
|
-
if (first.type === 'wpkh' || first.type === 'wsh')
|
|
2193
|
-
txType = 'segwit';
|
|
2194
|
-
if (first.type === 'sh') {
|
|
2195
|
-
if (!input.redeemScript)
|
|
2196
|
-
throw new Error('inputType: sh without redeemScript');
|
|
2197
|
-
let child = OutScript.decode(input.redeemScript);
|
|
2198
|
-
if (child.type === 'wpkh' || child.type === 'wsh')
|
|
2199
|
-
txType = 'segwit';
|
|
2200
|
-
stack.push(child);
|
|
2201
|
-
cur = child;
|
|
2202
|
-
type += `-${child.type}`;
|
|
2203
|
-
}
|
|
2204
|
-
// wsh can be inside sh
|
|
2205
|
-
if (cur.type === 'wsh') {
|
|
2206
|
-
if (!input.witnessScript)
|
|
2207
|
-
throw new Error('inputType: wsh without witnessScript');
|
|
2208
|
-
let child = OutScript.decode(input.witnessScript);
|
|
2209
|
-
if (child.type === 'wsh')
|
|
2210
|
-
txType = 'segwit';
|
|
2211
|
-
stack.push(child);
|
|
2212
|
-
cur = child;
|
|
2213
|
-
type += `-${child.type}`;
|
|
2214
|
-
}
|
|
2215
|
-
const last = stack[stack.length - 1];
|
|
2216
|
-
if (last.type === 'sh' || last.type === 'wsh')
|
|
2217
|
-
throw new Error('inputType: sh/wsh cannot be terminal type');
|
|
2218
|
-
const lastScript = OutScript.encode(last);
|
|
2219
|
-
const res = {
|
|
2220
|
-
type,
|
|
2221
|
-
txType,
|
|
2222
|
-
last,
|
|
2223
|
-
lastScript,
|
|
2224
|
-
defaultSighash,
|
|
2225
|
-
sighash: input.sighashType || defaultSighash,
|
|
2226
|
-
};
|
|
2227
|
-
if (txType === 'legacy' && !this.opts.allowLegacyWitnessUtxo && !input.nonWitnessUtxo) {
|
|
2228
|
-
throw new Error(`Transaction/sign: legacy input without nonWitnessUtxo, can result in attack that forces paying higher fees. Pass allowLegacyWitnessUtxo=true, if you sure`);
|
|
2229
|
-
}
|
|
2230
|
-
return res;
|
|
2231
|
-
}
|
|
2232
|
-
}
|
|
2233
2235
|
// Signer can be privateKey OR instance of bip32 HD stuff
|
|
2234
2236
|
signIdx(privateKey, idx, allowedSighash, _auxRand) {
|
|
2235
2237
|
this.checkInputIdx(idx);
|
|
2236
2238
|
const input = this.inputs[idx];
|
|
2237
|
-
const inputType = this.
|
|
2239
|
+
const inputType = getInputType(input, this.opts.allowLegacyWitnessUtxo);
|
|
2238
2240
|
// Handle BIP32 HDKey
|
|
2239
2241
|
if (!isBytes(privateKey)) {
|
|
2240
2242
|
if (!input.bip32Derivation || !input.bip32Derivation.length)
|
|
@@ -2279,11 +2281,11 @@ export class Transaction {
|
|
|
2279
2281
|
}
|
|
2280
2282
|
// Actual signing
|
|
2281
2283
|
// Taproot
|
|
2282
|
-
const prevOut =
|
|
2284
|
+
const prevOut = getPrevOut(input);
|
|
2283
2285
|
if (inputType.txType === 'taproot') {
|
|
2284
2286
|
if (input.tapBip32Derivation)
|
|
2285
2287
|
throw new Error('tapBip32Derivation unsupported');
|
|
2286
|
-
const prevOuts = this.inputs.map(
|
|
2288
|
+
const prevOuts = this.inputs.map(getPrevOut);
|
|
2287
2289
|
const prevOutScript = prevOuts.map((i) => i.script);
|
|
2288
2290
|
const amount = prevOuts.map((i) => i.amount);
|
|
2289
2291
|
let signed = false;
|
|
@@ -2383,7 +2385,7 @@ export class Transaction {
|
|
|
2383
2385
|
if (this.fee < 0n)
|
|
2384
2386
|
throw new Error('Outputs spends more than inputs amount');
|
|
2385
2387
|
const input = this.inputs[idx];
|
|
2386
|
-
const inputType = this.
|
|
2388
|
+
const inputType = getInputType(input, this.opts.allowLegacyWitnessUtxo);
|
|
2387
2389
|
// Taproot finalize
|
|
2388
2390
|
if (inputType.txType === 'taproot') {
|
|
2389
2391
|
if (input.tapKeySig)
|
|
@@ -2567,7 +2569,7 @@ export class Transaction {
|
|
|
2567
2569
|
}
|
|
2568
2570
|
clone() {
|
|
2569
2571
|
// deepClone probably faster, but this enforces that encoding is valid
|
|
2570
|
-
return Transaction.fromPSBT(this.toPSBT(
|
|
2572
|
+
return Transaction.fromPSBT(this.toPSBT(this.opts.PSBTVersion), this.opts);
|
|
2571
2573
|
}
|
|
2572
2574
|
}
|
|
2573
2575
|
// User facing API?
|
|
@@ -2621,4 +2623,376 @@ export function PSBTCombine(psbts) {
|
|
|
2621
2623
|
tx.combine(Transaction.fromPSBT(psbts[i]));
|
|
2622
2624
|
return tx.toPSBT();
|
|
2623
2625
|
}
|
|
2624
|
-
|
|
2626
|
+
function estimateInput(inputType, input, opts) {
|
|
2627
|
+
let script = P.EMPTY, witness;
|
|
2628
|
+
// schnorr sig is always 64 bytes. except for cases when sighash is not default!
|
|
2629
|
+
if (inputType.txType === 'taproot') {
|
|
2630
|
+
const SCHNORR_SIG_SIZE = inputType.sighash !== SignatureHash.DEFAULT ? 65 : 64;
|
|
2631
|
+
if (input.tapInternalKey && !P.equalBytes(input.tapInternalKey, TAPROOT_UNSPENDABLE_KEY)) {
|
|
2632
|
+
witness = [new Uint8Array(SCHNORR_SIG_SIZE)];
|
|
2633
|
+
}
|
|
2634
|
+
else if (input.tapLeafScript) {
|
|
2635
|
+
// If user want to select specific leaf (which can signed, it is possible to remove all other leafs manually);
|
|
2636
|
+
// Sort leafs by control block length.
|
|
2637
|
+
const leafs = input.tapLeafScript.sort((a, b) => TaprootControlBlock.encode(a[0]).length - TaprootControlBlock.encode(b[0]).length);
|
|
2638
|
+
for (const [cb, _script] of leafs) {
|
|
2639
|
+
// Last byte is version
|
|
2640
|
+
const script = _script.slice(0, -1);
|
|
2641
|
+
const outScript = OutScript.decode(script);
|
|
2642
|
+
let signatures = [];
|
|
2643
|
+
if (outScript.type === 'tr_ms') {
|
|
2644
|
+
const m = outScript.m;
|
|
2645
|
+
for (let i = 0; i < m; i++)
|
|
2646
|
+
signatures.push(new Uint8Array(SCHNORR_SIG_SIZE));
|
|
2647
|
+
const n = outScript.pubkeys.length - m;
|
|
2648
|
+
for (let i = 0; i < n; i++)
|
|
2649
|
+
signatures.push(P.EMPTY);
|
|
2650
|
+
}
|
|
2651
|
+
else if (outScript.type === 'tr_ns') {
|
|
2652
|
+
for (const _pub of outScript.pubkeys)
|
|
2653
|
+
signatures.push(new Uint8Array(SCHNORR_SIG_SIZE));
|
|
2654
|
+
}
|
|
2655
|
+
else
|
|
2656
|
+
throw new Error('Finalize: Unknown tapLeafScript');
|
|
2657
|
+
// Witness is stack, so last element will be used first
|
|
2658
|
+
witness = signatures.reverse().concat([script, TaprootControlBlock.encode(cb)]);
|
|
2659
|
+
break;
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
else
|
|
2663
|
+
throw new Error('estimateInput/taproot: unknown input');
|
|
2664
|
+
}
|
|
2665
|
+
else {
|
|
2666
|
+
// It is possible to grind signatures until it has minimal size (but changing fee value +N satoshi),
|
|
2667
|
+
// which will make estimations exact. But will be very hard for multi sig (need to make sure all signatures has small size).
|
|
2668
|
+
const SIG_SIZE = 72; // Maximum size of signatures
|
|
2669
|
+
const PUB_KEY_SIZE = 33;
|
|
2670
|
+
let inputScript = P.EMPTY;
|
|
2671
|
+
let inputWitness = [];
|
|
2672
|
+
if (inputType.last.type === 'ms') {
|
|
2673
|
+
const m = inputType.last.m;
|
|
2674
|
+
const sig = [0];
|
|
2675
|
+
for (let i = 0; i < m; i++)
|
|
2676
|
+
sig.push(new Uint8Array(SIG_SIZE));
|
|
2677
|
+
inputScript = Script.encode(sig);
|
|
2678
|
+
}
|
|
2679
|
+
else if (inputType.last.type === 'pk') {
|
|
2680
|
+
// 71 sig + 1 sighash
|
|
2681
|
+
inputScript = Script.encode([new Uint8Array(SIG_SIZE)]);
|
|
2682
|
+
}
|
|
2683
|
+
else if (inputType.last.type === 'pkh') {
|
|
2684
|
+
inputScript = Script.encode([new Uint8Array(SIG_SIZE), new Uint8Array(PUB_KEY_SIZE)]);
|
|
2685
|
+
}
|
|
2686
|
+
else if (inputType.last.type === 'wpkh') {
|
|
2687
|
+
inputScript = P.EMPTY;
|
|
2688
|
+
inputWitness = [new Uint8Array(SIG_SIZE), new Uint8Array(PUB_KEY_SIZE)];
|
|
2689
|
+
}
|
|
2690
|
+
else if (inputType.last.type === 'unknown' && !opts.allowUnknownInputs)
|
|
2691
|
+
throw new Error('Unknown inputs not allowed');
|
|
2692
|
+
if (inputType.type.includes('wsh-')) {
|
|
2693
|
+
// P2WSH
|
|
2694
|
+
if (inputScript.length && inputType.lastScript.length) {
|
|
2695
|
+
inputWitness = Script.decode(inputScript).map((i) => {
|
|
2696
|
+
if (i === 0)
|
|
2697
|
+
return P.EMPTY;
|
|
2698
|
+
if (isBytes(i))
|
|
2699
|
+
return i;
|
|
2700
|
+
throw new Error(`Wrong witness op=${i}`);
|
|
2701
|
+
});
|
|
2702
|
+
}
|
|
2703
|
+
inputWitness = inputWitness.concat(inputType.lastScript);
|
|
2704
|
+
}
|
|
2705
|
+
if (inputType.txType === 'segwit')
|
|
2706
|
+
witness = inputWitness;
|
|
2707
|
+
if (inputType.type.startsWith('sh-wsh-')) {
|
|
2708
|
+
script = Script.encode([Script.encode([0, new Uint8Array(sha256.outputLen)])]);
|
|
2709
|
+
}
|
|
2710
|
+
else if (inputType.type.startsWith('sh-')) {
|
|
2711
|
+
script = Script.encode([...Script.decode(inputScript), inputType.lastScript]);
|
|
2712
|
+
}
|
|
2713
|
+
else if (inputType.type.startsWith('wsh-')) {
|
|
2714
|
+
}
|
|
2715
|
+
else if (inputType.txType !== 'segwit')
|
|
2716
|
+
script = inputScript;
|
|
2717
|
+
}
|
|
2718
|
+
let weight = 160 + 4 * VarBytes.encode(script).length;
|
|
2719
|
+
let hasWitnesses = false;
|
|
2720
|
+
if (witness) {
|
|
2721
|
+
weight += RawWitness.encode(witness).length;
|
|
2722
|
+
hasWitnesses = true;
|
|
2723
|
+
}
|
|
2724
|
+
return { weight, hasWitnesses };
|
|
2725
|
+
}
|
|
2726
|
+
// Exported for tests, internal method
|
|
2727
|
+
export const _cmpBig = (a, b) => {
|
|
2728
|
+
const n = a - b;
|
|
2729
|
+
if (n < 0n)
|
|
2730
|
+
return -1;
|
|
2731
|
+
else if (n > 0n)
|
|
2732
|
+
return 1;
|
|
2733
|
+
return 0;
|
|
2734
|
+
};
|
|
2735
|
+
function getScript(o, opts = {}, network = NETWORK) {
|
|
2736
|
+
let script;
|
|
2737
|
+
if ('script' in o && o.script instanceof Uint8Array) {
|
|
2738
|
+
script = o.script;
|
|
2739
|
+
}
|
|
2740
|
+
if ('address' in o) {
|
|
2741
|
+
if (typeof o.address !== 'string')
|
|
2742
|
+
throw new Error(`Estimator: wrong output address=${o.address}`);
|
|
2743
|
+
script = OutScript.encode(Address(network).decode(o.address));
|
|
2744
|
+
}
|
|
2745
|
+
if (!script)
|
|
2746
|
+
throw new Error('Estimator: wrong output script');
|
|
2747
|
+
if (typeof o.amount !== 'bigint')
|
|
2748
|
+
throw new Error(`Estimator: wrong output amount=${o.amount}`);
|
|
2749
|
+
if (script && !opts.allowUnknownOutputs && OutScript.decode(script).type === 'unknown') {
|
|
2750
|
+
throw new Error('Estimator: unknown output script type, there is a chance that input is unspendable. Pass allowUnknownOutputs=true, if you sure');
|
|
2751
|
+
}
|
|
2752
|
+
if (!opts.disableScriptCheck)
|
|
2753
|
+
checkScript(script);
|
|
2754
|
+
return script;
|
|
2755
|
+
}
|
|
2756
|
+
// class, because we need to re-use normalized inputs, instead of parsing each time
|
|
2757
|
+
// internal stuff, exported for tests only
|
|
2758
|
+
export class _Estimator {
|
|
2759
|
+
constructor(inputs, outputs, opts) {
|
|
2760
|
+
this.inputs = inputs;
|
|
2761
|
+
this.outputs = outputs;
|
|
2762
|
+
this.opts = opts;
|
|
2763
|
+
// https://github.com/bitcoin/bitcoin/blob/f90603ac6d24f5263649675d51233f1fce8b2ecd/src/policy/policy.cpp#L44
|
|
2764
|
+
// 32 + 4 + 1 + 107 + 4
|
|
2765
|
+
// Dust used in accumExact + change address algo
|
|
2766
|
+
// - change address: can be smaller for segwit
|
|
2767
|
+
// - accumExact: ???
|
|
2768
|
+
this.dust = 148n; // compat with coinselect
|
|
2769
|
+
if (typeof opts.feePerByte !== 'bigint')
|
|
2770
|
+
throw new Error(`Estimator: wrong feePerByte=${opts.feePerByte}`);
|
|
2771
|
+
if (opts.dust) {
|
|
2772
|
+
if (typeof opts.dust !== 'bigint')
|
|
2773
|
+
throw new Error(`Estimator: wrong dust=${opts.dust}`);
|
|
2774
|
+
this.dust = opts.dust;
|
|
2775
|
+
}
|
|
2776
|
+
const network = opts.network || NETWORK;
|
|
2777
|
+
let amount = 0n;
|
|
2778
|
+
// Base weight: tx with outputs, no inputs
|
|
2779
|
+
let baseWeight = 32;
|
|
2780
|
+
for (const o of outputs) {
|
|
2781
|
+
const script = getScript(o, opts, opts.network);
|
|
2782
|
+
baseWeight += 32 + 4 * VarBytes.encode(script).length;
|
|
2783
|
+
amount += o.amount;
|
|
2784
|
+
}
|
|
2785
|
+
if (typeof opts.changeAddress !== 'string')
|
|
2786
|
+
throw new Error(`Estimator: wrong change address=${opts.changeAddress}`);
|
|
2787
|
+
let changeWeight = baseWeight +
|
|
2788
|
+
32 +
|
|
2789
|
+
4 * VarBytes.encode(OutScript.encode(Address(network).decode(opts.changeAddress))).length;
|
|
2790
|
+
baseWeight += 4 * CompactSizeLen.encode(outputs.length).length;
|
|
2791
|
+
// If there a lot of outputs change can change fee
|
|
2792
|
+
changeWeight += 4 * CompactSizeLen.encode(outputs.length + 1).length;
|
|
2793
|
+
this.baseWeight = baseWeight;
|
|
2794
|
+
this.changeWeight = changeWeight;
|
|
2795
|
+
this.amount = amount;
|
|
2796
|
+
this.normalizedInputs = this.inputs.map((i) => {
|
|
2797
|
+
const normalized = normalizeInput(i, undefined, undefined, opts.disableScriptCheck);
|
|
2798
|
+
inputBeforeSign(normalized); // check fields
|
|
2799
|
+
const inputType = getInputType(normalized, opts.allowLegacyWitnessUtxo);
|
|
2800
|
+
const prev = getPrevOut(normalized);
|
|
2801
|
+
const estimate = estimateInput(inputType, normalized, this.opts);
|
|
2802
|
+
const value = prev.amount - opts.feePerByte * BigInt(toVsize(estimate.weight)); // value = amount-fee
|
|
2803
|
+
return { inputType, normalized, amount: prev.amount, value, estimate };
|
|
2804
|
+
});
|
|
2805
|
+
}
|
|
2806
|
+
checkInputIdx(idx) {
|
|
2807
|
+
if (!Number.isSafeInteger(idx) || 0 > idx || idx >= this.inputs.length)
|
|
2808
|
+
throw new Error(`Wrong input index=${idx}`);
|
|
2809
|
+
return idx;
|
|
2810
|
+
}
|
|
2811
|
+
sortIndices(indices) {
|
|
2812
|
+
return indices.slice().sort((a, b) => {
|
|
2813
|
+
const ai = this.normalizedInputs[this.checkInputIdx(a)];
|
|
2814
|
+
const bi = this.normalizedInputs[this.checkInputIdx(b)];
|
|
2815
|
+
const out = _cmpBytes(ai.normalized.txid, bi.normalized.txid);
|
|
2816
|
+
if (out !== 0)
|
|
2817
|
+
return out;
|
|
2818
|
+
return ai.normalized.index - bi.normalized.index;
|
|
2819
|
+
});
|
|
2820
|
+
}
|
|
2821
|
+
sortOutputs(outputs) {
|
|
2822
|
+
const scripts = outputs.map((o) => getScript(o, this.opts, this.opts.network));
|
|
2823
|
+
const indices = outputs.map((_, j) => j);
|
|
2824
|
+
return indices.sort((a, b) => {
|
|
2825
|
+
const aa = outputs[a].amount;
|
|
2826
|
+
const ba = outputs[b].amount;
|
|
2827
|
+
const out = _cmpBig(aa, ba);
|
|
2828
|
+
if (out !== 0)
|
|
2829
|
+
return out;
|
|
2830
|
+
return _cmpBytes(scripts[a], scripts[b]);
|
|
2831
|
+
});
|
|
2832
|
+
}
|
|
2833
|
+
getSatoshi(weigth) {
|
|
2834
|
+
return this.opts.feePerByte * BigInt(toVsize(weigth));
|
|
2835
|
+
}
|
|
2836
|
+
// Sort by value instead of amount
|
|
2837
|
+
get biggest() {
|
|
2838
|
+
return this.inputs
|
|
2839
|
+
.map((_i, j) => j)
|
|
2840
|
+
.sort((a, b) => _cmpBig(this.normalizedInputs[b].value, this.normalizedInputs[a].value));
|
|
2841
|
+
}
|
|
2842
|
+
get smallest() {
|
|
2843
|
+
return this.biggest.reverse();
|
|
2844
|
+
}
|
|
2845
|
+
// These assume that UTXO array has historical order.
|
|
2846
|
+
// Otherwise, we have no way to know which tx is oldest
|
|
2847
|
+
// Explorers usually give UTXO in this order.
|
|
2848
|
+
get oldest() {
|
|
2849
|
+
return this.inputs.map((_i, j) => j);
|
|
2850
|
+
}
|
|
2851
|
+
get newest() {
|
|
2852
|
+
return this.oldest.reverse();
|
|
2853
|
+
}
|
|
2854
|
+
// exact - like blackjack from coinselect.
|
|
2855
|
+
// exact(biggest) will select one big utxo which is closer to targetValue+dust, if possible.
|
|
2856
|
+
// If not, it will accumulate largest utxo until value is close to targetValue+dust.
|
|
2857
|
+
accumulate(indices, exact = false, skipNegative = true, all = false) {
|
|
2858
|
+
const { feePerByte } = this.opts;
|
|
2859
|
+
// TODO: how to handle change addresses?
|
|
2860
|
+
// - cost of input
|
|
2861
|
+
// - cost of change output (if input requires change)
|
|
2862
|
+
// - cost of output spending
|
|
2863
|
+
// Dust threshold should be significantly bigger, no point in
|
|
2864
|
+
// creating an output, which cannot be spent.
|
|
2865
|
+
// coinselect doesn't consider cost of output address for dust.
|
|
2866
|
+
// Changing that can actually reduce privacy
|
|
2867
|
+
let weight = this.opts.alwaysChange ? this.changeWeight : this.baseWeight;
|
|
2868
|
+
let hasWitnesses = false;
|
|
2869
|
+
let num = 0;
|
|
2870
|
+
let inputsAmount = 0n;
|
|
2871
|
+
const targetAmount = this.amount;
|
|
2872
|
+
const res = [];
|
|
2873
|
+
let fee;
|
|
2874
|
+
for (const idx of indices) {
|
|
2875
|
+
this.checkInputIdx(idx);
|
|
2876
|
+
const { estimate, amount, value } = this.normalizedInputs[idx];
|
|
2877
|
+
let newWeight = weight + estimate.weight;
|
|
2878
|
+
if (!hasWitnesses && estimate.hasWitnesses)
|
|
2879
|
+
newWeight += 2; // enable witness if needed
|
|
2880
|
+
const totalWeight = newWeight + 4 * CompactSizeLen.encode(num).length; // number of outputs can change weight
|
|
2881
|
+
fee = this.getSatoshi(totalWeight);
|
|
2882
|
+
// Best case scenario exact(biggest) -> we find biggest output, less than target+threshold
|
|
2883
|
+
if (exact) {
|
|
2884
|
+
const dust = this.dust * feePerByte;
|
|
2885
|
+
// skip if added value is bigger than dust
|
|
2886
|
+
if (amount + inputsAmount > targetAmount + fee + dust)
|
|
2887
|
+
continue;
|
|
2888
|
+
}
|
|
2889
|
+
// Negative: cost of using input is more than value provided (negative)
|
|
2890
|
+
// By default 'blackjack' mode in coinselect doesn't use that, which means
|
|
2891
|
+
// it will use negative output if sorted by 'smallest'
|
|
2892
|
+
if (skipNegative && value <= 0n)
|
|
2893
|
+
continue;
|
|
2894
|
+
weight = newWeight;
|
|
2895
|
+
if (estimate.hasWitnesses)
|
|
2896
|
+
hasWitnesses = true;
|
|
2897
|
+
num++;
|
|
2898
|
+
inputsAmount += amount;
|
|
2899
|
+
res.push(idx);
|
|
2900
|
+
// inputsAmount is enough to cover cost of tx
|
|
2901
|
+
if (!all && targetAmount + fee < inputsAmount)
|
|
2902
|
+
return { indices: res, fee, weight: totalWeight, total: inputsAmount };
|
|
2903
|
+
}
|
|
2904
|
+
if (all) {
|
|
2905
|
+
const newWeight = weight + 4 * CompactSizeLen.encode(num).length;
|
|
2906
|
+
return { indices: res, fee, weight: newWeight, total: inputsAmount };
|
|
2907
|
+
}
|
|
2908
|
+
return undefined;
|
|
2909
|
+
}
|
|
2910
|
+
// Works like coinselect default method
|
|
2911
|
+
default() {
|
|
2912
|
+
const { biggest } = this;
|
|
2913
|
+
const exact = this.accumulate(biggest, true, false);
|
|
2914
|
+
if (exact)
|
|
2915
|
+
return exact;
|
|
2916
|
+
return this.accumulate(biggest);
|
|
2917
|
+
}
|
|
2918
|
+
select(strategy) {
|
|
2919
|
+
if (strategy === 'all') {
|
|
2920
|
+
return this.accumulate(this.inputs.map((_, j) => j), false, true, true);
|
|
2921
|
+
}
|
|
2922
|
+
if (strategy === 'default')
|
|
2923
|
+
return this.default();
|
|
2924
|
+
const data = {
|
|
2925
|
+
Oldest: () => this.oldest,
|
|
2926
|
+
Newest: () => this.newest,
|
|
2927
|
+
Smallest: () => this.smallest,
|
|
2928
|
+
Biggest: () => this.biggest,
|
|
2929
|
+
};
|
|
2930
|
+
if (strategy.startsWith('exact')) {
|
|
2931
|
+
const [exactData, left] = strategy.slice(5).split('/');
|
|
2932
|
+
if (!data[exactData])
|
|
2933
|
+
throw new Error(`Estimator.select: wrong strategy=${strategy}`);
|
|
2934
|
+
strategy = left;
|
|
2935
|
+
const exact = this.accumulate(data[exactData](), true, true);
|
|
2936
|
+
if (exact)
|
|
2937
|
+
return exact;
|
|
2938
|
+
}
|
|
2939
|
+
if (strategy.startsWith('accum')) {
|
|
2940
|
+
const accumData = strategy.slice(5);
|
|
2941
|
+
if (!data[accumData])
|
|
2942
|
+
throw new Error(`Estimator.select: wrong strategy=${strategy}`);
|
|
2943
|
+
return this.accumulate(data[accumData]());
|
|
2944
|
+
}
|
|
2945
|
+
throw new Error(`Estimator.select: wrong strategy=${strategy}`);
|
|
2946
|
+
}
|
|
2947
|
+
result(strategy) {
|
|
2948
|
+
const s = this.select(strategy);
|
|
2949
|
+
if (!s)
|
|
2950
|
+
return;
|
|
2951
|
+
const { indices, weight, total } = s;
|
|
2952
|
+
let needChange = this.opts.alwaysChange;
|
|
2953
|
+
const changeWeight = this.opts.alwaysChange
|
|
2954
|
+
? weight
|
|
2955
|
+
: weight + (this.changeWeight - this.baseWeight);
|
|
2956
|
+
const changeFee = this.getSatoshi(changeWeight);
|
|
2957
|
+
let fee = s.fee;
|
|
2958
|
+
const change = total - this.amount - changeFee;
|
|
2959
|
+
if (change > this.dust)
|
|
2960
|
+
needChange = true;
|
|
2961
|
+
let inputs = indices;
|
|
2962
|
+
let outputs = Array.from(this.outputs);
|
|
2963
|
+
if (needChange) {
|
|
2964
|
+
fee = changeFee;
|
|
2965
|
+
// this shouldn't happen!
|
|
2966
|
+
if (change < 0n)
|
|
2967
|
+
throw new Error(`Estimator.result: negative change=${change}`);
|
|
2968
|
+
outputs.push({ address: this.opts.changeAddress, amount: change });
|
|
2969
|
+
}
|
|
2970
|
+
if (this.opts.bip69) {
|
|
2971
|
+
inputs = this.sortIndices(inputs);
|
|
2972
|
+
outputs = this.sortOutputs(outputs).map((i) => outputs[i]);
|
|
2973
|
+
}
|
|
2974
|
+
const res = {
|
|
2975
|
+
inputs: inputs.map((i) => this.inputs[i]),
|
|
2976
|
+
outputs,
|
|
2977
|
+
fee,
|
|
2978
|
+
weight: this.opts.alwaysChange ? s.weight : changeWeight,
|
|
2979
|
+
change: !!needChange,
|
|
2980
|
+
};
|
|
2981
|
+
let tx;
|
|
2982
|
+
if (this.opts.createTx) {
|
|
2983
|
+
const { inputs, outputs } = res;
|
|
2984
|
+
tx = new Transaction(this.opts);
|
|
2985
|
+
for (const i of inputs)
|
|
2986
|
+
tx.addInput(i);
|
|
2987
|
+
for (const o of outputs)
|
|
2988
|
+
tx.addOutput({ ...o, script: getScript(o, this.opts, this.opts.network) });
|
|
2989
|
+
}
|
|
2990
|
+
return { ...res, tx };
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
export function selectUTXO(inputs, outputs, strategy, opts) {
|
|
2994
|
+
// Defaults: do we want bip69 by default?
|
|
2995
|
+
const _opts = { createTx: true, bip69: true, ...opts };
|
|
2996
|
+
const est = new _Estimator(inputs, outputs, _opts);
|
|
2997
|
+
return est.result(strategy);
|
|
2998
|
+
}
|