@bsv/sdk 1.5.3 → 1.6.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/dist/cjs/package.json +1 -1
- package/dist/cjs/src/primitives/AESGCM.js +113 -137
- package/dist/cjs/src/primitives/AESGCM.js.map +1 -1
- package/dist/cjs/src/primitives/BigNumber.js +1019 -3947
- package/dist/cjs/src/primitives/BigNumber.js.map +1 -1
- package/dist/cjs/src/primitives/K256.js +53 -37
- package/dist/cjs/src/primitives/K256.js.map +1 -1
- package/dist/cjs/src/primitives/Mersenne.js +16 -21
- package/dist/cjs/src/primitives/Mersenne.js.map +1 -1
- package/dist/cjs/src/primitives/MontgomoryMethod.js.map +1 -1
- package/dist/cjs/src/primitives/utils.js +27 -17
- package/dist/cjs/src/primitives/utils.js.map +1 -1
- package/dist/cjs/src/script/Spend.js +618 -858
- package/dist/cjs/src/script/Spend.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/primitives/AESGCM.js +112 -137
- package/dist/esm/src/primitives/AESGCM.js.map +1 -1
- package/dist/esm/src/primitives/BigNumber.js +1011 -3969
- package/dist/esm/src/primitives/BigNumber.js.map +1 -1
- package/dist/esm/src/primitives/K256.js +53 -37
- package/dist/esm/src/primitives/K256.js.map +1 -1
- package/dist/esm/src/primitives/Mersenne.js +16 -21
- package/dist/esm/src/primitives/Mersenne.js.map +1 -1
- package/dist/esm/src/primitives/MontgomoryMethod.js.map +1 -1
- package/dist/esm/src/primitives/utils.js +29 -17
- package/dist/esm/src/primitives/utils.js.map +1 -1
- package/dist/esm/src/script/Spend.js +618 -857
- package/dist/esm/src/script/Spend.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/primitives/AESGCM.d.ts.map +1 -1
- package/dist/types/src/primitives/BigNumber.d.ts +238 -1705
- package/dist/types/src/primitives/BigNumber.d.ts.map +1 -1
- package/dist/types/src/primitives/K256.d.ts.map +1 -1
- package/dist/types/src/primitives/Mersenne.d.ts +2 -2
- package/dist/types/src/primitives/Mersenne.d.ts.map +1 -1
- package/dist/types/src/primitives/utils.d.ts +2 -0
- package/dist/types/src/primitives/utils.d.ts.map +1 -1
- package/dist/types/src/script/Spend.d.ts +11 -1
- package/dist/types/src/script/Spend.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/docs/performance.md +70 -0
- package/docs/primitives.md +262 -3049
- package/package.json +1 -1
- package/src/auth/__tests/Peer.test.ts +38 -23
- package/src/auth/certificates/__tests/MasterCertificate.test.ts +27 -20
- package/src/auth/certificates/__tests/VerifiableCertificate.test.ts +24 -24
- package/src/primitives/AESGCM.ts +118 -164
- package/src/primitives/BigNumber.ts +867 -4180
- package/src/primitives/K256.ts +57 -37
- package/src/primitives/Mersenne.ts +16 -20
- package/src/primitives/MontgomoryMethod.ts +2 -2
- package/src/primitives/__tests/ReductionContext.test.ts +6 -1
- package/src/primitives/utils.ts +28 -17
- package/src/script/Spend.ts +634 -1309
- package/src/transaction/__tests/Transaction.test.ts +14 -16
- package/src/transaction/__tests/Transaction.benchmarks.test.ts +0 -237
|
@@ -7,13 +7,104 @@ import TransactionSignature from '../primitives/TransactionSignature.js';
|
|
|
7
7
|
import PublicKey from '../primitives/PublicKey.js';
|
|
8
8
|
import { verify } from '../primitives/ECDSA.js';
|
|
9
9
|
// These constants control the current behavior of the interpreter.
|
|
10
|
-
// In the future, all of them will go away.
|
|
11
10
|
const maxScriptElementSize = 1024 * 1024 * 1024;
|
|
12
11
|
const maxMultisigKeyCount = Math.pow(2, 31) - 1;
|
|
13
12
|
const requireMinimalPush = true;
|
|
14
13
|
const requirePushOnlyUnlockingScripts = true;
|
|
15
14
|
const requireLowSSignatures = true;
|
|
16
15
|
const requireCleanStack = true;
|
|
16
|
+
// --- Optimization: Pre-computed script numbers ---
|
|
17
|
+
const SCRIPTNUM_NEG_1 = Object.freeze(new BigNumber(-1).toScriptNum());
|
|
18
|
+
const SCRIPTNUMS_0_TO_16 = Object.freeze(Array.from({ length: 17 }, (_, i) => Object.freeze(new BigNumber(i).toScriptNum())));
|
|
19
|
+
// --- Helper functions ---
|
|
20
|
+
function compareNumberArrays(a, b) {
|
|
21
|
+
if (a.length !== b.length)
|
|
22
|
+
return false;
|
|
23
|
+
for (let i = 0; i < a.length; i++) {
|
|
24
|
+
if (a[i] !== b[i])
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
function isMinimallyEncodedHelper(buf, maxNumSize = Number.MAX_SAFE_INTEGER) {
|
|
30
|
+
if (buf.length > maxNumSize) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
if (buf.length > 0) {
|
|
34
|
+
if ((buf[buf.length - 1] & 0x7f) === 0) {
|
|
35
|
+
if (buf.length <= 1 || (buf[buf.length - 2] & 0x80) === 0) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
function isChecksigFormatHelper(buf) {
|
|
43
|
+
// This is a simplified check. The full DER check is more complex and typically
|
|
44
|
+
// done by TransactionSignature.fromChecksigFormat which can throw.
|
|
45
|
+
// This helper is mostly for early bailout or non-throwing checks if needed.
|
|
46
|
+
if (buf.length < 9 || buf.length > 73)
|
|
47
|
+
return false;
|
|
48
|
+
if (buf[0] !== 0x30)
|
|
49
|
+
return false; // DER SEQUENCE
|
|
50
|
+
if (buf[1] !== buf.length - 3)
|
|
51
|
+
return false; // Total length (excluding type and length byte for sequence, and hash type)
|
|
52
|
+
const rMarker = buf[2];
|
|
53
|
+
const rLen = buf[3];
|
|
54
|
+
if (rMarker !== 0x02)
|
|
55
|
+
return false; // DER INTEGER
|
|
56
|
+
if (rLen === 0)
|
|
57
|
+
return false; // R length is zero
|
|
58
|
+
if (5 + rLen >= buf.length)
|
|
59
|
+
return false; // S length misplaced or R too long
|
|
60
|
+
const sMarkerOffset = 4 + rLen;
|
|
61
|
+
const sMarker = buf[sMarkerOffset];
|
|
62
|
+
const sLen = buf[sMarkerOffset + 1];
|
|
63
|
+
if (sMarker !== 0x02)
|
|
64
|
+
return false; // DER INTEGER
|
|
65
|
+
if (sLen === 0)
|
|
66
|
+
return false; // S length is zero
|
|
67
|
+
// Check R value negative or excessively padded
|
|
68
|
+
if ((buf[4] & 0x80) !== 0)
|
|
69
|
+
return false; // R value negative
|
|
70
|
+
if (rLen > 1 && buf[4] === 0x00 && (buf[5] & 0x80) === 0)
|
|
71
|
+
return false; // R value excessively padded
|
|
72
|
+
// Check S value negative or excessively padded
|
|
73
|
+
const sValueOffset = sMarkerOffset + 2;
|
|
74
|
+
if ((buf[sValueOffset] & 0x80) !== 0)
|
|
75
|
+
return false; // S value negative
|
|
76
|
+
if (sLen > 1 && buf[sValueOffset] === 0x00 && (buf[sValueOffset + 1] & 0x80) === 0)
|
|
77
|
+
return false; // S value excessively padded
|
|
78
|
+
if (rLen + sLen + 7 !== buf.length)
|
|
79
|
+
return false; // Final length check including hash type
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
function isOpcodeDisabledHelper(op) {
|
|
83
|
+
return (op === OP.OP_2MUL ||
|
|
84
|
+
op === OP.OP_2DIV ||
|
|
85
|
+
op === OP.OP_VERIF ||
|
|
86
|
+
op === OP.OP_VERNOTIF ||
|
|
87
|
+
op === OP.OP_VER);
|
|
88
|
+
}
|
|
89
|
+
function isChunkMinimalPushHelper(chunk) {
|
|
90
|
+
const data = chunk.data;
|
|
91
|
+
const op = chunk.op;
|
|
92
|
+
if (!Array.isArray(data))
|
|
93
|
+
return true;
|
|
94
|
+
if (data.length === 0)
|
|
95
|
+
return op === OP.OP_0;
|
|
96
|
+
if (data.length === 1 && data[0] >= 1 && data[0] <= 16)
|
|
97
|
+
return op === OP.OP_1 + (data[0] - 1);
|
|
98
|
+
if (data.length === 1 && data[0] === 0x81)
|
|
99
|
+
return op === OP.OP_1NEGATE;
|
|
100
|
+
if (data.length <= 75)
|
|
101
|
+
return op === data.length;
|
|
102
|
+
if (data.length <= 255)
|
|
103
|
+
return op === OP.OP_PUSHDATA1;
|
|
104
|
+
if (data.length <= 65535)
|
|
105
|
+
return op === OP.OP_PUSHDATA2;
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
17
108
|
/**
|
|
18
109
|
* The Spend class represents a spend action within a Bitcoin SV transaction.
|
|
19
110
|
* It encapsulates all the necessary data required for spending a UTXO (Unspent Transaction Output)
|
|
@@ -98,7 +189,10 @@ export default class Spend {
|
|
|
98
189
|
this.unlockingScript = params.unlockingScript;
|
|
99
190
|
this.inputSequence = params.inputSequence;
|
|
100
191
|
this.lockTime = params.lockTime;
|
|
101
|
-
this.memoryLimit = params.memoryLimit ??
|
|
192
|
+
this.memoryLimit = params.memoryLimit ?? 32000000;
|
|
193
|
+
this.stack = [];
|
|
194
|
+
this.altStack = [];
|
|
195
|
+
this.ifStack = [];
|
|
102
196
|
this.stackMem = 0;
|
|
103
197
|
this.altStackMem = 0;
|
|
104
198
|
this.reset();
|
|
@@ -113,279 +207,182 @@ export default class Spend {
|
|
|
113
207
|
this.stackMem = 0;
|
|
114
208
|
this.altStackMem = 0;
|
|
115
209
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
// If the stack (or alt stack) is over the memory limit, evaluation has failed.
|
|
119
|
-
if (this.stackMem > this.memoryLimit) {
|
|
210
|
+
ensureStackMem(additional) {
|
|
211
|
+
if (this.stackMem + additional > this.memoryLimit) {
|
|
120
212
|
this.scriptEvaluationError('Stack memory usage has exceeded ' + String(this.memoryLimit) + ' bytes');
|
|
121
|
-
return false;
|
|
122
213
|
}
|
|
123
|
-
|
|
214
|
+
}
|
|
215
|
+
ensureAltStackMem(additional) {
|
|
216
|
+
if (this.altStackMem + additional > this.memoryLimit) {
|
|
124
217
|
this.scriptEvaluationError('Alt stack memory usage has exceeded ' + String(this.memoryLimit) + ' bytes');
|
|
125
|
-
return false;
|
|
126
218
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
219
|
+
}
|
|
220
|
+
pushStack(item) {
|
|
221
|
+
this.ensureStackMem(item.length);
|
|
222
|
+
this.stack.push(item);
|
|
223
|
+
this.stackMem += item.length;
|
|
224
|
+
}
|
|
225
|
+
pushStackCopy(item) {
|
|
226
|
+
this.ensureStackMem(item.length);
|
|
227
|
+
const copy = item.slice();
|
|
228
|
+
this.stack.push(copy);
|
|
229
|
+
this.stackMem += copy.length;
|
|
230
|
+
}
|
|
231
|
+
popStack() {
|
|
232
|
+
if (this.stack.length === 0) {
|
|
233
|
+
this.scriptEvaluationError('Attempted to pop from an empty stack.');
|
|
137
234
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
235
|
+
const item = this.stack.pop();
|
|
236
|
+
this.stackMem -= item.length;
|
|
237
|
+
return item;
|
|
238
|
+
}
|
|
239
|
+
stackTop(index = -1) {
|
|
240
|
+
// index = -1 for top, -2 for second top, etc.
|
|
241
|
+
// stack.length + index provides 0-based index from start
|
|
242
|
+
if (this.stack.length === 0 || this.stack.length < Math.abs(index) || (index >= 0 && index >= this.stack.length)) {
|
|
243
|
+
this.scriptEvaluationError(`Stack underflow accessing element at index ${index}. Stack length is ${this.stack.length}.`);
|
|
141
244
|
}
|
|
142
|
-
|
|
143
|
-
|
|
245
|
+
return this.stack[this.stack.length + index];
|
|
246
|
+
}
|
|
247
|
+
pushAltStack(item) {
|
|
248
|
+
this.ensureAltStackMem(item.length);
|
|
249
|
+
this.altStack.push(item);
|
|
250
|
+
this.altStackMem += item.length;
|
|
251
|
+
}
|
|
252
|
+
popAltStack() {
|
|
253
|
+
if (this.altStack.length === 0) {
|
|
254
|
+
this.scriptEvaluationError('Attempted to pop from an empty alt stack.');
|
|
144
255
|
}
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
};
|
|
152
|
-
const isChunkMinimal = (chunk) => {
|
|
153
|
-
const data = chunk.data;
|
|
154
|
-
const op = chunk.op;
|
|
155
|
-
if (!Array.isArray(data)) {
|
|
156
|
-
return true;
|
|
157
|
-
}
|
|
158
|
-
if (data.length === 0) {
|
|
159
|
-
// Could have used OP_0.
|
|
160
|
-
return op === OP.OP_0;
|
|
161
|
-
}
|
|
162
|
-
else if (data.length === 1 && data[0] >= 1 && data[0] <= 16) {
|
|
163
|
-
// Could have used OP_1 .. OP_16.
|
|
164
|
-
return op === OP.OP_1 + (data[0] - 1);
|
|
165
|
-
}
|
|
166
|
-
else if (data.length === 1 && data[0] === 0x81) {
|
|
167
|
-
// Could have used OP_1NEGATE.
|
|
168
|
-
return op === OP.OP_1NEGATE;
|
|
169
|
-
}
|
|
170
|
-
else if (data.length <= 75) {
|
|
171
|
-
// Could have used a direct push (opCode indicating number of bytes pushed + those bytes).
|
|
172
|
-
return op === data.length;
|
|
173
|
-
}
|
|
174
|
-
else if (data.length <= 255) {
|
|
175
|
-
// Could have used OP_PUSHDATA.
|
|
176
|
-
return op === OP.OP_PUSHDATA1;
|
|
177
|
-
}
|
|
178
|
-
else if (data.length <= 65535) {
|
|
179
|
-
// Could have used OP_PUSHDATA2.
|
|
180
|
-
return op === OP.OP_PUSHDATA2;
|
|
181
|
-
}
|
|
182
|
-
return true;
|
|
183
|
-
};
|
|
184
|
-
// Following example from sCrypt now using Number.MAX_SAFE_INTEGER (bsv/lib/transaction/input/input).
|
|
185
|
-
const isMinimallyEncoded = (buf, maxNumSize = Number.MAX_SAFE_INTEGER) => {
|
|
186
|
-
if (buf.length > maxNumSize) {
|
|
187
|
-
return false;
|
|
188
|
-
}
|
|
189
|
-
if (buf.length > 0) {
|
|
190
|
-
// Check that the number is encoded with the minimum possible number
|
|
191
|
-
// of bytes.
|
|
192
|
-
//
|
|
193
|
-
// If the most-significant-byte - excluding the sign bit - is zero
|
|
194
|
-
// then we're not minimal. Note how this test also rejects the
|
|
195
|
-
// negative-zero encoding, 0x80.
|
|
196
|
-
if ((buf[buf.length - 1] & 0x7f) === 0) {
|
|
197
|
-
// One exception: if there's more than one byte and the most
|
|
198
|
-
// significant bit of the second-most-significant-byte is set it
|
|
199
|
-
// would conflict with the sign bit. An example of this case is
|
|
200
|
-
// +-255, which encode to 0xff00 and 0xff80 respectively.
|
|
201
|
-
// (big-endian).
|
|
202
|
-
if (buf.length <= 1 || (buf[buf.length - 2] & 0x80) === 0) {
|
|
203
|
-
return false;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
return true;
|
|
208
|
-
};
|
|
209
|
-
const padDataToSize = (buf, len) => {
|
|
210
|
-
const b = buf;
|
|
211
|
-
while (b.length < len) {
|
|
212
|
-
b.unshift(0);
|
|
213
|
-
}
|
|
214
|
-
return b;
|
|
215
|
-
};
|
|
216
|
-
/**
|
|
217
|
-
* This function is translated from bitcoind's IsDERSignature and is used in
|
|
218
|
-
* the script interpreter. This "DER" format actually includes an extra byte,
|
|
219
|
-
* the nHashType, at the end. It is really the tx format, not DER format.
|
|
220
|
-
*
|
|
221
|
-
* A canonical signature exists of: [30] [total len] [02] [len R] [R] [02] [len S] [S] [hashtype]
|
|
222
|
-
* Where R and S are not negative (their first byte has its highest bit not set), and not
|
|
223
|
-
* excessively padded (do not start with a 0 byte, unless an otherwise negative number follows,
|
|
224
|
-
* in which case a single 0 byte is necessary and even required).
|
|
225
|
-
*
|
|
226
|
-
* See https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623
|
|
227
|
-
*/
|
|
228
|
-
const isChecksigFormat = (buf) => {
|
|
229
|
-
if (buf.length < 9) {
|
|
230
|
-
// Non-canonical signature: too short
|
|
231
|
-
return false;
|
|
232
|
-
}
|
|
233
|
-
if (buf.length > 73) {
|
|
234
|
-
// Non-canonical signature: too long
|
|
235
|
-
return false;
|
|
236
|
-
}
|
|
237
|
-
if (buf[0] !== 0x30) {
|
|
238
|
-
// Non-canonical signature: wrong type
|
|
239
|
-
return false;
|
|
240
|
-
}
|
|
241
|
-
if (buf[1] !== buf.length - 3) {
|
|
242
|
-
// Non-canonical signature: wrong length marker
|
|
243
|
-
return false;
|
|
244
|
-
}
|
|
245
|
-
const nLEnR = buf[3];
|
|
246
|
-
if (5 + nLEnR >= buf.length) {
|
|
247
|
-
// Non-canonical signature: S length misplaced
|
|
248
|
-
return false;
|
|
249
|
-
}
|
|
250
|
-
const nLEnS = buf[5 + nLEnR];
|
|
251
|
-
if (nLEnR + nLEnS + 7 !== buf.length) {
|
|
252
|
-
// Non-canonical signature: R+S length mismatch
|
|
253
|
-
return false;
|
|
254
|
-
}
|
|
255
|
-
const R = buf.slice(4);
|
|
256
|
-
if (buf[4 - 2] !== 0x02) {
|
|
257
|
-
// Non-canonical signature: R value type mismatch
|
|
258
|
-
return false;
|
|
259
|
-
}
|
|
260
|
-
if (buf[4 - 1] !== nLEnR) {
|
|
261
|
-
// Non-canonical signature: R length mismatch
|
|
262
|
-
return false;
|
|
263
|
-
}
|
|
264
|
-
if (nLEnR === 0) {
|
|
265
|
-
// Non-canonical signature: R length is zero
|
|
266
|
-
return false;
|
|
267
|
-
}
|
|
268
|
-
if ((R[0] & 0x80) !== 0) {
|
|
269
|
-
// Non-canonical signature: R value negative
|
|
270
|
-
return false;
|
|
271
|
-
}
|
|
272
|
-
if (nLEnR > 1 && R[0] === 0x00 && (R[1] & 0x80) === 0) {
|
|
273
|
-
// Non-canonical signature: R value excessively padded
|
|
274
|
-
return false;
|
|
275
|
-
}
|
|
276
|
-
const S = buf.slice(6 + nLEnR);
|
|
277
|
-
if (buf[6 + nLEnR - 2] !== 0x02) {
|
|
278
|
-
// Non-canonical signature: S value type mismatch
|
|
279
|
-
return false;
|
|
280
|
-
}
|
|
281
|
-
if (buf[6 + nLEnR - 1] !== nLEnS) {
|
|
282
|
-
// Non-canonical signature: S length mismatch
|
|
283
|
-
return false;
|
|
284
|
-
}
|
|
285
|
-
if (nLEnS === 0) {
|
|
286
|
-
// Non-canonical signature: S length is zero
|
|
287
|
-
return false;
|
|
288
|
-
}
|
|
289
|
-
if ((S[0] & 0x80) !== 0) {
|
|
290
|
-
// Non-canonical signature: S value negative
|
|
291
|
-
return false;
|
|
292
|
-
}
|
|
293
|
-
if (nLEnS > 1 && S[0] === 0x00 && (S[1] & 0x80) === 0) {
|
|
294
|
-
// Non-canonical signature: S value excessively padded
|
|
295
|
-
return false;
|
|
296
|
-
}
|
|
256
|
+
const item = this.altStack.pop();
|
|
257
|
+
this.altStackMem -= item.length;
|
|
258
|
+
return item;
|
|
259
|
+
}
|
|
260
|
+
checkSignatureEncoding(buf) {
|
|
261
|
+
if (buf.length === 0)
|
|
297
262
|
return true;
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
if (!isChecksigFormat(buf)) {
|
|
306
|
-
this.scriptEvaluationError('The signature format is invalid.');
|
|
307
|
-
}
|
|
308
|
-
const sig = TransactionSignature.fromChecksigFormat(buf);
|
|
263
|
+
if (!isChecksigFormatHelper(buf)) {
|
|
264
|
+
this.scriptEvaluationError('The signature format is invalid.'); // Generic message like original
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
const sig = TransactionSignature.fromChecksigFormat(buf); // This can throw for stricter DER rules
|
|
309
269
|
if (requireLowSSignatures && !sig.hasLowS()) {
|
|
310
270
|
this.scriptEvaluationError('The signature must have a low S value.');
|
|
271
|
+
return false;
|
|
311
272
|
}
|
|
312
273
|
if ((sig.scope & TransactionSignature.SIGHASH_FORKID) === 0) {
|
|
313
274
|
this.scriptEvaluationError('The signature must use SIGHASH_FORKID.');
|
|
314
275
|
return false;
|
|
315
276
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
277
|
+
}
|
|
278
|
+
catch (e) {
|
|
279
|
+
this.scriptEvaluationError('The signature format is invalid.');
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
checkPublicKeyEncoding(buf) {
|
|
285
|
+
if (buf.length === 0) {
|
|
286
|
+
this.scriptEvaluationError('Public key is empty.');
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
if (buf.length < 33) {
|
|
290
|
+
this.scriptEvaluationError('The public key is too short, it must be at least 33 bytes.');
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
if (buf[0] === 0x04) {
|
|
294
|
+
if (buf.length !== 65) {
|
|
295
|
+
this.scriptEvaluationError('The non-compressed public key must be 65 bytes.');
|
|
296
|
+
return false;
|
|
331
297
|
}
|
|
332
|
-
|
|
333
|
-
|
|
298
|
+
}
|
|
299
|
+
else if (buf[0] === 0x02 || buf[0] === 0x03) {
|
|
300
|
+
if (buf.length !== 33) {
|
|
301
|
+
this.scriptEvaluationError('The compressed public key must be 33 bytes.');
|
|
302
|
+
return false;
|
|
334
303
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
this.scriptEvaluationError('The public key is in an unknown format.');
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
PublicKey.fromDER(buf); // This can throw for stricter DER rules
|
|
311
|
+
}
|
|
312
|
+
catch (e) {
|
|
313
|
+
this.scriptEvaluationError('The public key is in an unknown format.');
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
verifySignature(sig, pubkey, subscript) {
|
|
319
|
+
const preimage = TransactionSignature.format({
|
|
320
|
+
sourceTXID: this.sourceTXID,
|
|
321
|
+
sourceOutputIndex: this.sourceOutputIndex,
|
|
322
|
+
sourceSatoshis: this.sourceSatoshis,
|
|
323
|
+
transactionVersion: this.transactionVersion,
|
|
324
|
+
otherInputs: this.otherInputs,
|
|
325
|
+
outputs: this.outputs,
|
|
326
|
+
inputIndex: this.inputIndex,
|
|
327
|
+
subscript,
|
|
328
|
+
inputSequence: this.inputSequence,
|
|
329
|
+
lockTime: this.lockTime,
|
|
330
|
+
scope: sig.scope
|
|
331
|
+
});
|
|
332
|
+
const hash = new BigNumber(Hash.hash256(preimage));
|
|
333
|
+
return verify(hash, sig, pubkey);
|
|
334
|
+
}
|
|
335
|
+
step() {
|
|
336
|
+
if (this.stackMem > this.memoryLimit) {
|
|
337
|
+
this.scriptEvaluationError('Stack memory usage has exceeded ' + String(this.memoryLimit) + ' bytes');
|
|
338
|
+
return false; // Error thrown
|
|
339
|
+
}
|
|
340
|
+
if (this.altStackMem > this.memoryLimit) {
|
|
341
|
+
this.scriptEvaluationError('Alt stack memory usage has exceeded ' + String(this.memoryLimit) + ' bytes');
|
|
342
|
+
return false; // Error thrown
|
|
343
|
+
}
|
|
344
|
+
if (this.context === 'UnlockingScript' &&
|
|
345
|
+
this.programCounter >= this.unlockingScript.chunks.length) {
|
|
346
|
+
this.context = 'LockingScript';
|
|
347
|
+
this.programCounter = 0;
|
|
348
|
+
}
|
|
349
|
+
const currentScript = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript;
|
|
350
|
+
if (this.programCounter >= currentScript.chunks.length) {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
const operation = currentScript.chunks[this.programCounter];
|
|
359
354
|
const currentOpcode = operation.op;
|
|
360
355
|
if (typeof currentOpcode === 'undefined') {
|
|
361
|
-
this.scriptEvaluationError(`
|
|
356
|
+
this.scriptEvaluationError(`Missing opcode in ${this.context} at pc=${this.programCounter}.`); // Error thrown
|
|
362
357
|
}
|
|
363
|
-
if (Array.isArray(operation.data) &&
|
|
364
|
-
|
|
365
|
-
this.scriptEvaluationError(`It's not currently possible to push data larger than ${maxScriptElementSize} bytes.`);
|
|
358
|
+
if (Array.isArray(operation.data) && operation.data.length > maxScriptElementSize) {
|
|
359
|
+
this.scriptEvaluationError(`Data push > ${maxScriptElementSize} bytes (pc=${this.programCounter}).`); // Error thrown
|
|
366
360
|
}
|
|
367
|
-
|
|
368
|
-
|
|
361
|
+
const isScriptExecuting = !this.ifStack.includes(false);
|
|
362
|
+
if (isScriptExecuting && isOpcodeDisabledHelper(currentOpcode)) {
|
|
363
|
+
this.scriptEvaluationError(`This opcode is currently disabled. (Opcode: ${OP[currentOpcode]}, PC: ${this.programCounter})`); // Error thrown
|
|
369
364
|
}
|
|
370
|
-
if (isScriptExecuting &&
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
if (requireMinimalPush && !isChunkMinimal(operation)) {
|
|
374
|
-
this.scriptEvaluationError('This data is not minimally-encoded.');
|
|
375
|
-
}
|
|
376
|
-
if (!Array.isArray(operation.data)) {
|
|
377
|
-
this.stack.push([]);
|
|
378
|
-
this.stackMem += 0;
|
|
379
|
-
}
|
|
380
|
-
else {
|
|
381
|
-
this.stack.push(operation.data);
|
|
382
|
-
this.stackMem += operation.data.length;
|
|
365
|
+
if (isScriptExecuting && currentOpcode >= 0 && currentOpcode <= OP.OP_PUSHDATA4) {
|
|
366
|
+
if (requireMinimalPush && !isChunkMinimalPushHelper(operation)) {
|
|
367
|
+
this.scriptEvaluationError(`This data is not minimally-encoded. (PC: ${this.programCounter})`); // Error thrown
|
|
383
368
|
}
|
|
369
|
+
this.pushStack(Array.isArray(operation.data) ? operation.data : []);
|
|
384
370
|
}
|
|
385
|
-
else if (isScriptExecuting ||
|
|
386
|
-
|
|
371
|
+
else if (isScriptExecuting || (currentOpcode >= OP.OP_IF && currentOpcode <= OP.OP_ENDIF)) {
|
|
372
|
+
let buf, buf1, buf2, buf3;
|
|
373
|
+
let x1, x2, x3;
|
|
374
|
+
let bn, bn1, bn2, bn3;
|
|
375
|
+
let n, size, fValue, fSuccess, subscript;
|
|
376
|
+
let bufSig, bufPubkey;
|
|
377
|
+
let sig, pubkey;
|
|
378
|
+
let i, ikey, isig, nKeysCount, nSigsCount, fOk;
|
|
387
379
|
switch (currentOpcode) {
|
|
388
380
|
case OP.OP_1NEGATE:
|
|
381
|
+
this.pushStackCopy(SCRIPTNUM_NEG_1);
|
|
382
|
+
break;
|
|
383
|
+
case OP.OP_0:
|
|
384
|
+
this.pushStackCopy(SCRIPTNUMS_0_TO_16[0]);
|
|
385
|
+
break;
|
|
389
386
|
case OP.OP_1:
|
|
390
387
|
case OP.OP_2:
|
|
391
388
|
case OP.OP_3:
|
|
@@ -403,13 +400,11 @@ export default class Spend {
|
|
|
403
400
|
case OP.OP_15:
|
|
404
401
|
case OP.OP_16:
|
|
405
402
|
n = currentOpcode - (OP.OP_1 - 1);
|
|
406
|
-
|
|
407
|
-
this.stack.push(buf);
|
|
408
|
-
this.stackMem += buf.length;
|
|
403
|
+
this.pushStackCopy(SCRIPTNUMS_0_TO_16[n]);
|
|
409
404
|
break;
|
|
410
405
|
case OP.OP_NOP:
|
|
411
|
-
case OP.OP_NOP2:
|
|
412
|
-
case OP.OP_NOP3:
|
|
406
|
+
case OP.OP_NOP2: // Formerly CHECKLOCKTIMEVERIFY
|
|
407
|
+
case OP.OP_NOP3: // Formerly CHECKSEQUENCEVERIFY
|
|
413
408
|
case OP.OP_NOP1:
|
|
414
409
|
case OP.OP_NOP4:
|
|
415
410
|
case OP.OP_NOP5:
|
|
@@ -418,6 +413,9 @@ export default class Spend {
|
|
|
418
413
|
case OP.OP_NOP8:
|
|
419
414
|
case OP.OP_NOP9:
|
|
420
415
|
case OP.OP_NOP10:
|
|
416
|
+
/* falls through */
|
|
417
|
+
// eslint-disable-next-line no-fallthrough
|
|
418
|
+
// eslint-disable-next-line no-fallthrough
|
|
421
419
|
case OP.OP_NOP11:
|
|
422
420
|
case OP.OP_NOP12:
|
|
423
421
|
case OP.OP_NOP13:
|
|
@@ -487,377 +485,267 @@ export default class Spend {
|
|
|
487
485
|
case OP.OP_NOTIF:
|
|
488
486
|
fValue = false;
|
|
489
487
|
if (isScriptExecuting) {
|
|
490
|
-
if (this.stack.length < 1)
|
|
488
|
+
if (this.stack.length < 1)
|
|
491
489
|
this.scriptEvaluationError('OP_IF and OP_NOTIF require at least one item on the stack when they are used!');
|
|
492
|
-
|
|
493
|
-
buf = this.stacktop(-1);
|
|
490
|
+
buf = this.popStack();
|
|
494
491
|
fValue = this.castToBool(buf);
|
|
495
|
-
if (currentOpcode === OP.OP_NOTIF)
|
|
492
|
+
if (currentOpcode === OP.OP_NOTIF)
|
|
496
493
|
fValue = !fValue;
|
|
497
|
-
}
|
|
498
|
-
poppedValue = this.stack.pop();
|
|
499
|
-
if (poppedValue != null) {
|
|
500
|
-
this.stackMem -= poppedValue.length;
|
|
501
|
-
}
|
|
502
|
-
clearPoppedValue();
|
|
503
494
|
}
|
|
504
495
|
this.ifStack.push(fValue);
|
|
505
496
|
break;
|
|
506
497
|
case OP.OP_ELSE:
|
|
507
|
-
if (this.ifStack.length === 0)
|
|
498
|
+
if (this.ifStack.length === 0)
|
|
508
499
|
this.scriptEvaluationError('OP_ELSE requires a preceeding OP_IF.');
|
|
509
|
-
|
|
510
|
-
this.ifStack[this.ifStack.length - 1] =
|
|
511
|
-
!this.ifStack[this.ifStack.length - 1];
|
|
500
|
+
this.ifStack[this.ifStack.length - 1] = !this.ifStack[this.ifStack.length - 1];
|
|
512
501
|
break;
|
|
513
502
|
case OP.OP_ENDIF:
|
|
514
|
-
if (this.ifStack.length === 0)
|
|
503
|
+
if (this.ifStack.length === 0)
|
|
515
504
|
this.scriptEvaluationError('OP_ENDIF requires a preceeding OP_IF.');
|
|
516
|
-
}
|
|
517
505
|
this.ifStack.pop();
|
|
518
506
|
break;
|
|
519
507
|
case OP.OP_VERIFY:
|
|
520
|
-
if (this.stack.length < 1)
|
|
508
|
+
if (this.stack.length < 1)
|
|
521
509
|
this.scriptEvaluationError('OP_VERIFY requires at least one item to be on the stack.');
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
poppedValue = this.stack.pop();
|
|
526
|
-
if (poppedValue != null) {
|
|
527
|
-
this.stackMem -= poppedValue.length;
|
|
528
|
-
}
|
|
529
|
-
clearPoppedValue();
|
|
530
|
-
if (!fValue) {
|
|
510
|
+
buf1 = this.stackTop();
|
|
511
|
+
fValue = this.castToBool(buf1);
|
|
512
|
+
if (!fValue)
|
|
531
513
|
this.scriptEvaluationError('OP_VERIFY requires the top stack value to be truthy.');
|
|
532
|
-
|
|
514
|
+
this.popStack();
|
|
533
515
|
break;
|
|
534
516
|
case OP.OP_RETURN:
|
|
535
|
-
if (this.context === 'UnlockingScript')
|
|
517
|
+
if (this.context === 'UnlockingScript')
|
|
536
518
|
this.programCounter = this.unlockingScript.chunks.length;
|
|
537
|
-
|
|
538
|
-
else {
|
|
519
|
+
else
|
|
539
520
|
this.programCounter = this.lockingScript.chunks.length;
|
|
540
|
-
}
|
|
541
521
|
this.ifStack = [];
|
|
522
|
+
this.programCounter--; // To counteract the final increment and ensure loop termination
|
|
542
523
|
break;
|
|
543
524
|
case OP.OP_TOALTSTACK:
|
|
544
|
-
if (this.stack.length < 1)
|
|
525
|
+
if (this.stack.length < 1)
|
|
545
526
|
this.scriptEvaluationError('OP_TOALTSTACK requires at oeast one item to be on the stack.');
|
|
546
|
-
|
|
547
|
-
poppedValue = this.stack.pop();
|
|
548
|
-
if (poppedValue != null) {
|
|
549
|
-
this.altStack.push(poppedValue);
|
|
550
|
-
this.altStackMem += poppedValue.length;
|
|
551
|
-
this.stackMem -= poppedValue.length;
|
|
552
|
-
}
|
|
553
|
-
clearPoppedValue();
|
|
527
|
+
this.pushAltStack(this.popStack());
|
|
554
528
|
break;
|
|
555
529
|
case OP.OP_FROMALTSTACK:
|
|
556
|
-
if (this.altStack.length < 1)
|
|
557
|
-
this.scriptEvaluationError('OP_FROMALTSTACK requires at least one item to be on the stack.');
|
|
558
|
-
|
|
559
|
-
poppedValue = this.altStack.pop();
|
|
560
|
-
if (poppedValue != null) {
|
|
561
|
-
this.stack.push(poppedValue);
|
|
562
|
-
this.stackMem += poppedValue.length;
|
|
563
|
-
this.altStackMem -= poppedValue.length;
|
|
564
|
-
}
|
|
565
|
-
clearPoppedValue();
|
|
530
|
+
if (this.altStack.length < 1)
|
|
531
|
+
this.scriptEvaluationError('OP_FROMALTSTACK requires at least one item to be on the stack.'); // "stack" here means altstack
|
|
532
|
+
this.pushStack(this.popAltStack());
|
|
566
533
|
break;
|
|
567
534
|
case OP.OP_2DROP:
|
|
568
|
-
if (this.stack.length < 2)
|
|
535
|
+
if (this.stack.length < 2)
|
|
569
536
|
this.scriptEvaluationError('OP_2DROP requires at least two items to be on the stack.');
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
if (poppedValue != null) {
|
|
573
|
-
this.stackMem -= poppedValue.length;
|
|
574
|
-
}
|
|
575
|
-
clearPoppedValue();
|
|
576
|
-
poppedValue = this.stack.pop();
|
|
577
|
-
if (poppedValue != null) {
|
|
578
|
-
this.stackMem -= poppedValue.length;
|
|
579
|
-
}
|
|
580
|
-
clearPoppedValue();
|
|
537
|
+
this.popStack();
|
|
538
|
+
this.popStack();
|
|
581
539
|
break;
|
|
582
540
|
case OP.OP_2DUP:
|
|
583
|
-
if (this.stack.length < 2)
|
|
541
|
+
if (this.stack.length < 2)
|
|
584
542
|
this.scriptEvaluationError('OP_2DUP requires at least two items to be on the stack.');
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
this.
|
|
589
|
-
this.stackMem += buf1.length;
|
|
590
|
-
this.stack.push([...buf2]);
|
|
591
|
-
this.stackMem += buf2.length;
|
|
543
|
+
buf1 = this.stackTop(-2);
|
|
544
|
+
buf2 = this.stackTop(-1);
|
|
545
|
+
this.pushStackCopy(buf1);
|
|
546
|
+
this.pushStackCopy(buf2);
|
|
592
547
|
break;
|
|
593
548
|
case OP.OP_3DUP:
|
|
594
|
-
if (this.stack.length < 3)
|
|
549
|
+
if (this.stack.length < 3)
|
|
595
550
|
this.scriptEvaluationError('OP_3DUP requires at least three items to be on the stack.');
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
this.
|
|
601
|
-
this.
|
|
602
|
-
this.stack.push([...buf2]);
|
|
603
|
-
this.stackMem += buf2.length;
|
|
604
|
-
this.stack.push([...buf3]);
|
|
605
|
-
this.stackMem += buf3.length;
|
|
551
|
+
buf1 = this.stackTop(-3);
|
|
552
|
+
buf2 = this.stackTop(-2);
|
|
553
|
+
buf3 = this.stackTop(-1);
|
|
554
|
+
this.pushStackCopy(buf1);
|
|
555
|
+
this.pushStackCopy(buf2);
|
|
556
|
+
this.pushStackCopy(buf3);
|
|
606
557
|
break;
|
|
607
558
|
case OP.OP_2OVER:
|
|
608
|
-
if (this.stack.length < 4)
|
|
559
|
+
if (this.stack.length < 4)
|
|
609
560
|
this.scriptEvaluationError('OP_2OVER requires at least four items to be on the stack.');
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
this.
|
|
614
|
-
this.stackMem += buf1.length;
|
|
615
|
-
this.stack.push([...buf2]);
|
|
616
|
-
this.stackMem += buf2.length;
|
|
561
|
+
buf1 = this.stackTop(-4);
|
|
562
|
+
buf2 = this.stackTop(-3);
|
|
563
|
+
this.pushStackCopy(buf1);
|
|
564
|
+
this.pushStackCopy(buf2);
|
|
617
565
|
break;
|
|
618
|
-
case OP.OP_2ROT:
|
|
619
|
-
if (this.stack.length < 6)
|
|
566
|
+
case OP.OP_2ROT: {
|
|
567
|
+
if (this.stack.length < 6)
|
|
620
568
|
this.scriptEvaluationError('OP_2ROT requires at least six items to be on the stack.');
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
this.
|
|
624
|
-
|
|
625
|
-
this.
|
|
626
|
-
|
|
569
|
+
const rot6 = this.popStack();
|
|
570
|
+
const rot5 = this.popStack();
|
|
571
|
+
const rot4 = this.popStack();
|
|
572
|
+
const rot3 = this.popStack();
|
|
573
|
+
const rot2 = this.popStack();
|
|
574
|
+
const rot1 = this.popStack();
|
|
575
|
+
this.pushStack(rot3);
|
|
576
|
+
this.pushStack(rot4);
|
|
577
|
+
this.pushStack(rot5);
|
|
578
|
+
this.pushStack(rot6);
|
|
579
|
+
this.pushStack(rot1);
|
|
580
|
+
this.pushStack(rot2);
|
|
627
581
|
break;
|
|
628
|
-
|
|
629
|
-
|
|
582
|
+
}
|
|
583
|
+
case OP.OP_2SWAP: {
|
|
584
|
+
if (this.stack.length < 4)
|
|
630
585
|
this.scriptEvaluationError('OP_2SWAP requires at least four items to be on the stack.');
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
this.
|
|
634
|
-
|
|
635
|
-
this.
|
|
636
|
-
this.
|
|
586
|
+
const swap4 = this.popStack();
|
|
587
|
+
const swap3 = this.popStack();
|
|
588
|
+
const swap2 = this.popStack();
|
|
589
|
+
const swap1 = this.popStack();
|
|
590
|
+
this.pushStack(swap3);
|
|
591
|
+
this.pushStack(swap4);
|
|
592
|
+
this.pushStack(swap1);
|
|
593
|
+
this.pushStack(swap2);
|
|
637
594
|
break;
|
|
595
|
+
}
|
|
638
596
|
case OP.OP_IFDUP:
|
|
639
|
-
if (this.stack.length < 1)
|
|
597
|
+
if (this.stack.length < 1)
|
|
640
598
|
this.scriptEvaluationError('OP_IFDUP requires at least one item to be on the stack.');
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
if (fValue) {
|
|
645
|
-
this.stack.push([...buf]);
|
|
646
|
-
this.stackMem += buf.length;
|
|
599
|
+
buf1 = this.stackTop();
|
|
600
|
+
if (this.castToBool(buf1)) {
|
|
601
|
+
this.pushStackCopy(buf1);
|
|
647
602
|
}
|
|
648
603
|
break;
|
|
649
604
|
case OP.OP_DEPTH:
|
|
650
|
-
|
|
651
|
-
this.stack.push(buf);
|
|
652
|
-
this.stackMem += buf.length;
|
|
605
|
+
this.pushStack(new BigNumber(this.stack.length).toScriptNum());
|
|
653
606
|
break;
|
|
654
607
|
case OP.OP_DROP:
|
|
655
|
-
if (this.stack.length < 1)
|
|
608
|
+
if (this.stack.length < 1)
|
|
656
609
|
this.scriptEvaluationError('OP_DROP requires at least one item to be on the stack.');
|
|
657
|
-
|
|
658
|
-
poppedValue = this.stack.pop();
|
|
659
|
-
if (poppedValue != null) {
|
|
660
|
-
this.stackMem -= poppedValue.length;
|
|
661
|
-
}
|
|
662
|
-
clearPoppedValue();
|
|
610
|
+
this.popStack();
|
|
663
611
|
break;
|
|
664
612
|
case OP.OP_DUP:
|
|
665
|
-
if (this.stack.length < 1)
|
|
613
|
+
if (this.stack.length < 1)
|
|
666
614
|
this.scriptEvaluationError('OP_DUP requires at least one item to be on the stack.');
|
|
667
|
-
|
|
668
|
-
this.stack.push([...this.stacktop(-1)]);
|
|
669
|
-
this.stackMem += this.stacktop(-1).length;
|
|
615
|
+
this.pushStackCopy(this.stackTop());
|
|
670
616
|
break;
|
|
671
617
|
case OP.OP_NIP:
|
|
672
|
-
if (this.stack.length < 2)
|
|
618
|
+
if (this.stack.length < 2)
|
|
673
619
|
this.scriptEvaluationError('OP_NIP requires at least two items to be on the stack.');
|
|
674
|
-
|
|
675
|
-
this.
|
|
676
|
-
this.
|
|
620
|
+
buf2 = this.popStack();
|
|
621
|
+
this.popStack();
|
|
622
|
+
this.pushStack(buf2);
|
|
677
623
|
break;
|
|
678
624
|
case OP.OP_OVER:
|
|
679
|
-
if (this.stack.length < 2)
|
|
625
|
+
if (this.stack.length < 2)
|
|
680
626
|
this.scriptEvaluationError('OP_OVER requires at least two items to be on the stack.');
|
|
681
|
-
|
|
682
|
-
this.stack.push([...this.stacktop(-2)]);
|
|
683
|
-
this.stackMem += this.stacktop(-2).length;
|
|
627
|
+
this.pushStackCopy(this.stackTop(-2));
|
|
684
628
|
break;
|
|
685
629
|
case OP.OP_PICK:
|
|
686
|
-
case OP.OP_ROLL:
|
|
687
|
-
if (this.stack.length < 2)
|
|
630
|
+
case OP.OP_ROLL: {
|
|
631
|
+
if (this.stack.length < 2)
|
|
688
632
|
this.scriptEvaluationError(`${OP[currentOpcode]} requires at least two items to be on the stack.`);
|
|
689
|
-
|
|
690
|
-
buf = this.stacktop(-1);
|
|
691
|
-
bn = BigNumber.fromScriptNum(buf, requireMinimalPush);
|
|
633
|
+
bn = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush);
|
|
692
634
|
n = bn.toNumber();
|
|
693
|
-
poppedValue = this.stack.pop();
|
|
694
|
-
if (poppedValue != null) {
|
|
695
|
-
this.stackMem -= poppedValue.length;
|
|
696
|
-
}
|
|
697
|
-
clearPoppedValue();
|
|
698
635
|
if (n < 0 || n >= this.stack.length) {
|
|
699
636
|
this.scriptEvaluationError(`${OP[currentOpcode]} requires the top stack element to be 0 or a positive number less than the current size of the stack.`);
|
|
700
637
|
}
|
|
701
|
-
|
|
638
|
+
const itemToMoveOrCopy = this.stack[this.stack.length - 1 - n];
|
|
702
639
|
if (currentOpcode === OP.OP_ROLL) {
|
|
703
|
-
this.stack.splice(this.stack.length -
|
|
704
|
-
this.stackMem -=
|
|
640
|
+
this.stack.splice(this.stack.length - 1 - n, 1);
|
|
641
|
+
this.stackMem -= itemToMoveOrCopy.length;
|
|
642
|
+
this.pushStack(itemToMoveOrCopy);
|
|
643
|
+
}
|
|
644
|
+
else { // OP_PICK
|
|
645
|
+
this.pushStackCopy(itemToMoveOrCopy);
|
|
705
646
|
}
|
|
706
|
-
this.stack.push([...buf]);
|
|
707
|
-
this.stackMem += buf.length;
|
|
708
647
|
break;
|
|
648
|
+
}
|
|
709
649
|
case OP.OP_ROT:
|
|
710
|
-
if (this.stack.length < 3)
|
|
650
|
+
if (this.stack.length < 3)
|
|
711
651
|
this.scriptEvaluationError('OP_ROT requires at least three items to be on the stack.');
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
this.
|
|
717
|
-
this.
|
|
718
|
-
this.stack[this.stack.length - 1] = x1;
|
|
652
|
+
x3 = this.popStack();
|
|
653
|
+
x2 = this.popStack();
|
|
654
|
+
x1 = this.popStack();
|
|
655
|
+
this.pushStack(x2);
|
|
656
|
+
this.pushStack(x3);
|
|
657
|
+
this.pushStack(x1);
|
|
719
658
|
break;
|
|
720
659
|
case OP.OP_SWAP:
|
|
721
|
-
if (this.stack.length < 2)
|
|
660
|
+
if (this.stack.length < 2)
|
|
722
661
|
this.scriptEvaluationError('OP_SWAP requires at least two items to be on the stack.');
|
|
723
|
-
|
|
724
|
-
x1 = this.
|
|
725
|
-
|
|
726
|
-
this.
|
|
727
|
-
this.stack[this.stack.length - 1] = x1;
|
|
662
|
+
x2 = this.popStack();
|
|
663
|
+
x1 = this.popStack();
|
|
664
|
+
this.pushStack(x2);
|
|
665
|
+
this.pushStack(x1);
|
|
728
666
|
break;
|
|
729
667
|
case OP.OP_TUCK:
|
|
730
|
-
if (this.stack.length < 2)
|
|
668
|
+
if (this.stack.length < 2)
|
|
731
669
|
this.scriptEvaluationError('OP_TUCK requires at least two items to be on the stack.');
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
670
|
+
buf1 = this.stackTop(-1); // Top element (x2)
|
|
671
|
+
// stack is [... rest, x1, x2]
|
|
672
|
+
// We want [... rest, x2_copy, x1, x2]
|
|
673
|
+
this.ensureStackMem(buf1.length);
|
|
674
|
+
this.stack.splice(this.stack.length - 2, 0, buf1.slice()); // Insert copy of x2 before x1
|
|
675
|
+
this.stackMem += buf1.length; // Account for the new copy
|
|
735
676
|
break;
|
|
736
677
|
case OP.OP_SIZE:
|
|
737
|
-
if (this.stack.length < 1)
|
|
678
|
+
if (this.stack.length < 1)
|
|
738
679
|
this.scriptEvaluationError('OP_SIZE requires at least one item to be on the stack.');
|
|
739
|
-
|
|
740
|
-
bn = new BigNumber(this.stacktop(-1).length);
|
|
741
|
-
this.stack.push(bn.toScriptNum());
|
|
742
|
-
this.stackMem += bn.toScriptNum().length;
|
|
680
|
+
this.pushStack(new BigNumber(this.stackTop().length).toScriptNum());
|
|
743
681
|
break;
|
|
744
682
|
case OP.OP_AND:
|
|
745
683
|
case OP.OP_OR:
|
|
746
|
-
case OP.OP_XOR:
|
|
747
|
-
if (this.stack.length < 2)
|
|
748
|
-
this.scriptEvaluationError(`${OP[currentOpcode]} requires at least
|
|
749
|
-
|
|
750
|
-
buf1 = this.
|
|
751
|
-
|
|
752
|
-
if (buf1.length !== buf2.length) {
|
|
684
|
+
case OP.OP_XOR: {
|
|
685
|
+
if (this.stack.length < 2)
|
|
686
|
+
this.scriptEvaluationError(`${OP[currentOpcode]} requires at least two items on the stack.`);
|
|
687
|
+
buf2 = this.popStack();
|
|
688
|
+
buf1 = this.popStack();
|
|
689
|
+
if (buf1.length !== buf2.length)
|
|
753
690
|
this.scriptEvaluationError(`${OP[currentOpcode]} requires the top two stack items to be the same size.`);
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
}
|
|
765
|
-
break;
|
|
766
|
-
case OP.OP_XOR:
|
|
767
|
-
for (let i = 0; i < buf1.length; i++) {
|
|
768
|
-
buf1[i] ^= buf2[i];
|
|
769
|
-
}
|
|
770
|
-
break;
|
|
771
|
-
}
|
|
772
|
-
// And pop vch2.
|
|
773
|
-
poppedValue = this.stack.pop();
|
|
774
|
-
if (poppedValue != null) {
|
|
775
|
-
this.stackMem -= poppedValue.length;
|
|
776
|
-
}
|
|
777
|
-
clearPoppedValue();
|
|
691
|
+
const resultBufBitwiseOp = new Array(buf1.length);
|
|
692
|
+
for (let k = 0; k < buf1.length; k++) {
|
|
693
|
+
if (currentOpcode === OP.OP_AND)
|
|
694
|
+
resultBufBitwiseOp[k] = buf1[k] & buf2[k];
|
|
695
|
+
else if (currentOpcode === OP.OP_OR)
|
|
696
|
+
resultBufBitwiseOp[k] = buf1[k] | buf2[k];
|
|
697
|
+
else
|
|
698
|
+
resultBufBitwiseOp[k] = buf1[k] ^ buf2[k];
|
|
699
|
+
}
|
|
700
|
+
this.pushStack(resultBufBitwiseOp);
|
|
778
701
|
break;
|
|
779
|
-
|
|
780
|
-
|
|
702
|
+
}
|
|
703
|
+
case OP.OP_INVERT: {
|
|
704
|
+
if (this.stack.length < 1)
|
|
781
705
|
this.scriptEvaluationError('OP_INVERT requires at least one item to be on the stack.');
|
|
706
|
+
buf = this.popStack();
|
|
707
|
+
const invertedBufOp = new Array(buf.length);
|
|
708
|
+
for (let k = 0; k < buf.length; k++) {
|
|
709
|
+
invertedBufOp[k] = (~buf[k]) & 0xff;
|
|
782
710
|
}
|
|
783
|
-
|
|
784
|
-
for (let i = 0; i < buf.length; i++) {
|
|
785
|
-
buf[i] = ~buf[i];
|
|
786
|
-
}
|
|
711
|
+
this.pushStack(invertedBufOp);
|
|
787
712
|
break;
|
|
713
|
+
}
|
|
788
714
|
case OP.OP_LSHIFT:
|
|
789
|
-
case OP.OP_RSHIFT:
|
|
790
|
-
if (this.stack.length < 2)
|
|
715
|
+
case OP.OP_RSHIFT: {
|
|
716
|
+
if (this.stack.length < 2)
|
|
791
717
|
this.scriptEvaluationError(`${OP[currentOpcode]} requires at least two items to be on the stack.`);
|
|
792
|
-
|
|
793
|
-
buf1 = this.
|
|
718
|
+
bn2 = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush); // n (shift amount)
|
|
719
|
+
buf1 = this.popStack(); // value to shift
|
|
720
|
+
n = bn2.toNumber();
|
|
721
|
+
if (n < 0)
|
|
722
|
+
this.scriptEvaluationError(`${OP[currentOpcode]} requires the top item on the stack not to be negative.`);
|
|
794
723
|
if (buf1.length === 0) {
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
this.stackMem -= poppedValue.length;
|
|
798
|
-
}
|
|
799
|
-
clearPoppedValue();
|
|
800
|
-
}
|
|
801
|
-
else {
|
|
802
|
-
bn1 = new BigNumber(buf1);
|
|
803
|
-
bn2 = BigNumber.fromScriptNum(this.stacktop(-1), requireMinimalPush);
|
|
804
|
-
n = bn2.toNumber();
|
|
805
|
-
if (n < 0) {
|
|
806
|
-
this.scriptEvaluationError(`${OP[currentOpcode]} requires the top item on the stack not to be negative.`);
|
|
807
|
-
}
|
|
808
|
-
poppedValue = this.stack.pop();
|
|
809
|
-
if (poppedValue != null) {
|
|
810
|
-
this.stackMem -= poppedValue.length;
|
|
811
|
-
}
|
|
812
|
-
clearPoppedValue();
|
|
813
|
-
poppedValue = this.stack.pop();
|
|
814
|
-
if (poppedValue != null) {
|
|
815
|
-
this.stackMem -= poppedValue.length;
|
|
816
|
-
}
|
|
817
|
-
clearPoppedValue();
|
|
818
|
-
let shifted;
|
|
819
|
-
if (currentOpcode === OP.OP_LSHIFT) {
|
|
820
|
-
shifted = bn1.ushln(n);
|
|
821
|
-
}
|
|
822
|
-
if (currentOpcode === OP.OP_RSHIFT) {
|
|
823
|
-
shifted = bn1.ushrn(n);
|
|
824
|
-
}
|
|
825
|
-
const bufShifted = padDataToSize(shifted.toArray().slice(buf1.length * -1), buf1.length);
|
|
826
|
-
this.stack.push(bufShifted);
|
|
827
|
-
this.stackMem += bufShifted.length;
|
|
724
|
+
this.pushStack([]);
|
|
725
|
+
break;
|
|
828
726
|
}
|
|
727
|
+
bn1 = new BigNumber(buf1);
|
|
728
|
+
let shiftedBn;
|
|
729
|
+
if (currentOpcode === OP.OP_LSHIFT)
|
|
730
|
+
shiftedBn = bn1.ushln(n);
|
|
731
|
+
else
|
|
732
|
+
shiftedBn = bn1.ushrn(n);
|
|
733
|
+
const shiftedArr = shiftedBn.toArray('le', buf1.length);
|
|
734
|
+
this.pushStack(shiftedArr);
|
|
829
735
|
break;
|
|
736
|
+
}
|
|
830
737
|
case OP.OP_EQUAL:
|
|
831
738
|
case OP.OP_EQUALVERIFY:
|
|
832
|
-
if (this.stack.length < 2)
|
|
739
|
+
if (this.stack.length < 2)
|
|
833
740
|
this.scriptEvaluationError(`${OP[currentOpcode]} requires at least two items to be on the stack.`);
|
|
834
|
-
|
|
835
|
-
buf1 = this.
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
poppedValue = this.stack.pop();
|
|
839
|
-
if (poppedValue != null) {
|
|
840
|
-
this.stackMem -= poppedValue.length;
|
|
841
|
-
}
|
|
842
|
-
clearPoppedValue();
|
|
843
|
-
poppedValue = this.stack.pop();
|
|
844
|
-
if (poppedValue != null) {
|
|
845
|
-
this.stackMem -= poppedValue.length;
|
|
846
|
-
}
|
|
847
|
-
clearPoppedValue();
|
|
848
|
-
this.stack.push(fEqual ? [1] : []);
|
|
849
|
-
this.stackMem += (fEqual ? 1 : 0);
|
|
741
|
+
buf2 = this.popStack();
|
|
742
|
+
buf1 = this.popStack();
|
|
743
|
+
fValue = compareNumberArrays(buf1, buf2);
|
|
744
|
+
this.pushStack(fValue ? [1] : []);
|
|
850
745
|
if (currentOpcode === OP.OP_EQUALVERIFY) {
|
|
851
|
-
if (
|
|
852
|
-
poppedValue = this.stack.pop();
|
|
853
|
-
if (poppedValue != null) {
|
|
854
|
-
this.stackMem -= poppedValue.length;
|
|
855
|
-
}
|
|
856
|
-
clearPoppedValue();
|
|
857
|
-
}
|
|
858
|
-
else {
|
|
746
|
+
if (!fValue)
|
|
859
747
|
this.scriptEvaluationError('OP_EQUALVERIFY requires the top two stack items to be equal.');
|
|
860
|
-
|
|
748
|
+
this.popStack();
|
|
861
749
|
}
|
|
862
750
|
break;
|
|
863
751
|
case OP.OP_1ADD:
|
|
@@ -866,46 +754,37 @@ export default class Spend {
|
|
|
866
754
|
case OP.OP_ABS:
|
|
867
755
|
case OP.OP_NOT:
|
|
868
756
|
case OP.OP_0NOTEQUAL:
|
|
869
|
-
if (this.stack.length < 1)
|
|
870
|
-
this.scriptEvaluationError(`${OP[currentOpcode]} requires at least one
|
|
871
|
-
|
|
872
|
-
buf = this.stacktop(-1);
|
|
873
|
-
bn = BigNumber.fromScriptNum(buf, requireMinimalPush);
|
|
757
|
+
if (this.stack.length < 1)
|
|
758
|
+
this.scriptEvaluationError(`${OP[currentOpcode]} requires at least one item to be on the stack.`);
|
|
759
|
+
bn = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush);
|
|
874
760
|
switch (currentOpcode) {
|
|
875
761
|
case OP.OP_1ADD:
|
|
876
|
-
bn = bn.
|
|
762
|
+
bn = bn.add(new BigNumber(1));
|
|
877
763
|
break;
|
|
878
764
|
case OP.OP_1SUB:
|
|
879
|
-
bn = bn.
|
|
765
|
+
bn = bn.sub(new BigNumber(1));
|
|
880
766
|
break;
|
|
881
767
|
case OP.OP_NEGATE:
|
|
882
768
|
bn = bn.neg();
|
|
883
769
|
break;
|
|
884
770
|
case OP.OP_ABS:
|
|
885
|
-
if (bn.
|
|
771
|
+
if (bn.isNeg())
|
|
886
772
|
bn = bn.neg();
|
|
887
|
-
}
|
|
888
773
|
break;
|
|
889
774
|
case OP.OP_NOT:
|
|
890
|
-
bn = new BigNumber(bn.cmpn(0) === 0 ? 1 : 0
|
|
775
|
+
bn = new BigNumber(bn.cmpn(0) === 0 ? 1 : 0);
|
|
891
776
|
break;
|
|
892
777
|
case OP.OP_0NOTEQUAL:
|
|
893
|
-
bn = new BigNumber(bn.cmpn(0) !== 0 ? 1 : 0
|
|
778
|
+
bn = new BigNumber(bn.cmpn(0) !== 0 ? 1 : 0);
|
|
894
779
|
break;
|
|
895
780
|
}
|
|
896
|
-
|
|
897
|
-
if (poppedValue != null) {
|
|
898
|
-
this.stackMem -= poppedValue.length;
|
|
899
|
-
}
|
|
900
|
-
clearPoppedValue();
|
|
901
|
-
this.stack.push(bn.toScriptNum());
|
|
902
|
-
this.stackMem += bn.toScriptNum().length;
|
|
781
|
+
this.pushStack(bn.toScriptNum());
|
|
903
782
|
break;
|
|
904
783
|
case OP.OP_ADD:
|
|
905
784
|
case OP.OP_SUB:
|
|
906
785
|
case OP.OP_MUL:
|
|
907
|
-
case OP.OP_MOD:
|
|
908
786
|
case OP.OP_DIV:
|
|
787
|
+
case OP.OP_MOD:
|
|
909
788
|
case OP.OP_BOOLAND:
|
|
910
789
|
case OP.OP_BOOLOR:
|
|
911
790
|
case OP.OP_NUMEQUAL:
|
|
@@ -916,276 +795,208 @@ export default class Spend {
|
|
|
916
795
|
case OP.OP_LESSTHANOREQUAL:
|
|
917
796
|
case OP.OP_GREATERTHANOREQUAL:
|
|
918
797
|
case OP.OP_MIN:
|
|
919
|
-
case OP.OP_MAX:
|
|
920
|
-
if (this.stack.length < 2)
|
|
798
|
+
case OP.OP_MAX: {
|
|
799
|
+
if (this.stack.length < 2)
|
|
921
800
|
this.scriptEvaluationError(`${OP[currentOpcode]} requires at least two items to be on the stack.`);
|
|
801
|
+
buf2 = this.popStack();
|
|
802
|
+
buf1 = this.popStack();
|
|
803
|
+
bn2 = BigNumber.fromScriptNum(buf2, requireMinimalPush);
|
|
804
|
+
bn1 = BigNumber.fromScriptNum(buf1, requireMinimalPush);
|
|
805
|
+
let predictedLen = 0;
|
|
806
|
+
switch (currentOpcode) {
|
|
807
|
+
case OP.OP_MUL:
|
|
808
|
+
predictedLen = bn1.byteLength() + bn2.byteLength();
|
|
809
|
+
break;
|
|
810
|
+
case OP.OP_ADD:
|
|
811
|
+
case OP.OP_SUB:
|
|
812
|
+
predictedLen = Math.max(bn1.byteLength(), bn2.byteLength()) + 1;
|
|
813
|
+
break;
|
|
814
|
+
default:
|
|
815
|
+
predictedLen = Math.max(bn1.byteLength(), bn2.byteLength());
|
|
922
816
|
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
bn = new BigNumber(0);
|
|
817
|
+
this.ensureStackMem(predictedLen);
|
|
818
|
+
let resultBnArithmetic = new BigNumber(0);
|
|
926
819
|
switch (currentOpcode) {
|
|
927
820
|
case OP.OP_ADD:
|
|
928
|
-
|
|
821
|
+
resultBnArithmetic = bn1.add(bn2);
|
|
929
822
|
break;
|
|
930
823
|
case OP.OP_SUB:
|
|
931
|
-
|
|
824
|
+
resultBnArithmetic = bn1.sub(bn2);
|
|
932
825
|
break;
|
|
933
826
|
case OP.OP_MUL:
|
|
934
|
-
|
|
827
|
+
resultBnArithmetic = bn1.mul(bn2);
|
|
935
828
|
break;
|
|
936
829
|
case OP.OP_DIV:
|
|
937
|
-
if (bn2.cmpn(0) === 0)
|
|
830
|
+
if (bn2.cmpn(0) === 0)
|
|
938
831
|
this.scriptEvaluationError('OP_DIV cannot divide by zero!');
|
|
939
|
-
|
|
940
|
-
bn = bn1.div(bn2);
|
|
832
|
+
resultBnArithmetic = bn1.div(bn2);
|
|
941
833
|
break;
|
|
942
834
|
case OP.OP_MOD:
|
|
943
|
-
if (bn2.cmpn(0) === 0)
|
|
835
|
+
if (bn2.cmpn(0) === 0)
|
|
944
836
|
this.scriptEvaluationError('OP_MOD cannot divide by zero!');
|
|
945
|
-
|
|
946
|
-
bn = bn1.mod(bn2);
|
|
837
|
+
resultBnArithmetic = bn1.mod(bn2);
|
|
947
838
|
break;
|
|
948
839
|
case OP.OP_BOOLAND:
|
|
949
|
-
|
|
840
|
+
resultBnArithmetic = new BigNumber((bn1.cmpn(0) !== 0 && bn2.cmpn(0) !== 0) ? 1 : 0);
|
|
950
841
|
break;
|
|
951
842
|
case OP.OP_BOOLOR:
|
|
952
|
-
|
|
843
|
+
resultBnArithmetic = new BigNumber((bn1.cmpn(0) !== 0 || bn2.cmpn(0) !== 0) ? 1 : 0);
|
|
953
844
|
break;
|
|
954
845
|
case OP.OP_NUMEQUAL:
|
|
955
|
-
|
|
846
|
+
resultBnArithmetic = new BigNumber(bn1.cmp(bn2) === 0 ? 1 : 0);
|
|
956
847
|
break;
|
|
957
848
|
case OP.OP_NUMEQUALVERIFY:
|
|
958
|
-
|
|
849
|
+
resultBnArithmetic = new BigNumber(bn1.cmp(bn2) === 0 ? 1 : 0);
|
|
959
850
|
break;
|
|
960
851
|
case OP.OP_NUMNOTEQUAL:
|
|
961
|
-
|
|
852
|
+
resultBnArithmetic = new BigNumber(bn1.cmp(bn2) !== 0 ? 1 : 0);
|
|
962
853
|
break;
|
|
963
854
|
case OP.OP_LESSTHAN:
|
|
964
|
-
|
|
855
|
+
resultBnArithmetic = new BigNumber(bn1.cmp(bn2) < 0 ? 1 : 0);
|
|
965
856
|
break;
|
|
966
857
|
case OP.OP_GREATERTHAN:
|
|
967
|
-
|
|
858
|
+
resultBnArithmetic = new BigNumber(bn1.cmp(bn2) > 0 ? 1 : 0);
|
|
968
859
|
break;
|
|
969
860
|
case OP.OP_LESSTHANOREQUAL:
|
|
970
|
-
|
|
861
|
+
resultBnArithmetic = new BigNumber(bn1.cmp(bn2) <= 0 ? 1 : 0);
|
|
971
862
|
break;
|
|
972
863
|
case OP.OP_GREATERTHANOREQUAL:
|
|
973
|
-
|
|
864
|
+
resultBnArithmetic = new BigNumber(bn1.cmp(bn2) >= 0 ? 1 : 0);
|
|
974
865
|
break;
|
|
975
866
|
case OP.OP_MIN:
|
|
976
|
-
|
|
867
|
+
resultBnArithmetic = bn1.cmp(bn2) < 0 ? bn1 : bn2;
|
|
977
868
|
break;
|
|
978
869
|
case OP.OP_MAX:
|
|
979
|
-
|
|
870
|
+
resultBnArithmetic = bn1.cmp(bn2) > 0 ? bn1 : bn2;
|
|
980
871
|
break;
|
|
981
872
|
}
|
|
982
|
-
|
|
983
|
-
if (poppedValue != null) {
|
|
984
|
-
this.stackMem -= poppedValue.length;
|
|
985
|
-
}
|
|
986
|
-
clearPoppedValue();
|
|
987
|
-
poppedValue = this.stack.pop();
|
|
988
|
-
if (poppedValue != null) {
|
|
989
|
-
this.stackMem -= poppedValue.length;
|
|
990
|
-
}
|
|
991
|
-
clearPoppedValue();
|
|
992
|
-
this.stack.push(bn.toScriptNum());
|
|
993
|
-
this.stackMem += bn.toScriptNum().length;
|
|
873
|
+
this.pushStack(resultBnArithmetic.toScriptNum());
|
|
994
874
|
if (currentOpcode === OP.OP_NUMEQUALVERIFY) {
|
|
995
|
-
if (this.castToBool(this.
|
|
996
|
-
poppedValue = this.stack.pop();
|
|
997
|
-
if (poppedValue != null) {
|
|
998
|
-
this.stackMem -= poppedValue.length;
|
|
999
|
-
}
|
|
1000
|
-
clearPoppedValue();
|
|
1001
|
-
}
|
|
1002
|
-
else {
|
|
875
|
+
if (!this.castToBool(this.stackTop()))
|
|
1003
876
|
this.scriptEvaluationError('OP_NUMEQUALVERIFY requires the top stack item to be truthy.');
|
|
1004
|
-
|
|
877
|
+
this.popStack();
|
|
1005
878
|
}
|
|
1006
879
|
break;
|
|
880
|
+
}
|
|
1007
881
|
case OP.OP_WITHIN:
|
|
1008
|
-
if (this.stack.length < 3)
|
|
882
|
+
if (this.stack.length < 3)
|
|
1009
883
|
this.scriptEvaluationError('OP_WITHIN requires at least three items to be on the stack.');
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
poppedValue = this.stack.pop();
|
|
1016
|
-
if (poppedValue != null) {
|
|
1017
|
-
this.stackMem -= poppedValue.length;
|
|
1018
|
-
}
|
|
1019
|
-
clearPoppedValue();
|
|
1020
|
-
poppedValue = this.stack.pop();
|
|
1021
|
-
if (poppedValue != null) {
|
|
1022
|
-
this.stackMem -= poppedValue.length;
|
|
1023
|
-
}
|
|
1024
|
-
clearPoppedValue();
|
|
1025
|
-
poppedValue = this.stack.pop();
|
|
1026
|
-
if (poppedValue != null) {
|
|
1027
|
-
this.stackMem -= poppedValue.length;
|
|
1028
|
-
}
|
|
1029
|
-
clearPoppedValue();
|
|
1030
|
-
this.stack.push(fValue ? [1] : []);
|
|
1031
|
-
this.stackMem += (fValue ? 1 : 0);
|
|
884
|
+
bn3 = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush); // max
|
|
885
|
+
bn2 = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush); // min
|
|
886
|
+
bn1 = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush); // x
|
|
887
|
+
fValue = bn1.cmp(bn2) >= 0 && bn1.cmp(bn3) < 0;
|
|
888
|
+
this.pushStack(fValue ? [1] : []);
|
|
1032
889
|
break;
|
|
1033
890
|
case OP.OP_RIPEMD160:
|
|
1034
891
|
case OP.OP_SHA1:
|
|
1035
892
|
case OP.OP_SHA256:
|
|
1036
893
|
case OP.OP_HASH160:
|
|
1037
894
|
case OP.OP_HASH256: {
|
|
1038
|
-
if (this.stack.length < 1)
|
|
895
|
+
if (this.stack.length < 1)
|
|
1039
896
|
this.scriptEvaluationError(`${OP[currentOpcode]} requires at least one item to be on the stack.`);
|
|
1040
|
-
|
|
1041
|
-
let
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
else if (currentOpcode === OP.
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
bufHash = Hash.hash160(buf);
|
|
1054
|
-
}
|
|
1055
|
-
else if (currentOpcode === OP.OP_HASH256) {
|
|
1056
|
-
bufHash = Hash.hash256(buf);
|
|
1057
|
-
}
|
|
1058
|
-
poppedValue = this.stack.pop();
|
|
1059
|
-
if (poppedValue != null) {
|
|
1060
|
-
this.stackMem -= poppedValue.length;
|
|
1061
|
-
}
|
|
1062
|
-
clearPoppedValue();
|
|
1063
|
-
this.stack.push(bufHash);
|
|
1064
|
-
this.stackMem += bufHash.length;
|
|
897
|
+
buf = this.popStack();
|
|
898
|
+
let hashResult = []; // Initialize to empty, to satisfy TS compiler
|
|
899
|
+
if (currentOpcode === OP.OP_RIPEMD160)
|
|
900
|
+
hashResult = Hash.ripemd160(buf);
|
|
901
|
+
else if (currentOpcode === OP.OP_SHA1)
|
|
902
|
+
hashResult = Hash.sha1(buf);
|
|
903
|
+
else if (currentOpcode === OP.OP_SHA256)
|
|
904
|
+
hashResult = Hash.sha256(buf);
|
|
905
|
+
else if (currentOpcode === OP.OP_HASH160)
|
|
906
|
+
hashResult = Hash.hash160(buf);
|
|
907
|
+
else if (currentOpcode === OP.OP_HASH256)
|
|
908
|
+
hashResult = Hash.hash256(buf);
|
|
909
|
+
this.pushStack(hashResult);
|
|
1065
910
|
break;
|
|
1066
911
|
}
|
|
1067
912
|
case OP.OP_CODESEPARATOR:
|
|
1068
913
|
this.lastCodeSeparator = this.programCounter;
|
|
1069
914
|
break;
|
|
1070
915
|
case OP.OP_CHECKSIG:
|
|
1071
|
-
case OP.OP_CHECKSIGVERIFY:
|
|
1072
|
-
if (this.stack.length < 2)
|
|
916
|
+
case OP.OP_CHECKSIGVERIFY: {
|
|
917
|
+
if (this.stack.length < 2)
|
|
1073
918
|
this.scriptEvaluationError(`${OP[currentOpcode]} requires at least two items to be on the stack.`);
|
|
1074
|
-
|
|
1075
|
-
bufSig = this.
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
if (this.context === 'UnlockingScript') {
|
|
1084
|
-
subscript = new Script(this.unlockingScript.chunks.slice(this.lastCodeSeparator ?? 0));
|
|
1085
|
-
}
|
|
1086
|
-
else {
|
|
1087
|
-
subscript = new Script(this.lockingScript.chunks.slice(this.lastCodeSeparator ?? 0));
|
|
1088
|
-
}
|
|
1089
|
-
// Drop the signature, since there's no way for a signature to sign itself
|
|
919
|
+
bufPubkey = this.popStack();
|
|
920
|
+
bufSig = this.popStack();
|
|
921
|
+
if (!this.checkSignatureEncoding(bufSig) || !this.checkPublicKeyEncoding(bufPubkey)) {
|
|
922
|
+
// Error already thrown by helpers
|
|
923
|
+
this.scriptEvaluationError(`${OP[currentOpcode]} requires correct encoding for the public key and signature.`); // Fallback, should be unreachable
|
|
924
|
+
}
|
|
925
|
+
const scriptForChecksig = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript;
|
|
926
|
+
const scriptCodeChunks = scriptForChecksig.chunks.slice(this.lastCodeSeparator === null ? 0 : this.lastCodeSeparator + 1);
|
|
927
|
+
subscript = new Script(scriptCodeChunks);
|
|
1090
928
|
subscript.findAndDelete(new Script().writeBin(bufSig));
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
this.scriptEvaluationError(`${OP[currentOpcode]} failed to verify the signature, and requires an empty signature when verification fails.`);
|
|
1102
|
-
}
|
|
1103
|
-
poppedValue = this.stack.pop();
|
|
1104
|
-
if (poppedValue != null) {
|
|
1105
|
-
this.stackMem -= poppedValue.length;
|
|
1106
|
-
}
|
|
1107
|
-
clearPoppedValue();
|
|
1108
|
-
poppedValue = this.stack.pop();
|
|
1109
|
-
if (poppedValue != null) {
|
|
1110
|
-
this.stackMem -= poppedValue.length;
|
|
929
|
+
fSuccess = false;
|
|
930
|
+
if (bufSig.length > 0) {
|
|
931
|
+
try {
|
|
932
|
+
sig = TransactionSignature.fromChecksigFormat(bufSig);
|
|
933
|
+
pubkey = PublicKey.fromDER(bufPubkey);
|
|
934
|
+
fSuccess = this.verifySignature(sig, pubkey, subscript);
|
|
935
|
+
}
|
|
936
|
+
catch (e) {
|
|
937
|
+
fSuccess = false;
|
|
938
|
+
}
|
|
1111
939
|
}
|
|
1112
|
-
|
|
1113
|
-
// stack.push_back(fSuccess ? vchTrue : vchFalse);
|
|
1114
|
-
this.stack.push(fSuccess ? [1] : []);
|
|
1115
|
-
this.stackMem += (fSuccess ? 1 : 0);
|
|
940
|
+
this.pushStack(fSuccess ? [1] : []);
|
|
1116
941
|
if (currentOpcode === OP.OP_CHECKSIGVERIFY) {
|
|
1117
|
-
if (fSuccess)
|
|
1118
|
-
poppedValue = this.stack.pop();
|
|
1119
|
-
if (poppedValue != null) {
|
|
1120
|
-
this.stackMem -= poppedValue.length;
|
|
1121
|
-
}
|
|
1122
|
-
clearPoppedValue();
|
|
1123
|
-
}
|
|
1124
|
-
else {
|
|
942
|
+
if (!fSuccess)
|
|
1125
943
|
this.scriptEvaluationError('OP_CHECKSIGVERIFY requires that a valid signature is provided.');
|
|
1126
|
-
|
|
944
|
+
this.popStack();
|
|
1127
945
|
}
|
|
1128
946
|
break;
|
|
947
|
+
}
|
|
1129
948
|
case OP.OP_CHECKMULTISIG:
|
|
1130
|
-
case OP.OP_CHECKMULTISIGVERIFY:
|
|
949
|
+
case OP.OP_CHECKMULTISIGVERIFY: {
|
|
1131
950
|
i = 1;
|
|
1132
951
|
if (this.stack.length < i) {
|
|
1133
|
-
this.scriptEvaluationError(`${OP[currentOpcode]} requires at least 1 item
|
|
952
|
+
this.scriptEvaluationError(`${OP[currentOpcode]} requires at least 1 item for nKeys.`);
|
|
1134
953
|
}
|
|
1135
|
-
nKeysCount = BigNumber.fromScriptNum(this.
|
|
1136
|
-
// TODO: Keys and opcount are parameterized in client. No magic numbers!
|
|
954
|
+
nKeysCount = BigNumber.fromScriptNum(this.stackTop(-i), requireMinimalPush).toNumber();
|
|
1137
955
|
if (nKeysCount < 0 || nKeysCount > maxMultisigKeyCount) {
|
|
1138
956
|
this.scriptEvaluationError(`${OP[currentOpcode]} requires a key count between 0 and ${maxMultisigKeyCount}.`);
|
|
1139
957
|
}
|
|
1140
958
|
ikey = ++i;
|
|
1141
959
|
i += nKeysCount;
|
|
1142
|
-
// ikey2 is the position of last non-signature item in
|
|
1143
|
-
// the stack. Top stack item = 1. With
|
|
1144
|
-
// SCRIPT_VERIFY_NULLFAIL, this is used for cleanup if
|
|
1145
|
-
// operation fails.
|
|
1146
|
-
ikey2 = nKeysCount + 2;
|
|
1147
960
|
if (this.stack.length < i) {
|
|
1148
|
-
this.scriptEvaluationError(`${OP[currentOpcode]}
|
|
961
|
+
this.scriptEvaluationError(`${OP[currentOpcode]} stack too small for nKeys and keys. Need ${i}, have ${this.stack.length}.`);
|
|
1149
962
|
}
|
|
1150
|
-
nSigsCount = BigNumber.fromScriptNum(this.
|
|
963
|
+
nSigsCount = BigNumber.fromScriptNum(this.stackTop(-i), requireMinimalPush).toNumber();
|
|
1151
964
|
if (nSigsCount < 0 || nSigsCount > nKeysCount) {
|
|
1152
965
|
this.scriptEvaluationError(`${OP[currentOpcode]} requires the number of signatures to be no greater than the number of keys.`);
|
|
1153
966
|
}
|
|
1154
967
|
isig = ++i;
|
|
1155
968
|
i += nSigsCount;
|
|
1156
969
|
if (this.stack.length < i) {
|
|
1157
|
-
this.scriptEvaluationError(`${OP[currentOpcode]}
|
|
970
|
+
this.scriptEvaluationError(`${OP[currentOpcode]} stack too small for N, keys, M, sigs, and dummy. Need ${i}, have ${this.stack.length}.`);
|
|
1158
971
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
}
|
|
1163
|
-
else {
|
|
1164
|
-
subscript = new Script(this.lockingScript.chunks.slice(this.lastCodeSeparator ?? 0));
|
|
1165
|
-
}
|
|
1166
|
-
// Drop the signatures, since there's no way for a signature to sign itself
|
|
972
|
+
const baseScriptCMS = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript;
|
|
973
|
+
const subscriptChunksCMS = baseScriptCMS.chunks.slice(this.lastCodeSeparator === null ? 0 : this.lastCodeSeparator + 1);
|
|
974
|
+
subscript = new Script(subscriptChunksCMS);
|
|
1167
975
|
for (let k = 0; k < nSigsCount; k++) {
|
|
1168
|
-
bufSig = this.
|
|
976
|
+
bufSig = this.stackTop(-isig - k); // Sigs are closer to top than keys
|
|
1169
977
|
subscript.findAndDelete(new Script().writeBin(bufSig));
|
|
1170
978
|
}
|
|
1171
979
|
fSuccess = true;
|
|
1172
980
|
while (fSuccess && nSigsCount > 0) {
|
|
1173
|
-
//
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
bufPubkey = this.stacktop(-ikey);
|
|
1177
|
-
if (!checkSignatureEncoding(bufSig) ||
|
|
1178
|
-
!checkPublicKeyEncoding(bufPubkey)) {
|
|
1179
|
-
this.scriptEvaluationError(`${OP[currentOpcode]} requires correct encoding for the public key and signature.`);
|
|
981
|
+
if (nKeysCount === 0) { // No more keys to check against but still sigs left
|
|
982
|
+
fSuccess = false;
|
|
983
|
+
break;
|
|
1180
984
|
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
985
|
+
bufSig = this.stackTop(-isig);
|
|
986
|
+
bufPubkey = this.stackTop(-ikey);
|
|
987
|
+
if (!this.checkSignatureEncoding(bufSig) || !this.checkPublicKeyEncoding(bufPubkey)) {
|
|
988
|
+
this.scriptEvaluationError(`${OP[currentOpcode]} requires correct encoding for the public key and signature.`);
|
|
1185
989
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
990
|
+
fOk = false;
|
|
991
|
+
if (bufSig.length > 0) {
|
|
992
|
+
try {
|
|
993
|
+
sig = TransactionSignature.fromChecksigFormat(bufSig);
|
|
994
|
+
pubkey = PublicKey.fromDER(bufPubkey);
|
|
995
|
+
fOk = this.verifySignature(sig, pubkey, subscript);
|
|
996
|
+
}
|
|
997
|
+
catch (e) {
|
|
998
|
+
fOk = false;
|
|
999
|
+
}
|
|
1189
1000
|
}
|
|
1190
1001
|
if (fOk) {
|
|
1191
1002
|
isig++;
|
|
@@ -1193,163 +1004,109 @@ export default class Spend {
|
|
|
1193
1004
|
}
|
|
1194
1005
|
ikey++;
|
|
1195
1006
|
nKeysCount--;
|
|
1196
|
-
// If there are more signatures left than keys left,
|
|
1197
|
-
// then too many signatures have failed
|
|
1198
1007
|
if (nSigsCount > nKeysCount) {
|
|
1199
1008
|
fSuccess = false;
|
|
1200
1009
|
}
|
|
1201
1010
|
}
|
|
1202
|
-
//
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
clearPoppedValue();
|
|
1215
|
-
}
|
|
1216
|
-
// A bug causes CHECKMULTISIG to consume one extra argument
|
|
1217
|
-
// whose contents were not checked in any way.
|
|
1218
|
-
//
|
|
1219
|
-
// Unfortunately this is a potential source of mutability,
|
|
1220
|
-
// so optionally verify it is exactly equal to zero prior
|
|
1221
|
-
// to removing it from the stack.
|
|
1011
|
+
// Correct total items consumed by op (N_val, keys, M_val, sigs, dummy)
|
|
1012
|
+
const itemsConsumedByOp = 1 + // N_val
|
|
1013
|
+
BigNumber.fromScriptNum(this.stackTop(-1), false).toNumber() + // keys
|
|
1014
|
+
1 + // M_val
|
|
1015
|
+
BigNumber.fromScriptNum(this.stackTop(-(1 + BigNumber.fromScriptNum(this.stackTop(-1), false).toNumber() + 1)), false).toNumber() + // sigs
|
|
1016
|
+
1; // dummy
|
|
1017
|
+
let popCount = itemsConsumedByOp - 1; // Pop all except dummy
|
|
1018
|
+
while (popCount > 0) {
|
|
1019
|
+
this.popStack();
|
|
1020
|
+
popCount--;
|
|
1021
|
+
}
|
|
1022
|
+
// Check and pop dummy
|
|
1222
1023
|
if (this.stack.length < 1) {
|
|
1223
|
-
this.scriptEvaluationError(`${OP[currentOpcode]} requires an extra item to be on the stack.`);
|
|
1024
|
+
this.scriptEvaluationError(`${OP[currentOpcode]} requires an extra item (dummy) to be on the stack.`);
|
|
1224
1025
|
}
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
this.scriptEvaluationError(`${OP[currentOpcode]} requires the extra stack item to be empty.`);
|
|
1026
|
+
const dummyBuf = this.popStack();
|
|
1027
|
+
if (dummyBuf.length > 0) { // SCRIPT_VERIFY_NULLDUMMY
|
|
1028
|
+
this.scriptEvaluationError(`${OP[currentOpcode]} requires the extra stack item (dummy) to be empty.`);
|
|
1228
1029
|
}
|
|
1229
|
-
|
|
1230
|
-
if (poppedValue != null) {
|
|
1231
|
-
this.stackMem -= poppedValue.length;
|
|
1232
|
-
}
|
|
1233
|
-
clearPoppedValue();
|
|
1234
|
-
this.stack.push(fSuccess ? [1] : []);
|
|
1235
|
-
this.stackMem += (fSuccess ? 1 : 0);
|
|
1030
|
+
this.pushStack(fSuccess ? [1] : []);
|
|
1236
1031
|
if (currentOpcode === OP.OP_CHECKMULTISIGVERIFY) {
|
|
1237
|
-
if (fSuccess)
|
|
1238
|
-
poppedValue = this.stack.pop();
|
|
1239
|
-
if (poppedValue != null) {
|
|
1240
|
-
this.stackMem -= poppedValue.length;
|
|
1241
|
-
}
|
|
1242
|
-
clearPoppedValue();
|
|
1243
|
-
}
|
|
1244
|
-
else {
|
|
1032
|
+
if (!fSuccess)
|
|
1245
1033
|
this.scriptEvaluationError('OP_CHECKMULTISIGVERIFY requires that a sufficient number of valid signatures are provided.');
|
|
1246
|
-
|
|
1034
|
+
this.popStack();
|
|
1247
1035
|
}
|
|
1248
1036
|
break;
|
|
1249
|
-
|
|
1250
|
-
|
|
1037
|
+
}
|
|
1038
|
+
case OP.OP_CAT: {
|
|
1039
|
+
if (this.stack.length < 2)
|
|
1251
1040
|
this.scriptEvaluationError('OP_CAT requires at least two items to be on the stack.');
|
|
1252
|
-
|
|
1253
|
-
buf1 = this.
|
|
1254
|
-
|
|
1255
|
-
if (
|
|
1041
|
+
buf2 = this.popStack();
|
|
1042
|
+
buf1 = this.popStack();
|
|
1043
|
+
const catResult = (buf1).concat(buf2);
|
|
1044
|
+
if (catResult.length > maxScriptElementSize)
|
|
1256
1045
|
this.scriptEvaluationError(`It's not currently possible to push data larger than ${maxScriptElementSize} bytes.`);
|
|
1257
|
-
|
|
1258
|
-
this.stack[this.stack.length - 2] = [...buf1, ...buf2];
|
|
1259
|
-
this.stackMem -= buf1.length;
|
|
1260
|
-
this.stackMem -= buf2.length;
|
|
1261
|
-
this.stackMem += buf1.length + buf2.length;
|
|
1262
|
-
poppedValue = this.stack.pop();
|
|
1263
|
-
if (poppedValue != null) {
|
|
1264
|
-
this.stackMem -= poppedValue.length;
|
|
1265
|
-
}
|
|
1266
|
-
clearPoppedValue();
|
|
1046
|
+
this.pushStack(catResult);
|
|
1267
1047
|
break;
|
|
1268
|
-
|
|
1269
|
-
|
|
1048
|
+
}
|
|
1049
|
+
case OP.OP_SPLIT: {
|
|
1050
|
+
if (this.stack.length < 2)
|
|
1270
1051
|
this.scriptEvaluationError('OP_SPLIT requires at least two items to be on the stack.');
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
n
|
|
1275
|
-
if (n < 0 || n > buf1.length) {
|
|
1052
|
+
const posBuf = this.popStack();
|
|
1053
|
+
const dataToSplit = this.popStack();
|
|
1054
|
+
n = BigNumber.fromScriptNum(posBuf, requireMinimalPush).toNumber();
|
|
1055
|
+
if (n < 0 || n > dataToSplit.length) {
|
|
1276
1056
|
this.scriptEvaluationError('OP_SPLIT requires the first stack item to be a non-negative number less than or equal to the size of the second-from-top stack item.');
|
|
1277
1057
|
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
// Copy buffer data, to slice it before
|
|
1281
|
-
buf2 = [...buf1];
|
|
1282
|
-
// Replace existing stack values by the new values.
|
|
1283
|
-
this.stack[this.stack.length - 2] = buf2.slice(0, n);
|
|
1284
|
-
this.stackMem -= buf1.length;
|
|
1285
|
-
this.stackMem += n;
|
|
1286
|
-
this.stack[this.stack.length - 1] = buf2.slice(n);
|
|
1287
|
-
this.stackMem += buf1.length - n;
|
|
1058
|
+
this.pushStack(dataToSplit.slice(0, n));
|
|
1059
|
+
this.pushStack(dataToSplit.slice(n));
|
|
1288
1060
|
break;
|
|
1289
|
-
|
|
1290
|
-
|
|
1061
|
+
}
|
|
1062
|
+
case OP.OP_NUM2BIN: {
|
|
1063
|
+
if (this.stack.length < 2)
|
|
1291
1064
|
this.scriptEvaluationError('OP_NUM2BIN requires at least two items to be on the stack.');
|
|
1065
|
+
size = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush).toNumber();
|
|
1066
|
+
if (size > maxScriptElementSize || size < 0) { // size can be 0
|
|
1067
|
+
this.scriptEvaluationError(`It's not currently possible to push data larger than ${maxScriptElementSize} bytes or negative size.`);
|
|
1292
1068
|
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
this.scriptEvaluationError(`It's not currently possible to push data larger than ${maxScriptElementSize} bytes.`);
|
|
1296
|
-
}
|
|
1297
|
-
poppedValue = this.stack.pop();
|
|
1298
|
-
if (poppedValue != null) {
|
|
1299
|
-
this.stackMem -= poppedValue.length;
|
|
1300
|
-
}
|
|
1301
|
-
clearPoppedValue();
|
|
1302
|
-
rawnum = this.stacktop(-1);
|
|
1303
|
-
// Try to see if we can fit that number in the number of
|
|
1304
|
-
// byte requested.
|
|
1305
|
-
rawnum = minimallyEncode(rawnum);
|
|
1069
|
+
let rawnum = this.popStack(); // This is the number to convert
|
|
1070
|
+
rawnum = minimallyEncode(rawnum); // Get its minimal scriptnum form
|
|
1306
1071
|
if (rawnum.length > size) {
|
|
1307
1072
|
this.scriptEvaluationError('OP_NUM2BIN requires that the size expressed in the top stack item is large enough to hold the value expressed in the second-from-top stack item.');
|
|
1308
1073
|
}
|
|
1309
|
-
// We already have an element of the right size, we
|
|
1310
|
-
// don't need to do anything.
|
|
1311
1074
|
if (rawnum.length === size) {
|
|
1312
|
-
this.
|
|
1075
|
+
this.pushStack(rawnum);
|
|
1313
1076
|
break;
|
|
1314
1077
|
}
|
|
1315
|
-
|
|
1078
|
+
const resultN2B = new Array(size).fill(0x00);
|
|
1079
|
+
let signbit = 0x00;
|
|
1316
1080
|
if (rawnum.length > 0) {
|
|
1317
|
-
signbit = rawnum[rawnum.length - 1] & 0x80;
|
|
1318
|
-
rawnum[rawnum.length - 1] &= 0x7f;
|
|
1081
|
+
signbit = rawnum[rawnum.length - 1] & 0x80; // Store sign bit
|
|
1082
|
+
rawnum[rawnum.length - 1] &= 0x7f; // Remove sign bit for padding
|
|
1319
1083
|
}
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
num[n] = rawnum[n];
|
|
1084
|
+
// Copy rawnum (now positive magnitude) into the result
|
|
1085
|
+
for (let k = 0; k < rawnum.length; k++) {
|
|
1086
|
+
resultN2B[k] = rawnum[k];
|
|
1324
1087
|
}
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1088
|
+
// If the original number was negative, the sign bit must be set on the new MSB
|
|
1089
|
+
if (signbit !== 0) {
|
|
1090
|
+
resultN2B[size - 1] |= 0x80;
|
|
1328
1091
|
}
|
|
1329
|
-
|
|
1330
|
-
this.stack[this.stack.length - 1] = num;
|
|
1331
|
-
this.stackMem -= rawnum.length;
|
|
1332
|
-
this.stackMem += size;
|
|
1092
|
+
this.pushStack(resultN2B);
|
|
1333
1093
|
break;
|
|
1334
|
-
|
|
1335
|
-
|
|
1094
|
+
}
|
|
1095
|
+
case OP.OP_BIN2NUM: {
|
|
1096
|
+
if (this.stack.length < 1)
|
|
1336
1097
|
this.scriptEvaluationError('OP_BIN2NUM requires at least one item to be on the stack.');
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
this.stack[this.stack.length - 1] = buf2;
|
|
1341
|
-
this.stackMem -= buf1.length;
|
|
1342
|
-
this.stackMem += buf2.length;
|
|
1343
|
-
// The resulting number must be a valid number.
|
|
1344
|
-
if (!isMinimallyEncoded(buf2)) {
|
|
1098
|
+
buf1 = this.popStack();
|
|
1099
|
+
const b2nResult = minimallyEncode(buf1);
|
|
1100
|
+
if (!isMinimallyEncodedHelper(b2nResult)) {
|
|
1345
1101
|
this.scriptEvaluationError('OP_BIN2NUM requires that the resulting number is valid.');
|
|
1346
1102
|
}
|
|
1103
|
+
this.pushStack(b2nResult);
|
|
1347
1104
|
break;
|
|
1105
|
+
}
|
|
1348
1106
|
default:
|
|
1349
|
-
this.scriptEvaluationError(
|
|
1107
|
+
this.scriptEvaluationError(`Invalid opcode ${currentOpcode} (pc=${this.programCounter}).`);
|
|
1350
1108
|
}
|
|
1351
1109
|
}
|
|
1352
|
-
// Finally, increment the program counter
|
|
1353
1110
|
this.programCounter++;
|
|
1354
1111
|
return true;
|
|
1355
1112
|
}
|
|
@@ -1375,35 +1132,39 @@ export default class Spend {
|
|
|
1375
1132
|
}
|
|
1376
1133
|
}
|
|
1377
1134
|
if (this.ifStack.length > 0) {
|
|
1378
|
-
this.scriptEvaluationError('Every OP_IF must be terminated prior to the end of the script.');
|
|
1135
|
+
this.scriptEvaluationError('Every OP_IF, OP_NOTIF, or OP_ELSE must be terminated with OP_ENDIF prior to the end of the script.');
|
|
1379
1136
|
}
|
|
1380
1137
|
if (requireCleanStack) {
|
|
1381
1138
|
if (this.stack.length !== 1) {
|
|
1382
|
-
this.scriptEvaluationError(
|
|
1139
|
+
this.scriptEvaluationError(`The clean stack rule requires exactly one item to be on the stack after script execution, found ${this.stack.length}.`);
|
|
1383
1140
|
}
|
|
1384
1141
|
}
|
|
1385
|
-
if (
|
|
1142
|
+
if (this.stack.length === 0) {
|
|
1143
|
+
this.scriptEvaluationError('The top stack element must be truthy after script evaluation (stack is empty).');
|
|
1144
|
+
}
|
|
1145
|
+
else if (!this.castToBool(this.stackTop())) {
|
|
1386
1146
|
this.scriptEvaluationError('The top stack element must be truthy after script evaluation.');
|
|
1387
1147
|
}
|
|
1388
1148
|
return true;
|
|
1389
1149
|
}
|
|
1390
|
-
stacktop(i) {
|
|
1391
|
-
return this.stack[this.stack.length + i];
|
|
1392
|
-
}
|
|
1393
1150
|
castToBool(val) {
|
|
1151
|
+
if (val.length === 0)
|
|
1152
|
+
return false;
|
|
1394
1153
|
for (let i = 0; i < val.length; i++) {
|
|
1395
1154
|
if (val[i] !== 0) {
|
|
1396
|
-
|
|
1397
|
-
if (i === val.length - 1 && val[i] === 0x80) {
|
|
1398
|
-
return false;
|
|
1399
|
-
}
|
|
1400
|
-
return true;
|
|
1155
|
+
return !(i === val.length - 1 && val[i] === 0x80);
|
|
1401
1156
|
}
|
|
1402
1157
|
}
|
|
1403
1158
|
return false;
|
|
1404
1159
|
}
|
|
1405
1160
|
scriptEvaluationError(str) {
|
|
1406
|
-
|
|
1161
|
+
const pcInfo = `Context: ${this.context}, PC: ${this.programCounter}`;
|
|
1162
|
+
const stackHex = this.stack.map(s => (s != null && typeof s.length !== 'undefined') ? toHex(s) : (s === null || s === undefined ? 'null/undef' : 'INVALID_STACK_ITEM')).join(', ');
|
|
1163
|
+
const altStackHex = this.altStack.map(s => (s != null && typeof s.length !== 'undefined') ? toHex(s) : (s === null || s === undefined ? 'null/undef' : 'INVALID_STACK_ITEM')).join(', ');
|
|
1164
|
+
const stackInfo = `Stack: [${stackHex}] (len: ${this.stack.length}, mem: ${this.stackMem})`;
|
|
1165
|
+
const altStackInfo = `AltStack: [${altStackHex}] (len: ${this.altStack.length}, mem: ${this.altStackMem})`;
|
|
1166
|
+
const ifStackInfo = `IfStack: [${this.ifStack.join(', ')}]`;
|
|
1167
|
+
throw new Error(`Script evaluation error: ${str}\nTXID: ${this.sourceTXID}, OutputIdx: ${this.sourceOutputIndex}\n${pcInfo}\n${stackInfo}\n${altStackInfo}\n${ifStackInfo}`);
|
|
1407
1168
|
}
|
|
1408
1169
|
}
|
|
1409
1170
|
//# sourceMappingURL=Spend.js.map
|