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.
- package/README.md +2 -1
- package/dist/ecc.d.ts +12 -0
- package/dist/ecc.d.ts.map +1 -1
- package/dist/ecc.js +9 -47
- package/dist/ecc.js.map +1 -1
- package/dist/ffi/ecash_lib_wasm_bg_browser.wasm +0 -0
- package/dist/ffi/ecash_lib_wasm_bg_browser.wasm.d.ts +18 -4
- package/dist/ffi/ecash_lib_wasm_bg_nodejs.wasm +0 -0
- package/dist/ffi/ecash_lib_wasm_bg_nodejs.wasm.d.ts +18 -4
- package/dist/ffi/ecash_lib_wasm_browser.d.ts +95 -4
- package/dist/ffi/ecash_lib_wasm_browser.js +245 -14
- package/dist/ffi/ecash_lib_wasm_nodejs.d.ts +77 -0
- package/dist/ffi/ecash_lib_wasm_nodejs.js +247 -14
- package/dist/hash.d.ts +21 -1
- package/dist/hash.d.ts.map +1 -1
- package/dist/hash.js +16 -10
- package/dist/hash.js.map +1 -1
- package/dist/hdwallet.d.ts +33 -0
- package/dist/hdwallet.d.ts.map +1 -0
- package/dist/hdwallet.js +148 -0
- package/dist/hdwallet.js.map +1 -0
- package/dist/hmac.d.ts +13 -0
- package/dist/hmac.d.ts.map +1 -0
- package/dist/hmac.js +63 -0
- package/dist/hmac.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/initBrowser.d.ts.map +1 -1
- package/dist/initBrowser.js +3 -0
- package/dist/initBrowser.js.map +1 -1
- package/dist/initNodeJs.d.ts.map +1 -1
- package/dist/initNodeJs.js +3 -0
- package/dist/initNodeJs.js.map +1 -1
- package/dist/io/bytes.d.ts +5 -3
- package/dist/io/bytes.d.ts.map +1 -1
- package/dist/io/bytes.js +15 -7
- package/dist/io/bytes.js.map +1 -1
- package/dist/io/writer.d.ts +4 -3
- package/dist/io/writer.d.ts.map +1 -1
- package/dist/io/writerbytes.d.ts +4 -3
- package/dist/io/writerbytes.d.ts.map +1 -1
- package/dist/io/writerbytes.js +7 -6
- package/dist/io/writerbytes.js.map +1 -1
- package/dist/io/writerlength.d.ts +4 -3
- package/dist/io/writerlength.d.ts.map +1 -1
- package/dist/io/writerlength.js +3 -3
- package/dist/io/writerlength.js.map +1 -1
- package/dist/mnemonic.d.ts +14 -0
- package/dist/mnemonic.d.ts.map +1 -0
- package/dist/mnemonic.js +123 -0
- package/dist/mnemonic.js.map +1 -0
- package/dist/pbkdf2.d.ts +11 -0
- package/dist/pbkdf2.d.ts.map +1 -0
- package/dist/pbkdf2.js +36 -0
- package/dist/pbkdf2.js.map +1 -0
- package/dist/script.d.ts +4 -0
- package/dist/script.d.ts.map +1 -1
- package/dist/script.js +6 -0
- package/dist/script.js.map +1 -1
- package/package.json +1 -1
- package/src/address/address.ts +346 -0
- package/src/address/legacyaddr.ts +129 -0
- package/src/consts.ts +8 -0
- package/src/ecc.ts +61 -0
- package/src/ffi/ecash_lib_wasm_bg_browser.wasm +0 -0
- package/src/ffi/ecash_lib_wasm_bg_browser.wasm.d.ts +32 -0
- package/src/ffi/ecash_lib_wasm_bg_nodejs.wasm +0 -0
- package/src/ffi/ecash_lib_wasm_bg_nodejs.wasm.d.ts +32 -0
- package/src/ffi/ecash_lib_wasm_browser.d.ts +183 -0
- package/src/ffi/ecash_lib_wasm_browser.js +571 -0
- package/src/ffi/ecash_lib_wasm_nodejs.d.ts +127 -0
- package/src/ffi/ecash_lib_wasm_nodejs.js +498 -0
- package/src/hash.ts +46 -0
- package/src/hdwallet.ts +181 -0
- package/src/hmac.ts +74 -0
- package/src/index.ts +29 -0
- package/src/indexBrowser.ts +6 -0
- package/src/indexNodeJs.ts +6 -0
- package/src/initBrowser.ts +21 -0
- package/src/initNodeJs.ts +20 -0
- package/src/io/bytes.ts +80 -0
- package/src/io/hex.ts +69 -0
- package/src/io/int.ts +6 -0
- package/src/io/str.ts +16 -0
- package/src/io/varsize.ts +49 -0
- package/src/io/writer.ts +20 -0
- package/src/io/writerbytes.ts +87 -0
- package/src/io/writerlength.ts +44 -0
- package/src/mnemonic.ts +153 -0
- package/src/op.ts +162 -0
- package/src/opcode.ts +154 -0
- package/src/pbkdf2.ts +52 -0
- package/src/script.ts +195 -0
- package/src/sigHashType.ts +190 -0
- package/src/test/testRunner.ts +209 -0
- package/src/token/alp.ts +146 -0
- package/src/token/common.ts +32 -0
- package/src/token/empp.ts +29 -0
- package/src/token/slp.ts +212 -0
- package/src/tx.ts +180 -0
- package/src/txBuilder.ts +262 -0
- package/src/unsignedTx.ts +359 -0
- package/tsconfig.json +2 -1
- package/wordlists/english.json +2053 -0
- package/.nyc_output/0fc40ca6-d52c-45eb-b31b-2601ce70b887.json +0 -1
- package/.nyc_output/ac5be6db-4e40-41f8-8b84-7598d4747e57.json +0 -1
- package/.nyc_output/b316d46f-5ea0-4e98-884a-bfbf9cc1d0f8.json +0 -1
- package/.nyc_output/f965566b-9422-4874-b45e-9eefda9c769c.json +0 -1
- package/.nyc_output/processinfo/0fc40ca6-d52c-45eb-b31b-2601ce70b887.json +0 -1
- package/.nyc_output/processinfo/ac5be6db-4e40-41f8-8b84-7598d4747e57.json +0 -1
- package/.nyc_output/processinfo/b316d46f-5ea0-4e98-884a-bfbf9cc1d0f8.json +0 -1
- package/.nyc_output/processinfo/f965566b-9422-4874-b45e-9eefda9c769c.json +0 -1
- package/.nyc_output/processinfo/index.json +0 -1
- package/dist/address/cashaddr.d.ts +0 -78
- package/dist/address/cashaddr.d.ts.map +0 -1
- package/dist/address/cashaddr.js +0 -543
- package/dist/address/cashaddr.js.map +0 -1
- package/dist/cashaddr/cashaddr.d.ts +0 -23
- package/dist/cashaddr/cashaddr.d.ts.map +0 -1
- package/dist/cashaddr/cashaddr.js +0 -325
- package/dist/cashaddr/cashaddr.js.map +0 -1
- package/global.d.ts +0 -64
- 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
|
+
}
|