@btc-vision/bitcoin 7.0.0-alpha.1 → 7.0.0-alpha.2
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/browser/address.d.ts +5 -1
- package/browser/address.d.ts.map +1 -1
- package/browser/branded.d.ts +3 -14
- package/browser/branded.d.ts.map +1 -1
- package/browser/ecc/context.d.ts.map +1 -1
- package/browser/index.d.ts +2 -1
- package/browser/index.d.ts.map +1 -1
- package/browser/index.js +2964 -2919
- package/browser/opcodes.d.ts +11 -0
- package/browser/opcodes.d.ts.map +1 -1
- package/browser/psbt/PsbtCache.d.ts +54 -0
- package/browser/psbt/PsbtCache.d.ts.map +1 -0
- package/browser/psbt/PsbtFinalizer.d.ts +21 -0
- package/browser/psbt/PsbtFinalizer.d.ts.map +1 -0
- package/browser/psbt/PsbtSigner.d.ts +32 -0
- package/browser/psbt/PsbtSigner.d.ts.map +1 -0
- package/browser/psbt/PsbtTransaction.d.ts +25 -0
- package/browser/psbt/PsbtTransaction.d.ts.map +1 -0
- package/browser/psbt/types.d.ts +13 -13
- package/browser/psbt/types.d.ts.map +1 -1
- package/browser/psbt/validation.d.ts +1 -1
- package/browser/psbt/validation.d.ts.map +1 -1
- package/browser/psbt.d.ts +27 -39
- package/browser/psbt.d.ts.map +1 -1
- package/browser/script.d.ts.map +1 -1
- package/browser/transaction.d.ts +4 -4
- package/browser/transaction.d.ts.map +1 -1
- package/browser/types.d.ts +4 -2
- package/browser/types.d.ts.map +1 -1
- package/browser/workers/index.d.ts +3 -50
- package/browser/workers/index.d.ts.map +1 -1
- package/browser/workers/index.node.d.ts +24 -0
- package/browser/workers/index.node.d.ts.map +1 -0
- package/build/address.d.ts +5 -1
- package/build/address.d.ts.map +1 -1
- package/build/address.js +29 -17
- package/build/address.js.map +1 -1
- package/build/branded.d.ts +3 -14
- package/build/branded.d.ts.map +1 -1
- package/build/branded.js +0 -5
- package/build/branded.js.map +1 -1
- package/build/ecc/context.d.ts.map +1 -1
- package/build/ecc/context.js +68 -45
- package/build/ecc/context.js.map +1 -1
- package/build/index.d.ts +2 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -1
- package/build/index.js.map +1 -1
- package/build/opcodes.d.ts +11 -0
- package/build/opcodes.d.ts.map +1 -1
- package/build/opcodes.js +19 -4
- package/build/opcodes.js.map +1 -1
- package/build/psbt/PsbtCache.d.ts +54 -0
- package/build/psbt/PsbtCache.d.ts.map +1 -0
- package/build/psbt/PsbtCache.js +249 -0
- package/build/psbt/PsbtCache.js.map +1 -0
- package/build/psbt/PsbtFinalizer.d.ts +21 -0
- package/build/psbt/PsbtFinalizer.d.ts.map +1 -0
- package/build/psbt/PsbtFinalizer.js +157 -0
- package/build/psbt/PsbtFinalizer.js.map +1 -0
- package/build/psbt/PsbtSigner.d.ts +32 -0
- package/build/psbt/PsbtSigner.d.ts.map +1 -0
- package/build/psbt/PsbtSigner.js +192 -0
- package/build/psbt/PsbtSigner.js.map +1 -0
- package/build/psbt/PsbtTransaction.d.ts +25 -0
- package/build/psbt/PsbtTransaction.d.ts.map +1 -0
- package/build/psbt/PsbtTransaction.js +61 -0
- package/build/psbt/PsbtTransaction.js.map +1 -0
- package/build/psbt/types.d.ts +13 -13
- package/build/psbt/types.d.ts.map +1 -1
- package/build/psbt/validation.d.ts +1 -1
- package/build/psbt/validation.d.ts.map +1 -1
- package/build/psbt.d.ts +27 -39
- package/build/psbt.d.ts.map +1 -1
- package/build/psbt.js +139 -746
- package/build/psbt.js.map +1 -1
- package/build/script.d.ts.map +1 -1
- package/build/script.js +2 -2
- package/build/script.js.map +1 -1
- package/build/transaction.d.ts +4 -4
- package/build/transaction.d.ts.map +1 -1
- package/build/transaction.js +5 -4
- package/build/transaction.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/build/types.d.ts +4 -2
- package/build/types.d.ts.map +1 -1
- package/build/types.js +9 -0
- package/build/types.js.map +1 -1
- package/build/workers/WorkerSigningPool.js +1 -1
- package/build/workers/WorkerSigningPool.js.map +1 -1
- package/build/workers/index.d.ts +3 -3
- package/build/workers/index.d.ts.map +1 -1
- package/build/workers/index.js +0 -3
- package/build/workers/index.js.map +1 -1
- package/build/workers/index.node.d.ts +24 -0
- package/build/workers/index.node.d.ts.map +1 -0
- package/build/workers/index.node.js +26 -0
- package/build/workers/index.node.js.map +1 -0
- package/package.json +28 -9
- package/src/address.ts +41 -18
- package/src/branded.ts +15 -13
- package/src/ecc/context.ts +75 -57
- package/src/index.ts +36 -1
- package/src/opcodes.ts +21 -4
- package/src/psbt/PsbtCache.ts +325 -0
- package/src/psbt/PsbtFinalizer.ts +213 -0
- package/src/psbt/PsbtSigner.ts +302 -0
- package/src/psbt/PsbtTransaction.ts +82 -0
- package/src/psbt/types.ts +13 -13
- package/src/psbt/validation.ts +1 -1
- package/src/psbt.ts +299 -1090
- package/src/script.ts +2 -2
- package/src/transaction.ts +9 -8
- package/src/types.ts +13 -0
- package/src/workers/WorkerSigningPool.ts +1 -1
- package/src/workers/index.node.ts +27 -0
- package/src/workers/index.ts +7 -9
- package/test/address.spec.ts +2 -2
- package/test/bitcoin.core.spec.ts +5 -2
- package/test/browser/payments.spec.ts +151 -0
- package/test/browser/psbt.spec.ts +1510 -0
- package/test/browser/script.spec.ts +223 -0
- package/test/browser/setup.ts +13 -0
- package/test/browser/workers-signing.spec.ts +537 -0
- package/test/crypto.spec.ts +2 -2
- package/test/fixtures/core/base58_encode_decode.json +12 -48
- package/test/fixtures/core/base58_keys_invalid.json +50 -150
- package/test/fixtures/core/sighash.json +1 -3
- package/test/fixtures/core/tx_valid.json +133 -501
- package/test/fixtures/embed.json +3 -11
- package/test/fixtures/p2ms.json +21 -91
- package/test/fixtures/p2pk.json +5 -24
- package/test/fixtures/p2pkh.json +7 -36
- package/test/fixtures/p2sh.json +8 -54
- package/test/fixtures/p2tr.json +2 -6
- package/test/fixtures/p2wpkh.json +7 -36
- package/test/fixtures/p2wsh.json +14 -59
- package/test/fixtures/psbt.json +2 -6
- package/test/fixtures/script.json +12 -48
- package/test/integration/addresses.spec.ts +11 -5
- package/test/integration/bip32.spec.ts +1 -1
- package/test/integration/cltv.spec.ts +10 -6
- package/test/integration/csv.spec.ts +10 -9
- package/test/integration/payments.spec.ts +8 -4
- package/test/integration/taproot.spec.ts +26 -6
- package/test/integration/transactions.spec.ts +22 -8
- package/test/payments.spec.ts +1 -1
- package/test/payments.utils.ts +1 -1
- package/test/psbt.spec.ts +250 -64
- package/test/script_signature.spec.ts +1 -1
- package/test/transaction.spec.ts +18 -5
- package/test/tsconfig.json +6 -20
- package/test/workers-pool.spec.ts +22 -23
- package/test/workers-signing.spec.ts +7 -3
- package/test/workers.spec.ts +6 -7
- package/typedoc.json +11 -1
- package/vitest.config.browser.ts +68 -0
- package/browser/ecpair.d.ts +0 -99
- package/src/ecpair.d.ts +0 -99
- package/test/taproot-cache.spec.ts +0 -694
package/test/psbt.spec.ts
CHANGED
|
@@ -2,25 +2,36 @@ import assert from 'assert';
|
|
|
2
2
|
import { BIP32Factory } from '@btc-vision/bip32';
|
|
3
3
|
import * as ecc from 'tiny-secp256k1';
|
|
4
4
|
import * as crypto from 'crypto';
|
|
5
|
-
import { ECPairFactory } from 'ecpair';
|
|
6
5
|
import { beforeEach, describe, it } from 'vitest';
|
|
7
6
|
|
|
8
7
|
import { convertScriptTree } from './payments.utils.js';
|
|
9
8
|
import { LEAF_VERSION_TAPSCRIPT } from '../src/payments/bip341.js';
|
|
10
9
|
import { tapTreeFromList, tapTreeToList } from '../src/psbt/bip371.js';
|
|
11
|
-
import type {
|
|
12
|
-
import {
|
|
13
|
-
import
|
|
10
|
+
import type { Bytes32, EccLib, MessageHash, PrivateKey, PublicKey, Satoshi, Script, Signature, Taptree, } from '../src/types.js';
|
|
11
|
+
import type { HDSigner, Signer, SignerAsync, ValidateSigFunction } from '../src/index.js';
|
|
12
|
+
import { initEccLib, networks, payments, Psbt } from '../src/index.js';
|
|
14
13
|
import { equals } from '../src/io/index.js';
|
|
15
14
|
|
|
16
15
|
import preFixtures from './fixtures/psbt.json' with { type: 'json' };
|
|
17
16
|
import taprootFixtures from './fixtures/p2tr.json' with { type: 'json' };
|
|
17
|
+
import { ECPairSigner, createNobleBackend } from '@btc-vision/ecpair';
|
|
18
|
+
import type { Network } from '../src/networks.js';
|
|
18
19
|
|
|
19
20
|
const bip32 = BIP32Factory(ecc);
|
|
20
|
-
const
|
|
21
|
+
const backend = createNobleBackend();
|
|
22
|
+
const ECPair = {
|
|
23
|
+
makeRandom: (opts?: { network?: Network }) =>
|
|
24
|
+
ECPairSigner.makeRandom(backend, opts?.network ?? networks.bitcoin),
|
|
25
|
+
fromWIF: (wif: string, network?: Network | Network[]) =>
|
|
26
|
+
ECPairSigner.fromWIF(backend, wif, network ?? networks.bitcoin),
|
|
27
|
+
fromPublicKey: (pubkey: Uint8Array, opts?: { network?: Network }) =>
|
|
28
|
+
ECPairSigner.fromPublicKey(backend, pubkey as PublicKey, opts?.network ?? networks.bitcoin),
|
|
29
|
+
fromPrivateKey: (key: Uint8Array, opts?: { network?: Network }) =>
|
|
30
|
+
ECPairSigner.fromPrivateKey(backend, key as PrivateKey, opts?.network ?? networks.bitcoin),
|
|
31
|
+
};
|
|
21
32
|
|
|
22
33
|
const validator: ValidateSigFunction = (pubkey, msghash, signature): boolean =>
|
|
23
|
-
ECPair.fromPublicKey(pubkey).verify(msghash, signature);
|
|
34
|
+
ECPair.fromPublicKey(pubkey).verify(msghash, signature as Signature);
|
|
24
35
|
|
|
25
36
|
const schnorrValidator: ValidateSigFunction = (pubkey, msghash, signature): boolean =>
|
|
26
37
|
ecc.verifySchnorr(msghash, pubkey, signature);
|
|
@@ -146,7 +157,7 @@ describe(`Psbt`, () => {
|
|
|
146
157
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
147
158
|
// @ts-ignore // cannot find tapLeafHashToSign
|
|
148
159
|
f.keys.forEach(({ inputToSign, tapLeafHashToSign, WIF }) => {
|
|
149
|
-
const keyPair = ECPair.fromWIF(WIF,
|
|
160
|
+
const keyPair = ECPair.fromWIF(WIF, networks.testnet);
|
|
150
161
|
if (tapLeafHashToSign)
|
|
151
162
|
psbt.signTaprootInput(
|
|
152
163
|
inputToSign,
|
|
@@ -509,7 +520,10 @@ describe(`Psbt`, () => {
|
|
|
509
520
|
const f = fixtures.finalizeInput.finalizeTapleafByHash;
|
|
510
521
|
const psbt = Psbt.fromBase64(f.psbt);
|
|
511
522
|
|
|
512
|
-
psbt.finalizeTaprootInput(
|
|
523
|
+
psbt.finalizeTaprootInput(
|
|
524
|
+
f.index,
|
|
525
|
+
Buffer.from(f.leafHash, 'hex') as unknown as Bytes32,
|
|
526
|
+
);
|
|
513
527
|
|
|
514
528
|
assert.strictEqual(psbt.toBase64(), f.result);
|
|
515
529
|
});
|
|
@@ -519,7 +533,10 @@ describe(`Psbt`, () => {
|
|
|
519
533
|
const psbt = Psbt.fromBase64(f.psbt);
|
|
520
534
|
|
|
521
535
|
assert.throws(() => {
|
|
522
|
-
psbt.finalizeTaprootInput(
|
|
536
|
+
psbt.finalizeTaprootInput(
|
|
537
|
+
f.index,
|
|
538
|
+
Buffer.from(f.leafHash, 'hex').reverse() as unknown as Bytes32,
|
|
539
|
+
);
|
|
523
540
|
}, new RegExp('Can not finalize taproot input #0. Signature for tapleaf script not found.'));
|
|
524
541
|
});
|
|
525
542
|
|
|
@@ -557,7 +574,10 @@ describe(`Psbt`, () => {
|
|
|
557
574
|
}, new RegExp('No script found for input #0'));
|
|
558
575
|
psbt.updateInput(0, {
|
|
559
576
|
witnessUtxo: {
|
|
560
|
-
script: Buffer.from(
|
|
577
|
+
script: Buffer.from(
|
|
578
|
+
'0014d85c2b71d0060b09c9886aeb815e50991dda124d',
|
|
579
|
+
'hex',
|
|
580
|
+
) as unknown as Script,
|
|
561
581
|
value: 200000n as Satoshi,
|
|
562
582
|
},
|
|
563
583
|
});
|
|
@@ -730,7 +750,10 @@ describe(`Psbt`, () => {
|
|
|
730
750
|
...(redeemGetter ? { redeemScript: redeemGetter(publicKey) } : {}),
|
|
731
751
|
...(witnessGetter ? { witnessScript: witnessGetter(publicKey) } : {}),
|
|
732
752
|
}).addOutput({
|
|
733
|
-
script: Buffer.from(
|
|
753
|
+
script: Buffer.from(
|
|
754
|
+
'0014d85c2b71d0060b09c9886aeb815e50991dda124d',
|
|
755
|
+
'hex',
|
|
756
|
+
) as unknown as Script,
|
|
734
757
|
value: 1800n as Satoshi,
|
|
735
758
|
});
|
|
736
759
|
if (finalize) psbt.signInput(0, key).finalizeInput(0);
|
|
@@ -904,7 +927,10 @@ describe(`Psbt`, () => {
|
|
|
904
927
|
hash: '0000000000000000000000000000000000000000000000000000000000000000',
|
|
905
928
|
index: 0,
|
|
906
929
|
}).addOutput({
|
|
907
|
-
script: Buffer.from(
|
|
930
|
+
script: Buffer.from(
|
|
931
|
+
'0014000102030405060708090a0b0c0d0e0f00010203',
|
|
932
|
+
'hex',
|
|
933
|
+
) as unknown as Script,
|
|
908
934
|
value: 2000n as Satoshi,
|
|
909
935
|
bip32Derivation: [
|
|
910
936
|
{
|
|
@@ -936,7 +962,9 @@ describe(`Psbt`, () => {
|
|
|
936
962
|
index: 0,
|
|
937
963
|
}).addOutput({
|
|
938
964
|
script: payments.p2sh({
|
|
939
|
-
redeem:
|
|
965
|
+
redeem: payments.p2wsh({
|
|
966
|
+
redeem: { output: scriptWithPubkey as unknown as Script },
|
|
967
|
+
}),
|
|
940
968
|
}).output!,
|
|
941
969
|
value: 1337n as Satoshi,
|
|
942
970
|
});
|
|
@@ -945,31 +973,45 @@ describe(`Psbt`, () => {
|
|
|
945
973
|
psbt.outputHasPubkey(0, testPubkey);
|
|
946
974
|
}, new RegExp('scriptPubkey is P2SH but redeemScript missing'));
|
|
947
975
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
976
|
+
const psbt2 = new Psbt();
|
|
977
|
+
psbt2.addInput({
|
|
978
|
+
hash: '0000000000000000000000000000000000000000000000000000000000000000',
|
|
979
|
+
index: 0,
|
|
980
|
+
}).addOutput({
|
|
981
|
+
script: payments.p2wsh({
|
|
982
|
+
redeem: { output: Buffer.from([0x51]) as unknown as Script },
|
|
983
|
+
}).output!,
|
|
984
|
+
value: 1337n as Satoshi,
|
|
985
|
+
});
|
|
951
986
|
|
|
952
987
|
assert.throws(() => {
|
|
953
|
-
|
|
988
|
+
psbt2.outputHasPubkey(0, testPubkey);
|
|
954
989
|
}, new RegExp('scriptPubkey or redeemScript is P2WSH but witnessScript missing'));
|
|
955
990
|
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
}).
|
|
991
|
+
const psbt3 = new Psbt();
|
|
992
|
+
psbt3.addInput({
|
|
993
|
+
hash: '0000000000000000000000000000000000000000000000000000000000000000',
|
|
994
|
+
index: 0,
|
|
995
|
+
}).addOutput({
|
|
996
|
+
script: payments.p2sh({
|
|
997
|
+
redeem: payments.p2wsh({
|
|
998
|
+
redeem: { output: scriptWithPubkey as unknown as Script },
|
|
999
|
+
}),
|
|
1000
|
+
}).output!,
|
|
1001
|
+
value: 1337n as Satoshi,
|
|
1002
|
+
});
|
|
961
1003
|
|
|
962
|
-
|
|
1004
|
+
psbt3.updateOutput(0, {
|
|
963
1005
|
redeemScript: payments.p2wsh({
|
|
964
1006
|
redeem: { output: scriptWithPubkey as unknown as Script },
|
|
965
1007
|
}).output!,
|
|
966
1008
|
});
|
|
967
1009
|
|
|
968
1010
|
assert.throws(() => {
|
|
969
|
-
|
|
1011
|
+
psbt3.outputHasPubkey(0, testPubkey);
|
|
970
1012
|
}, new RegExp('scriptPubkey or redeemScript is P2WSH but witnessScript missing'));
|
|
971
1013
|
|
|
972
|
-
delete
|
|
1014
|
+
delete psbt3.data.outputs[0].redeemScript;
|
|
973
1015
|
|
|
974
1016
|
psbt.updateOutput(0, {
|
|
975
1017
|
witnessScript: scriptWithPubkey,
|
|
@@ -1003,7 +1045,8 @@ describe(`Psbt`, () => {
|
|
|
1003
1045
|
assert.strictEqual(clone.toBase64(), psbt.toBase64());
|
|
1004
1046
|
assert.strictEqual(clone.toBase64(), notAClone.toBase64());
|
|
1005
1047
|
assert.strictEqual(psbt.toBase64(), notAClone.toBase64());
|
|
1006
|
-
|
|
1048
|
+
// Mutate data layer to prove clone is independent
|
|
1049
|
+
psbt.data.inputs[0].partialSig = [];
|
|
1007
1050
|
assert.notStrictEqual(clone.toBase64(), psbt.toBase64());
|
|
1008
1051
|
assert.notStrictEqual(clone.toBase64(), notAClone.toBase64());
|
|
1009
1052
|
assert.strictEqual(psbt.toBase64(), notAClone.toBase64());
|
|
@@ -1014,9 +1057,9 @@ describe(`Psbt`, () => {
|
|
|
1014
1057
|
it('Sets the maximumFeeRate value', () => {
|
|
1015
1058
|
const psbt = new Psbt();
|
|
1016
1059
|
|
|
1017
|
-
assert.strictEqual(
|
|
1060
|
+
assert.strictEqual(psbt.maximumFeeRate, 5000);
|
|
1018
1061
|
psbt.setMaximumFeeRate(6000);
|
|
1019
|
-
assert.strictEqual(
|
|
1062
|
+
assert.strictEqual(psbt.maximumFeeRate, 6000);
|
|
1020
1063
|
});
|
|
1021
1064
|
});
|
|
1022
1065
|
|
|
@@ -1154,11 +1197,149 @@ describe(`Psbt`, () => {
|
|
|
1154
1197
|
psbt.finalizeAllInputs();
|
|
1155
1198
|
|
|
1156
1199
|
assert.strictEqual(psbt.getFeeRate(), f.fee);
|
|
1157
|
-
|
|
1200
|
+
// Calling again returns the same cached value
|
|
1158
1201
|
assert.strictEqual(psbt.getFeeRate(), f.fee);
|
|
1159
1202
|
});
|
|
1160
1203
|
});
|
|
1161
1204
|
|
|
1205
|
+
describe('getFee and getFeeRate return correct values', () => {
|
|
1206
|
+
it('computes fee as inputAmount - outputAmount for nonWitnessUtxo', () => {
|
|
1207
|
+
// nonWitnessUtxo output[0] = 90,000 sats; we send 80,000 sats => fee = 10,000 sats
|
|
1208
|
+
const alice = ECPair.fromWIF('L2uPYXe17xSTqbCjZvL2DsyXPCbXspvcu5mHLDYUgzdUbZGSKrSr');
|
|
1209
|
+
const psbt = new Psbt();
|
|
1210
|
+
const inputValue = 90_000n;
|
|
1211
|
+
const outputValue = 80_000n as Satoshi;
|
|
1212
|
+
|
|
1213
|
+
psbt.addInput({
|
|
1214
|
+
hash: '7d067b4a697a09d2c3cff7d4d9506c9955e93bff41bf82d439da7d030382bc3e',
|
|
1215
|
+
index: 0,
|
|
1216
|
+
nonWitnessUtxo: Buffer.from(
|
|
1217
|
+
'0200000001f9f34e95b9d5c8abcd20fc5bd4a825d1517be62f0f775e5f36da944d9' +
|
|
1218
|
+
'452e550000000006b483045022100c86e9a111afc90f64b4904bd609e9eaed80d48' +
|
|
1219
|
+
'ca17c162b1aca0a788ac3526f002207bb79b60d4fc6526329bf18a77135dc566020' +
|
|
1220
|
+
'9e761da46e1c2f1152ec013215801210211755115eabf846720f5cb18f248666fec' +
|
|
1221
|
+
'631e5e1e66009ce3710ceea5b1ad13ffffffff01905f0100000000001976a9148bb' +
|
|
1222
|
+
'c95d2709c71607c60ee3f097c1217482f518d88ac00000000',
|
|
1223
|
+
'hex',
|
|
1224
|
+
),
|
|
1225
|
+
sighashType: 1,
|
|
1226
|
+
});
|
|
1227
|
+
psbt.addOutput({
|
|
1228
|
+
address: '1KRMKfeZcmosxALVYESdPNez1AP1mEtywp',
|
|
1229
|
+
value: outputValue,
|
|
1230
|
+
});
|
|
1231
|
+
psbt.signInput(0, alice);
|
|
1232
|
+
psbt.finalizeAllInputs();
|
|
1233
|
+
|
|
1234
|
+
const expectedFee = Number(inputValue - outputValue); // 10,000
|
|
1235
|
+
const fee = psbt.getFee();
|
|
1236
|
+
assert.strictEqual(fee, expectedFee, `fee should be ${expectedFee}, got ${fee}`);
|
|
1237
|
+
|
|
1238
|
+
const tx = psbt.extractTransaction(true);
|
|
1239
|
+
const vsize = tx.virtualSize();
|
|
1240
|
+
const expectedFeeRate = Math.floor(expectedFee / vsize);
|
|
1241
|
+
const feeRate = psbt.getFeeRate();
|
|
1242
|
+
assert.strictEqual(
|
|
1243
|
+
feeRate,
|
|
1244
|
+
expectedFeeRate,
|
|
1245
|
+
`feeRate should be fee/vsize = ${expectedFee}/${vsize} = ${expectedFeeRate}, got ${feeRate}`,
|
|
1246
|
+
);
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
it('computes fee as inputAmount - outputAmount for witnessUtxo', () => {
|
|
1250
|
+
const alice = ECPair.fromWIF('L2uPYXe17xSTqbCjZvL2DsyXPCbXspvcu5mHLDYUgzdUbZGSKrSr');
|
|
1251
|
+
const p2wpkh = payments.p2wpkh({ pubkey: alice.publicKey as PublicKey });
|
|
1252
|
+
const inputValue = 50_000n;
|
|
1253
|
+
const outputValue = 40_000n as Satoshi;
|
|
1254
|
+
|
|
1255
|
+
const psbt = new Psbt();
|
|
1256
|
+
psbt.addInput({
|
|
1257
|
+
hash: '0000000000000000000000000000000000000000000000000000000000000001',
|
|
1258
|
+
index: 0,
|
|
1259
|
+
witnessUtxo: {
|
|
1260
|
+
script: p2wpkh.output! as Script,
|
|
1261
|
+
value: inputValue as Satoshi,
|
|
1262
|
+
},
|
|
1263
|
+
});
|
|
1264
|
+
psbt.addOutput({
|
|
1265
|
+
address: '1KRMKfeZcmosxALVYESdPNez1AP1mEtywp',
|
|
1266
|
+
value: outputValue,
|
|
1267
|
+
});
|
|
1268
|
+
psbt.signInput(0, alice);
|
|
1269
|
+
psbt.finalizeAllInputs();
|
|
1270
|
+
|
|
1271
|
+
const expectedFee = Number(inputValue - outputValue); // 10,000
|
|
1272
|
+
const fee = psbt.getFee();
|
|
1273
|
+
assert.strictEqual(fee, expectedFee, `fee should be ${expectedFee}, got ${fee}`);
|
|
1274
|
+
|
|
1275
|
+
const tx = psbt.extractTransaction(true);
|
|
1276
|
+
const vsize = tx.virtualSize();
|
|
1277
|
+
const expectedFeeRate = Math.floor(expectedFee / vsize);
|
|
1278
|
+
const feeRate = psbt.getFeeRate();
|
|
1279
|
+
assert.strictEqual(
|
|
1280
|
+
feeRate,
|
|
1281
|
+
expectedFeeRate,
|
|
1282
|
+
`feeRate should be fee/vsize = ${expectedFee}/${vsize} = ${expectedFeeRate}, got ${feeRate}`,
|
|
1283
|
+
);
|
|
1284
|
+
assert.ok(feeRate > 0, 'feeRate must be positive');
|
|
1285
|
+
});
|
|
1286
|
+
|
|
1287
|
+
it('computes fee correctly with multiple inputs and outputs', () => {
|
|
1288
|
+
const alice = ECPair.fromWIF('L2uPYXe17xSTqbCjZvL2DsyXPCbXspvcu5mHLDYUgzdUbZGSKrSr');
|
|
1289
|
+
const p2wpkh = payments.p2wpkh({ pubkey: alice.publicKey as PublicKey });
|
|
1290
|
+
|
|
1291
|
+
const input1Value = 30_000n;
|
|
1292
|
+
const input2Value = 25_000n;
|
|
1293
|
+
const output1Value = 20_000n as Satoshi;
|
|
1294
|
+
const output2Value = 15_000n as Satoshi;
|
|
1295
|
+
|
|
1296
|
+
const psbt = new Psbt();
|
|
1297
|
+
psbt.addInput({
|
|
1298
|
+
hash: '0000000000000000000000000000000000000000000000000000000000000001',
|
|
1299
|
+
index: 0,
|
|
1300
|
+
witnessUtxo: {
|
|
1301
|
+
script: p2wpkh.output! as Script,
|
|
1302
|
+
value: input1Value as Satoshi,
|
|
1303
|
+
},
|
|
1304
|
+
});
|
|
1305
|
+
psbt.addInput({
|
|
1306
|
+
hash: '0000000000000000000000000000000000000000000000000000000000000002',
|
|
1307
|
+
index: 0,
|
|
1308
|
+
witnessUtxo: {
|
|
1309
|
+
script: p2wpkh.output! as Script,
|
|
1310
|
+
value: input2Value as Satoshi,
|
|
1311
|
+
},
|
|
1312
|
+
});
|
|
1313
|
+
psbt.addOutput({
|
|
1314
|
+
address: '1KRMKfeZcmosxALVYESdPNez1AP1mEtywp',
|
|
1315
|
+
value: output1Value,
|
|
1316
|
+
});
|
|
1317
|
+
psbt.addOutput({
|
|
1318
|
+
address: '1KRMKfeZcmosxALVYESdPNez1AP1mEtywp',
|
|
1319
|
+
value: output2Value,
|
|
1320
|
+
});
|
|
1321
|
+
psbt.signAllInputs(alice);
|
|
1322
|
+
psbt.finalizeAllInputs();
|
|
1323
|
+
|
|
1324
|
+
const totalIn = input1Value + input2Value; // 55,000
|
|
1325
|
+
const totalOut = output1Value + output2Value; // 35,000
|
|
1326
|
+
const expectedFee = Number(totalIn - totalOut); // 20,000
|
|
1327
|
+
const fee = psbt.getFee();
|
|
1328
|
+
assert.strictEqual(fee, expectedFee, `fee should be ${expectedFee}, got ${fee}`);
|
|
1329
|
+
|
|
1330
|
+
const tx = psbt.extractTransaction(true);
|
|
1331
|
+
const vsize = tx.virtualSize();
|
|
1332
|
+
const expectedFeeRate = Math.floor(expectedFee / vsize);
|
|
1333
|
+
const feeRate = psbt.getFeeRate();
|
|
1334
|
+
assert.strictEqual(
|
|
1335
|
+
feeRate,
|
|
1336
|
+
expectedFeeRate,
|
|
1337
|
+
`feeRate should be ${expectedFee}/${vsize} = ${expectedFeeRate}, got ${feeRate}`,
|
|
1338
|
+
);
|
|
1339
|
+
assert.ok(feeRate > 0, 'feeRate must be positive');
|
|
1340
|
+
});
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1162
1343
|
describe('create 1-to-1 transaction', () => {
|
|
1163
1344
|
it('creates and signs a 1-to-1 transaction correctly', () => {
|
|
1164
1345
|
const alice = ECPair.fromWIF('L2uPYXe17xSTqbCjZvL2DsyXPCbXspvcu5mHLDYUgzdUbZGSKrSr');
|
|
@@ -1213,49 +1394,56 @@ describe(`Psbt`, () => {
|
|
|
1213
1394
|
),
|
|
1214
1395
|
);
|
|
1215
1396
|
assert.strictEqual(psbt instanceof Psbt, true);
|
|
1216
|
-
assert.
|
|
1397
|
+
assert.strictEqual(typeof psbt.version, 'number');
|
|
1217
1398
|
});
|
|
1218
1399
|
it('fromBase64 returns Psbt type (not base class)', () => {
|
|
1219
1400
|
const psbt = Psbt.fromBase64('cHNidP8BAAoBAAAAAAAAAAAAAAAA');
|
|
1220
1401
|
assert.strictEqual(psbt instanceof Psbt, true);
|
|
1221
|
-
assert.
|
|
1402
|
+
assert.strictEqual(typeof psbt.version, 'number');
|
|
1222
1403
|
});
|
|
1223
1404
|
it('fromHex returns Psbt type (not base class)', () => {
|
|
1224
1405
|
const psbt = Psbt.fromHex('70736274ff01000a01000000000000000000000000');
|
|
1225
1406
|
assert.strictEqual(psbt instanceof Psbt, true);
|
|
1226
|
-
assert.
|
|
1407
|
+
assert.strictEqual(typeof psbt.version, 'number');
|
|
1227
1408
|
});
|
|
1228
1409
|
});
|
|
1229
1410
|
|
|
1230
1411
|
describe('Cache', () => {
|
|
1231
|
-
it('non-witness UTXOs are
|
|
1412
|
+
it('non-witness UTXOs are stored after updateInput', () => {
|
|
1232
1413
|
const f = fixtures.cache.nonWitnessUtxo;
|
|
1233
1414
|
const psbt = Psbt.fromBase64(f.psbt);
|
|
1234
1415
|
const index = f.inputIndex;
|
|
1235
1416
|
|
|
1236
|
-
//
|
|
1237
|
-
assert.strictEqual(
|
|
1238
|
-
(psbt as any).__CACHE.nonWitnessUtxoBufCache[index],
|
|
1239
|
-
undefined,
|
|
1240
|
-
);
|
|
1417
|
+
// nonWitnessUtxo is not set before updateInput
|
|
1418
|
+
assert.strictEqual(psbt.data.inputs[index].nonWitnessUtxo, undefined);
|
|
1241
1419
|
|
|
1242
|
-
//
|
|
1420
|
+
// After updateInput, the nonWitnessUtxo is stored on the input
|
|
1243
1421
|
psbt.updateInput(index, {
|
|
1244
1422
|
nonWitnessUtxo: f.nonWitnessUtxo as any,
|
|
1245
1423
|
});
|
|
1246
|
-
|
|
1247
|
-
assert.ok(equals((psbt as any).__CACHE.nonWitnessUtxoBufCache[index], value));
|
|
1424
|
+
assert.ok(psbt.data.inputs[index].nonWitnessUtxo);
|
|
1248
1425
|
assert.ok(
|
|
1249
1426
|
equals(
|
|
1250
|
-
|
|
1427
|
+
psbt.data.inputs[index].nonWitnessUtxo!,
|
|
1251
1428
|
f.nonWitnessUtxo as any,
|
|
1252
1429
|
),
|
|
1253
1430
|
);
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
it('nonWitnessUtxo remains a plain data property (no defineProperty)', () => {
|
|
1434
|
+
const f = fixtures.cache.nonWitnessUtxo;
|
|
1435
|
+
const psbt = Psbt.fromBase64(f.psbt);
|
|
1436
|
+
const index = f.inputIndex;
|
|
1254
1437
|
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1438
|
+
psbt.updateInput(index, {
|
|
1439
|
+
nonWitnessUtxo: f.nonWitnessUtxo as any,
|
|
1440
|
+
});
|
|
1441
|
+
|
|
1442
|
+
const input = psbt.data.inputs[index];
|
|
1443
|
+
const desc = Object.getOwnPropertyDescriptor(input, 'nonWitnessUtxo');
|
|
1444
|
+
assert.ok(desc, 'property should exist');
|
|
1445
|
+
assert.strictEqual(desc!.get, undefined, 'should not have a getter');
|
|
1446
|
+
assert.strictEqual(desc!.set, undefined, 'should not have a setter');
|
|
1259
1447
|
});
|
|
1260
1448
|
});
|
|
1261
1449
|
|
|
@@ -1264,22 +1452,18 @@ describe(`Psbt`, () => {
|
|
|
1264
1452
|
const psbt = new Psbt();
|
|
1265
1453
|
|
|
1266
1454
|
assert.strictEqual(psbt.version, 2);
|
|
1267
|
-
assert.strictEqual(psbt.version, (psbt as any).__CACHE.tx.version);
|
|
1268
1455
|
|
|
1269
1456
|
psbt.version = 1;
|
|
1270
1457
|
assert.strictEqual(psbt.version, 1);
|
|
1271
|
-
assert.strictEqual(psbt.version, (psbt as any).__CACHE.tx.version);
|
|
1272
1458
|
});
|
|
1273
1459
|
|
|
1274
1460
|
it('.locktime is exposed and is settable', () => {
|
|
1275
1461
|
const psbt = new Psbt();
|
|
1276
1462
|
|
|
1277
1463
|
assert.strictEqual(psbt.locktime, 0);
|
|
1278
|
-
assert.strictEqual(psbt.locktime, (psbt as any).__CACHE.tx.locktime);
|
|
1279
1464
|
|
|
1280
1465
|
psbt.locktime = 123;
|
|
1281
1466
|
assert.strictEqual(psbt.locktime, 123);
|
|
1282
|
-
assert.strictEqual(psbt.locktime, (psbt as any).__CACHE.tx.locktime);
|
|
1283
1467
|
});
|
|
1284
1468
|
|
|
1285
1469
|
it('.txInputs is exposed as a readonly clone', () => {
|
|
@@ -1289,19 +1473,20 @@ describe(`Psbt`, () => {
|
|
|
1289
1473
|
psbt.addInput({ hash, index });
|
|
1290
1474
|
|
|
1291
1475
|
const input = psbt.txInputs[0];
|
|
1292
|
-
const
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
assert.strictEqual(input.index, internalInput.index);
|
|
1296
|
-
assert.strictEqual(input.sequence, internalInput.sequence);
|
|
1476
|
+
const originalHash = new Uint8Array(input.hash);
|
|
1477
|
+
const originalIndex = input.index;
|
|
1478
|
+
const originalSequence = input.sequence;
|
|
1297
1479
|
|
|
1480
|
+
// Mutate the returned clone
|
|
1298
1481
|
input.hash[0] = 123;
|
|
1299
1482
|
input.index = 123;
|
|
1300
1483
|
input.sequence = 123;
|
|
1301
1484
|
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
assert.
|
|
1485
|
+
// Internal state should be unchanged
|
|
1486
|
+
const fresh = psbt.txInputs[0];
|
|
1487
|
+
assert.ok(equals(fresh.hash, originalHash));
|
|
1488
|
+
assert.strictEqual(fresh.index, originalIndex);
|
|
1489
|
+
assert.strictEqual(fresh.sequence, originalSequence);
|
|
1305
1490
|
});
|
|
1306
1491
|
|
|
1307
1492
|
it('.txOutputs is exposed as a readonly clone', () => {
|
|
@@ -1311,18 +1496,19 @@ describe(`Psbt`, () => {
|
|
|
1311
1496
|
psbt.addOutput({ address, value });
|
|
1312
1497
|
|
|
1313
1498
|
const output = psbt.txOutputs[0];
|
|
1314
|
-
const internalInput = (psbt as any).__CACHE.tx.outs[0];
|
|
1315
|
-
|
|
1316
1499
|
assert.strictEqual(output.address, address);
|
|
1317
1500
|
|
|
1318
|
-
|
|
1319
|
-
|
|
1501
|
+
const originalScript = new Uint8Array(output.script);
|
|
1502
|
+
const originalValue = output.value;
|
|
1320
1503
|
|
|
1504
|
+
// Mutate the returned clone
|
|
1321
1505
|
output.script[0] = 123;
|
|
1322
1506
|
output.value = 123n;
|
|
1323
1507
|
|
|
1324
|
-
|
|
1325
|
-
|
|
1508
|
+
// Internal state should be unchanged
|
|
1509
|
+
const fresh = psbt.txOutputs[0];
|
|
1510
|
+
assert.ok(equals(fresh.script, originalScript));
|
|
1511
|
+
assert.strictEqual(fresh.value, originalValue);
|
|
1326
1512
|
});
|
|
1327
1513
|
});
|
|
1328
1514
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from 'assert';
|
|
2
2
|
import { describe, it } from 'vitest';
|
|
3
3
|
import { signature as bscriptSig } from '../src/script.js';
|
|
4
|
-
import {
|
|
4
|
+
import { concat, fromHex, toHex } from '../src/io/index.js';
|
|
5
5
|
import fixtures from './fixtures/signature.json' with { type: 'json' };
|
|
6
6
|
|
|
7
7
|
describe('Script Signatures', () => {
|
package/test/transaction.spec.ts
CHANGED
|
@@ -2,7 +2,7 @@ import assert from 'assert';
|
|
|
2
2
|
import { beforeEach, describe, it } from 'vitest';
|
|
3
3
|
import { Transaction } from '../src/index.js';
|
|
4
4
|
import * as bscript from '../src/script.js';
|
|
5
|
-
import type { Bytes32,
|
|
5
|
+
import type { Bytes32, Satoshi, Script } from '../src/types.js';
|
|
6
6
|
import fixtures from './fixtures/transaction.json' with { type: 'json' };
|
|
7
7
|
|
|
8
8
|
describe('Transaction', () => {
|
|
@@ -189,8 +189,14 @@ describe('Transaction', () => {
|
|
|
189
189
|
describe('addOutput', () => {
|
|
190
190
|
it('returns an index', () => {
|
|
191
191
|
const tx = new Transaction();
|
|
192
|
-
assert.strictEqual(
|
|
193
|
-
|
|
192
|
+
assert.strictEqual(
|
|
193
|
+
tx.addOutput(Buffer.alloc(0) as unknown as Script, 0n as Satoshi),
|
|
194
|
+
0,
|
|
195
|
+
);
|
|
196
|
+
assert.strictEqual(
|
|
197
|
+
tx.addOutput(Buffer.alloc(0) as unknown as Script, 0n as Satoshi),
|
|
198
|
+
1,
|
|
199
|
+
);
|
|
194
200
|
});
|
|
195
201
|
});
|
|
196
202
|
|
|
@@ -293,7 +299,12 @@ describe('Transaction', () => {
|
|
|
293
299
|
const tx = Transaction.fromHex(f.txHex);
|
|
294
300
|
const script = bscript.fromASM(f.script);
|
|
295
301
|
|
|
296
|
-
const hash = tx.hashForWitnessV0(
|
|
302
|
+
const hash = tx.hashForWitnessV0(
|
|
303
|
+
f.inIndex,
|
|
304
|
+
script,
|
|
305
|
+
BigInt(f.value) as Satoshi,
|
|
306
|
+
f.type,
|
|
307
|
+
);
|
|
297
308
|
assert.strictEqual(Buffer.from(hash).toString('hex'), f.hash);
|
|
298
309
|
},
|
|
299
310
|
);
|
|
@@ -303,7 +314,9 @@ describe('Transaction', () => {
|
|
|
303
314
|
describe('taprootSigning', () => {
|
|
304
315
|
fixtures.taprootSigning.forEach((f) => {
|
|
305
316
|
const tx = Transaction.fromHex(f.txHex);
|
|
306
|
-
const prevOutScripts = f.utxos.map(({ scriptHex }) =>
|
|
317
|
+
const prevOutScripts = f.utxos.map(({ scriptHex }) =>
|
|
318
|
+
Buffer.from(scriptHex, 'hex'),
|
|
319
|
+
) as unknown as Script[];
|
|
307
320
|
const values = f.utxos.map(({ value }) => BigInt(value)) as Satoshi[];
|
|
308
321
|
|
|
309
322
|
f.cases.forEach((c) => {
|
package/test/tsconfig.json
CHANGED
|
@@ -7,17 +7,9 @@
|
|
|
7
7
|
"declaration": false,
|
|
8
8
|
"allowSyntheticDefaultImports": true,
|
|
9
9
|
"rootDir": "../",
|
|
10
|
-
"rootDirs": [
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
],
|
|
14
|
-
"types": [
|
|
15
|
-
"node",
|
|
16
|
-
"mocha"
|
|
17
|
-
],
|
|
18
|
-
"lib": [
|
|
19
|
-
"ES2021"
|
|
20
|
-
],
|
|
10
|
+
"rootDirs": ["../src", "../types"],
|
|
11
|
+
"types": ["node"],
|
|
12
|
+
"lib": ["ES2021"],
|
|
21
13
|
"resolvePackageJsonImports": true,
|
|
22
14
|
"allowJs": false,
|
|
23
15
|
"resolveJsonModule": true,
|
|
@@ -34,15 +26,9 @@
|
|
|
34
26
|
"noUnusedParameters": true,
|
|
35
27
|
"baseUrl": ".",
|
|
36
28
|
"paths": {
|
|
37
|
-
"../src/*": [
|
|
38
|
-
"../ts_src/*"
|
|
39
|
-
]
|
|
29
|
+
"../src/*": ["../ts_src/*"]
|
|
40
30
|
}
|
|
41
31
|
},
|
|
42
|
-
"include": [
|
|
43
|
-
|
|
44
|
-
],
|
|
45
|
-
"exclude": [
|
|
46
|
-
"../ts_src/**/*.ts"
|
|
47
|
-
]
|
|
32
|
+
"include": ["./**/*.ts"],
|
|
33
|
+
"exclude": ["../ts_src/**/*.ts"]
|
|
48
34
|
}
|