@scure/btc-signer 2.3.0 → 2.4.1
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 +49 -10
- package/index.d.ts +4 -3
- package/index.js +3 -3
- package/musig2.d.ts +17 -3
- package/musig2.js +18 -6
- package/net.js +7 -2
- package/package.json +12 -11
- package/payment.d.ts +16 -5
- package/payment.js +118 -27
- package/psbt.d.ts +680 -9
- package/psbt.js +187 -38
- package/src/_type_test.ts +14 -0
- package/src/index.ts +4 -2
- package/src/musig2.ts +32 -7
- package/src/net.ts +7 -2
- package/src/payment.ts +145 -32
- package/src/psbt.ts +210 -35
- package/src/transaction.ts +790 -136
- package/src/utils.ts +24 -2
- package/src/utxo.ts +185 -71
- package/transaction.d.ts +40 -6
- package/transaction.js +667 -123
- package/utils.d.ts +15 -1
- package/utils.js +21 -2
- package/utxo.d.ts +192 -1
- package/utxo.js +166 -69
package/src/utils.ts
CHANGED
|
@@ -411,10 +411,32 @@ export function taprootTweakPubkey(pubKey: TArg<Bytes>, h: TArg<Bytes>): TRet<[B
|
|
|
411
411
|
// This is the fixed BIP 341 H example, not the privacy-preserving H + rG variant.
|
|
412
412
|
// Downstream helpers use exact-byte equality with it to recognize
|
|
413
413
|
// library-generated script-only outputs.
|
|
414
|
-
|
|
415
|
-
export
|
|
414
|
+
// Keep the value used by library internals private: exported Uint8Arrays are mutable, so the
|
|
415
|
+
// public compatibility export below cannot safely be a source of cryptographic key material.
|
|
416
|
+
const INTERNAL_TAPROOT_NUMS: TRet<Bytes> = /* @__PURE__ */ (() =>
|
|
416
417
|
sha256(Point.BASE.toBytes(false)) as TRet<Bytes>)();
|
|
417
418
|
|
|
419
|
+
/**
|
|
420
|
+
* Standard unspendable internal key used for script-only Taproot outputs.
|
|
421
|
+
* @deprecated Use {@link taprootNumsKey} to receive an owned copy.
|
|
422
|
+
*/
|
|
423
|
+
export const TAPROOT_UNSPENDABLE_KEY: TRet<Bytes> = /* @__PURE__ */ (() =>
|
|
424
|
+
Uint8Array.from(INTERNAL_TAPROOT_NUMS) as TRet<Bytes>)();
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Returns an owned copy of the library's stable Taproot NUMS key.
|
|
428
|
+
* @returns A new 32-byte NUMS key copy.
|
|
429
|
+
* @example
|
|
430
|
+
* Obtain an internal key without sharing mutable exported storage.
|
|
431
|
+
* ```ts
|
|
432
|
+
* import { taprootNumsKey } from '@scure/btc-signer/utils.js';
|
|
433
|
+
* const internalKey = taprootNumsKey();
|
|
434
|
+
* ```
|
|
435
|
+
*/
|
|
436
|
+
export function taprootNumsKey(): TRet<Bytes> {
|
|
437
|
+
return Uint8Array.from(INTERNAL_TAPROOT_NUMS) as TRet<Bytes>;
|
|
438
|
+
}
|
|
439
|
+
|
|
418
440
|
/** Bitcoin network parameters. */
|
|
419
441
|
export type BTC_NETWORK = {
|
|
420
442
|
/** Human-readable prefix used by Bech32 and Bech32m addresses. */
|
package/src/utxo.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { hex } from '@scure/base';
|
|
2
2
|
import * as P from 'micro-packed';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Address,
|
|
5
|
+
type CustomScript,
|
|
6
|
+
OutScript,
|
|
7
|
+
_WitnessOutScript,
|
|
8
|
+
checkScript,
|
|
9
|
+
tapLeafHash,
|
|
10
|
+
} from './payment.ts';
|
|
4
11
|
import * as psbt from './psbt.ts';
|
|
5
|
-
import { CompactSizeLen, Script } from './script.ts';
|
|
12
|
+
import { CompactSizeLen, RawWitness, Script } from './script.ts';
|
|
6
13
|
import {
|
|
7
14
|
SignatureHash,
|
|
8
15
|
Transaction,
|
|
@@ -20,12 +27,14 @@ import {
|
|
|
20
27
|
type Bytes,
|
|
21
28
|
NETWORK,
|
|
22
29
|
PubT,
|
|
23
|
-
TAPROOT_UNSPENDABLE_KEY,
|
|
24
30
|
type TArg,
|
|
31
|
+
type TRet,
|
|
25
32
|
compareBytes,
|
|
26
33
|
equalBytes,
|
|
27
34
|
isBytes,
|
|
28
35
|
sha256,
|
|
36
|
+
taprootNumsKey,
|
|
37
|
+
taprootTweakPubkey,
|
|
29
38
|
validatePubkey,
|
|
30
39
|
validateObject,
|
|
31
40
|
} from './utils.ts';
|
|
@@ -43,6 +52,7 @@ export type Accumulated =
|
|
|
43
52
|
}
|
|
44
53
|
| undefined;
|
|
45
54
|
type TapLeafScript = psbt.TransactionInput['tapLeafScript'];
|
|
55
|
+
type TapLeaf = NonNullable<TapLeafScript>[number];
|
|
46
56
|
type TB = Parameters<typeof psbt.TaprootControlBlock.encode>[0];
|
|
47
57
|
const encodeTapBlock = (item: TB) => psbt.TaprootControlBlock.encode(item);
|
|
48
58
|
// Be friendly to bad ECMAScript parsers by not using bigint literals.
|
|
@@ -51,70 +61,165 @@ const _0n = /* @__PURE__ */ BigInt(0), _3n = /* @__PURE__ */ BigInt(3);
|
|
|
51
61
|
// Serialized length of VarBytes(data) without allocating the encoded copy
|
|
52
62
|
const varLen = (dataLen: number) => CompactSizeLen.encode(dataLen).length + dataLen;
|
|
53
63
|
|
|
64
|
+
const tapLeafWitness = (
|
|
65
|
+
leaf: TArg<TapLeaf>,
|
|
66
|
+
sigSize: number,
|
|
67
|
+
customScripts?: TArg<CustomScript[]>,
|
|
68
|
+
pubkeys?: Set<string>,
|
|
69
|
+
unknownError = 'Finalize: Unknown tapLeafScript'
|
|
70
|
+
): TRet<Bytes[] | undefined> => {
|
|
71
|
+
const [cb, _script] = leaf as TapLeaf;
|
|
72
|
+
const _customScripts = customScripts as CustomScript[] | undefined;
|
|
73
|
+
// Last byte is version
|
|
74
|
+
const script = _script.slice(0, -1);
|
|
75
|
+
const ver = _script[_script.length - 1];
|
|
76
|
+
const outs = OutScript.decode(script);
|
|
77
|
+
const available = (pubkey: TArg<Bytes>) => !pubkeys || pubkeys.has(hex.encode(pubkey as Bytes));
|
|
78
|
+
const empty = () => new Uint8Array(sigSize);
|
|
79
|
+
let signatures: Bytes[] = [];
|
|
80
|
+
if (outs.type === 'tr_ms') {
|
|
81
|
+
let added = 0;
|
|
82
|
+
for (const pubkey of outs.pubkeys) {
|
|
83
|
+
if (added === outs.m || !available(pubkey)) signatures.push(P.EMPTY);
|
|
84
|
+
else {
|
|
85
|
+
signatures.push(empty());
|
|
86
|
+
added++;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (added !== outs.m) return;
|
|
90
|
+
} else if (outs.type === 'tr_ns') {
|
|
91
|
+
for (const pubkey of outs.pubkeys) {
|
|
92
|
+
if (!available(pubkey)) return;
|
|
93
|
+
signatures.push(empty());
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
if (!_customScripts) throw new Error(unknownError);
|
|
97
|
+
const leafHash = tapLeafHash(script, ver);
|
|
98
|
+
const scriptDecoded = Script.decode(script);
|
|
99
|
+
const scriptPubkeys = scriptDecoded.filter((i) => {
|
|
100
|
+
if (!isBytes(i)) return false;
|
|
101
|
+
try {
|
|
102
|
+
validatePubkey(i, PubT.schnorr);
|
|
103
|
+
return true;
|
|
104
|
+
} catch (e) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}) as Bytes[];
|
|
108
|
+
const availablePubkeys = scriptPubkeys.filter(available);
|
|
109
|
+
// A custom finalizer may treat an unexpected empty signature list as malformed input. If the
|
|
110
|
+
// script embeds keys but none are owned, the path is unavailable without consulting the hook.
|
|
111
|
+
if (pubkeys && scriptPubkeys.length && !availablePubkeys.length) return;
|
|
112
|
+
let recognized = false;
|
|
113
|
+
for (const c of _customScripts) {
|
|
114
|
+
if (!c.finalizeTaproot) continue;
|
|
115
|
+
const csEncoded = c.encode(scriptDecoded);
|
|
116
|
+
if (csEncoded === undefined) continue;
|
|
117
|
+
recognized = true;
|
|
118
|
+
const finalized = c.finalizeTaproot(
|
|
119
|
+
script,
|
|
120
|
+
csEncoded,
|
|
121
|
+
availablePubkeys.map((pubKey) => [{ pubKey, leafHash }, empty()])
|
|
122
|
+
);
|
|
123
|
+
if (finalized) return finalized.concat(encodeTapBlock(cb)) as TRet<Bytes[]>;
|
|
124
|
+
}
|
|
125
|
+
if (recognized && pubkeys) return;
|
|
126
|
+
throw new Error(unknownError);
|
|
127
|
+
}
|
|
128
|
+
// Witness is stack, so last element will be used first
|
|
129
|
+
return signatures.reverse().concat([script, encodeTapBlock(cb)]) as TRet<Bytes[]>;
|
|
130
|
+
};
|
|
131
|
+
|
|
54
132
|
function iterLeafs(
|
|
55
133
|
tapLeafScript: TArg<TapLeafScript>,
|
|
56
134
|
sigSize: number,
|
|
57
135
|
customScripts?: TArg<CustomScript[]>
|
|
58
|
-
) {
|
|
136
|
+
): TRet<Bytes[]> {
|
|
59
137
|
const _tapLeafScript = tapLeafScript as TapLeafScript;
|
|
60
138
|
const _customScripts = customScripts as CustomScript[] | undefined;
|
|
61
139
|
if (!_tapLeafScript || !_tapLeafScript.length) throw new Error('no leafs');
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
140
|
+
// Start with the old shallowest-path order for stable equal-weight ties. Full witness size can
|
|
141
|
+
// reverse that order when a shallow leaf needs a larger script or more signatures.
|
|
142
|
+
const leafs = _tapLeafScript
|
|
143
|
+
.slice()
|
|
144
|
+
.sort((a, b) => encodeTapBlock(a[0]).length - encodeTapBlock(b[0]).length);
|
|
145
|
+
let smallest: Bytes[] | undefined;
|
|
146
|
+
let smallestSize = Number.POSITIVE_INFINITY;
|
|
147
|
+
for (const leaf of leafs) {
|
|
148
|
+
const witness = tapLeafWitness(leaf, sigSize, _customScripts);
|
|
149
|
+
if (!witness) continue;
|
|
150
|
+
const size = RawWitness.encode(witness).length;
|
|
151
|
+
if (size >= smallestSize) continue;
|
|
152
|
+
smallest = witness;
|
|
153
|
+
smallestSize = size;
|
|
154
|
+
}
|
|
155
|
+
if (!smallest) throw new Error('there was no witness');
|
|
156
|
+
return smallest as TRet<Bytes[]>;
|
|
157
|
+
}
|
|
76
158
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
159
|
+
/**
|
|
160
|
+
* Removes Taproot spend paths that cannot be satisfied by the supplied Schnorr public keys.
|
|
161
|
+
* Non-Taproot inputs are retained, and caller-owned input metadata is never mutated.
|
|
162
|
+
* @param inputs - candidate PSBT input records to filter
|
|
163
|
+
* @param pubkeys - available x-only Schnorr public keys
|
|
164
|
+
* @returns Copies of inputs that retain at least one available path
|
|
165
|
+
* @example
|
|
166
|
+
* Filter wallet UTXOs before selecting coins.
|
|
167
|
+
* ```ts
|
|
168
|
+
* import { filterTaproot } from '@scure/btc-signer/utxo.js';
|
|
169
|
+
* import { pubSchnorr, randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
|
|
170
|
+
* const spendable = filterTaproot([], [pubSchnorr(randomPrivateKeyBytes())]);
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
export function filterTaproot(
|
|
174
|
+
inputs: TArg<psbt.TransactionInputUpdate[]>,
|
|
175
|
+
pubkeys: TArg<Bytes[]>
|
|
176
|
+
): TRet<psbt.TransactionInputUpdate[]> {
|
|
177
|
+
aarray(inputs, 'inputs');
|
|
178
|
+
aarray(pubkeys, 'pubkeys');
|
|
179
|
+
const _inputs = inputs as psbt.TransactionInputUpdate[];
|
|
180
|
+
const _pubkeys = pubkeys as Bytes[];
|
|
181
|
+
const keys = new Set<string>();
|
|
182
|
+
for (const pubkey of _pubkeys) keys.add(hex.encode(validatePubkey(pubkey, PubT.schnorr)));
|
|
183
|
+
const res: psbt.TransactionInputUpdate[] = [];
|
|
184
|
+
for (const input of _inputs) {
|
|
185
|
+
const filtered = { ...input };
|
|
186
|
+
// BIP371 path fields are optional and empty keyed lists encode as absence, so the committed
|
|
187
|
+
// previous-output script is the authoritative input type.
|
|
188
|
+
const prevScript = _WitnessOutScript.decode(
|
|
189
|
+
getPrevOut(filtered as TArg<psbt.TransactionInput>).script
|
|
190
|
+
);
|
|
191
|
+
if (prevScript.type !== 'tr') {
|
|
192
|
+
res.push(filtered);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (filtered.tapInternalKey) {
|
|
196
|
+
// A control block can reconstruct the root, but BIP371 does not require that inference.
|
|
197
|
+
// Omission may be broken data or an earlier filter deliberately disabling the key path.
|
|
198
|
+
// Recognize root omission only when the prevout proves this is an empty-tree commitment.
|
|
199
|
+
const hasRoot =
|
|
200
|
+
filtered.tapMerkleRoot !== undefined ||
|
|
201
|
+
equalBytes(taprootTweakPubkey(filtered.tapInternalKey, P.EMPTY)[0], prevScript.pubkey);
|
|
202
|
+
if (
|
|
203
|
+
!hasRoot ||
|
|
204
|
+
equalBytes(filtered.tapInternalKey, taprootNumsKey()) ||
|
|
205
|
+
(!keys.has(hex.encode(filtered.tapInternalKey)) && !keys.has(hex.encode(prevScript.pubkey)))
|
|
206
|
+
)
|
|
207
|
+
delete filtered.tapInternalKey;
|
|
113
208
|
}
|
|
114
|
-
|
|
115
|
-
|
|
209
|
+
if (filtered.tapLeafScript) {
|
|
210
|
+
const sigSize =
|
|
211
|
+
filtered.sighashType !== undefined && filtered.sighashType !== SignatureHash.DEFAULT
|
|
212
|
+
? 65
|
|
213
|
+
: 64;
|
|
214
|
+
const leafs = filtered.tapLeafScript.filter((leaf) =>
|
|
215
|
+
tapLeafWitness(leaf, sigSize, undefined, keys, 'filterTaproot: unknown Taproot leaf')
|
|
216
|
+
);
|
|
217
|
+
if (leafs.length) filtered.tapLeafScript = leafs;
|
|
218
|
+
else delete filtered.tapLeafScript;
|
|
219
|
+
}
|
|
220
|
+
if (filtered.tapInternalKey || filtered.tapLeafScript) res.push(filtered);
|
|
116
221
|
}
|
|
117
|
-
|
|
222
|
+
return res as TRet<psbt.TransactionInputUpdate[]>;
|
|
118
223
|
}
|
|
119
224
|
|
|
120
225
|
function estimateInput(
|
|
@@ -130,13 +235,9 @@ function estimateInput(
|
|
|
130
235
|
// schnorr sig is always 64 bytes. except for cases when sighash is not default!
|
|
131
236
|
if (inputType.txType === 'taproot') {
|
|
132
237
|
const SCHNORR_SIG_SIZE = inputType.sighash !== SignatureHash.DEFAULT ? 65 : 64;
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
// on the online side. Callers that know only script-path signing is
|
|
137
|
-
// possible should omit `tapInternalKey` or pre-filter `tapLeafScript`
|
|
138
|
-
// before estimation.
|
|
139
|
-
if (_input.tapInternalKey && !equalBytes(_input.tapInternalKey, TAPROOT_UNSPENDABLE_KEY)) {
|
|
238
|
+
// A real internal key and every retained leaf declare paths available to this caller. Use
|
|
239
|
+
// filterTaproot before estimation when the supplied input contains unavailable paths.
|
|
240
|
+
if (_input.tapInternalKey && !equalBytes(_input.tapInternalKey, taprootNumsKey())) {
|
|
140
241
|
witness = [new Uint8Array(SCHNORR_SIG_SIZE)];
|
|
141
242
|
} else if (_input.tapLeafScript) {
|
|
142
243
|
witness = iterLeafs(_input.tapLeafScript, SCHNORR_SIG_SIZE, _opts.customScripts);
|
|
@@ -219,6 +320,8 @@ export type EstimatorOpts = TxOpts & {
|
|
|
219
320
|
createTx?: boolean; // Create tx inside selection
|
|
220
321
|
requiredInputs?: psbt.TransactionInputUpdate[]; // these inputs always will be used
|
|
221
322
|
allowSameUtxo?: boolean; // allow using UTXO multiple times (for test purposes)
|
|
323
|
+
/** Filter Taproot candidate paths to those satisfiable by these Schnorr public keys. */
|
|
324
|
+
filterTaproot?: Bytes[];
|
|
222
325
|
};
|
|
223
326
|
|
|
224
327
|
function getScript(o: TArg<Output>, opts: TArg<TxOpts> = {}, network = NETWORK) {
|
|
@@ -241,7 +344,7 @@ function getScript(o: TArg<Output>, opts: TArg<TxOpts> = {}, network = NETWORK)
|
|
|
241
344
|
// Keep selector-only `createTx: false` flows aligned with the transaction/PSBT output boundary:
|
|
242
345
|
// satoshi-denominated outputs are not allowed to go negative.
|
|
243
346
|
abigint(_o.amount, 'output.amount');
|
|
244
|
-
if (script && !_opts.allowUnknownOutputs &&
|
|
347
|
+
if (script && !_opts.allowUnknownOutputs && _WitnessOutScript.decode(script).type === 'unknown') {
|
|
245
348
|
throw new Error(
|
|
246
349
|
'Estimator: unknown output script type, there is a chance that input is unspendable. Pass allowUnknownOutputs=true, if you sure'
|
|
247
350
|
);
|
|
@@ -258,10 +361,7 @@ type AccumStrategy = `accum${SortStrategy}`;
|
|
|
258
361
|
|
|
259
362
|
/** Supported UTXO selection strategies. */
|
|
260
363
|
export type SelectionStrategy =
|
|
261
|
-
| '
|
|
262
|
-
| 'default'
|
|
263
|
-
| AccumStrategy
|
|
264
|
-
| `${ExactStrategy}/${AccumStrategy}`;
|
|
364
|
+
'all' | 'default' | AccumStrategy | `${ExactStrategy}/${AccumStrategy}`;
|
|
265
365
|
|
|
266
366
|
// class, because we need to re-use normalized inputs, instead of parsing each time
|
|
267
367
|
// internal stuff, exported for tests only
|
|
@@ -284,6 +384,9 @@ export class _Estimator {
|
|
|
284
384
|
private outputs: Output[];
|
|
285
385
|
private opts: EstimatorOpts;
|
|
286
386
|
constructor(inputs: psbt.TransactionInputUpdate[], outputs: Output[], opts: EstimatorOpts) {
|
|
387
|
+
// EstimatorOpts extends TxOpts, so resolve the complete transaction policy once even when
|
|
388
|
+
// createTx=false. This keeps selection normalization and a returned Transaction identical.
|
|
389
|
+
opts = new Transaction(opts).opts as EstimatorOpts;
|
|
287
390
|
this.outputs = outputs;
|
|
288
391
|
this.opts = opts;
|
|
289
392
|
// Zero-fee estimation is useful on regtest/in tests, but negative fee rates would make
|
|
@@ -350,7 +453,8 @@ export class _Estimator {
|
|
|
350
453
|
undefined,
|
|
351
454
|
undefined,
|
|
352
455
|
opts.disableScriptCheck,
|
|
353
|
-
opts.
|
|
456
|
+
opts.unknown!,
|
|
457
|
+
opts.proprietary!
|
|
354
458
|
);
|
|
355
459
|
inputBeforeSign(normalized as TArg<psbt.TransactionInput>); // check fields
|
|
356
460
|
const key = `${hex.encode(normalized.txid!)}:${normalized.index}`;
|
|
@@ -632,6 +736,16 @@ export function selectUTXO(
|
|
|
632
736
|
astring(strategy, 'strategy');
|
|
633
737
|
// Public wrapper defaults to BIP69 ordering and tx construction unless callers override them.
|
|
634
738
|
const _opts = { createTx: true, bip69: true, ...(opts as EstimatorOpts) };
|
|
635
|
-
|
|
739
|
+
let candidates = inputs as psbt.TransactionInputUpdate[];
|
|
740
|
+
if (_opts.filterTaproot !== undefined) {
|
|
741
|
+
candidates = filterTaproot(candidates, _opts.filterTaproot);
|
|
742
|
+
if (_opts.requiredInputs) {
|
|
743
|
+
const requiredInputs = filterTaproot(_opts.requiredInputs, _opts.filterTaproot);
|
|
744
|
+
if (requiredInputs.length !== _opts.requiredInputs.length)
|
|
745
|
+
throw new Error('filterTaproot: required input has no available Taproot path');
|
|
746
|
+
_opts.requiredInputs = requiredInputs;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
const est = new _Estimator(candidates, outputs as Output[], _opts);
|
|
636
750
|
return est.result(strategy);
|
|
637
751
|
}
|
package/transaction.d.ts
CHANGED
|
@@ -74,6 +74,8 @@ export declare const def: <T>(value: T | undefined, def: T) => T;
|
|
|
74
74
|
* ```
|
|
75
75
|
*/
|
|
76
76
|
export declare function cloneDeep<T>(obj: T): T;
|
|
77
|
+
/** PSBT unknown/proprietary-field handling policy. */
|
|
78
|
+
export type Unknowns = psbt.Unknowns;
|
|
77
79
|
/** Transaction construction and parsing options. */
|
|
78
80
|
export interface TxOpts {
|
|
79
81
|
/** Transaction version to place into new transactions and imported PSBTs. */
|
|
@@ -98,17 +100,36 @@ export interface TxOpts {
|
|
|
98
100
|
allowUnknowInput?: boolean;
|
|
99
101
|
/** Allow signing and finalizing inputs with unknown script shapes. */
|
|
100
102
|
allowUnknownInputs?: boolean;
|
|
101
|
-
/** Skip redeem-script and
|
|
103
|
+
/** Skip redeem/witness-script and Taproot commitment consistency checks. */
|
|
102
104
|
disableScriptCheck?: boolean;
|
|
103
105
|
/** Match the odd empty-output encoding used by `bip174js`. */
|
|
104
106
|
bip174jsCompat?: boolean;
|
|
105
107
|
/** Permit legacy inputs that only provide witness UTXO data. */
|
|
106
108
|
allowLegacyWitnessUtxo?: boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Before signing, require every input to provide a full previous transaction whose txid and
|
|
111
|
+
* selected output match the input. Use this for untrusted or multi-party PSBTs to prevent forged
|
|
112
|
+
* witness-UTXO amounts from hiding an excessive transaction fee.
|
|
113
|
+
*/
|
|
114
|
+
strictPrevoutValidation?: boolean;
|
|
107
115
|
/** Grind ECDSA signatures until they use a low-R encoding. */
|
|
108
116
|
lowR?: boolean;
|
|
109
117
|
/** UNSAFE: additional custom payment-script codecs and finalizers. */
|
|
110
118
|
customScripts?: CustomScript[];
|
|
111
|
-
/**
|
|
119
|
+
/** Unknown PSBT field policy. Defaults to `strip`. */
|
|
120
|
+
unknown?: Unknowns;
|
|
121
|
+
/** Proprietary PSBT field policy. Defaults to the resolved {@link unknown} policy. */
|
|
122
|
+
proprietary?: Unknowns;
|
|
123
|
+
/**
|
|
124
|
+
* Treat an absent PSBTv2 transaction-modifiable field as allowing input/output changes. Older
|
|
125
|
+
* scure versions emitted PSBTv2 without this field, so this opts out of strict BIP370 behavior
|
|
126
|
+
* when upgrading and editing their PSBTs.
|
|
127
|
+
*/
|
|
128
|
+
allowMissingTxModifiable?: boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Deprecated alias for {@link unknown}: true selects `ignore`, false selects `strip`.
|
|
131
|
+
* @deprecated Use `unknown`.
|
|
132
|
+
*/
|
|
112
133
|
allowUnknown?: boolean;
|
|
113
134
|
}
|
|
114
135
|
/**
|
|
@@ -200,9 +221,12 @@ export declare function getPrevOut(input: TArg<psbt.TransactionInput>): P.Unwrap
|
|
|
200
221
|
* @param i - input update to normalize
|
|
201
222
|
* @param cur - existing input value to merge with
|
|
202
223
|
* @param allowedFields - fields that may still change on signed inputs
|
|
203
|
-
* @param disableScriptCheck - whether to skip
|
|
204
|
-
* @param
|
|
224
|
+
* @param disableScriptCheck - whether to skip wrapper and Taproot commitment sanity checks
|
|
225
|
+
* @param unknown - handling policy for unknown PSBT fields
|
|
226
|
+
* @param proprietary - handling policy for proprietary PSBT fields
|
|
205
227
|
* @returns Normalized PSBT input.
|
|
228
|
+
* @throws If the update conflicts with the existing input or its signatures. {@link Error}
|
|
229
|
+
* @throws If a numeric input field is outside its wire or protocol range. {@link RangeError}
|
|
206
230
|
* @example
|
|
207
231
|
* Accept hex txids from callers in the same display-order form used by `Transaction.id`, then
|
|
208
232
|
* normalize them into the repo's internal `TransactionInput` shape.
|
|
@@ -216,7 +240,7 @@ export declare function getPrevOut(input: TArg<psbt.TransactionInput>): P.Unwrap
|
|
|
216
240
|
* });
|
|
217
241
|
* ```
|
|
218
242
|
*/
|
|
219
|
-
export declare function normalizeInput(i: TArg<psbt.TransactionInputUpdate>, cur?: TArg<PSBTInputs>, allowedFields?: TArg<readonly (keyof PSBTInputs)[]>, disableScriptCheck?: boolean,
|
|
243
|
+
export declare function normalizeInput(i: TArg<psbt.TransactionInputUpdate>, cur?: TArg<PSBTInputs>, allowedFields?: TArg<readonly (keyof PSBTInputs)[]>, disableScriptCheck?: boolean, unknown?: Unknowns | boolean, proprietary?: Unknowns | boolean): TRet<PSBTInputs>;
|
|
220
244
|
/**
|
|
221
245
|
* Determines how an input should be signed and finalized.
|
|
222
246
|
* Wrapper consistency is expected to be validated earlier by {@link normalizeInput}
|
|
@@ -305,13 +329,21 @@ export declare class Transaction {
|
|
|
305
329
|
private outputs;
|
|
306
330
|
readonly opts: ReturnType<typeof validateOpts>;
|
|
307
331
|
constructor(opts?: TxOpts);
|
|
332
|
+
private isPSBTv2;
|
|
333
|
+
private requireTxModifiable;
|
|
334
|
+
private txModifiablePolicy;
|
|
335
|
+
private modifiable;
|
|
336
|
+
private get txModifiable();
|
|
308
337
|
static fromRaw(raw: Bytes, opts?: TxOpts): Transaction;
|
|
309
338
|
static fromPSBT(psbt_: Bytes, opts?: TxOpts): Transaction;
|
|
310
339
|
toPSBT(PSBTVersion?: number | undefined): Uint8Array;
|
|
311
340
|
get lockTime(): number;
|
|
312
341
|
get version(): number;
|
|
313
342
|
private inputStatus;
|
|
343
|
+
private cleanFinalInput;
|
|
314
344
|
private inputSighash;
|
|
345
|
+
private signatures;
|
|
346
|
+
private signedInputKeys;
|
|
315
347
|
private signStatus;
|
|
316
348
|
get isFinal(): boolean;
|
|
317
349
|
get hasWitnesses(): boolean;
|
|
@@ -323,6 +355,7 @@ export declare class Transaction {
|
|
|
323
355
|
get hash(): string;
|
|
324
356
|
get id(): string;
|
|
325
357
|
private checkInputIdx;
|
|
358
|
+
private validatePrevoutsForSigning;
|
|
326
359
|
getInput(idx: number): psbt.TransactionInput;
|
|
327
360
|
get inputsLength(): number;
|
|
328
361
|
addInput(input: TArg<psbt.TransactionInputUpdate>, _ignoreSignStatus?: boolean): number;
|
|
@@ -350,6 +383,7 @@ export declare class Transaction {
|
|
|
350
383
|
/**
|
|
351
384
|
* Merges multiple PSBT blobs into one.
|
|
352
385
|
* @param psbts - PSBT byte arrays to combine
|
|
386
|
+
* @param opts - Transaction parsing, combination, and serialization options. See {@link TxOpts}.
|
|
353
387
|
* @returns Combined PSBT bytes.
|
|
354
388
|
* @throws If the PSBT list is empty or the partial transactions cannot be combined. {@link Error}
|
|
355
389
|
* @example
|
|
@@ -360,7 +394,7 @@ export declare class Transaction {
|
|
|
360
394
|
* PSBTCombine([psbt, psbt]);
|
|
361
395
|
* ```
|
|
362
396
|
*/
|
|
363
|
-
export declare function PSBTCombine(psbts: TArg<Bytes[]>): TRet<Bytes>;
|
|
397
|
+
export declare function PSBTCombine(psbts: TArg<Bytes[]>, opts?: TArg<TxOpts>): TRet<Bytes>;
|
|
364
398
|
/**
|
|
365
399
|
* Parses a BIP32 path string into child indices.
|
|
366
400
|
* @param path - derivation path such as `m/0'/1`
|