@scure/btc-signer 2.3.0 → 2.4.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 +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/utils.d.ts
CHANGED
|
@@ -264,8 +264,22 @@ export declare function taprootTweakPrivKey(privKey: TArg<Bytes>, merkleRoot?: T
|
|
|
264
264
|
* ```
|
|
265
265
|
*/
|
|
266
266
|
export declare function taprootTweakPubkey(pubKey: TArg<Bytes>, h: TArg<Bytes>): TRet<[Bytes, number]>;
|
|
267
|
-
/**
|
|
267
|
+
/**
|
|
268
|
+
* Standard unspendable internal key used for script-only Taproot outputs.
|
|
269
|
+
* @deprecated Use {@link taprootNumsKey} to receive an owned copy.
|
|
270
|
+
*/
|
|
268
271
|
export declare const TAPROOT_UNSPENDABLE_KEY: TRet<Bytes>;
|
|
272
|
+
/**
|
|
273
|
+
* Returns an owned copy of the library's stable Taproot NUMS key.
|
|
274
|
+
* @returns A new 32-byte NUMS key copy.
|
|
275
|
+
* @example
|
|
276
|
+
* Obtain an internal key without sharing mutable exported storage.
|
|
277
|
+
* ```ts
|
|
278
|
+
* import { taprootNumsKey } from '@scure/btc-signer/utils.js';
|
|
279
|
+
* const internalKey = taprootNumsKey();
|
|
280
|
+
* ```
|
|
281
|
+
*/
|
|
282
|
+
export declare function taprootNumsKey(): TRet<Bytes>;
|
|
269
283
|
/** Bitcoin network parameters. */
|
|
270
284
|
export type BTC_NETWORK = {
|
|
271
285
|
/** Human-readable prefix used by Bech32 and Bech32m addresses. */
|
package/utils.js
CHANGED
|
@@ -373,8 +373,27 @@ export function taprootTweakPubkey(pubKey, h) {
|
|
|
373
373
|
// This is the fixed BIP 341 H example, not the privacy-preserving H + rG variant.
|
|
374
374
|
// Downstream helpers use exact-byte equality with it to recognize
|
|
375
375
|
// library-generated script-only outputs.
|
|
376
|
-
|
|
377
|
-
export
|
|
376
|
+
// Keep the value used by library internals private: exported Uint8Arrays are mutable, so the
|
|
377
|
+
// public compatibility export below cannot safely be a source of cryptographic key material.
|
|
378
|
+
const INTERNAL_TAPROOT_NUMS = /* @__PURE__ */ (() => sha256(Point.BASE.toBytes(false)))();
|
|
379
|
+
/**
|
|
380
|
+
* Standard unspendable internal key used for script-only Taproot outputs.
|
|
381
|
+
* @deprecated Use {@link taprootNumsKey} to receive an owned copy.
|
|
382
|
+
*/
|
|
383
|
+
export const TAPROOT_UNSPENDABLE_KEY = /* @__PURE__ */ (() => Uint8Array.from(INTERNAL_TAPROOT_NUMS))();
|
|
384
|
+
/**
|
|
385
|
+
* Returns an owned copy of the library's stable Taproot NUMS key.
|
|
386
|
+
* @returns A new 32-byte NUMS key copy.
|
|
387
|
+
* @example
|
|
388
|
+
* Obtain an internal key without sharing mutable exported storage.
|
|
389
|
+
* ```ts
|
|
390
|
+
* import { taprootNumsKey } from '@scure/btc-signer/utils.js';
|
|
391
|
+
* const internalKey = taprootNumsKey();
|
|
392
|
+
* ```
|
|
393
|
+
*/
|
|
394
|
+
export function taprootNumsKey() {
|
|
395
|
+
return Uint8Array.from(INTERNAL_TAPROOT_NUMS);
|
|
396
|
+
}
|
|
378
397
|
/** Bitcoin mainnet network parameters. */
|
|
379
398
|
export const NETWORK = /* @__PURE__ */ Object.freeze({
|
|
380
399
|
bech32: 'bc',
|
package/utxo.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as P from 'micro-packed';
|
|
2
2
|
import * as psbt from './psbt.ts';
|
|
3
3
|
import { Transaction, type TxOpts } from './transaction.ts';
|
|
4
|
-
import { type Bytes, NETWORK, type TArg } from './utils.ts';
|
|
4
|
+
import { type Bytes, NETWORK, type TArg, type TRet } from './utils.ts';
|
|
5
5
|
/** Minimal output target accepted by the UTXO selector. */
|
|
6
6
|
export type Output = {
|
|
7
7
|
address: string;
|
|
@@ -17,6 +17,21 @@ export type Accumulated = {
|
|
|
17
17
|
weight: number;
|
|
18
18
|
total: bigint;
|
|
19
19
|
} | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Removes Taproot spend paths that cannot be satisfied by the supplied Schnorr public keys.
|
|
22
|
+
* Non-Taproot inputs are retained, and caller-owned input metadata is never mutated.
|
|
23
|
+
* @param inputs - candidate PSBT input records to filter
|
|
24
|
+
* @param pubkeys - available x-only Schnorr public keys
|
|
25
|
+
* @returns Copies of inputs that retain at least one available path
|
|
26
|
+
* @example
|
|
27
|
+
* Filter wallet UTXOs before selecting coins.
|
|
28
|
+
* ```ts
|
|
29
|
+
* import { filterTaproot } from '@scure/btc-signer/utxo.js';
|
|
30
|
+
* import { pubSchnorr, randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
|
|
31
|
+
* const spendable = filterTaproot([], [pubSchnorr(randomPrivateKeyBytes())]);
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function filterTaproot(inputs: TArg<psbt.TransactionInputUpdate[]>, pubkeys: TArg<Bytes[]>): TRet<psbt.TransactionInputUpdate[]>;
|
|
20
35
|
export declare const _cmpBig: (a: bigint, b: bigint) => 0 | 1 | -1;
|
|
21
36
|
/** Options for fee estimation and UTXO selection. */
|
|
22
37
|
export type EstimatorOpts = TxOpts & {
|
|
@@ -30,6 +45,8 @@ export type EstimatorOpts = TxOpts & {
|
|
|
30
45
|
createTx?: boolean;
|
|
31
46
|
requiredInputs?: psbt.TransactionInputUpdate[];
|
|
32
47
|
allowSameUtxo?: boolean;
|
|
48
|
+
/** Filter Taproot candidate paths to those satisfiable by these Schnorr public keys. */
|
|
49
|
+
filterTaproot?: Bytes[];
|
|
33
50
|
};
|
|
34
51
|
type SortStrategy = 'Newest' | 'Oldest' | 'Smallest' | 'Biggest';
|
|
35
52
|
type ExactStrategy = `exact${SortStrategy}`;
|
|
@@ -118,6 +135,25 @@ export declare class _Estimator {
|
|
|
118
135
|
}>][] | undefined;
|
|
119
136
|
tapInternalKey?: Bytes | undefined;
|
|
120
137
|
tapMerkleRoot?: Bytes | undefined;
|
|
138
|
+
p2cKeyTweak?: [Bytes, Bytes][] | undefined;
|
|
139
|
+
musig2ParticipantPubkeys?: [Bytes, Bytes[]][] | undefined;
|
|
140
|
+
musig2PubNonce?: [TArg<{
|
|
141
|
+
participantPubkey: Bytes;
|
|
142
|
+
aggregatePubkey: Bytes;
|
|
143
|
+
leafHash?: Bytes;
|
|
144
|
+
}>, Bytes][] | undefined;
|
|
145
|
+
musig2PartialSig?: [TArg<{
|
|
146
|
+
participantPubkey: Bytes;
|
|
147
|
+
aggregatePubkey: Bytes;
|
|
148
|
+
leafHash?: Bytes;
|
|
149
|
+
}>, Bytes][] | undefined;
|
|
150
|
+
spEcdhShare?: [Bytes, Bytes][] | undefined;
|
|
151
|
+
spDleq?: [Bytes, Bytes][] | undefined;
|
|
152
|
+
spSpendBip32Derivation?: [Bytes, P.StructInput<{
|
|
153
|
+
fingerprint: number;
|
|
154
|
+
path: number[];
|
|
155
|
+
}>][] | undefined;
|
|
156
|
+
spTweak?: Bytes | undefined;
|
|
121
157
|
proprietary?: [Bytes, Bytes][] | undefined;
|
|
122
158
|
} & {
|
|
123
159
|
unknown?: [P.UnwrapCoder<P.CoderType<P.StructInput<{
|
|
@@ -270,6 +306,74 @@ export declare class _Estimator {
|
|
|
270
306
|
}])[]) | undefined;
|
|
271
307
|
tapInternalKey?: (Bytes & Uint8Array<ArrayBuffer>) | undefined;
|
|
272
308
|
tapMerkleRoot?: (Bytes & Uint8Array<ArrayBuffer>) | undefined;
|
|
309
|
+
p2cKeyTweak?: ([Bytes, Bytes][] & ([Bytes, Bytes] & [Bytes & Uint8Array<ArrayBuffer>, Bytes & Uint8Array<ArrayBuffer>])[]) | undefined;
|
|
310
|
+
musig2ParticipantPubkeys?: ([Bytes, Bytes[]][] & ([Bytes, Bytes[]] & [Bytes & Uint8Array<ArrayBuffer>, Bytes[] & (Bytes & Uint8Array<ArrayBuffer>)[]])[]) | undefined;
|
|
311
|
+
musig2PubNonce?: ([TArg<{
|
|
312
|
+
participantPubkey: Bytes;
|
|
313
|
+
aggregatePubkey: Bytes;
|
|
314
|
+
leafHash?: Bytes;
|
|
315
|
+
}>, Bytes][] & ([TArg<{
|
|
316
|
+
participantPubkey: Bytes;
|
|
317
|
+
aggregatePubkey: Bytes;
|
|
318
|
+
leafHash?: Bytes;
|
|
319
|
+
}>, Bytes] & [({
|
|
320
|
+
participantPubkey: Bytes;
|
|
321
|
+
aggregatePubkey: Bytes;
|
|
322
|
+
leafHash?: Bytes;
|
|
323
|
+
} & {
|
|
324
|
+
participantPubkey: Bytes & Uint8Array<ArrayBuffer>;
|
|
325
|
+
aggregatePubkey: Bytes & Uint8Array<ArrayBuffer>;
|
|
326
|
+
leafHash?: (Bytes & Uint8Array<ArrayBuffer>) | undefined;
|
|
327
|
+
}) | ({
|
|
328
|
+
participantPubkey: TArg<Bytes>;
|
|
329
|
+
aggregatePubkey: TArg<Bytes>;
|
|
330
|
+
leafHash?: TArg<Bytes | undefined>;
|
|
331
|
+
} & {
|
|
332
|
+
participantPubkey: (Bytes & Uint8Array<ArrayBuffer>) | (Uint8Array<ArrayBufferLike> & Uint8Array<ArrayBuffer>);
|
|
333
|
+
aggregatePubkey: (Bytes & Uint8Array<ArrayBuffer>) | (Uint8Array<ArrayBufferLike> & Uint8Array<ArrayBuffer>);
|
|
334
|
+
leafHash?: (Bytes & Uint8Array<ArrayBuffer>) | (Uint8Array<ArrayBufferLike> & Uint8Array<ArrayBuffer>) | undefined;
|
|
335
|
+
}), Bytes & Uint8Array<ArrayBuffer>])[]) | undefined;
|
|
336
|
+
musig2PartialSig?: ([TArg<{
|
|
337
|
+
participantPubkey: Bytes;
|
|
338
|
+
aggregatePubkey: Bytes;
|
|
339
|
+
leafHash?: Bytes;
|
|
340
|
+
}>, Bytes][] & ([TArg<{
|
|
341
|
+
participantPubkey: Bytes;
|
|
342
|
+
aggregatePubkey: Bytes;
|
|
343
|
+
leafHash?: Bytes;
|
|
344
|
+
}>, Bytes] & [({
|
|
345
|
+
participantPubkey: Bytes;
|
|
346
|
+
aggregatePubkey: Bytes;
|
|
347
|
+
leafHash?: Bytes;
|
|
348
|
+
} & {
|
|
349
|
+
participantPubkey: Bytes & Uint8Array<ArrayBuffer>;
|
|
350
|
+
aggregatePubkey: Bytes & Uint8Array<ArrayBuffer>;
|
|
351
|
+
leafHash?: (Bytes & Uint8Array<ArrayBuffer>) | undefined;
|
|
352
|
+
}) | ({
|
|
353
|
+
participantPubkey: TArg<Bytes>;
|
|
354
|
+
aggregatePubkey: TArg<Bytes>;
|
|
355
|
+
leafHash?: TArg<Bytes | undefined>;
|
|
356
|
+
} & {
|
|
357
|
+
participantPubkey: (Bytes & Uint8Array<ArrayBuffer>) | (Uint8Array<ArrayBufferLike> & Uint8Array<ArrayBuffer>);
|
|
358
|
+
aggregatePubkey: (Bytes & Uint8Array<ArrayBuffer>) | (Uint8Array<ArrayBufferLike> & Uint8Array<ArrayBuffer>);
|
|
359
|
+
leafHash?: (Bytes & Uint8Array<ArrayBuffer>) | (Uint8Array<ArrayBufferLike> & Uint8Array<ArrayBuffer>) | undefined;
|
|
360
|
+
}), Bytes & Uint8Array<ArrayBuffer>])[]) | undefined;
|
|
361
|
+
spEcdhShare?: ([Bytes, Bytes][] & ([Bytes, Bytes] & [Bytes & Uint8Array<ArrayBuffer>, Bytes & Uint8Array<ArrayBuffer>])[]) | undefined;
|
|
362
|
+
spDleq?: ([Bytes, Bytes][] & ([Bytes, Bytes] & [Bytes & Uint8Array<ArrayBuffer>, Bytes & Uint8Array<ArrayBuffer>])[]) | undefined;
|
|
363
|
+
spSpendBip32Derivation?: ([Bytes, P.StructInput<{
|
|
364
|
+
fingerprint: number;
|
|
365
|
+
path: number[];
|
|
366
|
+
}>][] & ([Bytes, P.StructInput<{
|
|
367
|
+
fingerprint: number;
|
|
368
|
+
path: number[];
|
|
369
|
+
}>] & [Bytes & Uint8Array<ArrayBuffer>, {
|
|
370
|
+
path: number[];
|
|
371
|
+
fingerprint: number;
|
|
372
|
+
} & {} & {
|
|
373
|
+
path: number[] & number[];
|
|
374
|
+
fingerprint: number;
|
|
375
|
+
}])[]) | undefined;
|
|
376
|
+
spTweak?: (Bytes & Uint8Array<ArrayBuffer>) | undefined;
|
|
273
377
|
proprietary?: ([Bytes, Bytes][] & ([Bytes, Bytes] & [Bytes & Uint8Array<ArrayBuffer>, Bytes & Uint8Array<ArrayBuffer>])[]) | undefined;
|
|
274
378
|
unknown?: ([P.StructInput<{
|
|
275
379
|
type: number;
|
|
@@ -383,6 +487,25 @@ export declare function selectUTXO(inputs: TArg<psbt.TransactionInputUpdate[]>,
|
|
|
383
487
|
}>][] | undefined;
|
|
384
488
|
tapInternalKey?: Bytes | undefined;
|
|
385
489
|
tapMerkleRoot?: Bytes | undefined;
|
|
490
|
+
p2cKeyTweak?: [Bytes, Bytes][] | undefined;
|
|
491
|
+
musig2ParticipantPubkeys?: [Bytes, Bytes[]][] | undefined;
|
|
492
|
+
musig2PubNonce?: [TArg<{
|
|
493
|
+
participantPubkey: Bytes;
|
|
494
|
+
aggregatePubkey: Bytes;
|
|
495
|
+
leafHash?: Bytes;
|
|
496
|
+
}>, Bytes][] | undefined;
|
|
497
|
+
musig2PartialSig?: [TArg<{
|
|
498
|
+
participantPubkey: Bytes;
|
|
499
|
+
aggregatePubkey: Bytes;
|
|
500
|
+
leafHash?: Bytes;
|
|
501
|
+
}>, Bytes][] | undefined;
|
|
502
|
+
spEcdhShare?: [Bytes, Bytes][] | undefined;
|
|
503
|
+
spDleq?: [Bytes, Bytes][] | undefined;
|
|
504
|
+
spSpendBip32Derivation?: [Bytes, P.StructInput<{
|
|
505
|
+
fingerprint: number;
|
|
506
|
+
path: number[];
|
|
507
|
+
}>][] | undefined;
|
|
508
|
+
spTweak?: Bytes | undefined;
|
|
386
509
|
proprietary?: [Bytes, Bytes][] | undefined;
|
|
387
510
|
} & {
|
|
388
511
|
unknown?: [P.UnwrapCoder<P.CoderType<P.StructInput<{
|
|
@@ -535,6 +658,74 @@ export declare function selectUTXO(inputs: TArg<psbt.TransactionInputUpdate[]>,
|
|
|
535
658
|
}])[]) | undefined;
|
|
536
659
|
tapInternalKey?: (Bytes & Uint8Array<ArrayBuffer>) | undefined;
|
|
537
660
|
tapMerkleRoot?: (Bytes & Uint8Array<ArrayBuffer>) | undefined;
|
|
661
|
+
p2cKeyTweak?: ([Bytes, Bytes][] & ([Bytes, Bytes] & [Bytes & Uint8Array<ArrayBuffer>, Bytes & Uint8Array<ArrayBuffer>])[]) | undefined;
|
|
662
|
+
musig2ParticipantPubkeys?: ([Bytes, Bytes[]][] & ([Bytes, Bytes[]] & [Bytes & Uint8Array<ArrayBuffer>, Bytes[] & (Bytes & Uint8Array<ArrayBuffer>)[]])[]) | undefined;
|
|
663
|
+
musig2PubNonce?: ([TArg<{
|
|
664
|
+
participantPubkey: Bytes;
|
|
665
|
+
aggregatePubkey: Bytes;
|
|
666
|
+
leafHash?: Bytes;
|
|
667
|
+
}>, Bytes][] & ([TArg<{
|
|
668
|
+
participantPubkey: Bytes;
|
|
669
|
+
aggregatePubkey: Bytes;
|
|
670
|
+
leafHash?: Bytes;
|
|
671
|
+
}>, Bytes] & [({
|
|
672
|
+
participantPubkey: Bytes;
|
|
673
|
+
aggregatePubkey: Bytes;
|
|
674
|
+
leafHash?: Bytes;
|
|
675
|
+
} & {
|
|
676
|
+
participantPubkey: Bytes & Uint8Array<ArrayBuffer>;
|
|
677
|
+
aggregatePubkey: Bytes & Uint8Array<ArrayBuffer>;
|
|
678
|
+
leafHash?: (Bytes & Uint8Array<ArrayBuffer>) | undefined;
|
|
679
|
+
}) | ({
|
|
680
|
+
participantPubkey: TArg<Bytes>;
|
|
681
|
+
aggregatePubkey: TArg<Bytes>;
|
|
682
|
+
leafHash?: TArg<Bytes | undefined>;
|
|
683
|
+
} & {
|
|
684
|
+
participantPubkey: (Bytes & Uint8Array<ArrayBuffer>) | (Uint8Array<ArrayBufferLike> & Uint8Array<ArrayBuffer>);
|
|
685
|
+
aggregatePubkey: (Bytes & Uint8Array<ArrayBuffer>) | (Uint8Array<ArrayBufferLike> & Uint8Array<ArrayBuffer>);
|
|
686
|
+
leafHash?: (Bytes & Uint8Array<ArrayBuffer>) | (Uint8Array<ArrayBufferLike> & Uint8Array<ArrayBuffer>) | undefined;
|
|
687
|
+
}), Bytes & Uint8Array<ArrayBuffer>])[]) | undefined;
|
|
688
|
+
musig2PartialSig?: ([TArg<{
|
|
689
|
+
participantPubkey: Bytes;
|
|
690
|
+
aggregatePubkey: Bytes;
|
|
691
|
+
leafHash?: Bytes;
|
|
692
|
+
}>, Bytes][] & ([TArg<{
|
|
693
|
+
participantPubkey: Bytes;
|
|
694
|
+
aggregatePubkey: Bytes;
|
|
695
|
+
leafHash?: Bytes;
|
|
696
|
+
}>, Bytes] & [({
|
|
697
|
+
participantPubkey: Bytes;
|
|
698
|
+
aggregatePubkey: Bytes;
|
|
699
|
+
leafHash?: Bytes;
|
|
700
|
+
} & {
|
|
701
|
+
participantPubkey: Bytes & Uint8Array<ArrayBuffer>;
|
|
702
|
+
aggregatePubkey: Bytes & Uint8Array<ArrayBuffer>;
|
|
703
|
+
leafHash?: (Bytes & Uint8Array<ArrayBuffer>) | undefined;
|
|
704
|
+
}) | ({
|
|
705
|
+
participantPubkey: TArg<Bytes>;
|
|
706
|
+
aggregatePubkey: TArg<Bytes>;
|
|
707
|
+
leafHash?: TArg<Bytes | undefined>;
|
|
708
|
+
} & {
|
|
709
|
+
participantPubkey: (Bytes & Uint8Array<ArrayBuffer>) | (Uint8Array<ArrayBufferLike> & Uint8Array<ArrayBuffer>);
|
|
710
|
+
aggregatePubkey: (Bytes & Uint8Array<ArrayBuffer>) | (Uint8Array<ArrayBufferLike> & Uint8Array<ArrayBuffer>);
|
|
711
|
+
leafHash?: (Bytes & Uint8Array<ArrayBuffer>) | (Uint8Array<ArrayBufferLike> & Uint8Array<ArrayBuffer>) | undefined;
|
|
712
|
+
}), Bytes & Uint8Array<ArrayBuffer>])[]) | undefined;
|
|
713
|
+
spEcdhShare?: ([Bytes, Bytes][] & ([Bytes, Bytes] & [Bytes & Uint8Array<ArrayBuffer>, Bytes & Uint8Array<ArrayBuffer>])[]) | undefined;
|
|
714
|
+
spDleq?: ([Bytes, Bytes][] & ([Bytes, Bytes] & [Bytes & Uint8Array<ArrayBuffer>, Bytes & Uint8Array<ArrayBuffer>])[]) | undefined;
|
|
715
|
+
spSpendBip32Derivation?: ([Bytes, P.StructInput<{
|
|
716
|
+
fingerprint: number;
|
|
717
|
+
path: number[];
|
|
718
|
+
}>][] & ([Bytes, P.StructInput<{
|
|
719
|
+
fingerprint: number;
|
|
720
|
+
path: number[];
|
|
721
|
+
}>] & [Bytes & Uint8Array<ArrayBuffer>, {
|
|
722
|
+
path: number[];
|
|
723
|
+
fingerprint: number;
|
|
724
|
+
} & {} & {
|
|
725
|
+
path: number[] & number[];
|
|
726
|
+
fingerprint: number;
|
|
727
|
+
}])[]) | undefined;
|
|
728
|
+
spTweak?: (Bytes & Uint8Array<ArrayBuffer>) | undefined;
|
|
538
729
|
proprietary?: ([Bytes, Bytes][] & ([Bytes, Bytes] & [Bytes & Uint8Array<ArrayBuffer>, Bytes & Uint8Array<ArrayBuffer>])[]) | undefined;
|
|
539
730
|
unknown?: ([P.StructInput<{
|
|
540
731
|
type: number;
|
package/utxo.js
CHANGED
|
@@ -1,81 +1,169 @@
|
|
|
1
1
|
import { hex } from '@scure/base';
|
|
2
2
|
import * as P from 'micro-packed';
|
|
3
|
-
import { Address, OutScript, checkScript, tapLeafHash } from "./payment.js";
|
|
3
|
+
import { Address, OutScript, _WitnessOutScript, checkScript, tapLeafHash, } from "./payment.js";
|
|
4
4
|
import * as psbt from "./psbt.js";
|
|
5
|
-
import { CompactSizeLen, Script } from "./script.js";
|
|
5
|
+
import { CompactSizeLen, RawWitness, Script } from "./script.js";
|
|
6
6
|
import { SignatureHash, Transaction, getInputType, getPrevOut, inputBeforeSign, normalizeInput, toVsize, } from "./transaction.js";
|
|
7
|
-
import { abigint, aarray, astring, NETWORK, PubT,
|
|
7
|
+
import { abigint, aarray, astring, NETWORK, PubT, compareBytes, equalBytes, isBytes, sha256, taprootNumsKey, taprootTweakPubkey, validatePubkey, validateObject, } from "./utils.js";
|
|
8
8
|
const encodeTapBlock = (item) => psbt.TaprootControlBlock.encode(item);
|
|
9
9
|
// Be friendly to bad ECMAScript parsers by not using bigint literals.
|
|
10
10
|
// prettier-ignore
|
|
11
11
|
const _0n = /* @__PURE__ */ BigInt(0), _3n = /* @__PURE__ */ BigInt(3);
|
|
12
12
|
// Serialized length of VarBytes(data) without allocating the encoded copy
|
|
13
13
|
const varLen = (dataLen) => CompactSizeLen.encode(dataLen).length + dataLen;
|
|
14
|
+
const tapLeafWitness = (leaf, sigSize, customScripts, pubkeys, unknownError = 'Finalize: Unknown tapLeafScript') => {
|
|
15
|
+
const [cb, _script] = leaf;
|
|
16
|
+
const _customScripts = customScripts;
|
|
17
|
+
// Last byte is version
|
|
18
|
+
const script = _script.slice(0, -1);
|
|
19
|
+
const ver = _script[_script.length - 1];
|
|
20
|
+
const outs = OutScript.decode(script);
|
|
21
|
+
const available = (pubkey) => !pubkeys || pubkeys.has(hex.encode(pubkey));
|
|
22
|
+
const empty = () => new Uint8Array(sigSize);
|
|
23
|
+
let signatures = [];
|
|
24
|
+
if (outs.type === 'tr_ms') {
|
|
25
|
+
let added = 0;
|
|
26
|
+
for (const pubkey of outs.pubkeys) {
|
|
27
|
+
if (added === outs.m || !available(pubkey))
|
|
28
|
+
signatures.push(P.EMPTY);
|
|
29
|
+
else {
|
|
30
|
+
signatures.push(empty());
|
|
31
|
+
added++;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (added !== outs.m)
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
else if (outs.type === 'tr_ns') {
|
|
38
|
+
for (const pubkey of outs.pubkeys) {
|
|
39
|
+
if (!available(pubkey))
|
|
40
|
+
return;
|
|
41
|
+
signatures.push(empty());
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
if (!_customScripts)
|
|
46
|
+
throw new Error(unknownError);
|
|
47
|
+
const leafHash = tapLeafHash(script, ver);
|
|
48
|
+
const scriptDecoded = Script.decode(script);
|
|
49
|
+
const scriptPubkeys = scriptDecoded.filter((i) => {
|
|
50
|
+
if (!isBytes(i))
|
|
51
|
+
return false;
|
|
52
|
+
try {
|
|
53
|
+
validatePubkey(i, PubT.schnorr);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
const availablePubkeys = scriptPubkeys.filter(available);
|
|
61
|
+
// A custom finalizer may treat an unexpected empty signature list as malformed input. If the
|
|
62
|
+
// script embeds keys but none are owned, the path is unavailable without consulting the hook.
|
|
63
|
+
if (pubkeys && scriptPubkeys.length && !availablePubkeys.length)
|
|
64
|
+
return;
|
|
65
|
+
let recognized = false;
|
|
66
|
+
for (const c of _customScripts) {
|
|
67
|
+
if (!c.finalizeTaproot)
|
|
68
|
+
continue;
|
|
69
|
+
const csEncoded = c.encode(scriptDecoded);
|
|
70
|
+
if (csEncoded === undefined)
|
|
71
|
+
continue;
|
|
72
|
+
recognized = true;
|
|
73
|
+
const finalized = c.finalizeTaproot(script, csEncoded, availablePubkeys.map((pubKey) => [{ pubKey, leafHash }, empty()]));
|
|
74
|
+
if (finalized)
|
|
75
|
+
return finalized.concat(encodeTapBlock(cb));
|
|
76
|
+
}
|
|
77
|
+
if (recognized && pubkeys)
|
|
78
|
+
return;
|
|
79
|
+
throw new Error(unknownError);
|
|
80
|
+
}
|
|
81
|
+
// Witness is stack, so last element will be used first
|
|
82
|
+
return signatures.reverse().concat([script, encodeTapBlock(cb)]);
|
|
83
|
+
};
|
|
14
84
|
function iterLeafs(tapLeafScript, sigSize, customScripts) {
|
|
15
85
|
const _tapLeafScript = tapLeafScript;
|
|
16
86
|
const _customScripts = customScripts;
|
|
17
87
|
if (!_tapLeafScript || !_tapLeafScript.length)
|
|
18
88
|
throw new Error('no leafs');
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
for (const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
89
|
+
// Start with the old shallowest-path order for stable equal-weight ties. Full witness size can
|
|
90
|
+
// reverse that order when a shallow leaf needs a larger script or more signatures.
|
|
91
|
+
const leafs = _tapLeafScript
|
|
92
|
+
.slice()
|
|
93
|
+
.sort((a, b) => encodeTapBlock(a[0]).length - encodeTapBlock(b[0]).length);
|
|
94
|
+
let smallest;
|
|
95
|
+
let smallestSize = Number.POSITIVE_INFINITY;
|
|
96
|
+
for (const leaf of leafs) {
|
|
97
|
+
const witness = tapLeafWitness(leaf, sigSize, _customScripts);
|
|
98
|
+
if (!witness)
|
|
99
|
+
continue;
|
|
100
|
+
const size = RawWitness.encode(witness).length;
|
|
101
|
+
if (size >= smallestSize)
|
|
102
|
+
continue;
|
|
103
|
+
smallest = witness;
|
|
104
|
+
smallestSize = size;
|
|
105
|
+
}
|
|
106
|
+
if (!smallest)
|
|
107
|
+
throw new Error('there was no witness');
|
|
108
|
+
return smallest;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Removes Taproot spend paths that cannot be satisfied by the supplied Schnorr public keys.
|
|
112
|
+
* Non-Taproot inputs are retained, and caller-owned input metadata is never mutated.
|
|
113
|
+
* @param inputs - candidate PSBT input records to filter
|
|
114
|
+
* @param pubkeys - available x-only Schnorr public keys
|
|
115
|
+
* @returns Copies of inputs that retain at least one available path
|
|
116
|
+
* @example
|
|
117
|
+
* Filter wallet UTXOs before selecting coins.
|
|
118
|
+
* ```ts
|
|
119
|
+
* import { filterTaproot } from '@scure/btc-signer/utxo.js';
|
|
120
|
+
* import { pubSchnorr, randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
|
|
121
|
+
* const spendable = filterTaproot([], [pubSchnorr(randomPrivateKeyBytes())]);
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
export function filterTaproot(inputs, pubkeys) {
|
|
125
|
+
aarray(inputs, 'inputs');
|
|
126
|
+
aarray(pubkeys, 'pubkeys');
|
|
127
|
+
const _inputs = inputs;
|
|
128
|
+
const _pubkeys = pubkeys;
|
|
129
|
+
const keys = new Set();
|
|
130
|
+
for (const pubkey of _pubkeys)
|
|
131
|
+
keys.add(hex.encode(validatePubkey(pubkey, PubT.schnorr)));
|
|
132
|
+
const res = [];
|
|
133
|
+
for (const input of _inputs) {
|
|
134
|
+
const filtered = { ...input };
|
|
135
|
+
// BIP371 path fields are optional and empty keyed lists encode as absence, so the committed
|
|
136
|
+
// previous-output script is the authoritative input type.
|
|
137
|
+
const prevScript = _WitnessOutScript.decode(getPrevOut(filtered).script);
|
|
138
|
+
if (prevScript.type !== 'tr') {
|
|
139
|
+
res.push(filtered);
|
|
140
|
+
continue;
|
|
39
141
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
142
|
+
if (filtered.tapInternalKey) {
|
|
143
|
+
// A control block can reconstruct the root, but BIP371 does not require that inference.
|
|
144
|
+
// Omission may be broken data or an earlier filter deliberately disabling the key path.
|
|
145
|
+
// Recognize root omission only when the prevout proves this is an empty-tree commitment.
|
|
146
|
+
const hasRoot = filtered.tapMerkleRoot !== undefined ||
|
|
147
|
+
equalBytes(taprootTweakPubkey(filtered.tapInternalKey, P.EMPTY)[0], prevScript.pubkey);
|
|
148
|
+
if (!hasRoot ||
|
|
149
|
+
equalBytes(filtered.tapInternalKey, taprootNumsKey()) ||
|
|
150
|
+
(!keys.has(hex.encode(filtered.tapInternalKey)) && !keys.has(hex.encode(prevScript.pubkey))))
|
|
151
|
+
delete filtered.tapInternalKey;
|
|
43
152
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (csEncoded === undefined)
|
|
54
|
-
continue;
|
|
55
|
-
const pubKeys = scriptDecoded.filter((i) => {
|
|
56
|
-
if (!isBytes(i))
|
|
57
|
-
return false;
|
|
58
|
-
try {
|
|
59
|
-
validatePubkey(i, PubT.schnorr);
|
|
60
|
-
return true;
|
|
61
|
-
}
|
|
62
|
-
catch (e) {
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
const finalized = c.finalizeTaproot(script, csEncoded, pubKeys.map((pubKey) => [{ pubKey, leafHash }, empty()]));
|
|
67
|
-
if (!finalized)
|
|
68
|
-
continue;
|
|
69
|
-
return finalized.concat(encodeTapBlock(cb));
|
|
70
|
-
}
|
|
71
|
-
// UTXO selection may run without the real signer/finalizer process. When no matching local
|
|
72
|
-
// finalizeTaproot hook exists, keep a minimal script-path witness lower bound here instead of
|
|
73
|
-
// failing selection; callers that need exact fee estimates must provide the matching hook.
|
|
153
|
+
if (filtered.tapLeafScript) {
|
|
154
|
+
const sigSize = filtered.sighashType !== undefined && filtered.sighashType !== SignatureHash.DEFAULT
|
|
155
|
+
? 65
|
|
156
|
+
: 64;
|
|
157
|
+
const leafs = filtered.tapLeafScript.filter((leaf) => tapLeafWitness(leaf, sigSize, undefined, keys, 'filterTaproot: unknown Taproot leaf'));
|
|
158
|
+
if (leafs.length)
|
|
159
|
+
filtered.tapLeafScript = leafs;
|
|
160
|
+
else
|
|
161
|
+
delete filtered.tapLeafScript;
|
|
74
162
|
}
|
|
75
|
-
|
|
76
|
-
|
|
163
|
+
if (filtered.tapInternalKey || filtered.tapLeafScript)
|
|
164
|
+
res.push(filtered);
|
|
77
165
|
}
|
|
78
|
-
|
|
166
|
+
return res;
|
|
79
167
|
}
|
|
80
168
|
function estimateInput(inputType, input, opts) {
|
|
81
169
|
const _input = input;
|
|
@@ -85,13 +173,9 @@ function estimateInput(inputType, input, opts) {
|
|
|
85
173
|
// schnorr sig is always 64 bytes. except for cases when sighash is not default!
|
|
86
174
|
if (inputType.txType === 'taproot') {
|
|
87
175
|
const SCHNORR_SIG_SIZE = inputType.sighash !== SignatureHash.DEFAULT ? 65 : 64;
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
// on the online side. Callers that know only script-path signing is
|
|
92
|
-
// possible should omit `tapInternalKey` or pre-filter `tapLeafScript`
|
|
93
|
-
// before estimation.
|
|
94
|
-
if (_input.tapInternalKey && !equalBytes(_input.tapInternalKey, TAPROOT_UNSPENDABLE_KEY)) {
|
|
176
|
+
// A real internal key and every retained leaf declare paths available to this caller. Use
|
|
177
|
+
// filterTaproot before estimation when the supplied input contains unavailable paths.
|
|
178
|
+
if (_input.tapInternalKey && !equalBytes(_input.tapInternalKey, taprootNumsKey())) {
|
|
95
179
|
witness = [new Uint8Array(SCHNORR_SIG_SIZE)];
|
|
96
180
|
}
|
|
97
181
|
else if (_input.tapLeafScript) {
|
|
@@ -195,7 +279,7 @@ function getScript(o, opts = {}, network = NETWORK) {
|
|
|
195
279
|
// Keep selector-only `createTx: false` flows aligned with the transaction/PSBT output boundary:
|
|
196
280
|
// satoshi-denominated outputs are not allowed to go negative.
|
|
197
281
|
abigint(_o.amount, 'output.amount');
|
|
198
|
-
if (script && !_opts.allowUnknownOutputs &&
|
|
282
|
+
if (script && !_opts.allowUnknownOutputs && _WitnessOutScript.decode(script).type === 'unknown') {
|
|
199
283
|
throw new Error('Estimator: unknown output script type, there is a chance that input is unspendable. Pass allowUnknownOutputs=true, if you sure');
|
|
200
284
|
}
|
|
201
285
|
if (!_opts.disableScriptCheck)
|
|
@@ -217,6 +301,9 @@ export class _Estimator {
|
|
|
217
301
|
outputs;
|
|
218
302
|
opts;
|
|
219
303
|
constructor(inputs, outputs, opts) {
|
|
304
|
+
// EstimatorOpts extends TxOpts, so resolve the complete transaction policy once even when
|
|
305
|
+
// createTx=false. This keeps selection normalization and a returned Transaction identical.
|
|
306
|
+
opts = new Transaction(opts).opts;
|
|
220
307
|
this.outputs = outputs;
|
|
221
308
|
this.opts = opts;
|
|
222
309
|
// Zero-fee estimation is useful on regtest/in tests, but negative fee rates would make
|
|
@@ -273,7 +360,7 @@ export class _Estimator {
|
|
|
273
360
|
}
|
|
274
361
|
const inputKeys = new Set();
|
|
275
362
|
this.normalizedInputs = allInputs.map((i) => {
|
|
276
|
-
const normalized = normalizeInput(i, undefined, undefined, opts.disableScriptCheck, opts.
|
|
363
|
+
const normalized = normalizeInput(i, undefined, undefined, opts.disableScriptCheck, opts.unknown, opts.proprietary);
|
|
277
364
|
inputBeforeSign(normalized); // check fields
|
|
278
365
|
const key = `${hex.encode(normalized.txid)}:${normalized.index}`;
|
|
279
366
|
if (!opts.allowSameUtxo && inputKeys.has(key))
|
|
@@ -557,6 +644,16 @@ export function selectUTXO(inputs, outputs, strategy, opts) {
|
|
|
557
644
|
astring(strategy, 'strategy');
|
|
558
645
|
// Public wrapper defaults to BIP69 ordering and tx construction unless callers override them.
|
|
559
646
|
const _opts = { createTx: true, bip69: true, ...opts };
|
|
560
|
-
|
|
647
|
+
let candidates = inputs;
|
|
648
|
+
if (_opts.filterTaproot !== undefined) {
|
|
649
|
+
candidates = filterTaproot(candidates, _opts.filterTaproot);
|
|
650
|
+
if (_opts.requiredInputs) {
|
|
651
|
+
const requiredInputs = filterTaproot(_opts.requiredInputs, _opts.filterTaproot);
|
|
652
|
+
if (requiredInputs.length !== _opts.requiredInputs.length)
|
|
653
|
+
throw new Error('filterTaproot: required input has no available Taproot path');
|
|
654
|
+
_opts.requiredInputs = requiredInputs;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
const est = new _Estimator(candidates, outputs, _opts);
|
|
561
658
|
return est.result(strategy);
|
|
562
659
|
}
|