@scure/btc-signer 1.5.0 → 1.7.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 +146 -25
- package/_type_test.js +2 -2
- package/_type_test.js.map +1 -1
- package/esm/_type_test.js +2 -2
- package/esm/_type_test.js.map +1 -1
- package/esm/index.d.ts +8 -6
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +7 -10
- package/esm/index.js.map +1 -1
- package/esm/musig2.d.ts +148 -0
- package/esm/musig2.d.ts.map +1 -0
- package/esm/musig2.js +405 -0
- package/esm/musig2.js.map +1 -0
- package/esm/payment.d.ts +43 -76
- package/esm/payment.d.ts.map +1 -1
- package/esm/payment.js +15 -2
- package/esm/payment.js.map +1 -1
- package/esm/psbt.d.ts +463 -463
- package/esm/psbt.d.ts.map +1 -1
- package/esm/psbt.js +10 -7
- package/esm/psbt.js.map +1 -1
- package/esm/script.d.ts +19 -17
- package/esm/script.d.ts.map +1 -1
- package/esm/script.js.map +1 -1
- package/esm/transaction.d.ts +13 -123
- package/esm/transaction.d.ts.map +1 -1
- package/esm/transaction.js +8 -9
- package/esm/transaction.js.map +1 -1
- package/esm/utils.d.ts +17 -12
- package/esm/utils.d.ts.map +1 -1
- package/esm/utils.js +6 -4
- package/esm/utils.js.map +1 -1
- package/esm/utxo.d.ts +96 -94
- package/esm/utxo.d.ts.map +1 -1
- package/esm/utxo.js +19 -15
- package/esm/utxo.js.map +1 -1
- package/index.d.ts +8 -6
- package/index.d.ts.map +1 -1
- package/index.js +20 -22
- package/index.js.map +1 -1
- package/musig2.d.ts +148 -0
- package/musig2.d.ts.map +1 -0
- package/musig2.js +417 -0
- package/musig2.js.map +1 -0
- package/package.json +22 -14
- package/payment.d.ts +43 -76
- package/payment.d.ts.map +1 -1
- package/payment.js +14 -1
- package/payment.js.map +1 -1
- package/psbt.d.ts +463 -463
- package/psbt.d.ts.map +1 -1
- package/psbt.js +13 -10
- package/psbt.js.map +1 -1
- package/script.d.ts +19 -17
- package/script.d.ts.map +1 -1
- package/script.js.map +1 -1
- package/src/_type_test.ts +2 -2
- package/src/index.ts +25 -14
- package/src/musig2.ts +517 -0
- package/src/payment.ts +74 -27
- package/src/psbt.ts +36 -14
- package/src/script.ts +5 -5
- package/src/transaction.ts +61 -39
- package/src/utils.ts +26 -16
- package/src/utxo.ts +52 -31
- package/transaction.d.ts +13 -123
- package/transaction.d.ts.map +1 -1
- package/transaction.js +17 -18
- package/transaction.js.map +1 -1
- package/utils.d.ts +17 -12
- package/utils.d.ts.map +1 -1
- package/utils.js +6 -4
- package/utils.js.map +1 -1
- package/utxo.d.ts +96 -94
- package/utxo.d.ts.map +1 -1
- package/utxo.js +17 -13
- package/utxo.js.map +1 -1
package/src/payment.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { bech32, bech32m, type Coder, createBase58check, hex } from '@scure/base';
|
|
2
2
|
import * as P from 'micro-packed';
|
|
3
|
-
import { TaprootControlBlock, TransactionInput } from './psbt.js';
|
|
4
|
-
import { OpToNum,
|
|
5
|
-
import { Bytes, NETWORK } from './utils.js';
|
|
3
|
+
import { TaprootControlBlock, type TransactionInput } from './psbt.js';
|
|
4
|
+
import { OpToNum, Script, type ScriptType, VarBytes } from './script.js';
|
|
6
5
|
import * as u from './utils.js';
|
|
6
|
+
import { type BTC_NETWORK, type Bytes, NETWORK } from './utils.js';
|
|
7
7
|
|
|
8
8
|
// We need following items:
|
|
9
9
|
// - encode/decode output script
|
|
@@ -18,6 +18,20 @@ export type P2Ret = {
|
|
|
18
18
|
witnessScript?: Bytes;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
// Pay to Anchor (P2A)
|
|
22
|
+
type OutP2AType = { type: 'p2a'; script: Bytes };
|
|
23
|
+
const OutP2A: Coder<OptScript, OutP2AType | undefined> = {
|
|
24
|
+
encode(from: ScriptType): OutP2AType | undefined {
|
|
25
|
+
if (from.length !== 2 || from[0] !== 1 || !u.isBytes(from[1]) || hex.encode(from[1]) !== '4e73')
|
|
26
|
+
return;
|
|
27
|
+
return { type: 'p2a', script: Script.encode(from) };
|
|
28
|
+
},
|
|
29
|
+
decode: (to: OutP2AType): OptScript => {
|
|
30
|
+
if (to.type !== 'p2a') return;
|
|
31
|
+
return [1, hex.decode('4e73')];
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
21
35
|
// Public Key (P2PK)
|
|
22
36
|
type OutPKType = { type: 'pk'; pubkey: Bytes };
|
|
23
37
|
export type OptScript = ScriptType | undefined;
|
|
@@ -189,6 +203,7 @@ const OutUnknown: Coder<OptScript, OutUnknownType | undefined> = {
|
|
|
189
203
|
// /Payments
|
|
190
204
|
|
|
191
205
|
const OutScripts = [
|
|
206
|
+
OutP2A,
|
|
192
207
|
OutPK,
|
|
193
208
|
OutPKH,
|
|
194
209
|
OutSH,
|
|
@@ -224,7 +239,22 @@ export type CustomScript = Coder<OptScript, CustomScriptOut | undefined> & {
|
|
|
224
239
|
};
|
|
225
240
|
|
|
226
241
|
// We can validate this once, because of packed & coders
|
|
227
|
-
export const OutScript
|
|
242
|
+
export const OutScript: P.CoderType<
|
|
243
|
+
NonNullable<
|
|
244
|
+
| OutP2AType
|
|
245
|
+
| OutPKType
|
|
246
|
+
| OutPKHType
|
|
247
|
+
| OutSHType
|
|
248
|
+
| OutWSHType
|
|
249
|
+
| OutWPKHType
|
|
250
|
+
| OutMSType
|
|
251
|
+
| OutTRType
|
|
252
|
+
| OutTRNSType
|
|
253
|
+
| OutTRMSType
|
|
254
|
+
| OutUnknownType
|
|
255
|
+
| undefined
|
|
256
|
+
>
|
|
257
|
+
> = P.validate(_OutScript, (i) => {
|
|
228
258
|
if (i.type === 'pk' && !isValidPubkey(i.pubkey, u.PubT.ecdsa))
|
|
229
259
|
throw new Error('OutScript/pk: wrong key');
|
|
230
260
|
if (
|
|
@@ -267,7 +297,7 @@ function checkWSH(s: OutWSHType, witnessScript: Bytes) {
|
|
|
267
297
|
throw new Error(`checkScript: P2${w.type} cannot be wrapped in P2WSH`);
|
|
268
298
|
}
|
|
269
299
|
|
|
270
|
-
export function checkScript(script?: Bytes, redeemScript?: Bytes, witnessScript?: Bytes) {
|
|
300
|
+
export function checkScript(script?: Bytes, redeemScript?: Bytes, witnessScript?: Bytes): void {
|
|
271
301
|
if (script) {
|
|
272
302
|
const s = OutScript.decode(script);
|
|
273
303
|
// ms||pk maybe work, but there will be no address, hard to spend
|
|
@@ -300,7 +330,7 @@ function uniqPubkey(pubkeys: Bytes[]) {
|
|
|
300
330
|
}
|
|
301
331
|
|
|
302
332
|
// @ts-ignore
|
|
303
|
-
export const p2pk = (pubkey: Bytes, network = NETWORK): P2Ret => {
|
|
333
|
+
export const p2pk = (pubkey: Bytes, network: BTC_NETWORK = NETWORK): P2Ret => {
|
|
304
334
|
// network is unused
|
|
305
335
|
if (!isValidPubkey(pubkey, u.PubT.ecdsa)) throw new Error('P2PK: invalid publicKey');
|
|
306
336
|
return {
|
|
@@ -308,7 +338,7 @@ export const p2pk = (pubkey: Bytes, network = NETWORK): P2Ret => {
|
|
|
308
338
|
script: OutScript.encode({ type: 'pk', pubkey }),
|
|
309
339
|
};
|
|
310
340
|
};
|
|
311
|
-
export const p2pkh = (publicKey: Bytes, network = NETWORK): P2Ret => {
|
|
341
|
+
export const p2pkh = (publicKey: Bytes, network: BTC_NETWORK = NETWORK): P2Ret => {
|
|
312
342
|
if (!isValidPubkey(publicKey, u.PubT.ecdsa)) throw new Error('P2PKH: invalid publicKey');
|
|
313
343
|
const hash = u.hash160(publicKey);
|
|
314
344
|
return {
|
|
@@ -317,7 +347,7 @@ export const p2pkh = (publicKey: Bytes, network = NETWORK): P2Ret => {
|
|
|
317
347
|
address: Address(network).encode({ type: 'pkh', hash }),
|
|
318
348
|
};
|
|
319
349
|
};
|
|
320
|
-
export const p2sh = (child: P2Ret, network = NETWORK): P2Ret => {
|
|
350
|
+
export const p2sh = (child: P2Ret, network: BTC_NETWORK = NETWORK): P2Ret => {
|
|
321
351
|
// It is already tested inside noble-hashes and checkScript
|
|
322
352
|
const cs = child.script;
|
|
323
353
|
if (!u.isBytes(cs)) throw new Error(`Wrong script: ${typeof child.script}, expected Uint8Array`);
|
|
@@ -333,7 +363,7 @@ export const p2sh = (child: P2Ret, network = NETWORK): P2Ret => {
|
|
|
333
363
|
if (child.witnessScript) res.witnessScript = child.witnessScript;
|
|
334
364
|
return res;
|
|
335
365
|
};
|
|
336
|
-
export const p2wsh = (child: P2Ret, network = NETWORK): P2Ret => {
|
|
366
|
+
export const p2wsh = (child: P2Ret, network: BTC_NETWORK = NETWORK): P2Ret => {
|
|
337
367
|
const cs = child.script;
|
|
338
368
|
if (!u.isBytes(cs)) throw new Error(`Wrong script: ${typeof cs}, expected Uint8Array`);
|
|
339
369
|
const hash = u.sha256(cs);
|
|
@@ -346,7 +376,7 @@ export const p2wsh = (child: P2Ret, network = NETWORK): P2Ret => {
|
|
|
346
376
|
address: Address(network).encode({ type: 'wsh', hash }),
|
|
347
377
|
};
|
|
348
378
|
};
|
|
349
|
-
export const p2wpkh = (publicKey: Bytes, network = NETWORK): P2Ret => {
|
|
379
|
+
export const p2wpkh = (publicKey: Bytes, network: BTC_NETWORK = NETWORK): P2Ret => {
|
|
350
380
|
if (!isValidPubkey(publicKey, u.PubT.ecdsa)) throw new Error('P2WPKH: invalid publicKey');
|
|
351
381
|
if (publicKey.length === 65) throw new Error('P2WPKH: uncompressed public key');
|
|
352
382
|
const hash = u.hash160(publicKey);
|
|
@@ -361,7 +391,7 @@ export const p2ms = (m: number, pubkeys: Bytes[], allowSamePubkeys = false): P2R
|
|
|
361
391
|
return { type: 'ms', script: OutScript.encode({ type: 'ms', pubkeys, m }) };
|
|
362
392
|
};
|
|
363
393
|
|
|
364
|
-
type HashedTree =
|
|
394
|
+
export type HashedTree =
|
|
365
395
|
| { type: 'leaf'; version?: number; script: Bytes; hash: Bytes }
|
|
366
396
|
| { type: 'branch'; left: HashedTree; right: HashedTree; hash: Bytes };
|
|
367
397
|
function checkTaprootScript(
|
|
@@ -409,9 +439,9 @@ function checkTaprootScript(
|
|
|
409
439
|
}
|
|
410
440
|
|
|
411
441
|
export type P2TROut = P2Ret & {
|
|
412
|
-
tweakedPubkey:
|
|
413
|
-
tapInternalKey:
|
|
414
|
-
tapMerkleRoot?:
|
|
442
|
+
tweakedPubkey: Bytes;
|
|
443
|
+
tapInternalKey: Bytes;
|
|
444
|
+
tapMerkleRoot?: Bytes;
|
|
415
445
|
tapLeafScript?: TransactionInput['tapLeafScript'];
|
|
416
446
|
leaves?: TaprootLeaf[];
|
|
417
447
|
};
|
|
@@ -451,7 +481,7 @@ export function taprootListToTree(taprootList: TaprootScriptList): TaprootScript
|
|
|
451
481
|
return (last?.childs || last) as TaprootScriptTree;
|
|
452
482
|
}
|
|
453
483
|
|
|
454
|
-
type TaprootLeaf = {
|
|
484
|
+
export type TaprootLeaf = {
|
|
455
485
|
type: 'leaf';
|
|
456
486
|
version?: number;
|
|
457
487
|
script: Bytes;
|
|
@@ -459,7 +489,7 @@ type TaprootLeaf = {
|
|
|
459
489
|
path: Bytes[];
|
|
460
490
|
};
|
|
461
491
|
|
|
462
|
-
type HashedTreeWithPath =
|
|
492
|
+
export type HashedTreeWithPath =
|
|
463
493
|
| TaprootLeaf
|
|
464
494
|
| {
|
|
465
495
|
type: 'branch';
|
|
@@ -526,7 +556,7 @@ function taprootHashTree(
|
|
|
526
556
|
}
|
|
527
557
|
|
|
528
558
|
export const TAP_LEAF_VERSION = 0xc0;
|
|
529
|
-
export const tapLeafHash = (script: Bytes, version = TAP_LEAF_VERSION) =>
|
|
559
|
+
export const tapLeafHash = (script: Bytes, version: number = TAP_LEAF_VERSION): Bytes =>
|
|
530
560
|
u.tagSchnorr('TapLeaf', new Uint8Array([version]), VarBytes.encode(script));
|
|
531
561
|
|
|
532
562
|
// Works as key OR tree.
|
|
@@ -535,7 +565,7 @@ export const tapLeafHash = (script: Bytes, version = TAP_LEAF_VERSION) =>
|
|
|
535
565
|
export function p2tr(
|
|
536
566
|
internalPubKey?: Bytes | string,
|
|
537
567
|
tree?: TaprootScriptTree,
|
|
538
|
-
network = NETWORK,
|
|
568
|
+
network: BTC_NETWORK = NETWORK,
|
|
539
569
|
allowUnknownOutputs = false,
|
|
540
570
|
customScripts?: CustomScript[]
|
|
541
571
|
): P2TROut {
|
|
@@ -631,7 +661,14 @@ export const p2tr_ns = (m: number, pubkeys: Bytes[], allowSamePubkeys = false):
|
|
|
631
661
|
// Taproot public key (case of p2tr_ns)
|
|
632
662
|
export const p2tr_pk = (pubkey: Bytes): P2Ret => p2tr_ns(1, [pubkey], undefined)[0];
|
|
633
663
|
|
|
634
|
-
export function p2tr_ms(
|
|
664
|
+
export function p2tr_ms(
|
|
665
|
+
m: number,
|
|
666
|
+
pubkeys: Bytes[],
|
|
667
|
+
allowSamePubkeys = false
|
|
668
|
+
): {
|
|
669
|
+
type: string;
|
|
670
|
+
script: Uint8Array;
|
|
671
|
+
} {
|
|
635
672
|
if (!allowSamePubkeys) uniqPubkey(pubkeys);
|
|
636
673
|
return {
|
|
637
674
|
type: 'tr_ms',
|
|
@@ -640,7 +677,11 @@ export function p2tr_ms(m: number, pubkeys: Bytes[], allowSamePubkeys = false) {
|
|
|
640
677
|
}
|
|
641
678
|
|
|
642
679
|
// Simple pubkey address, without complex scripts
|
|
643
|
-
export function getAddress(
|
|
680
|
+
export function getAddress(
|
|
681
|
+
type: 'pkh' | 'wpkh' | 'tr',
|
|
682
|
+
privKey: Bytes,
|
|
683
|
+
network: BTC_NETWORK = NETWORK
|
|
684
|
+
): string | undefined {
|
|
644
685
|
if (type === 'tr') {
|
|
645
686
|
return p2tr(u.pubSchnorr(privKey), undefined, network).address;
|
|
646
687
|
}
|
|
@@ -650,20 +691,26 @@ export function getAddress(type: 'pkh' | 'wpkh' | 'tr', privKey: Bytes, network
|
|
|
650
691
|
throw new Error(`getAddress: unknown type=${type}`);
|
|
651
692
|
}
|
|
652
693
|
|
|
653
|
-
export const _sortPubkeys = (pubkeys: Bytes[]) =>
|
|
694
|
+
export const _sortPubkeys = (pubkeys: Bytes[]): Uint8Array[] =>
|
|
695
|
+
Array.from(pubkeys).sort(u.compareBytes);
|
|
654
696
|
|
|
655
697
|
export function multisig(
|
|
656
698
|
m: number,
|
|
657
699
|
pubkeys: Bytes[],
|
|
658
700
|
sorted = false,
|
|
659
701
|
witness = false,
|
|
660
|
-
network = NETWORK
|
|
661
|
-
) {
|
|
702
|
+
network: BTC_NETWORK = NETWORK
|
|
703
|
+
): P2Ret {
|
|
662
704
|
const ms = p2ms(m, sorted ? _sortPubkeys(pubkeys) : pubkeys);
|
|
663
705
|
return witness ? p2wsh(ms, network) : p2sh(ms, network);
|
|
664
706
|
}
|
|
665
707
|
|
|
666
|
-
export function sortedMultisig(
|
|
708
|
+
export function sortedMultisig(
|
|
709
|
+
m: number,
|
|
710
|
+
pubkeys: Bytes[],
|
|
711
|
+
witness = false,
|
|
712
|
+
network: BTC_NETWORK = NETWORK
|
|
713
|
+
): P2Ret {
|
|
667
714
|
return multisig(m, pubkeys, true, witness, network);
|
|
668
715
|
}
|
|
669
716
|
|
|
@@ -686,7 +733,7 @@ function formatKey(hashed: Bytes, prefix: number[]): string {
|
|
|
686
733
|
return base58check.encode(u.concatBytes(Uint8Array.from(prefix), hashed));
|
|
687
734
|
}
|
|
688
735
|
|
|
689
|
-
export function WIF(network = NETWORK): Coder<Bytes, string> {
|
|
736
|
+
export function WIF(network: BTC_NETWORK = NETWORK): Coder<Bytes, string> {
|
|
690
737
|
return {
|
|
691
738
|
encode(privKey: Bytes) {
|
|
692
739
|
const compressed = u.concatBytes(privKey, new Uint8Array([0x01]));
|
|
@@ -705,7 +752,7 @@ export function WIF(network = NETWORK): Coder<Bytes, string> {
|
|
|
705
752
|
}
|
|
706
753
|
|
|
707
754
|
// Returns OutType, which can be used to create outscript
|
|
708
|
-
export function Address(network = NETWORK) {
|
|
755
|
+
export function Address(network: BTC_NETWORK = NETWORK) {
|
|
709
756
|
return {
|
|
710
757
|
encode(from: P.UnwrapCoder<OutScriptType>): string {
|
|
711
758
|
const { type } = from;
|
package/src/psbt.ts
CHANGED
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
import { hex } from '@scure/base';
|
|
2
2
|
import * as P from 'micro-packed';
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
CompactSize,
|
|
5
|
+
CompactSizeLen,
|
|
6
|
+
RawOldTx,
|
|
7
|
+
RawOutput,
|
|
8
|
+
RawTx,
|
|
9
|
+
RawWitness,
|
|
10
|
+
VarBytes,
|
|
11
|
+
} from './script.js';
|
|
5
12
|
import { Transaction } from './transaction.js'; // circular
|
|
6
|
-
import { Bytes, compareBytes, PubT, validatePubkey
|
|
13
|
+
import { type Bytes, compareBytes, equalBytes, PubT, validatePubkey } from './utils.js';
|
|
7
14
|
|
|
8
15
|
// PSBT BIP174, BIP370, BIP371
|
|
9
16
|
|
|
10
17
|
// Can be 33 or 64 bytes
|
|
11
|
-
const PubKeyECDSA = P.validate(P.bytes(null), (pub) =>
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
const PubKeyECDSA: P.CoderType<Uint8Array> = P.validate(P.bytes(null), (pub) =>
|
|
19
|
+
validatePubkey(pub, PubT.ecdsa)
|
|
20
|
+
);
|
|
21
|
+
const PubKeySchnorr: P.CoderType<Uint8Array> = P.validate(P.bytes(32), (pub) =>
|
|
22
|
+
validatePubkey(pub, PubT.schnorr)
|
|
23
|
+
);
|
|
24
|
+
const SignatureSchnorr: P.CoderType<Uint8Array> = P.validate(P.bytes(null), (sig) => {
|
|
14
25
|
if (sig.length !== 64 && sig.length !== 65)
|
|
15
26
|
throw new Error('Schnorr signature should be 64 or 65 bytes long');
|
|
16
27
|
return sig;
|
|
@@ -51,9 +62,9 @@ const tapTree = P.array(
|
|
|
51
62
|
})
|
|
52
63
|
);
|
|
53
64
|
|
|
54
|
-
const BytesInf = P.bytes(null); // Bytes will conflict with Bytes type
|
|
55
|
-
const Bytes20 = P.bytes(20);
|
|
56
|
-
const Bytes32 = P.bytes(32);
|
|
65
|
+
const BytesInf: P.CoderType<P.Bytes> = P.bytes(null); // Bytes will conflict with Bytes type
|
|
66
|
+
const Bytes20: P.CoderType<P.Bytes> = P.bytes(20);
|
|
67
|
+
const Bytes32: P.CoderType<P.Bytes> = P.bytes(32);
|
|
57
68
|
// versionsRequiringExclusing = !versionsAllowsInclusion (as set)
|
|
58
69
|
// {name: [tag, keyCoder, valueCoder, versionsRequiringInclusion, versionsRequiringExclusing, versionsAllowsInclusion, silentIgnore]}
|
|
59
70
|
// SilentIgnore: we use some v2 fields for v1 representation too, so we just clean them before serialize
|
|
@@ -167,7 +178,12 @@ function PSBTKeyInfo(info: PSBTKeyMapInfo) {
|
|
|
167
178
|
|
|
168
179
|
type PSBTKeyMap = Record<string, PSBTKeyMapInfo>;
|
|
169
180
|
|
|
170
|
-
const PSBTUnknownKey
|
|
181
|
+
const PSBTUnknownKey: P.CoderType<
|
|
182
|
+
P.StructInput<{
|
|
183
|
+
type: number;
|
|
184
|
+
key: Uint8Array;
|
|
185
|
+
}>
|
|
186
|
+
> = P.struct({ type: CompactSizeLen, key: P.bytes(null) });
|
|
171
187
|
type PSBTUnknownFields = { unknown?: [P.UnwrapCoder<typeof PSBTUnknownKey>, Bytes][] };
|
|
172
188
|
export type PSBTKeyMapKeys<T extends PSBTKeyMap> = {
|
|
173
189
|
-readonly [K in keyof T]?: T[K][1] extends false
|
|
@@ -399,7 +415,7 @@ export function cleanPSBTFields<T extends PSBTKeyMap>(
|
|
|
399
415
|
version: number,
|
|
400
416
|
info: T,
|
|
401
417
|
lst: PSBTKeyMapKeys<T>
|
|
402
|
-
) {
|
|
418
|
+
): PSBTKeyMapKeys<T> {
|
|
403
419
|
const out: PSBTKeyMapKeys<T> = {};
|
|
404
420
|
for (const _k in lst) {
|
|
405
421
|
const k = _k as string & keyof PSBTKeyMapKeys<T>;
|
|
@@ -442,7 +458,8 @@ export function mergeKeyMap<T extends PSBTKeyMap>(
|
|
|
442
458
|
psbtEnum: T,
|
|
443
459
|
val: PSBTKeyMapKeys<T>,
|
|
444
460
|
cur?: PSBTKeyMapKeys<T>,
|
|
445
|
-
allowedFields?: (keyof PSBTKeyMapKeys<T>)[]
|
|
461
|
+
allowedFields?: (keyof PSBTKeyMapKeys<T>)[],
|
|
462
|
+
allowUnknown?: boolean
|
|
446
463
|
): PSBTKeyMapKeys<T> {
|
|
447
464
|
const res: PSBTKeyMapKeys<T> = { ...cur, ...val };
|
|
448
465
|
// All arguments can be provided as hex
|
|
@@ -501,8 +518,13 @@ export function mergeKeyMap<T extends PSBTKeyMap>(
|
|
|
501
518
|
throw new Error(`Cannot change signed field=${k}`);
|
|
502
519
|
}
|
|
503
520
|
}
|
|
504
|
-
// Remove unknown keys
|
|
505
|
-
for (const k in res)
|
|
521
|
+
// Remove unknown keys except the "unknown" array if allowUnknown is true
|
|
522
|
+
for (const k in res) {
|
|
523
|
+
if (!psbtEnum[k]) {
|
|
524
|
+
if (allowUnknown && k === 'unknown') continue;
|
|
525
|
+
delete res[k];
|
|
526
|
+
}
|
|
527
|
+
}
|
|
506
528
|
return res;
|
|
507
529
|
}
|
|
508
530
|
|
package/src/script.ts
CHANGED
|
@@ -81,7 +81,7 @@ export function ScriptNum(bytesLimit = 6, forceMinimal = false): P.CoderType<big
|
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
export function OpToNum(op: ScriptOP, bytesLimit = 4, forceMinimal = true) {
|
|
84
|
+
export function OpToNum(op: ScriptOP, bytesLimit = 4, forceMinimal = true): number | undefined {
|
|
85
85
|
if (typeof op === 'number') return op;
|
|
86
86
|
if (isBytes(op)) {
|
|
87
87
|
try {
|
|
@@ -197,13 +197,13 @@ export const CompactSize: P.CoderType<bigint> = P.wrap({
|
|
|
197
197
|
});
|
|
198
198
|
|
|
199
199
|
// Same thing, but in number instead of bigint. Checks for safe integer inside
|
|
200
|
-
export const CompactSizeLen = P.apply(CompactSize, P.coders.numberBigint);
|
|
200
|
+
export const CompactSizeLen: P.CoderType<number> = P.apply(CompactSize, P.coders.numberBigint);
|
|
201
201
|
|
|
202
202
|
// ui8a of size <CompactSize>
|
|
203
|
-
export const VarBytes = P.bytes(CompactSize);
|
|
203
|
+
export const VarBytes: P.CoderType<Uint8Array> = P.bytes(CompactSize);
|
|
204
204
|
|
|
205
205
|
// SegWit v0 stack of witness buffers
|
|
206
|
-
export const RawWitness = P.array(CompactSizeLen, VarBytes);
|
|
206
|
+
export const RawWitness: P.CoderType<Uint8Array[]> = P.array(CompactSizeLen, VarBytes);
|
|
207
207
|
|
|
208
208
|
// Array of size <CompactSize>
|
|
209
209
|
export const BTCArray = <T>(t: P.CoderType<T>): P.CoderType<T[]> => P.array(CompactSize, t);
|
|
@@ -235,7 +235,7 @@ function validateRawTx(tx: P.UnwrapCoder<typeof _RawTx>) {
|
|
|
235
235
|
throw new Error('Segwit flag with empty witnesses array');
|
|
236
236
|
return tx;
|
|
237
237
|
}
|
|
238
|
-
export const RawTx = P.validate(_RawTx, validateRawTx);
|
|
238
|
+
export const RawTx: typeof _RawTx = P.validate(_RawTx, validateRawTx);
|
|
239
239
|
// Pre-SegWit serialization format (for PSBTv0)
|
|
240
240
|
export const RawOldTx = P.struct({
|
|
241
241
|
version: P.I32LE,
|
package/src/transaction.ts
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import * as P from 'micro-packed';
|
|
2
1
|
import { hex } from '@scure/base';
|
|
3
|
-
import
|
|
2
|
+
import * as P from 'micro-packed';
|
|
3
|
+
import { Address, type CustomScript, OutScript, checkScript, tapLeafHash } from './payment.js';
|
|
4
4
|
import * as psbt from './psbt.js'; // circular
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import {
|
|
6
|
+
CompactSizeLen,
|
|
7
|
+
RawOldTx,
|
|
8
|
+
RawOutput,
|
|
9
|
+
RawTx,
|
|
10
|
+
RawWitness,
|
|
11
|
+
Script,
|
|
12
|
+
VarBytes,
|
|
13
|
+
} from './script.js';
|
|
8
14
|
import * as u from './utils.js';
|
|
9
|
-
import {
|
|
15
|
+
import { type Bytes, NETWORK, concatBytes, equalBytes, isBytes } from './utils.js';
|
|
16
|
+
import { getInputType, getPrevOut, normalizeInput, toVsize } from './utxo.js'; // circular
|
|
10
17
|
|
|
11
18
|
const EMPTY32 = new Uint8Array(32);
|
|
12
19
|
const EMPTY_OUTPUT: P.UnwrapCoder<typeof RawOutput> = {
|
|
@@ -30,10 +37,10 @@ export const PRECISION = 8;
|
|
|
30
37
|
export const DEFAULT_VERSION = 2;
|
|
31
38
|
export const DEFAULT_LOCKTIME = 0;
|
|
32
39
|
export const DEFAULT_SEQUENCE = 4294967295;
|
|
33
|
-
export const Decimal = P.coders.decimal(PRECISION);
|
|
40
|
+
export const Decimal: P.Coder<bigint, string> = P.coders.decimal(PRECISION);
|
|
34
41
|
|
|
35
42
|
// Same as value || def, but doesn't overwrites zero ('0', 0, 0n, etc)
|
|
36
|
-
export const def = <T>(value: T | undefined, def: T) => (value === undefined ? def : value);
|
|
43
|
+
export const def = <T>(value: T | undefined, def: T): T => (value === undefined ? def : value);
|
|
37
44
|
|
|
38
45
|
export function cloneDeep<T>(obj: T): T {
|
|
39
46
|
if (Array.isArray(obj)) return obj.map((i) => cloneDeep(i)) as unknown as T;
|
|
@@ -54,7 +61,7 @@ export function cloneDeep<T>(obj: T): T {
|
|
|
54
61
|
|
|
55
62
|
// Mostly security features, hardened defaults;
|
|
56
63
|
// but you still can parse other people tx with unspendable outputs and stuff if you want
|
|
57
|
-
export
|
|
64
|
+
export interface TxOpts {
|
|
58
65
|
version?: number;
|
|
59
66
|
lockTime?: number;
|
|
60
67
|
PSBTVersion?: number;
|
|
@@ -77,7 +84,9 @@ export type TxOpts = {
|
|
|
77
84
|
allowLegacyWitnessUtxo?: boolean;
|
|
78
85
|
lowR?: boolean; // Use lowR signatures
|
|
79
86
|
customScripts?: CustomScript[]; // UNSAFE: Custom payment scripts
|
|
80
|
-
|
|
87
|
+
// Allow to add additional unknown keys/values to the "unknown" array member
|
|
88
|
+
allowUnknown?: boolean;
|
|
89
|
+
}
|
|
81
90
|
|
|
82
91
|
/**
|
|
83
92
|
* Internal, exported only for backwards-compat. Use `SigHash` instead.
|
|
@@ -166,7 +175,7 @@ function unpackSighash(hashType: number) {
|
|
|
166
175
|
};
|
|
167
176
|
}
|
|
168
177
|
|
|
169
|
-
function validateOpts(opts: TxOpts) {
|
|
178
|
+
function validateOpts(opts: TxOpts): Readonly<TxOpts> {
|
|
170
179
|
if (opts !== undefined && {}.toString.call(opts) !== '[object Object]')
|
|
171
180
|
throw new Error(`Wrong object type for transaction options: ${opts}`);
|
|
172
181
|
|
|
@@ -182,7 +191,8 @@ function validateOpts(opts: TxOpts) {
|
|
|
182
191
|
if (typeof _opts.allowUnknowOutput !== 'undefined')
|
|
183
192
|
opts.allowUnknownOutputs = _opts.allowUnknowOutput;
|
|
184
193
|
// 0 and -1 happens in tests
|
|
185
|
-
if (![-1, 0, 1, 2].includes(_opts.version))
|
|
194
|
+
if (![-1, 0, 1, 2, 3].includes(_opts.version))
|
|
195
|
+
throw new Error(`Unknown version: ${_opts.version}`);
|
|
186
196
|
if (typeof _opts.lockTime !== 'number') throw new Error('Transaction lock time should be number');
|
|
187
197
|
P.U32LE.encode(_opts.lockTime); // Additional range checks that lockTime
|
|
188
198
|
// There is no PSBT v1, and any new version will probably have fields which we don't know how to parse, which
|
|
@@ -233,7 +243,7 @@ export class Transaction {
|
|
|
233
243
|
}
|
|
234
244
|
|
|
235
245
|
// Import
|
|
236
|
-
static fromRaw(raw: Bytes, opts: TxOpts = {}) {
|
|
246
|
+
static fromRaw(raw: Bytes, opts: TxOpts = {}): Transaction {
|
|
237
247
|
const parsed = RawTx.decode(raw);
|
|
238
248
|
const tx = new Transaction({ ...opts, version: parsed.version, lockTime: parsed.lockTime });
|
|
239
249
|
for (const o of parsed.outputs) tx.addOutput(o);
|
|
@@ -246,7 +256,7 @@ export class Transaction {
|
|
|
246
256
|
return tx;
|
|
247
257
|
}
|
|
248
258
|
// PSBT
|
|
249
|
-
static fromPSBT(psbt_: Bytes, opts: TxOpts = {}) {
|
|
259
|
+
static fromPSBT(psbt_: Bytes, opts: TxOpts = {}): Transaction {
|
|
250
260
|
let parsed: P.UnwrapCoder<typeof psbt.RawPSBTV0>;
|
|
251
261
|
try {
|
|
252
262
|
parsed = psbt.RawPSBTV0.decode(psbt_);
|
|
@@ -281,7 +291,7 @@ export class Transaction {
|
|
|
281
291
|
if (lockTime !== DEFAULT_LOCKTIME) tx.global.fallbackLocktime = lockTime;
|
|
282
292
|
return tx;
|
|
283
293
|
}
|
|
284
|
-
toPSBT(PSBTVersion = this.opts.PSBTVersion) {
|
|
294
|
+
toPSBT(PSBTVersion: number | undefined = this.opts.PSBTVersion): Uint8Array {
|
|
285
295
|
if (PSBTVersion !== 0 && PSBTVersion !== 2)
|
|
286
296
|
throw new Error(`Wrong PSBT version=${PSBTVersion}`);
|
|
287
297
|
// if (PSBTVersion === 0 && this.inputs.length === 0) {
|
|
@@ -338,7 +348,7 @@ export class Transaction {
|
|
|
338
348
|
}
|
|
339
349
|
|
|
340
350
|
// BIP370 lockTime (https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki#determining-lock-time)
|
|
341
|
-
get lockTime() {
|
|
351
|
+
get lockTime(): number {
|
|
342
352
|
let height = DEFAULT_LOCKTIME;
|
|
343
353
|
let heightCnt = 0;
|
|
344
354
|
let time = DEFAULT_LOCKTIME;
|
|
@@ -358,7 +368,7 @@ export class Transaction {
|
|
|
358
368
|
return this.global.fallbackLocktime || DEFAULT_LOCKTIME;
|
|
359
369
|
}
|
|
360
370
|
|
|
361
|
-
get version() {
|
|
371
|
+
get version(): number {
|
|
362
372
|
// Should be not possible
|
|
363
373
|
if (this.global.txVersion === undefined) throw new Error('No global.txVersion');
|
|
364
374
|
return this.global.txVersion;
|
|
@@ -419,7 +429,7 @@ export class Transaction {
|
|
|
419
429
|
return { addInput, addOutput, inputs, outputs };
|
|
420
430
|
}
|
|
421
431
|
|
|
422
|
-
get isFinal() {
|
|
432
|
+
get isFinal(): boolean {
|
|
423
433
|
for (let idx = 0; idx < this.inputs.length; idx++)
|
|
424
434
|
if (this.inputStatus(idx) !== 'finalized') return false;
|
|
425
435
|
return true;
|
|
@@ -453,7 +463,7 @@ export class Transaction {
|
|
|
453
463
|
get vsize(): number {
|
|
454
464
|
return toVsize(this.weight);
|
|
455
465
|
}
|
|
456
|
-
toBytes(withScriptSig = false, withWitness = false) {
|
|
466
|
+
toBytes(withScriptSig = false, withWitness = false): Uint8Array {
|
|
457
467
|
return RawTx.encode({
|
|
458
468
|
version: this.version,
|
|
459
469
|
lockTime: this.lockTime,
|
|
@@ -469,15 +479,15 @@ export class Transaction {
|
|
|
469
479
|
get unsignedTx(): Bytes {
|
|
470
480
|
return this.toBytes(false, false);
|
|
471
481
|
}
|
|
472
|
-
get hex() {
|
|
482
|
+
get hex(): string {
|
|
473
483
|
return hex.encode(this.toBytes(true, this.hasWitnesses));
|
|
474
484
|
}
|
|
475
485
|
|
|
476
|
-
get hash() {
|
|
486
|
+
get hash(): string {
|
|
477
487
|
if (!this.isFinal) throw new Error('Transaction is not finalized');
|
|
478
488
|
return hex.encode(u.sha256x2(this.toBytes(true)));
|
|
479
489
|
}
|
|
480
|
-
get id() {
|
|
490
|
+
get id(): string {
|
|
481
491
|
if (!this.isFinal) throw new Error('Transaction is not finalized');
|
|
482
492
|
return hex.encode(u.sha256x2(this.toBytes(true)).reverse());
|
|
483
493
|
}
|
|
@@ -486,11 +496,11 @@ export class Transaction {
|
|
|
486
496
|
if (!Number.isSafeInteger(idx) || 0 > idx || idx >= this.inputs.length)
|
|
487
497
|
throw new Error(`Wrong input index=${idx}`);
|
|
488
498
|
}
|
|
489
|
-
getInput(idx: number) {
|
|
499
|
+
getInput(idx: number): psbt.TransactionInput {
|
|
490
500
|
this.checkInputIdx(idx);
|
|
491
501
|
return cloneDeep(this.inputs[idx]);
|
|
492
502
|
}
|
|
493
|
-
get inputsLength() {
|
|
503
|
+
get inputsLength(): number {
|
|
494
504
|
return this.inputs.length;
|
|
495
505
|
}
|
|
496
506
|
// Modification
|
|
@@ -500,7 +510,7 @@ export class Transaction {
|
|
|
500
510
|
this.inputs.push(normalizeInput(input, undefined, undefined, this.opts.disableScriptCheck));
|
|
501
511
|
return this.inputs.length - 1;
|
|
502
512
|
}
|
|
503
|
-
updateInput(idx: number, input: psbt.TransactionInputUpdate, _ignoreSignStatus = false) {
|
|
513
|
+
updateInput(idx: number, input: psbt.TransactionInputUpdate, _ignoreSignStatus = false): void {
|
|
504
514
|
this.checkInputIdx(idx);
|
|
505
515
|
let allowedFields = undefined;
|
|
506
516
|
if (!_ignoreSignStatus) {
|
|
@@ -512,7 +522,8 @@ export class Transaction {
|
|
|
512
522
|
input,
|
|
513
523
|
this.inputs[idx],
|
|
514
524
|
allowedFields,
|
|
515
|
-
this.opts.disableScriptCheck
|
|
525
|
+
this.opts.disableScriptCheck,
|
|
526
|
+
this.opts.allowUnknown
|
|
516
527
|
);
|
|
517
528
|
}
|
|
518
529
|
// Output stuff
|
|
@@ -520,17 +531,17 @@ export class Transaction {
|
|
|
520
531
|
if (!Number.isSafeInteger(idx) || 0 > idx || idx >= this.outputs.length)
|
|
521
532
|
throw new Error(`Wrong output index=${idx}`);
|
|
522
533
|
}
|
|
523
|
-
getOutput(idx: number) {
|
|
534
|
+
getOutput(idx: number): psbt.TransactionOutput {
|
|
524
535
|
this.checkOutputIdx(idx);
|
|
525
536
|
return cloneDeep(this.outputs[idx]);
|
|
526
537
|
}
|
|
527
|
-
getOutputAddress(idx: number, network = NETWORK): string | undefined {
|
|
538
|
+
getOutputAddress(idx: number, network: u.BTC_NETWORK = NETWORK): string | undefined {
|
|
528
539
|
const out = this.getOutput(idx);
|
|
529
540
|
if (!out.script) return;
|
|
530
541
|
return Address(network).encode(OutScript.decode(out.script));
|
|
531
542
|
}
|
|
532
543
|
|
|
533
|
-
get outputsLength() {
|
|
544
|
+
get outputsLength(): number {
|
|
534
545
|
return this.outputs.length;
|
|
535
546
|
}
|
|
536
547
|
private normalizeOutput(
|
|
@@ -548,7 +559,7 @@ export class Transaction {
|
|
|
548
559
|
if (script === undefined) script = cur?.script;
|
|
549
560
|
let res: psbt.PSBTKeyMapKeys<typeof psbt.PSBTOutput> = { ...cur, ...o, amount, script };
|
|
550
561
|
if (res.amount === undefined) delete res.amount;
|
|
551
|
-
res = psbt.mergeKeyMap(psbt.PSBTOutput, res, cur, allowedFields);
|
|
562
|
+
res = psbt.mergeKeyMap(psbt.PSBTOutput, res, cur, allowedFields, this.opts.allowUnknown);
|
|
552
563
|
psbt.PSBTOutputCoder.encode(res);
|
|
553
564
|
if (
|
|
554
565
|
res.script &&
|
|
@@ -568,7 +579,7 @@ export class Transaction {
|
|
|
568
579
|
this.outputs.push(this.normalizeOutput(o));
|
|
569
580
|
return this.outputs.length - 1;
|
|
570
581
|
}
|
|
571
|
-
updateOutput(idx: number, output: psbt.TransactionOutputUpdate, _ignoreSignStatus = false) {
|
|
582
|
+
updateOutput(idx: number, output: psbt.TransactionOutputUpdate, _ignoreSignStatus = false): void {
|
|
572
583
|
this.checkOutputIdx(idx);
|
|
573
584
|
let allowedFields = undefined;
|
|
574
585
|
if (!_ignoreSignStatus) {
|
|
@@ -578,7 +589,7 @@ export class Transaction {
|
|
|
578
589
|
}
|
|
579
590
|
this.outputs[idx] = this.normalizeOutput(output, this.outputs[idx], allowedFields);
|
|
580
591
|
}
|
|
581
|
-
addOutputAddress(address: string, amount: bigint, network = NETWORK): number {
|
|
592
|
+
addOutputAddress(address: string, amount: bigint, network: u.BTC_NETWORK = NETWORK): number {
|
|
582
593
|
return this.addOutput({ script: OutScript.encode(Address(network).decode(address)), amount });
|
|
583
594
|
}
|
|
584
595
|
// Utils
|
|
@@ -633,7 +644,12 @@ export class Transaction {
|
|
|
633
644
|
});
|
|
634
645
|
return u.sha256x2(tmpTx, P.I32LE.encode(hashType));
|
|
635
646
|
}
|
|
636
|
-
preimageWitnessV0(
|
|
647
|
+
preimageWitnessV0(
|
|
648
|
+
idx: number,
|
|
649
|
+
prevOutScript: Bytes,
|
|
650
|
+
hashType: number,
|
|
651
|
+
amount: bigint
|
|
652
|
+
): Uint8Array {
|
|
637
653
|
const { isAny, isNone, isSingle } = unpackSighash(hashType);
|
|
638
654
|
let inputHash = EMPTY32;
|
|
639
655
|
let sequenceHash = EMPTY32;
|
|
@@ -671,7 +687,7 @@ export class Transaction {
|
|
|
671
687
|
leafScript?: Bytes,
|
|
672
688
|
leafVer = 0xc0,
|
|
673
689
|
annex?: Bytes
|
|
674
|
-
) {
|
|
690
|
+
): Uint8Array {
|
|
675
691
|
if (!Array.isArray(amount) || this.inputs.length !== amount.length)
|
|
676
692
|
throw new Error(`Invalid amounts array=${amount}`);
|
|
677
693
|
if (!Array.isArray(prevOutScript) || this.inputs.length !== prevOutScript.length)
|
|
@@ -878,7 +894,7 @@ export class Transaction {
|
|
|
878
894
|
return num;
|
|
879
895
|
}
|
|
880
896
|
|
|
881
|
-
finalizeIdx(idx: number) {
|
|
897
|
+
finalizeIdx(idx: number): void {
|
|
882
898
|
this.checkInputIdx(idx);
|
|
883
899
|
if (this.fee < 0n) throw new Error('Outputs spends more than inputs amount');
|
|
884
900
|
const input = this.inputs[idx];
|
|
@@ -1027,10 +1043,10 @@ export class Transaction {
|
|
|
1027
1043
|
if (finalScriptWitness) input.finalScriptWitness = finalScriptWitness;
|
|
1028
1044
|
cleanFinalInput(input);
|
|
1029
1045
|
}
|
|
1030
|
-
finalize() {
|
|
1046
|
+
finalize(): void {
|
|
1031
1047
|
for (let i = 0; i < this.inputs.length; i++) this.finalizeIdx(i);
|
|
1032
1048
|
}
|
|
1033
|
-
extract() {
|
|
1049
|
+
extract(): Uint8Array {
|
|
1034
1050
|
if (!this.isFinal) throw new Error('Transaction has unfinalized inputs');
|
|
1035
1051
|
if (!this.outputs.length) throw new Error('Transaction has no outputs');
|
|
1036
1052
|
if (this.fee < 0n) throw new Error('Outputs spends more than inputs amount');
|
|
@@ -1057,12 +1073,18 @@ export class Transaction {
|
|
|
1057
1073
|
: P.EMPTY;
|
|
1058
1074
|
if (!equalBytes(thisUnsigned, otherUnsigned))
|
|
1059
1075
|
throw new Error(`Transaction/combine: different unsigned tx`);
|
|
1060
|
-
this.global = psbt.mergeKeyMap(
|
|
1076
|
+
this.global = psbt.mergeKeyMap(
|
|
1077
|
+
psbt.PSBTGlobal,
|
|
1078
|
+
this.global,
|
|
1079
|
+
other.global,
|
|
1080
|
+
undefined,
|
|
1081
|
+
this.opts.allowUnknown
|
|
1082
|
+
);
|
|
1061
1083
|
for (let i = 0; i < this.inputs.length; i++) this.updateInput(i, other.inputs[i], true);
|
|
1062
1084
|
for (let i = 0; i < this.outputs.length; i++) this.updateOutput(i, other.outputs[i], true);
|
|
1063
1085
|
return this;
|
|
1064
1086
|
}
|
|
1065
|
-
clone() {
|
|
1087
|
+
clone(): Transaction {
|
|
1066
1088
|
// deepClone probably faster, but this enforces that encoding is valid
|
|
1067
1089
|
return Transaction.fromPSBT(this.toPSBT(this.opts.PSBTVersion), this.opts);
|
|
1068
1090
|
}
|