ecash-lib 1.2.2-rc9 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/README.md +2 -1
  2. package/dist/ecc.d.ts +12 -0
  3. package/dist/ecc.d.ts.map +1 -1
  4. package/dist/ecc.js +9 -47
  5. package/dist/ecc.js.map +1 -1
  6. package/dist/ffi/ecash_lib_wasm_bg_browser.wasm +0 -0
  7. package/dist/ffi/ecash_lib_wasm_bg_browser.wasm.d.ts +18 -4
  8. package/dist/ffi/ecash_lib_wasm_bg_nodejs.wasm +0 -0
  9. package/dist/ffi/ecash_lib_wasm_bg_nodejs.wasm.d.ts +18 -4
  10. package/dist/ffi/ecash_lib_wasm_browser.d.ts +95 -4
  11. package/dist/ffi/ecash_lib_wasm_browser.js +245 -14
  12. package/dist/ffi/ecash_lib_wasm_nodejs.d.ts +77 -0
  13. package/dist/ffi/ecash_lib_wasm_nodejs.js +247 -14
  14. package/dist/hash.d.ts +21 -1
  15. package/dist/hash.d.ts.map +1 -1
  16. package/dist/hash.js +16 -10
  17. package/dist/hash.js.map +1 -1
  18. package/dist/hdwallet.d.ts +33 -0
  19. package/dist/hdwallet.d.ts.map +1 -0
  20. package/dist/hdwallet.js +148 -0
  21. package/dist/hdwallet.js.map +1 -0
  22. package/dist/hmac.d.ts +13 -0
  23. package/dist/hmac.d.ts.map +1 -0
  24. package/dist/hmac.js +63 -0
  25. package/dist/hmac.js.map +1 -0
  26. package/dist/index.d.ts +3 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +3 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/initBrowser.d.ts.map +1 -1
  31. package/dist/initBrowser.js +3 -0
  32. package/dist/initBrowser.js.map +1 -1
  33. package/dist/initNodeJs.d.ts.map +1 -1
  34. package/dist/initNodeJs.js +3 -0
  35. package/dist/initNodeJs.js.map +1 -1
  36. package/dist/io/bytes.d.ts +5 -3
  37. package/dist/io/bytes.d.ts.map +1 -1
  38. package/dist/io/bytes.js +15 -7
  39. package/dist/io/bytes.js.map +1 -1
  40. package/dist/io/writer.d.ts +4 -3
  41. package/dist/io/writer.d.ts.map +1 -1
  42. package/dist/io/writerbytes.d.ts +4 -3
  43. package/dist/io/writerbytes.d.ts.map +1 -1
  44. package/dist/io/writerbytes.js +7 -6
  45. package/dist/io/writerbytes.js.map +1 -1
  46. package/dist/io/writerlength.d.ts +4 -3
  47. package/dist/io/writerlength.d.ts.map +1 -1
  48. package/dist/io/writerlength.js +3 -3
  49. package/dist/io/writerlength.js.map +1 -1
  50. package/dist/mnemonic.d.ts +14 -0
  51. package/dist/mnemonic.d.ts.map +1 -0
  52. package/dist/mnemonic.js +123 -0
  53. package/dist/mnemonic.js.map +1 -0
  54. package/dist/pbkdf2.d.ts +11 -0
  55. package/dist/pbkdf2.d.ts.map +1 -0
  56. package/dist/pbkdf2.js +36 -0
  57. package/dist/pbkdf2.js.map +1 -0
  58. package/dist/script.d.ts +4 -0
  59. package/dist/script.d.ts.map +1 -1
  60. package/dist/script.js +6 -0
  61. package/dist/script.js.map +1 -1
  62. package/package.json +1 -1
  63. package/src/address/address.ts +346 -0
  64. package/src/address/legacyaddr.ts +129 -0
  65. package/src/consts.ts +8 -0
  66. package/src/ecc.ts +61 -0
  67. package/src/ffi/ecash_lib_wasm_bg_browser.wasm +0 -0
  68. package/src/ffi/ecash_lib_wasm_bg_browser.wasm.d.ts +32 -0
  69. package/src/ffi/ecash_lib_wasm_bg_nodejs.wasm +0 -0
  70. package/src/ffi/ecash_lib_wasm_bg_nodejs.wasm.d.ts +32 -0
  71. package/src/ffi/ecash_lib_wasm_browser.d.ts +183 -0
  72. package/src/ffi/ecash_lib_wasm_browser.js +571 -0
  73. package/src/ffi/ecash_lib_wasm_nodejs.d.ts +127 -0
  74. package/src/ffi/ecash_lib_wasm_nodejs.js +498 -0
  75. package/src/hash.ts +46 -0
  76. package/src/hdwallet.ts +181 -0
  77. package/src/hmac.ts +74 -0
  78. package/src/index.ts +29 -0
  79. package/src/indexBrowser.ts +6 -0
  80. package/src/indexNodeJs.ts +6 -0
  81. package/src/initBrowser.ts +21 -0
  82. package/src/initNodeJs.ts +20 -0
  83. package/src/io/bytes.ts +80 -0
  84. package/src/io/hex.ts +69 -0
  85. package/src/io/int.ts +6 -0
  86. package/src/io/str.ts +16 -0
  87. package/src/io/varsize.ts +49 -0
  88. package/src/io/writer.ts +20 -0
  89. package/src/io/writerbytes.ts +87 -0
  90. package/src/io/writerlength.ts +44 -0
  91. package/src/mnemonic.ts +153 -0
  92. package/src/op.ts +162 -0
  93. package/src/opcode.ts +154 -0
  94. package/src/pbkdf2.ts +52 -0
  95. package/src/script.ts +195 -0
  96. package/src/sigHashType.ts +190 -0
  97. package/src/test/testRunner.ts +209 -0
  98. package/src/token/alp.ts +146 -0
  99. package/src/token/common.ts +32 -0
  100. package/src/token/empp.ts +29 -0
  101. package/src/token/slp.ts +212 -0
  102. package/src/tx.ts +180 -0
  103. package/src/txBuilder.ts +262 -0
  104. package/src/unsignedTx.ts +359 -0
  105. package/tsconfig.json +2 -1
  106. package/wordlists/english.json +2053 -0
  107. package/.nyc_output/0fc40ca6-d52c-45eb-b31b-2601ce70b887.json +0 -1
  108. package/.nyc_output/ac5be6db-4e40-41f8-8b84-7598d4747e57.json +0 -1
  109. package/.nyc_output/b316d46f-5ea0-4e98-884a-bfbf9cc1d0f8.json +0 -1
  110. package/.nyc_output/f965566b-9422-4874-b45e-9eefda9c769c.json +0 -1
  111. package/.nyc_output/processinfo/0fc40ca6-d52c-45eb-b31b-2601ce70b887.json +0 -1
  112. package/.nyc_output/processinfo/ac5be6db-4e40-41f8-8b84-7598d4747e57.json +0 -1
  113. package/.nyc_output/processinfo/b316d46f-5ea0-4e98-884a-bfbf9cc1d0f8.json +0 -1
  114. package/.nyc_output/processinfo/f965566b-9422-4874-b45e-9eefda9c769c.json +0 -1
  115. package/.nyc_output/processinfo/index.json +0 -1
  116. package/dist/address/cashaddr.d.ts +0 -78
  117. package/dist/address/cashaddr.d.ts.map +0 -1
  118. package/dist/address/cashaddr.js +0 -543
  119. package/dist/address/cashaddr.js.map +0 -1
  120. package/dist/cashaddr/cashaddr.d.ts +0 -23
  121. package/dist/cashaddr/cashaddr.d.ts.map +0 -1
  122. package/dist/cashaddr/cashaddr.js +0 -325
  123. package/dist/cashaddr/cashaddr.js.map +0 -1
  124. package/global.d.ts +0 -64
  125. package/test.log +0 -82
package/src/script.ts ADDED
@@ -0,0 +1,195 @@
1
+ // Copyright (c) 2024 The Bitcoin developers
2
+ // Distributed under the MIT software license, see the accompanying
3
+ // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
+
5
+ import { readVarSize, writeVarSize } from './io/varsize.js';
6
+ import { Writer } from './io/writer.js';
7
+ import { WriterLength } from './io/writerlength.js';
8
+ import { WriterBytes } from './io/writerbytes.js';
9
+ import { toHex, fromHex } from './io/hex.js';
10
+ import { Op, pushBytesOp, readOp, writeOp } from './op.js';
11
+ import {
12
+ OP_CHECKSIG,
13
+ OP_CODESEPARATOR,
14
+ OP_DUP,
15
+ OP_EQUAL,
16
+ OP_EQUALVERIFY,
17
+ OP_HASH160,
18
+ } from './opcode.js';
19
+ import { Bytes } from './io/bytes.js';
20
+ import { Address } from './address/address';
21
+
22
+ /** A Bitcoin Script locking/unlocking a UTXO */
23
+ export class Script {
24
+ public bytecode: Uint8Array;
25
+
26
+ /** Create a new Script with the given bytecode or empty */
27
+ public constructor(bytecode?: Uint8Array) {
28
+ this.bytecode = bytecode ?? new Uint8Array();
29
+ }
30
+
31
+ /**
32
+ * Write the script to the writer with the script size as VARINT
33
+ * prepended.
34
+ **/
35
+ public writeWithSize(writer: Writer) {
36
+ writeVarSize(this.bytecode.length, writer);
37
+ writer.putBytes(this.bytecode);
38
+ }
39
+
40
+ public static readWithSize(bytes: Bytes) {
41
+ const size = readVarSize(bytes);
42
+ return new Script(bytes.readBytes(Number(size)));
43
+ }
44
+
45
+ /** Build a Script from the given Script Ops */
46
+ public static fromOps(ops: Op[]): Script {
47
+ let scriptSize = 0;
48
+ for (const op of ops) {
49
+ const writerLength = new WriterLength();
50
+ writeOp(op, writerLength);
51
+ scriptSize += writerLength.length;
52
+ }
53
+ const bytecodeWriter = new WriterBytes(scriptSize);
54
+ for (const op of ops) {
55
+ writeOp(op, bytecodeWriter);
56
+ }
57
+ return new Script(bytecodeWriter.data);
58
+ }
59
+
60
+ public static fromAddress(address: string): Script {
61
+ // make Address from address
62
+ const thisAddress = Address.fromCashAddress(address);
63
+
64
+ switch (thisAddress.type) {
65
+ case 'p2pkh': {
66
+ return Script.p2pkh(fromHex(thisAddress.hash));
67
+ }
68
+ case 'p2sh': {
69
+ return Script.p2sh(fromHex(thisAddress.hash));
70
+ }
71
+ default: {
72
+ // Note we should never get here, as Address constructor
73
+ // only supports p2pkh and p2sh
74
+ throw new Error(
75
+ `Unsupported address type: ${thisAddress.type}`,
76
+ );
77
+ }
78
+ }
79
+ }
80
+
81
+ /** Iterate over the Ops of this Script */
82
+ public ops(): ScriptOpIter {
83
+ return new ScriptOpIter(new Bytes(this.bytecode));
84
+ }
85
+
86
+ /** Create a deep copy of this Script */
87
+ public copy(): Script {
88
+ return new Script(new Uint8Array(this.bytecode));
89
+ }
90
+
91
+ /**
92
+ * Find the n-th OP_CODESEPARATOR (0-based) and cut out the bytecode
93
+ * following it. Required for signing BIP143 scripts that have an
94
+ * OP_CODESEPARATOR.
95
+ *
96
+ * Throw an error if the n-th OP_CODESEPARATOR doesn't exist.
97
+ *
98
+ * Historically this opcode has been seen as obscure and useless, but in
99
+ * BIP143 sighash-based covenants, basically every covenant benefits from
100
+ * its usage, by trimming down the sighash preimage size and thus tx size.
101
+ *
102
+ * Really long Scripts will have a big BIP143 preimage, which costs precious
103
+ * bytes (and the preimage might even go over the 520 pushdata limit).
104
+ * This can be trimmed down to just one single byte by ending the covenant
105
+ * in `... OP_CODESEPARATOR OP_CHECKSIG`, in which case the BIP143 signature
106
+ * algo will cut out everything after the OP_CODESEPARATOR, so only the
107
+ * OP_CHECKSIG remains.
108
+ * If the covenant bytecode is 520 or so, this would save 519 bytes.
109
+ */
110
+ public cutOutCodesep(nCodesep: number): Script {
111
+ const ops = this.ops();
112
+ let op: Op | undefined;
113
+ let nCodesepsFound = 0;
114
+ while ((op = ops.next()) !== undefined) {
115
+ if (op == OP_CODESEPARATOR) {
116
+ if (nCodesepsFound == nCodesep) {
117
+ return new Script(this.bytecode.slice(ops.bytes.idx));
118
+ }
119
+ nCodesepsFound++;
120
+ }
121
+ }
122
+ throw new Error('OP_CODESEPARATOR not found');
123
+ }
124
+
125
+ /**
126
+ * Whether the Script is a P2SH Script.
127
+ * Matches CScript::IsPayToScriptHash in /src/script/script.h.
128
+ **/
129
+ public isP2sh(): boolean {
130
+ if (this.bytecode.length != 23) {
131
+ return false;
132
+ }
133
+ return (
134
+ this.bytecode[0] == OP_HASH160 &&
135
+ this.bytecode[1] == 20 &&
136
+ this.bytecode[22] == OP_EQUAL
137
+ );
138
+ }
139
+
140
+ /**
141
+ * Return hex string of this Script's bytecode
142
+ */
143
+ public toHex(): string {
144
+ return toHex(this.bytecode);
145
+ }
146
+
147
+ /** Build a P2SH script for the given script hash */
148
+ public static p2sh(scriptHash: Uint8Array): Script {
149
+ if (scriptHash.length !== 20) {
150
+ throw new Error(
151
+ `scriptHash length must be 20, got ${scriptHash.length}`,
152
+ );
153
+ }
154
+ return Script.fromOps([OP_HASH160, pushBytesOp(scriptHash), OP_EQUAL]);
155
+ }
156
+
157
+ /** Build a P2PKH script for the given public key hash */
158
+ public static p2pkh(pkh: Uint8Array): Script {
159
+ if (pkh.length !== 20) {
160
+ throw new Error(`pkh length must be 20, got ${pkh.length}`);
161
+ }
162
+ return Script.fromOps([
163
+ OP_DUP,
164
+ OP_HASH160,
165
+ pushBytesOp(pkh),
166
+ OP_EQUALVERIFY,
167
+ OP_CHECKSIG,
168
+ ]);
169
+ }
170
+
171
+ /** Build a scriptSig for spending a P2PKH output */
172
+ public static p2pkhSpend(pk: Uint8Array, sig: Uint8Array): Script {
173
+ return Script.fromOps([pushBytesOp(sig), pushBytesOp(pk)]);
174
+ }
175
+ }
176
+
177
+ /** Iterator over the Ops of a Script. */
178
+ export class ScriptOpIter {
179
+ bytes: Bytes;
180
+
181
+ public constructor(bytes: Bytes) {
182
+ this.bytes = bytes;
183
+ }
184
+
185
+ /**
186
+ * Read the next Op and return it, or `undefined` if there are no more Ops.
187
+ * Throws an error if reading the next op failed.
188
+ */
189
+ public next(): Op | undefined {
190
+ if (this.bytes.idx >= this.bytes.data.length) {
191
+ return undefined;
192
+ }
193
+ return readOp(this.bytes);
194
+ }
195
+ }
@@ -0,0 +1,190 @@
1
+ // Copyright (c) 2024 The Bitcoin developers
2
+ // Distributed under the MIT software license, see the accompanying
3
+ // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
+
5
+ /** Type of sighash used to sign for an input for a OP_CHECKSIG operation. */
6
+ export class SigHashType {
7
+ /** Variant of the sighash, e.g. LEGACY or BIP143 */
8
+ public variant: SigHashTypeVariant;
9
+ /** How inputs are signed, e.g. FIXED or ANYONECANPAY */
10
+ public inputType: SigHashTypeInputs;
11
+ /** How outputs are signed, e.g. ALL, NONE or SINGLE */
12
+ public outputType: SigHashTypeOutputs;
13
+
14
+ public constructor(params: {
15
+ variant: SigHashTypeVariant;
16
+ inputType: SigHashTypeInputs;
17
+ outputType: SigHashTypeOutputs;
18
+ }) {
19
+ this.variant = params.variant;
20
+ this.inputType = params.inputType;
21
+ this.outputType = params.outputType;
22
+ }
23
+
24
+ /** Reconstruct a SigHashType from the flags */
25
+ public static fromInt(flags: number): SigHashType | undefined {
26
+ if (flags > 0xff || flags < 0) {
27
+ return undefined;
28
+ }
29
+ // No bits may be set other than 0x80, 0x40, 0x02 and 0x01
30
+ if ((flags & 0x3c) != 0) {
31
+ return undefined;
32
+ }
33
+ const outputFlags = flags & 0x03;
34
+ if (outputFlags == 0) {
35
+ // 0 is not a valid output type
36
+ return undefined;
37
+ }
38
+ return new SigHashType({
39
+ variant:
40
+ flags & 0x40
41
+ ? SigHashTypeVariant.BIP143
42
+ : SigHashTypeVariant.LEGACY,
43
+ inputType:
44
+ flags & 0x80
45
+ ? SigHashTypeInputs.ANYONECANPAY
46
+ : SigHashTypeInputs.FIXED,
47
+ outputType:
48
+ outputFlags == 1
49
+ ? SigHashTypeOutputs.ALL
50
+ : outputFlags == 2
51
+ ? SigHashTypeOutputs.NONE
52
+ : SigHashTypeOutputs.SINGLE,
53
+ });
54
+ }
55
+
56
+ /** Get the sighash type as integer flags */
57
+ public toInt(): number {
58
+ return this.variant | this.inputType | this.outputType;
59
+ }
60
+ }
61
+
62
+ /** Variant of the sighash */
63
+ export enum SigHashTypeVariant {
64
+ /** Original Satoshi, pre-BIP143 sighash */
65
+ LEGACY = 0,
66
+ /** New BIP143 sighash introduced by UAHF */
67
+ BIP143 = 0x40,
68
+ }
69
+
70
+ /** How tx inputs are signed */
71
+ export enum SigHashTypeInputs {
72
+ /** Inputs are fixed, no other inputs can added/removeed */
73
+ FIXED = 0,
74
+ /** Inputs are arbitrary, other inputs can be added/removed */
75
+ ANYONECANPAY = 0x80,
76
+ }
77
+
78
+ /** How tx outputs are signed */
79
+ export enum SigHashTypeOutputs {
80
+ /** All outputs are signed, no outputs can be added/removed */
81
+ ALL = 1,
82
+ /** No outputs are signed, they can be anything */
83
+ NONE = 2,
84
+ /** The output with the identical index as this input is signed */
85
+ SINGLE = 3,
86
+ }
87
+
88
+ /** ALL|BIP143 */
89
+ export const ALL_BIP143: SigHashType = new SigHashType({
90
+ variant: SigHashTypeVariant.BIP143,
91
+ inputType: SigHashTypeInputs.FIXED,
92
+ outputType: SigHashTypeOutputs.ALL,
93
+ });
94
+
95
+ /** ALL|ANYONECANPAY|BIP143 */
96
+ export const ALL_ANYONECANPAY_BIP143: SigHashType = new SigHashType({
97
+ variant: SigHashTypeVariant.BIP143,
98
+ inputType: SigHashTypeInputs.ANYONECANPAY,
99
+ outputType: SigHashTypeOutputs.ALL,
100
+ });
101
+
102
+ /** NONE|BIP143 */
103
+ export const NONE_BIP143: SigHashType = new SigHashType({
104
+ variant: SigHashTypeVariant.BIP143,
105
+ inputType: SigHashTypeInputs.FIXED,
106
+ outputType: SigHashTypeOutputs.NONE,
107
+ });
108
+
109
+ /** NONE|ANYONECANPAY|BIP143 */
110
+ export const NONE_ANYONECANPAY_BIP143: SigHashType = new SigHashType({
111
+ variant: SigHashTypeVariant.BIP143,
112
+ inputType: SigHashTypeInputs.ANYONECANPAY,
113
+ outputType: SigHashTypeOutputs.NONE,
114
+ });
115
+
116
+ /** SINGLE|BIP143 */
117
+ export const SINGLE_BIP143: SigHashType = new SigHashType({
118
+ variant: SigHashTypeVariant.BIP143,
119
+ inputType: SigHashTypeInputs.FIXED,
120
+ outputType: SigHashTypeOutputs.SINGLE,
121
+ });
122
+
123
+ /** SINGLE|ANYONECANPAY|BIP143 */
124
+ export const SINGLE_ANYONECANPAY_BIP143: SigHashType = new SigHashType({
125
+ variant: SigHashTypeVariant.BIP143,
126
+ inputType: SigHashTypeInputs.ANYONECANPAY,
127
+ outputType: SigHashTypeOutputs.SINGLE,
128
+ });
129
+
130
+ /** ALL|LEGACY */
131
+ export const ALL_LEGACY: SigHashType = new SigHashType({
132
+ variant: SigHashTypeVariant.LEGACY,
133
+ inputType: SigHashTypeInputs.FIXED,
134
+ outputType: SigHashTypeOutputs.ALL,
135
+ });
136
+
137
+ /** ALL|ANYONECANPAY|LEGACY */
138
+ export const ALL_ANYONECANPAY_LEGACY: SigHashType = new SigHashType({
139
+ variant: SigHashTypeVariant.LEGACY,
140
+ inputType: SigHashTypeInputs.ANYONECANPAY,
141
+ outputType: SigHashTypeOutputs.ALL,
142
+ });
143
+
144
+ /** NONE|LEGACY */
145
+ export const NONE_LEGACY: SigHashType = new SigHashType({
146
+ variant: SigHashTypeVariant.LEGACY,
147
+ inputType: SigHashTypeInputs.FIXED,
148
+ outputType: SigHashTypeOutputs.NONE,
149
+ });
150
+
151
+ /** NONE|ANYONECANPAY|LEGACY */
152
+ export const NONE_ANYONECANPAY_LEGACY: SigHashType = new SigHashType({
153
+ variant: SigHashTypeVariant.LEGACY,
154
+ inputType: SigHashTypeInputs.ANYONECANPAY,
155
+ outputType: SigHashTypeOutputs.NONE,
156
+ });
157
+
158
+ /** SINGLE|LEGACY */
159
+ export const SINGLE_LEGACY: SigHashType = new SigHashType({
160
+ variant: SigHashTypeVariant.LEGACY,
161
+ inputType: SigHashTypeInputs.FIXED,
162
+ outputType: SigHashTypeOutputs.SINGLE,
163
+ });
164
+
165
+ /** SINGLE|ANYONECANPAY|LEGACY */
166
+ export const SINGLE_ANYONECANPAY_LEGACY: SigHashType = new SigHashType({
167
+ variant: SigHashTypeVariant.LEGACY,
168
+ inputType: SigHashTypeInputs.ANYONECANPAY,
169
+ outputType: SigHashTypeOutputs.SINGLE,
170
+ });
171
+
172
+ /** List of BIP143 sighashes (FORKID) */
173
+ export const SIG_HASH_TYPES_BIP143 = [
174
+ ALL_BIP143,
175
+ ALL_ANYONECANPAY_BIP143,
176
+ NONE_BIP143,
177
+ NONE_ANYONECANPAY_BIP143,
178
+ SINGLE_BIP143,
179
+ SINGLE_ANYONECANPAY_BIP143,
180
+ ];
181
+
182
+ /** List of legacy sighashes (OG Bitcoin signature) */
183
+ export const SIG_HASH_TYPES_LEGACY = [
184
+ ALL_LEGACY,
185
+ ALL_ANYONECANPAY_LEGACY,
186
+ NONE_LEGACY,
187
+ NONE_ANYONECANPAY_LEGACY,
188
+ SINGLE_LEGACY,
189
+ SINGLE_ANYONECANPAY_LEGACY,
190
+ ];
@@ -0,0 +1,209 @@
1
+ // Copyright (c) 2024 The Bitcoin developers
2
+ // Distributed under the MIT software license, see the accompanying
3
+ // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
+
5
+ import type { ChronikClient } from 'chronik-client';
6
+ import type { ChildProcess } from 'node:child_process';
7
+
8
+ import { Ecc } from '../ecc.js';
9
+ import { shaRmd160 } from '../hash.js';
10
+ import { fromHex, toHex } from '../io/hex.js';
11
+ import { pushBytesOp } from '../op.js';
12
+ import { OP_1, OP_RETURN } from '../opcode.js';
13
+ import { Script } from '../script.js';
14
+ import { OutPoint, Tx } from '../tx.js';
15
+ import { TxBuilder } from '../txBuilder.js';
16
+
17
+ const OP_TRUE_SCRIPT = Script.fromOps([OP_1]);
18
+ const OP_TRUE_SCRIPT_SIG = Script.fromOps([
19
+ pushBytesOp(OP_TRUE_SCRIPT.bytecode),
20
+ ]);
21
+ // Like OP_TRUE_SCRIPT but much bigger to avoid undersize
22
+ const ANYONE_SCRIPT = Script.fromOps([pushBytesOp(fromHex('01'.repeat(100)))]);
23
+ const ANYONE_SCRIPT_SIG = Script.fromOps([pushBytesOp(ANYONE_SCRIPT.bytecode)]);
24
+
25
+ export class TestRunner {
26
+ public ecc: Ecc;
27
+ public runner: ChildProcess;
28
+ public chronik: ChronikClient;
29
+ private coinsTxid: string | undefined;
30
+ private coinValue: number | undefined;
31
+ private lastUsedOutIdx: number;
32
+
33
+ private constructor(
34
+ ecc: Ecc,
35
+ runner: ChildProcess,
36
+ chronik: ChronikClient,
37
+ ) {
38
+ this.ecc = ecc;
39
+ this.runner = runner;
40
+ this.chronik = chronik;
41
+ this.coinsTxid = undefined;
42
+ this.lastUsedOutIdx = 0;
43
+ }
44
+
45
+ public static async setup(
46
+ setupScript: string = 'setup_scripts/ecash-lib_base',
47
+ ): Promise<TestRunner> {
48
+ const { ChronikClient } = await import('chronik-client');
49
+ const { spawn } = await import('node:child_process');
50
+ const events = await import('node:events');
51
+ const statusEvent = new events.EventEmitter();
52
+
53
+ const runner = spawn(
54
+ 'python3',
55
+ [
56
+ 'test/functional/test_runner.py',
57
+ // Place the setup in the python file
58
+ setupScript,
59
+ ],
60
+ {
61
+ stdio: ['ipc'],
62
+ // Needs to be set dynamically and the Bitcoin ABC
63
+ // node has to be built first.
64
+ cwd: process.env.BUILD_DIR || '.',
65
+ },
66
+ );
67
+
68
+ // Redirect stdout so we can see the messages from the test runner
69
+ runner.stdout?.pipe(process.stdout);
70
+ runner.stderr?.pipe(process.stderr);
71
+
72
+ runner.on('error', function (error) {
73
+ console.log('Test runner error, aborting: ' + error);
74
+ runner.kill();
75
+ process.exit(-1);
76
+ });
77
+
78
+ runner.on('exit', function (code, signal) {
79
+ // The test runner failed, make sure to propagate the error
80
+ if (code !== null && code !== undefined && code != 0) {
81
+ console.log('Test runner completed with code ' + code);
82
+ process.exit(code);
83
+ }
84
+
85
+ // The test runner was aborted by a signal, make sure to return an
86
+ // error
87
+ if (signal !== null && signal !== undefined) {
88
+ console.log('Test runner aborted by signal ' + signal);
89
+ process.exit(-2);
90
+ }
91
+
92
+ // In all other cases, let the test return its own status as
93
+ // expected
94
+ });
95
+
96
+ runner.on('spawn', function () {
97
+ console.log('Test runner started');
98
+ });
99
+
100
+ let chronik: ChronikClient | undefined = undefined;
101
+ runner.on('message', async function (message: any) {
102
+ if (message && message.test_info && message.test_info.chronik) {
103
+ console.log(
104
+ 'Setting chronik url to ',
105
+ message.test_info.chronik,
106
+ );
107
+ chronik = new ChronikClient(message.test_info.chronik);
108
+ }
109
+
110
+ if (message && message.status) {
111
+ while (!statusEvent.emit(message.status)) {
112
+ await new Promise(resolve => setTimeout(resolve, 100));
113
+ }
114
+ }
115
+ });
116
+
117
+ const ecc = new Ecc();
118
+
119
+ // We got the coins, can fan out now
120
+ await (events as any).once(statusEvent, 'ready');
121
+
122
+ if (chronik === undefined) {
123
+ throw new Event('Chronik is undefined');
124
+ }
125
+
126
+ return new TestRunner(ecc, runner, chronik);
127
+ }
128
+
129
+ public async setupCoins(
130
+ numCoins: number,
131
+ coinValue: number,
132
+ ): Promise<void> {
133
+ const opTrueScriptHash = shaRmd160(OP_TRUE_SCRIPT.bytecode);
134
+ const utxos = (
135
+ await this.chronik.script('p2sh', toHex(opTrueScriptHash)).utxos()
136
+ ).utxos;
137
+ const anyoneScriptHash = shaRmd160(ANYONE_SCRIPT.bytecode);
138
+ const anyoneP2sh = Script.p2sh(anyoneScriptHash);
139
+ const tx = new Tx({
140
+ inputs: utxos.map(utxo => ({
141
+ prevOut: utxo.outpoint,
142
+ script: OP_TRUE_SCRIPT_SIG,
143
+ sequence: 0xffffffff,
144
+ })),
145
+ });
146
+ const utxosValue = utxos.reduce((a, b) => a + b.value, 0);
147
+ for (let i = 0; i < numCoins; ++i) {
148
+ tx.outputs.push({
149
+ value: coinValue,
150
+ script: anyoneP2sh,
151
+ });
152
+ }
153
+ tx.outputs.push({
154
+ value: 0,
155
+ script: Script.fromOps([OP_RETURN]),
156
+ });
157
+ tx.outputs[tx.outputs.length - 1].value =
158
+ utxosValue - numCoins * coinValue - tx.serSize();
159
+
160
+ this.coinsTxid = (await this.chronik.broadcastTx(tx.ser())).txid;
161
+ this.coinValue = coinValue;
162
+ }
163
+
164
+ public getOutpoint(): OutPoint {
165
+ if (this.coinsTxid === undefined) {
166
+ throw new Error('TestRunner.coinsTxid undefined, call setupCoins');
167
+ }
168
+ return {
169
+ txid: this.coinsTxid,
170
+ outIdx: this.lastUsedOutIdx++, // use value, then increment
171
+ };
172
+ }
173
+
174
+ public async sendToScript(
175
+ value: number | number[],
176
+ script: Script,
177
+ ): Promise<string> {
178
+ const coinValue = this.coinValue!;
179
+ const values = Array.isArray(value) ? value : [value];
180
+ const setupTxBuilder = new TxBuilder({
181
+ inputs: [
182
+ {
183
+ input: {
184
+ prevOut: this.getOutpoint(),
185
+ script: ANYONE_SCRIPT_SIG,
186
+ sequence: 0xffffffff,
187
+ signData: {
188
+ value: coinValue,
189
+ },
190
+ },
191
+ },
192
+ ],
193
+ outputs: [
194
+ ...values.map(value => ({ value, script })),
195
+ Script.fromOps([OP_RETURN]), // burn leftover
196
+ ],
197
+ });
198
+ const setupTx = setupTxBuilder.sign(this.ecc, 1000, 546);
199
+ return (await this.chronik.broadcastTx(setupTx.ser())).txid;
200
+ }
201
+
202
+ public generate() {
203
+ this.runner.send('generate');
204
+ }
205
+
206
+ public stop() {
207
+ this.runner.send('stop');
208
+ }
209
+ }