@caravan/psbt 1.0.1 → 1.1.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/.turbo/turbo-build.log +8 -8
- package/.turbo/turbo-lint.log +19 -14
- package/.turbo/turbo-test.log +44 -44
- package/CHANGELOG.md +6 -0
- package/README.md +71 -16
- package/dist/index.d.mts +67 -12
- package/dist/index.d.ts +67 -12
- package/dist/index.js +162 -13
- package/dist/index.mjs +162 -13
- package/package.json +1 -1
- package/src/psbtv2/psbtv2.test.ts +150 -1
- package/src/psbtv2/psbtv2.ts +268 -22
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PsbtV2, getPsbtVersionNumber } from "./";
|
|
2
2
|
import { test } from "@jest/globals";
|
|
3
|
+
import { KeyType, PsbtGlobalTxModifiableBits } from "./types";
|
|
3
4
|
|
|
4
5
|
const BIP_370_VECTORS_INVALID_PSBT = [
|
|
5
6
|
// Case: PSBTv0 but with PSBT_GLOBAL_VERSION set to 2.
|
|
@@ -818,6 +819,149 @@ describe("PsbtV2", () => {
|
|
|
818
819
|
});
|
|
819
820
|
});
|
|
820
821
|
|
|
822
|
+
describe("PsbtV2.isReadyForConstructor", () => {
|
|
823
|
+
let psbt: PsbtV2;
|
|
824
|
+
|
|
825
|
+
beforeEach(() => {
|
|
826
|
+
psbt = new PsbtV2();
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
it("Returns not ready for Constructor when PSBT_GLOBAL_FALLBACK_LOCKTIME is not set", () => {
|
|
830
|
+
psbt.PSBT_GLOBAL_FALLBACK_LOCKTIME = null;
|
|
831
|
+
expect(psbt.isReadyForConstructor).toBe(false);
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
it("Returns not ready for Constructor when neither inputs or outputs are modifiable", () => {
|
|
835
|
+
psbt.PSBT_GLOBAL_TX_MODIFIABLE = [];
|
|
836
|
+
expect(psbt.isReadyForConstructor).toBe(false);
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
it("Returns ready for Constructor when created with the object class constructor method", () => {
|
|
840
|
+
expect(psbt.isReadyForConstructor).toBe(true);
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
it("Returns ready for Constructor when a custom PSBT_GLOBAL_FALLBACK_LOCKTIME has been set", () => {
|
|
844
|
+
psbt.PSBT_GLOBAL_FALLBACK_LOCKTIME = 500000000;
|
|
845
|
+
expect(psbt.isReadyForConstructor).toBe(true);
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
it("Returns ready for Constructor when at least inputs or outputs are modifiable", () => {
|
|
849
|
+
psbt.PSBT_GLOBAL_TX_MODIFIABLE = [PsbtGlobalTxModifiableBits.INPUTS];
|
|
850
|
+
expect(psbt.isReadyForConstructor).toBe(true);
|
|
851
|
+
psbt.PSBT_GLOBAL_TX_MODIFIABLE = [PsbtGlobalTxModifiableBits.OUTPUTS];
|
|
852
|
+
expect(psbt.isReadyForConstructor).toBe(true);
|
|
853
|
+
});
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
describe("PsbtV2.isReadyForUpdater", () => {
|
|
857
|
+
let psbt: PsbtV2;
|
|
858
|
+
|
|
859
|
+
beforeEach(() => {
|
|
860
|
+
psbt = new PsbtV2();
|
|
861
|
+
psbt.addInput({ previousTxId: Buffer.from([0x00]), outputIndex: 0 });
|
|
862
|
+
psbt.PSBT_GLOBAL_TX_MODIFIABLE = [PsbtGlobalTxModifiableBits.INPUTS];
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
it("Returns not ready for Updater when there are no inputs to update", () => {
|
|
866
|
+
psbt.deleteInput(0);
|
|
867
|
+
expect(psbt.isReadyForUpdater).toBe(false);
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
it("Returns not ready for Updater when there are no modifiable inputs", () => {
|
|
871
|
+
psbt.PSBT_GLOBAL_TX_MODIFIABLE = [];
|
|
872
|
+
expect(psbt.isReadyForUpdater).toBe(false);
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
it("Returns ready for Updater when it has at least one input and inputs are modifiable", () => {
|
|
876
|
+
expect(psbt.isReadyForUpdater).toBe(true);
|
|
877
|
+
});
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
describe("PsbtV2.isReadyForSigner", () => {
|
|
881
|
+
let psbt: PsbtV2;
|
|
882
|
+
|
|
883
|
+
beforeEach(() => {
|
|
884
|
+
psbt = new PsbtV2();
|
|
885
|
+
psbt.addInput({ previousTxId: Buffer.from([0x00]), outputIndex: 0 });
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
it("Returns not ready for Signer when there are no inputs to sign", () => {
|
|
889
|
+
psbt.deleteInput(0);
|
|
890
|
+
expect(psbt.isReadyForSigner).toBe(false);
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
it("Returns not ready for Signer when the psbt has already finalized inputs", () => {
|
|
894
|
+
jest
|
|
895
|
+
.spyOn(psbt, "isReadyForTransactionExtractor", "get")
|
|
896
|
+
.mockReturnValue(true);
|
|
897
|
+
expect(psbt.isReadyForSigner).toBe(false);
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
it("Returns ready for Signer when the psbt has an input for signing", () => {
|
|
901
|
+
expect(psbt.isReadyForSigner).toBe(true);
|
|
902
|
+
});
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
describe("PsbtV2.isReadyForTransactionExtractor", () => {
|
|
906
|
+
let psbt: PsbtV2;
|
|
907
|
+
|
|
908
|
+
beforeEach(() => {
|
|
909
|
+
psbt = new PsbtV2();
|
|
910
|
+
// Create finalized non-witness input
|
|
911
|
+
psbt.addInput({
|
|
912
|
+
previousTxId: Buffer.from([0x00]),
|
|
913
|
+
outputIndex: 0,
|
|
914
|
+
});
|
|
915
|
+
(psbt as any).inputMaps[0].set(
|
|
916
|
+
KeyType.PSBT_IN_NON_WITNESS_UTXO,
|
|
917
|
+
Buffer.from([0x01]),
|
|
918
|
+
);
|
|
919
|
+
(psbt as any).inputMaps[0].set(
|
|
920
|
+
KeyType.PSBT_IN_FINAL_SCRIPTSIG,
|
|
921
|
+
Buffer.from([0x00]),
|
|
922
|
+
);
|
|
923
|
+
// Create finalized witness input
|
|
924
|
+
psbt.addInput({
|
|
925
|
+
previousTxId: Buffer.from([0x00]),
|
|
926
|
+
outputIndex: 1,
|
|
927
|
+
});
|
|
928
|
+
(psbt as any).inputMaps[1].set(
|
|
929
|
+
KeyType.PSBT_IN_WITNESS_UTXO,
|
|
930
|
+
Buffer.from([0x01]),
|
|
931
|
+
);
|
|
932
|
+
(psbt as any).inputMaps[1].set(
|
|
933
|
+
KeyType.PSBT_IN_FINAL_SCRIPTWITNESS,
|
|
934
|
+
Buffer.from([0x01]),
|
|
935
|
+
);
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
it("Returns not ready for Transaction Extractor when there are no finalized scripts", () => {
|
|
939
|
+
// Unset script from second input
|
|
940
|
+
(psbt as any).inputMaps[1].delete(KeyType.PSBT_IN_FINAL_SCRIPTWITNESS);
|
|
941
|
+
expect(psbt.isReadyForTransactionExtractor).toBe(false);
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
it("Returns not ready for Transaction Extractor when there are missing UTXOs", () => {
|
|
945
|
+
(psbt as any).inputMaps[1].delete(
|
|
946
|
+
KeyType.PSBT_IN_WITNESS_UTXO,
|
|
947
|
+
Buffer.from([0x00]),
|
|
948
|
+
);
|
|
949
|
+
expect(psbt.isReadyForTransactionExtractor).toBe(false);
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
it("Returns not ready for Transaction Extractor when extra fields have not been removed", () => {
|
|
953
|
+
(psbt as any).inputMaps[1].set(
|
|
954
|
+
KeyType.PSBT_IN_TAP_BIP32_DERIVATION,
|
|
955
|
+
Buffer.from([0x01]),
|
|
956
|
+
);
|
|
957
|
+
expect(psbt.isReadyForTransactionExtractor).toBe(false);
|
|
958
|
+
});
|
|
959
|
+
|
|
960
|
+
it("Returns ready for Transaction Extractor when the Input Finalizer's job has been completed", () => {
|
|
961
|
+
expect(psbt.isReadyForTransactionExtractor).toBe(true);
|
|
962
|
+
});
|
|
963
|
+
});
|
|
964
|
+
|
|
821
965
|
describe("PsbtV2.nLockTime", () => {
|
|
822
966
|
it("Returns 0 when No locktimes specified", () => {
|
|
823
967
|
const vect = BIP_370_VECTORS_VALID_PSBT[14];
|
|
@@ -1024,9 +1168,14 @@ describe("PsbtV2.addPartialSig", () => {
|
|
|
1024
1168
|
it("Throws on validation failures", () => {
|
|
1025
1169
|
const addSig = (index: number, pub?: any, sig?: any) =>
|
|
1026
1170
|
psbt.addPartialSig(index, pub, sig);
|
|
1027
|
-
|
|
1171
|
+
|
|
1172
|
+
// No inputs, so it's not ready for Signer
|
|
1173
|
+
expect(() => addSig(0)).toThrow(
|
|
1174
|
+
"The PsbtV2 is not ready for a Signer. Partial sigs cannot be added.",
|
|
1175
|
+
);
|
|
1028
1176
|
|
|
1029
1177
|
psbt.addInput({ previousTxId: Buffer.from([0x00]), outputIndex: 0 });
|
|
1178
|
+
expect(() => addSig(1)).toThrow("PsbtV2 has no input at 1");
|
|
1030
1179
|
expect(() => addSig(0)).toThrow(
|
|
1031
1180
|
"PsbtV2.addPartialSig() missing argument pubkey",
|
|
1032
1181
|
);
|
package/src/psbtv2/psbtv2.ts
CHANGED
|
@@ -44,7 +44,10 @@ export class PsbtV2 extends PsbtV2Maps {
|
|
|
44
44
|
*/
|
|
45
45
|
|
|
46
46
|
get PSBT_GLOBAL_XPUB() {
|
|
47
|
-
return getNonUniqueKeyTypeValues(
|
|
47
|
+
return getNonUniqueKeyTypeValues(
|
|
48
|
+
this.globalMap,
|
|
49
|
+
KeyType.PSBT_GLOBAL_XPUB,
|
|
50
|
+
) as NonUniqueKeyTypeValue[];
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
get PSBT_GLOBAL_TX_VERSION() {
|
|
@@ -189,7 +192,7 @@ export class PsbtV2 extends PsbtV2Maps {
|
|
|
189
192
|
return getNonUniqueKeyTypeValues(
|
|
190
193
|
this.globalMap,
|
|
191
194
|
KeyType.PSBT_GLOBAL_PROPRIETARY,
|
|
192
|
-
);
|
|
195
|
+
) as NonUniqueKeyTypeValue[];
|
|
193
196
|
}
|
|
194
197
|
|
|
195
198
|
/**
|
|
@@ -210,7 +213,7 @@ export class PsbtV2 extends PsbtV2Maps {
|
|
|
210
213
|
);
|
|
211
214
|
}
|
|
212
215
|
|
|
213
|
-
get PSBT_IN_PARTIAL_SIG()
|
|
216
|
+
get PSBT_IN_PARTIAL_SIG() {
|
|
214
217
|
return getNonUniqueKeyTypeValues(
|
|
215
218
|
this.inputMaps,
|
|
216
219
|
KeyType.PSBT_IN_PARTIAL_SIG,
|
|
@@ -242,7 +245,7 @@ export class PsbtV2 extends PsbtV2Maps {
|
|
|
242
245
|
return getNonUniqueKeyTypeValues(
|
|
243
246
|
this.inputMaps,
|
|
244
247
|
KeyType.PSBT_IN_BIP32_DERIVATION,
|
|
245
|
-
);
|
|
248
|
+
) as NonUniqueKeyTypeValue[][];
|
|
246
249
|
}
|
|
247
250
|
|
|
248
251
|
get PSBT_IN_FINAL_SCRIPTSIG() {
|
|
@@ -267,19 +270,31 @@ export class PsbtV2 extends PsbtV2Maps {
|
|
|
267
270
|
}
|
|
268
271
|
|
|
269
272
|
get PSBT_IN_RIPEMD160() {
|
|
270
|
-
return getNonUniqueKeyTypeValues(
|
|
273
|
+
return getNonUniqueKeyTypeValues(
|
|
274
|
+
this.inputMaps,
|
|
275
|
+
KeyType.PSBT_IN_RIPEMD160,
|
|
276
|
+
) as NonUniqueKeyTypeValue[][];
|
|
271
277
|
}
|
|
272
278
|
|
|
273
279
|
get PSBT_IN_SHA256() {
|
|
274
|
-
return getNonUniqueKeyTypeValues(
|
|
280
|
+
return getNonUniqueKeyTypeValues(
|
|
281
|
+
this.inputMaps,
|
|
282
|
+
KeyType.PSBT_IN_SHA256,
|
|
283
|
+
) as NonUniqueKeyTypeValue[][];
|
|
275
284
|
}
|
|
276
285
|
|
|
277
286
|
get PSBT_IN_HASH160() {
|
|
278
|
-
return getNonUniqueKeyTypeValues(
|
|
287
|
+
return getNonUniqueKeyTypeValues(
|
|
288
|
+
this.inputMaps,
|
|
289
|
+
KeyType.PSBT_IN_HASH160,
|
|
290
|
+
) as NonUniqueKeyTypeValue[][];
|
|
279
291
|
}
|
|
280
292
|
|
|
281
293
|
get PSBT_IN_HASH256() {
|
|
282
|
-
return getNonUniqueKeyTypeValues(
|
|
294
|
+
return getNonUniqueKeyTypeValues(
|
|
295
|
+
this.inputMaps,
|
|
296
|
+
KeyType.PSBT_IN_HASH256,
|
|
297
|
+
) as NonUniqueKeyTypeValue[][];
|
|
283
298
|
}
|
|
284
299
|
|
|
285
300
|
get PSBT_IN_PREVIOUS_TXID() {
|
|
@@ -338,21 +353,21 @@ export class PsbtV2 extends PsbtV2Maps {
|
|
|
338
353
|
return getNonUniqueKeyTypeValues(
|
|
339
354
|
this.inputMaps,
|
|
340
355
|
KeyType.PSBT_IN_TAP_SCRIPT_SIG,
|
|
341
|
-
);
|
|
356
|
+
) as NonUniqueKeyTypeValue[][];
|
|
342
357
|
}
|
|
343
358
|
|
|
344
359
|
get PSBT_IN_TAP_LEAF_SCRIPT() {
|
|
345
360
|
return getNonUniqueKeyTypeValues(
|
|
346
361
|
this.inputMaps,
|
|
347
362
|
KeyType.PSBT_IN_TAP_LEAF_SCRIPT,
|
|
348
|
-
);
|
|
363
|
+
) as NonUniqueKeyTypeValue[][];
|
|
349
364
|
}
|
|
350
365
|
|
|
351
366
|
get PSBT_IN_TAP_BIP32_DERIVATION() {
|
|
352
367
|
return getNonUniqueKeyTypeValues(
|
|
353
368
|
this.inputMaps,
|
|
354
369
|
KeyType.PSBT_IN_TAP_BIP32_DERIVATION,
|
|
355
|
-
);
|
|
370
|
+
) as NonUniqueKeyTypeValue[][];
|
|
356
371
|
}
|
|
357
372
|
|
|
358
373
|
get PSBT_IN_TAP_INTERNAL_KEY() {
|
|
@@ -455,6 +470,191 @@ export class PsbtV2 extends PsbtV2Maps {
|
|
|
455
470
|
);
|
|
456
471
|
}
|
|
457
472
|
|
|
473
|
+
/**
|
|
474
|
+
* Operator Role Validation Getters
|
|
475
|
+
*/
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Returns true if the PsbtV2 is ready for an operator taking the Constructor
|
|
479
|
+
* role.
|
|
480
|
+
*
|
|
481
|
+
* This check assumes that the Creator used this class's constructor method to
|
|
482
|
+
* initialize the PsbtV2 without passing a psbt (constructor defaults were
|
|
483
|
+
* set).
|
|
484
|
+
*/
|
|
485
|
+
get isReadyForConstructor() {
|
|
486
|
+
// The Creator role (likely via the class constructor) must ensure at least
|
|
487
|
+
// the following value has been initialized. The psbt cannot be passed to
|
|
488
|
+
// the Constructor until it is set.
|
|
489
|
+
if (this.PSBT_GLOBAL_FALLBACK_LOCKTIME === null) {
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// At least inputs or outputs must still be modifiable.
|
|
494
|
+
if (
|
|
495
|
+
!this.isModifiable([PsbtGlobalTxModifiableBits.INPUTS]) &&
|
|
496
|
+
!this.isModifiable([PsbtGlobalTxModifiableBits.OUTPUTS])
|
|
497
|
+
) {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Returns true if the PsbtV2 is ready for an operator taking the Updater
|
|
506
|
+
* role.
|
|
507
|
+
*
|
|
508
|
+
* Before signatures are added, but after an input is added, a PsbtV2 is
|
|
509
|
+
* likely to be ready for Constructor, ready for Updater, and ready for Signer
|
|
510
|
+
* simultaneously.
|
|
511
|
+
*
|
|
512
|
+
* According to BIP370, the Updater can modify the sequence number, but it is
|
|
513
|
+
* unclear if the Updater retains permissions provided in psbtv0 (BIP174). It
|
|
514
|
+
* is likely not the case that the Updater has the same permissions as
|
|
515
|
+
* previously because it seems to now be the realm of the Constructor to add
|
|
516
|
+
* inputs and outputs.
|
|
517
|
+
*/
|
|
518
|
+
get isReadyForUpdater() {
|
|
519
|
+
// In psbtv2, the Updater can set the sequence number, but an input must
|
|
520
|
+
// exist for this to be set.
|
|
521
|
+
if (this.PSBT_GLOBAL_INPUT_COUNT === 0) {
|
|
522
|
+
return false;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Inputs must still be modifiable
|
|
526
|
+
if (!this.isModifiable([PsbtGlobalTxModifiableBits.INPUTS])) {
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return true;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Returns true if the PsbtV2 is ready for an operator taking the Signer role.
|
|
535
|
+
*/
|
|
536
|
+
get isReadyForSigner() {
|
|
537
|
+
// An input must exist before it can be signed.
|
|
538
|
+
if (this.PSBT_GLOBAL_INPUT_COUNT === 0) {
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// TODO: Maybe it makes sense to more granularly check if the psbt is fully
|
|
543
|
+
// signed or has minimum signatures. Until then, just check that sigs have
|
|
544
|
+
// not been finalized.
|
|
545
|
+
if (this.isReadyForTransactionExtractor) {
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return true;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Returns true if the PsbtV2 is ready for an operator taking the Combiner
|
|
554
|
+
* role.
|
|
555
|
+
*/
|
|
556
|
+
get isReadyForCombiner() {
|
|
557
|
+
// The combiner can potentially provide everything that's missing when
|
|
558
|
+
// merging another psbt. If it's at least ready for updates from the
|
|
559
|
+
// following roles, then it's ready for a Combiner.
|
|
560
|
+
return (
|
|
561
|
+
this.isReadyForConstructor ||
|
|
562
|
+
this.isReadyForUpdater ||
|
|
563
|
+
this.isReadyForSigner
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Unimplemented. Returns false.
|
|
569
|
+
*/
|
|
570
|
+
get isReadyForInputFinalizer() {
|
|
571
|
+
// Checks to see if the psbt contains everything needed to finalize inputs.
|
|
572
|
+
// This can become quite complicated considering multisig and taproot.
|
|
573
|
+
console.warn(
|
|
574
|
+
"PsbtV2.isReadyForInputFinalizer has been called, however, this getter is unimplemented and shouldn't be used.",
|
|
575
|
+
);
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Returns true if the PsbtV2 is ready for an operator taking the Transaction
|
|
581
|
+
* Extractor role.
|
|
582
|
+
*
|
|
583
|
+
* If all the inputs have been finalized, then the psbt is ready for the
|
|
584
|
+
* Transaction Extractor. According to BIP 174, it's the responsibility of the
|
|
585
|
+
* Input Finalizer to add scriptSigs or scriptWitnesses and then remove other
|
|
586
|
+
* details besides the UTXO. This getter checks that the Input Finalizer has
|
|
587
|
+
* finished its job.
|
|
588
|
+
*/
|
|
589
|
+
get isReadyForTransactionExtractor() {
|
|
590
|
+
// Iterate over all inputs
|
|
591
|
+
|
|
592
|
+
for (let i = 0; i < this.PSBT_GLOBAL_INPUT_COUNT; i++) {
|
|
593
|
+
// Check for finalized script
|
|
594
|
+
if (
|
|
595
|
+
!this.PSBT_IN_FINAL_SCRIPTSIG[i] &&
|
|
596
|
+
!this.PSBT_IN_FINAL_SCRIPTWITNESS[i]
|
|
597
|
+
) {
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Check that the corresponding UTXO is still available
|
|
602
|
+
if (
|
|
603
|
+
(this.PSBT_IN_FINAL_SCRIPTSIG[i] &&
|
|
604
|
+
!this.PSBT_IN_NON_WITNESS_UTXO[i]) ||
|
|
605
|
+
(this.PSBT_IN_FINAL_SCRIPTWITNESS[i] && !this.PSBT_IN_WITNESS_UTXO[i])
|
|
606
|
+
) {
|
|
607
|
+
return false;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Check that Input Finalizer removed other values from the input.
|
|
611
|
+
//
|
|
612
|
+
// Test vectors from BIP 370 indicate that a missing PSBT_IN_OUTPUT_INDEX
|
|
613
|
+
// or PSBT_IN_PREVIOUS_TXID should be an invalid psbt, so the getters for
|
|
614
|
+
// these keys will throw unless the values are set. However, the BIP also
|
|
615
|
+
// requires that the Input Finalizer removes all other values from the
|
|
616
|
+
// input map except for the finalized scripts and UTXOs. Since removal of
|
|
617
|
+
// the above mentioned keys will result in an invalid psbt, it's decided
|
|
618
|
+
// here that it's safe to ignore the fact that those keys have not been
|
|
619
|
+
// removed.
|
|
620
|
+
if (
|
|
621
|
+
// Strings
|
|
622
|
+
this.PSBT_IN_REDEEM_SCRIPT[i] ||
|
|
623
|
+
this.PSBT_IN_WITNESS_SCRIPT[i] ||
|
|
624
|
+
this.PSBT_IN_POR_COMMITMENT[i] ||
|
|
625
|
+
this.PSBT_IN_TAP_KEY_SIG[i] ||
|
|
626
|
+
this.PSBT_IN_TAP_INTERNAL_KEY[i] ||
|
|
627
|
+
this.PSBT_IN_TAP_MERKLE_ROOT[i] ||
|
|
628
|
+
// Numbers
|
|
629
|
+
this.PSBT_IN_SIGHASH_TYPE[i] !== null ||
|
|
630
|
+
this.PSBT_IN_SEQUENCE[i] !== null ||
|
|
631
|
+
this.PSBT_IN_REQUIRED_TIME_LOCKTIME[i] !== null ||
|
|
632
|
+
this.PSBT_IN_REQUIRED_HEIGHT_LOCKTIME[i] !== null ||
|
|
633
|
+
// Arrays of non-unique keytype values
|
|
634
|
+
this.PSBT_IN_PARTIAL_SIG[i].filter((el) => el !== null).length > 0 ||
|
|
635
|
+
this.PSBT_IN_BIP32_DERIVATION[i].filter((el) => el !== null).length >
|
|
636
|
+
0 ||
|
|
637
|
+
this.PSBT_IN_RIPEMD160[i].filter((el) => el !== null).length > 0 ||
|
|
638
|
+
this.PSBT_IN_SHA256[i].filter((el) => el !== null).length > 0 ||
|
|
639
|
+
this.PSBT_IN_HASH160[i].filter((el) => el !== null).length > 0 ||
|
|
640
|
+
this.PSBT_IN_HASH256[i].filter((el) => el !== null).length > 0 ||
|
|
641
|
+
this.PSBT_IN_TAP_SCRIPT_SIG[i].filter((el) => el !== null).length > 0 ||
|
|
642
|
+
this.PSBT_IN_TAP_LEAF_SCRIPT[i].filter((el) => el !== null).length >
|
|
643
|
+
0 ||
|
|
644
|
+
this.PSBT_IN_TAP_BIP32_DERIVATION[i].filter((el) => el !== null)
|
|
645
|
+
.length > 0
|
|
646
|
+
) {
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// This input has been finalized. Continue checking the next one.
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// All inputs have been finalized, so this psbt is ready for transaction
|
|
654
|
+
// extraction.
|
|
655
|
+
return true;
|
|
656
|
+
}
|
|
657
|
+
|
|
458
658
|
/**
|
|
459
659
|
* Other Getters/Setters
|
|
460
660
|
*/
|
|
@@ -530,7 +730,15 @@ export class PsbtV2 extends PsbtV2Maps {
|
|
|
530
730
|
this.PSBT_GLOBAL_TX_VERSION = 2;
|
|
531
731
|
this.PSBT_GLOBAL_INPUT_COUNT = 0;
|
|
532
732
|
this.PSBT_GLOBAL_OUTPUT_COUNT = 0;
|
|
733
|
+
|
|
734
|
+
// TODO: Right now these values are setting a default. How can it be made to
|
|
735
|
+
// accept values on the constructor method? The Creator role should be
|
|
736
|
+
// allowed to configure these.
|
|
533
737
|
this.PSBT_GLOBAL_FALLBACK_LOCKTIME = 0;
|
|
738
|
+
this.PSBT_GLOBAL_TX_MODIFIABLE = [
|
|
739
|
+
PsbtGlobalTxModifiableBits.INPUTS,
|
|
740
|
+
PsbtGlobalTxModifiableBits.OUTPUTS,
|
|
741
|
+
];
|
|
534
742
|
}
|
|
535
743
|
|
|
536
744
|
/**
|
|
@@ -591,6 +799,11 @@ export class PsbtV2 extends PsbtV2Maps {
|
|
|
591
799
|
* defined for PsbtV2.
|
|
592
800
|
*/
|
|
593
801
|
public dangerouslySetGlobalTxVersion1() {
|
|
802
|
+
if (!this.isReadyForConstructor) {
|
|
803
|
+
throw Error(
|
|
804
|
+
"The PsbtV2 is not ready for a Constructor. The PSBT_GLOBAL_TX_VERSION should not be forced to version 1.",
|
|
805
|
+
);
|
|
806
|
+
}
|
|
594
807
|
console.warn("Dangerously setting PsbtV2.PSBT_GLOBAL_TX_VERSION to 1!");
|
|
595
808
|
const bw = new BufferWriter();
|
|
596
809
|
bw.writeI32(1);
|
|
@@ -638,6 +851,17 @@ export class PsbtV2 extends PsbtV2Maps {
|
|
|
638
851
|
// significant validation concerning this step detailed in the BIP0370
|
|
639
852
|
// Constructor role:
|
|
640
853
|
// https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki#constructor
|
|
854
|
+
//
|
|
855
|
+
// TODO: This method must properly handle the SIGHASH_SINGLE flag. If the
|
|
856
|
+
// `PSBT_GLOBAL_TX_MODIFIABLE` flag `SIGHASH_SINGLE` is present and a
|
|
857
|
+
// signature is present, then adding or removing inputs or outputs before a
|
|
858
|
+
// signature with sighash_single must happen atomically in pairs.
|
|
859
|
+
|
|
860
|
+
if (!this.isReadyForConstructor) {
|
|
861
|
+
throw Error(
|
|
862
|
+
"The PsbtV2 is not ready for a Constructor. Inputs cannot be added.",
|
|
863
|
+
);
|
|
864
|
+
}
|
|
641
865
|
if (!this.isModifiable([PsbtGlobalTxModifiableBits.INPUTS])) {
|
|
642
866
|
throw Error(
|
|
643
867
|
"PsbtV2.PSBT_GLOBAL_TX_MODIFIABLE inputs cannot be modified.",
|
|
@@ -704,6 +928,11 @@ export class PsbtV2 extends PsbtV2Maps {
|
|
|
704
928
|
path: string;
|
|
705
929
|
}[];
|
|
706
930
|
}) {
|
|
931
|
+
if (!this.isReadyForConstructor) {
|
|
932
|
+
throw Error(
|
|
933
|
+
"The PsbtV2 is not ready for a Constructor. Outputs cannot be added.",
|
|
934
|
+
);
|
|
935
|
+
}
|
|
707
936
|
if (!this.isModifiable([PsbtGlobalTxModifiableBits.OUTPUTS])) {
|
|
708
937
|
throw Error(
|
|
709
938
|
"PsbtV2.PSBT_GLOBAL_TX_MODIFIABLE outputs cannot be modified.",
|
|
@@ -747,6 +976,11 @@ export class PsbtV2 extends PsbtV2Maps {
|
|
|
747
976
|
* Removes an input-map from inputMaps.
|
|
748
977
|
*/
|
|
749
978
|
public deleteInput(index: number) {
|
|
979
|
+
if (!this.isReadyForConstructor) {
|
|
980
|
+
throw Error(
|
|
981
|
+
"The PsbtV2 is not ready for a Constructor. Inputs cannot be removed.",
|
|
982
|
+
);
|
|
983
|
+
}
|
|
750
984
|
if (!this.isModifiable([PsbtGlobalTxModifiableBits.INPUTS])) {
|
|
751
985
|
throw Error(
|
|
752
986
|
"PsbtV2.PSBT_GLOBAL_TX_MODIFIABLE inputs cannot be modified.",
|
|
@@ -761,6 +995,11 @@ export class PsbtV2 extends PsbtV2Maps {
|
|
|
761
995
|
* Removes an output-map from outputMaps.
|
|
762
996
|
*/
|
|
763
997
|
public deleteOutput(index: number) {
|
|
998
|
+
if (!this.isReadyForConstructor) {
|
|
999
|
+
throw Error(
|
|
1000
|
+
"The PsbtV2 is not ready for a Constructor. Outputs cannot be removed.",
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
764
1003
|
if (!this.isModifiable([PsbtGlobalTxModifiableBits.OUTPUTS])) {
|
|
765
1004
|
throw Error(
|
|
766
1005
|
"PsbtV2.PSBT_GLOBAL_TX_MODIFIABLE outputs cannot be modified.",
|
|
@@ -782,7 +1021,7 @@ export class PsbtV2 extends PsbtV2Maps {
|
|
|
782
1021
|
}
|
|
783
1022
|
|
|
784
1023
|
/**
|
|
785
|
-
* Checks that provided flags are present in PSBT_GLOBAL_TX_MODIFIABLE.
|
|
1024
|
+
* Checks that all provided flags are present in PSBT_GLOBAL_TX_MODIFIABLE.
|
|
786
1025
|
*/
|
|
787
1026
|
private isModifiable(flags: PsbtGlobalTxModifiableBits[]) {
|
|
788
1027
|
for (const flag of flags) {
|
|
@@ -808,6 +1047,11 @@ export class PsbtV2 extends PsbtV2Maps {
|
|
|
808
1047
|
* https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#signer
|
|
809
1048
|
*/
|
|
810
1049
|
public addPartialSig(inputIndex: number, pubkey: Buffer, sig: Buffer) {
|
|
1050
|
+
if (!this.isReadyForSigner) {
|
|
1051
|
+
throw Error(
|
|
1052
|
+
"The PsbtV2 is not ready for a Signer. Partial sigs cannot be added.",
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
811
1055
|
if (!this.inputMaps[inputIndex]) {
|
|
812
1056
|
throw Error(`PsbtV2 has no input at ${inputIndex}`);
|
|
813
1057
|
}
|
|
@@ -845,6 +1089,8 @@ export class PsbtV2 extends PsbtV2Maps {
|
|
|
845
1089
|
* the pubkey exists.
|
|
846
1090
|
*/
|
|
847
1091
|
public removePartialSig(inputIndex: number, pubkey?: Buffer) {
|
|
1092
|
+
// TODO: What role is allowed to remove a partial sig? Perform that
|
|
1093
|
+
// role-check validation here.
|
|
848
1094
|
const input = this.inputMaps[inputIndex];
|
|
849
1095
|
|
|
850
1096
|
if (!input) {
|
|
@@ -909,7 +1155,11 @@ export class PsbtV2 extends PsbtV2Maps {
|
|
|
909
1155
|
}
|
|
910
1156
|
|
|
911
1157
|
/**
|
|
912
|
-
* Attempts to return a PsbtV2 by converting from a PsbtV0 string or Buffer
|
|
1158
|
+
* Attempts to return a PsbtV2 by converting from a PsbtV0 string or Buffer.
|
|
1159
|
+
*
|
|
1160
|
+
* This method first starts with a fresh PsbtV2 having just been created. It
|
|
1161
|
+
* then takes the PsbtV2 through its operator saga through the Signer role. In
|
|
1162
|
+
* this sense validation for each operator role will be performed.
|
|
913
1163
|
*/
|
|
914
1164
|
static FromV0(psbt: string | Buffer, allowTxnVersion1 = false): PsbtV2 {
|
|
915
1165
|
const psbtv0Buf = bufferize(psbt);
|
|
@@ -918,11 +1168,6 @@ export class PsbtV2 extends PsbtV2Maps {
|
|
|
918
1168
|
|
|
919
1169
|
// Creator Role
|
|
920
1170
|
const psbtv2 = new PsbtV2();
|
|
921
|
-
// Set it fully modifiable so that we can add the v0 inputs and outputs.
|
|
922
|
-
psbtv2.PSBT_GLOBAL_TX_MODIFIABLE = [
|
|
923
|
-
PsbtGlobalTxModifiableBits.INPUTS,
|
|
924
|
-
PsbtGlobalTxModifiableBits.OUTPUTS,
|
|
925
|
-
];
|
|
926
1171
|
const txVersion = psbtv0.data.getTransaction().readInt32LE(0);
|
|
927
1172
|
if (txVersion === 1 && allowTxnVersion1) {
|
|
928
1173
|
psbtv2.dangerouslySetGlobalTxVersion1();
|
|
@@ -932,7 +1177,7 @@ export class PsbtV2 extends PsbtV2Maps {
|
|
|
932
1177
|
.readInt32LE(0);
|
|
933
1178
|
}
|
|
934
1179
|
|
|
935
|
-
//
|
|
1180
|
+
// Constructor Role
|
|
936
1181
|
for (const globalXpub of psbtv0GlobalMap.globalXpub ?? []) {
|
|
937
1182
|
psbtv2.addGlobalXpub(
|
|
938
1183
|
globalXpub.extendedPubkey,
|
|
@@ -941,8 +1186,7 @@ export class PsbtV2 extends PsbtV2Maps {
|
|
|
941
1186
|
);
|
|
942
1187
|
}
|
|
943
1188
|
|
|
944
|
-
|
|
945
|
-
let txInputs: any = [];
|
|
1189
|
+
const txInputs: any = [];
|
|
946
1190
|
for (const [index, txInput] of psbtv0.txInputs.entries()) {
|
|
947
1191
|
txInputs[index] = txInput;
|
|
948
1192
|
}
|
|
@@ -964,7 +1208,7 @@ export class PsbtV2 extends PsbtV2Maps {
|
|
|
964
1208
|
});
|
|
965
1209
|
}
|
|
966
1210
|
|
|
967
|
-
|
|
1211
|
+
const txOutputs: any = [];
|
|
968
1212
|
for (const [index, txOutput] of psbtv0.txOutputs.entries()) {
|
|
969
1213
|
txOutputs[index] = txOutput;
|
|
970
1214
|
}
|
|
@@ -980,6 +1224,8 @@ export class PsbtV2 extends PsbtV2Maps {
|
|
|
980
1224
|
});
|
|
981
1225
|
}
|
|
982
1226
|
|
|
1227
|
+
// Signer Role
|
|
1228
|
+
|
|
983
1229
|
// Finally, add partialSigs to inputs. This has to be performed last since
|
|
984
1230
|
// it may change PSBT_GLOBAL_TX_MODIFIABLE preventing inputs or outputs from
|
|
985
1231
|
// being added.
|