@bsv/sdk 2.0.13 → 2.0.15
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 -0
- package/dist/cjs/package.json +14 -14
- package/dist/cjs/src/identity/IdentityClient.js +3 -3
- package/dist/cjs/src/identity/IdentityClient.js.map +1 -1
- package/dist/cjs/src/identity/types/index.js +1 -1
- package/dist/cjs/src/identity/types/index.js.map +1 -1
- package/dist/cjs/src/primitives/Hash.js +1 -1
- package/dist/cjs/src/primitives/Hash.js.map +1 -1
- package/dist/cjs/src/primitives/TransactionSignature.js +10 -3
- package/dist/cjs/src/primitives/TransactionSignature.js.map +1 -1
- package/dist/cjs/src/script/Script.js +60 -13
- package/dist/cjs/src/script/Script.js.map +1 -1
- package/dist/cjs/src/script/Spend.js +434 -59
- package/dist/cjs/src/script/Spend.js.map +1 -1
- package/dist/cjs/src/storage/StorageUploader.js +315 -96
- package/dist/cjs/src/storage/StorageUploader.js.map +1 -1
- package/dist/cjs/src/storage/index.js +3 -1
- package/dist/cjs/src/storage/index.js.map +1 -1
- package/dist/cjs/src/transaction/http/BinaryFetchClient.js +6 -2
- package/dist/cjs/src/transaction/http/BinaryFetchClient.js.map +1 -1
- package/dist/cjs/src/transaction/http/DefaultHttpClient.js +8 -4
- package/dist/cjs/src/transaction/http/DefaultHttpClient.js.map +1 -1
- package/dist/cjs/src/wallet/WERR_REVIEW_ACTIONS.js +2 -2
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/identity/IdentityClient.js +3 -3
- package/dist/esm/src/identity/IdentityClient.js.map +1 -1
- package/dist/esm/src/identity/types/index.js +1 -1
- package/dist/esm/src/identity/types/index.js.map +1 -1
- package/dist/esm/src/primitives/Hash.js +1 -1
- package/dist/esm/src/primitives/Hash.js.map +1 -1
- package/dist/esm/src/primitives/TransactionSignature.js +10 -3
- package/dist/esm/src/primitives/TransactionSignature.js.map +1 -1
- package/dist/esm/src/script/Script.js +60 -13
- package/dist/esm/src/script/Script.js.map +1 -1
- package/dist/esm/src/script/Spend.js +438 -59
- package/dist/esm/src/script/Spend.js.map +1 -1
- package/dist/esm/src/storage/StorageUploader.js +319 -95
- package/dist/esm/src/storage/StorageUploader.js.map +1 -1
- package/dist/esm/src/storage/index.js +1 -1
- package/dist/esm/src/storage/index.js.map +1 -1
- package/dist/esm/src/transaction/http/BinaryFetchClient.js +6 -2
- package/dist/esm/src/transaction/http/BinaryFetchClient.js.map +1 -1
- package/dist/esm/src/transaction/http/DefaultHttpClient.js +8 -4
- package/dist/esm/src/transaction/http/DefaultHttpClient.js.map +1 -1
- package/dist/esm/src/wallet/WERR_REVIEW_ACTIONS.js +2 -2
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/primitives/TransactionSignature.d.ts +1 -0
- package/dist/types/src/primitives/TransactionSignature.d.ts.map +1 -1
- package/dist/types/src/script/Script.d.ts +1 -0
- package/dist/types/src/script/Script.d.ts.map +1 -1
- package/dist/types/src/script/ScriptChunk.d.ts +1 -0
- package/dist/types/src/script/ScriptChunk.d.ts.map +1 -1
- package/dist/types/src/script/Spend.d.ts +29 -0
- package/dist/types/src/script/Spend.d.ts.map +1 -1
- package/dist/types/src/storage/StorageUploader.d.ts +94 -55
- package/dist/types/src/storage/StorageUploader.d.ts.map +1 -1
- package/dist/types/src/storage/index.d.ts +1 -1
- package/dist/types/src/storage/index.d.ts.map +1 -1
- package/dist/types/src/transaction/http/BinaryFetchClient.d.ts.map +1 -1
- package/dist/types/src/transaction/http/DefaultHttpClient.d.ts +2 -2
- package/dist/types/src/transaction/http/DefaultHttpClient.d.ts.map +1 -1
- package/dist/types/src/wallet/WERR_REVIEW_ACTIONS.d.ts +2 -2
- package/dist/types/src/wallet/Wallet.interfaces.d.ts +2 -2
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +4 -5
- package/docs/index.md +3 -1
- package/docs/reference/identity.md +1 -1
- package/docs/reference/primitives.md +1 -0
- package/docs/reference/script.md +7 -0
- package/docs/reference/storage.md +214 -85
- package/docs/reference/transaction.md +4 -4
- package/docs/reference/wallet.md +4 -4
- package/package.json +14 -14
- package/src/identity/IdentityClient.ts +3 -3
- package/src/identity/__tests/IdentityClient.additional.test.ts +3 -3
- package/src/identity/types/index.ts +1 -1
- package/src/primitives/Hash.ts +1 -1
- package/src/primitives/TransactionSignature.ts +11 -3
- package/src/script/Script.ts +59 -13
- package/src/script/ScriptChunk.ts +1 -0
- package/src/script/Spend.ts +483 -61
- package/src/script/__tests/NormativeVectors.test.ts +465 -0
- package/src/script/__tests/fixtures/SOURCES.md +25 -0
- package/src/script/__tests/fixtures/bitcoin-sv/script_tests.json +2591 -0
- package/src/script/__tests/fixtures/bitcoin-sv/sighash.json +1003 -0
- package/src/script/__tests/fixtures/bitcoin-sv/tx_invalid.json +285 -0
- package/src/script/__tests/fixtures/bitcoin-sv/tx_valid.json +367 -0
- package/src/script/__tests/fixtures/teranode/script_tests.json +2432 -0
- package/src/script/__tests/fixtures/teranode/sighash.json +1003 -0
- package/src/script/__tests/fixtures/teranode/tx_invalid.json +285 -0
- package/src/script/__tests/fixtures/teranode/tx_valid.json +367 -0
- package/src/storage/StorageUploader.ts +427 -105
- package/src/storage/__tests/StorageUploader.test.ts +881 -64
- package/src/storage/index.ts +1 -1
- package/src/transaction/broadcasters/__tests/ARC.test.ts +26 -4
- package/src/transaction/broadcasters/__tests/WhatsOnChainBroadcaster.test.ts +26 -4
- package/src/transaction/chaintrackers/__tests/WhatsOnChainChainTracker.test.ts +32 -10
- package/src/transaction/http/BinaryFetchClient.ts +5 -2
- package/src/transaction/http/DefaultHttpClient.ts +7 -4
- package/src/transaction/http/__tests/DefaultHttpClient.additional.test.ts +19 -1
- package/src/wallet/WERR_REVIEW_ACTIONS.ts +2 -2
- package/src/wallet/Wallet.interfaces.ts +2 -2
- package/dist/cjs/src/auth/clients/__tests__/AuthFetch.additional.test.js +0 -827
- package/dist/cjs/src/auth/clients/__tests__/AuthFetch.additional.test.js.map +0 -1
- package/dist/cjs/src/auth/clients/__tests__/AuthFetch.test.js +0 -266
- package/dist/cjs/src/auth/clients/__tests__/AuthFetch.test.js.map +0 -1
- package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js +0 -654
- package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js.map +0 -1
- package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js +0 -144
- package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js.map +0 -1
- package/dist/esm/src/auth/clients/__tests__/AuthFetch.additional.test.js +0 -825
- package/dist/esm/src/auth/clients/__tests__/AuthFetch.additional.test.js.map +0 -1
- package/dist/esm/src/auth/clients/__tests__/AuthFetch.test.js +0 -264
- package/dist/esm/src/auth/clients/__tests__/AuthFetch.test.js.map +0 -1
- package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js +0 -619
- package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js.map +0 -1
- package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js +0 -109
- package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js.map +0 -1
- package/dist/types/src/auth/clients/__tests__/AuthFetch.additional.test.d.ts +0 -21
- package/dist/types/src/auth/clients/__tests__/AuthFetch.additional.test.d.ts.map +0 -1
- package/dist/types/src/auth/clients/__tests__/AuthFetch.test.d.ts +0 -2
- package/dist/types/src/auth/clients/__tests__/AuthFetch.test.d.ts.map +0 -1
- package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.d.ts +0 -2
- package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.d.ts.map +0 -1
- package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.test.d.ts +0 -2
- package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.test.d.ts.map +0 -1
- package/dist/umd/bundle.js.map +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import LockingScript from './LockingScript.js';
|
|
1
2
|
import Script from './Script.js';
|
|
2
3
|
import BigNumber from '../primitives/BigNumber.js';
|
|
3
4
|
import OP from './OP.js';
|
|
@@ -9,8 +10,14 @@ import PublicKey from '../primitives/PublicKey.js';
|
|
|
9
10
|
import { verify } from '../primitives/ECDSA.js';
|
|
10
11
|
// These constants control the current behavior of the interpreter.
|
|
11
12
|
const maxScriptElementSize = 1024 * 1024 * 1024;
|
|
13
|
+
const maxScriptElementSizeBeforeGenesis = 520;
|
|
14
|
+
const maxScriptSizeBeforeGenesis = 10000;
|
|
15
|
+
const maxOpsBeforeGenesis = 500;
|
|
16
|
+
const maxStackItemsBeforeGenesis = 1000;
|
|
12
17
|
const maxMultisigKeyCount = Math.pow(2, 31) - 1;
|
|
13
18
|
const maxMultisigKeyCountBigInt = BigInt(maxMultisigKeyCount);
|
|
19
|
+
const maxMultisigKeyCountBeforeGenesis = 20;
|
|
20
|
+
const sequenceLocktimeDisableFlag = 0x80000000;
|
|
14
21
|
// --- Optimization: Pre-computed script numbers ---
|
|
15
22
|
const SCRIPTNUM_NEG_1 = Object.freeze(new BigNumber(-1).toScriptNum());
|
|
16
23
|
const SCRIPTNUMS_0_TO_16 = Object.freeze(Array.from({ length: 17 }, (_, i) => Object.freeze(new BigNumber(i).toScriptNum())));
|
|
@@ -135,10 +142,14 @@ export default class Spend {
|
|
|
135
142
|
stack;
|
|
136
143
|
altStack;
|
|
137
144
|
ifStack;
|
|
145
|
+
elseStack;
|
|
138
146
|
memoryLimit;
|
|
139
147
|
stackMem;
|
|
140
148
|
altStackMem;
|
|
141
149
|
isRelaxedOverride;
|
|
150
|
+
verifyFlags;
|
|
151
|
+
executedOpCount;
|
|
152
|
+
returningFromConditional;
|
|
142
153
|
sigHashCache;
|
|
143
154
|
/**
|
|
144
155
|
* @constructor
|
|
@@ -188,11 +199,21 @@ export default class Spend {
|
|
|
188
199
|
this.lockTime = params.lockTime;
|
|
189
200
|
this.memoryLimit = params.memoryLimit ?? 32000000;
|
|
190
201
|
this.isRelaxedOverride = params.isRelaxed === true;
|
|
202
|
+
this.verifyFlags = params.verifyFlags === undefined
|
|
203
|
+
? undefined
|
|
204
|
+
: new Set((Array.isArray(params.verifyFlags)
|
|
205
|
+
? params.verifyFlags
|
|
206
|
+
: params.verifyFlags.split(','))
|
|
207
|
+
.map(flag => flag.trim())
|
|
208
|
+
.filter(flag => flag.length > 0));
|
|
191
209
|
this.stack = [];
|
|
192
210
|
this.altStack = [];
|
|
193
211
|
this.ifStack = [];
|
|
212
|
+
this.elseStack = [];
|
|
194
213
|
this.stackMem = 0;
|
|
195
214
|
this.altStackMem = 0;
|
|
215
|
+
this.executedOpCount = 0;
|
|
216
|
+
this.returningFromConditional = false;
|
|
196
217
|
this.sigHashCache = { hashOutputsSingle: new Map() };
|
|
197
218
|
this.reset();
|
|
198
219
|
}
|
|
@@ -200,6 +221,75 @@ export default class Spend {
|
|
|
200
221
|
return this.isRelaxedOverride ||
|
|
201
222
|
(this.transactionVersion > 1);
|
|
202
223
|
}
|
|
224
|
+
hasExplicitFlags() {
|
|
225
|
+
return this.verifyFlags !== undefined;
|
|
226
|
+
}
|
|
227
|
+
hasFlag(flag) {
|
|
228
|
+
return this.verifyFlags?.has(flag) === true;
|
|
229
|
+
}
|
|
230
|
+
isAfterGenesis() {
|
|
231
|
+
if (this.hasExplicitFlags()) {
|
|
232
|
+
return this.hasFlag('GENESIS') ||
|
|
233
|
+
this.hasFlag('UTXO_AFTER_GENESIS') ||
|
|
234
|
+
this.hasFlag('UTXO_AFTER_CHRONICLE');
|
|
235
|
+
}
|
|
236
|
+
return this.isRelaxed();
|
|
237
|
+
}
|
|
238
|
+
isAfterChronicle() {
|
|
239
|
+
if (this.hasExplicitFlags())
|
|
240
|
+
return this.hasFlag('UTXO_AFTER_CHRONICLE');
|
|
241
|
+
return this.isRelaxed();
|
|
242
|
+
}
|
|
243
|
+
shouldEnforceMinimalData() {
|
|
244
|
+
if (this.hasExplicitFlags())
|
|
245
|
+
return this.hasFlag('MINIMALDATA');
|
|
246
|
+
return !this.isRelaxed();
|
|
247
|
+
}
|
|
248
|
+
shouldEnforceLowS() {
|
|
249
|
+
if (this.hasExplicitFlags())
|
|
250
|
+
return this.hasFlag('LOW_S');
|
|
251
|
+
return !this.isRelaxed();
|
|
252
|
+
}
|
|
253
|
+
shouldEnforceNullDummy() {
|
|
254
|
+
if (this.hasExplicitFlags())
|
|
255
|
+
return this.hasFlag('NULLDUMMY');
|
|
256
|
+
return !this.isRelaxed();
|
|
257
|
+
}
|
|
258
|
+
shouldEnforceSigPushOnly() {
|
|
259
|
+
if (this.hasExplicitFlags())
|
|
260
|
+
return this.hasFlag('SIGPUSHONLY');
|
|
261
|
+
return !this.isRelaxed();
|
|
262
|
+
}
|
|
263
|
+
shouldEnforceCleanStack() {
|
|
264
|
+
if (this.hasExplicitFlags())
|
|
265
|
+
return this.hasFlag('CLEANSTACK');
|
|
266
|
+
return !this.isRelaxed();
|
|
267
|
+
}
|
|
268
|
+
shouldEnforceDerSignatures() {
|
|
269
|
+
if (this.hasExplicitFlags()) {
|
|
270
|
+
return this.hasFlag('DERSIG') ||
|
|
271
|
+
this.hasFlag('STRICTENC') ||
|
|
272
|
+
this.hasFlag('LOW_S') ||
|
|
273
|
+
this.hasFlag('SIGHASH_FORKID');
|
|
274
|
+
}
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
shouldEnforceStrictEncoding() {
|
|
278
|
+
if (this.hasExplicitFlags()) {
|
|
279
|
+
return this.hasFlag('STRICTENC') || this.hasFlag('SIGHASH_FORKID');
|
|
280
|
+
}
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
scriptNumMaxSize() {
|
|
284
|
+
if (this.hasExplicitFlags() && !this.isAfterGenesis())
|
|
285
|
+
return 4;
|
|
286
|
+
return undefined;
|
|
287
|
+
}
|
|
288
|
+
maxPushSize() {
|
|
289
|
+
if (this.hasExplicitFlags() && !this.isAfterGenesis())
|
|
290
|
+
return maxScriptElementSizeBeforeGenesis;
|
|
291
|
+
return maxScriptElementSize;
|
|
292
|
+
}
|
|
203
293
|
reset() {
|
|
204
294
|
this.context = 'UnlockingScript';
|
|
205
295
|
this.programCounter = 0;
|
|
@@ -207,8 +297,11 @@ export default class Spend {
|
|
|
207
297
|
this.stack = [];
|
|
208
298
|
this.altStack = [];
|
|
209
299
|
this.ifStack = [];
|
|
300
|
+
this.elseStack = [];
|
|
210
301
|
this.stackMem = 0;
|
|
211
302
|
this.altStackMem = 0;
|
|
303
|
+
this.executedOpCount = 0;
|
|
304
|
+
this.returningFromConditional = false;
|
|
212
305
|
this.sigHashCache = { hashOutputsSingle: new Map() };
|
|
213
306
|
}
|
|
214
307
|
ensureStackMem(additional) {
|
|
@@ -252,6 +345,14 @@ export default class Spend {
|
|
|
252
345
|
}
|
|
253
346
|
return this.stack[this.stack.length + index];
|
|
254
347
|
}
|
|
348
|
+
setStack(items) {
|
|
349
|
+
this.stack = items.map(item => item.slice());
|
|
350
|
+
this.stackMem = this.stack.reduce((total, item) => total + item.length, 0);
|
|
351
|
+
}
|
|
352
|
+
clearAltStack() {
|
|
353
|
+
this.altStack = [];
|
|
354
|
+
this.altStackMem = 0;
|
|
355
|
+
}
|
|
255
356
|
pushAltStack(item) {
|
|
256
357
|
this.ensureAltStackMem(item.length);
|
|
257
358
|
this.altStack.push(item);
|
|
@@ -269,27 +370,124 @@ export default class Spend {
|
|
|
269
370
|
this.altStackMem -= item.length;
|
|
270
371
|
return item;
|
|
271
372
|
}
|
|
373
|
+
readScriptNumber(buf) {
|
|
374
|
+
try {
|
|
375
|
+
return BigNumber.fromScriptNum(buf, this.shouldEnforceMinimalData(), this.scriptNumMaxSize());
|
|
376
|
+
}
|
|
377
|
+
catch (e) {
|
|
378
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
379
|
+
this.scriptEvaluationError(message);
|
|
380
|
+
}
|
|
381
|
+
return new BigNumber(0);
|
|
382
|
+
}
|
|
383
|
+
isDefinedHashType(scope) {
|
|
384
|
+
const baseType = scope & 0x1f;
|
|
385
|
+
return baseType >= TransactionSignature.SIGHASH_ALL &&
|
|
386
|
+
baseType <= TransactionSignature.SIGHASH_SINGLE;
|
|
387
|
+
}
|
|
272
388
|
checkSignatureEncoding(buf) {
|
|
273
389
|
if (buf.length === 0)
|
|
274
390
|
return true;
|
|
275
|
-
|
|
391
|
+
const enforceDer = this.shouldEnforceDerSignatures();
|
|
392
|
+
if (enforceDer && !isChecksigFormatHelper(buf)) {
|
|
276
393
|
this.scriptEvaluationError('The signature format is invalid.'); // Generic message like original
|
|
277
394
|
return false;
|
|
278
395
|
}
|
|
279
396
|
try {
|
|
280
397
|
const sig = TransactionSignature.fromChecksigFormat(buf); // This can throw for stricter DER rules
|
|
281
|
-
if (
|
|
398
|
+
if (this.shouldEnforceStrictEncoding() && !this.isDefinedHashType(sig.scope)) {
|
|
399
|
+
this.scriptEvaluationError('The signature hash type is invalid.');
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
if (this.shouldEnforceStrictEncoding() &&
|
|
403
|
+
(sig.scope & TransactionSignature.SIGHASH_CHRONICLE) !== 0 &&
|
|
404
|
+
!this.isAfterChronicle()) {
|
|
405
|
+
this.scriptEvaluationError('The signature hash type is invalid before Chronicle.');
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
const hasForkId = (sig.scope & TransactionSignature.SIGHASH_FORKID) !== 0;
|
|
409
|
+
if (this.hasExplicitFlags()) {
|
|
410
|
+
if (this.hasFlag('SIGHASH_FORKID') && !hasForkId) {
|
|
411
|
+
this.scriptEvaluationError('The signature must use SIGHASH_FORKID.');
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
if (!this.hasFlag('SIGHASH_FORKID') && !this.isAfterGenesis() && hasForkId) {
|
|
415
|
+
this.scriptEvaluationError('The signature must not use SIGHASH_FORKID.');
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (this.shouldEnforceLowS() && !sig.hasLowS()) {
|
|
282
420
|
this.scriptEvaluationError('The signature must have a low S value.');
|
|
283
421
|
return false;
|
|
284
422
|
}
|
|
285
423
|
}
|
|
286
424
|
catch {
|
|
287
|
-
|
|
288
|
-
|
|
425
|
+
if (enforceDer) {
|
|
426
|
+
this.scriptEvaluationError('The signature format is invalid.');
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
289
429
|
}
|
|
290
430
|
return true;
|
|
291
431
|
}
|
|
432
|
+
parseChecksigSignature(buf) {
|
|
433
|
+
try {
|
|
434
|
+
return TransactionSignature.fromChecksigFormat(buf);
|
|
435
|
+
}
|
|
436
|
+
catch (e) {
|
|
437
|
+
if (this.shouldEnforceDerSignatures())
|
|
438
|
+
throw e;
|
|
439
|
+
return this.parseLaxChecksigSignature(buf);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
readLaxDERLength(buf, position) {
|
|
443
|
+
const first = buf[position.value++];
|
|
444
|
+
if (first === undefined)
|
|
445
|
+
throw new Error('Invalid DER length');
|
|
446
|
+
if ((first & 0x80) === 0)
|
|
447
|
+
return first;
|
|
448
|
+
const lengthBytes = first & 0x7f;
|
|
449
|
+
if (lengthBytes === 0 || position.value + lengthBytes > buf.length) {
|
|
450
|
+
throw new Error('Invalid DER length');
|
|
451
|
+
}
|
|
452
|
+
let length = 0;
|
|
453
|
+
for (let i = 0; i < lengthBytes; i++) {
|
|
454
|
+
length = (length << 8) | (buf[position.value++] ?? 0);
|
|
455
|
+
}
|
|
456
|
+
return length;
|
|
457
|
+
}
|
|
458
|
+
parseLaxDERInteger(buf, position, sequenceEnd) {
|
|
459
|
+
if (position.value >= sequenceEnd || buf[position.value++] !== 0x02) {
|
|
460
|
+
throw new Error('Invalid DER integer');
|
|
461
|
+
}
|
|
462
|
+
const length = this.readLaxDERLength(buf, position);
|
|
463
|
+
if (position.value + length > sequenceEnd) {
|
|
464
|
+
throw new Error('Invalid DER integer length');
|
|
465
|
+
}
|
|
466
|
+
let bytes = buf.slice(position.value, position.value + length);
|
|
467
|
+
position.value += length;
|
|
468
|
+
while (bytes.length > 1 && bytes[0] === 0)
|
|
469
|
+
bytes = bytes.slice(1);
|
|
470
|
+
if (bytes.length === 0)
|
|
471
|
+
bytes = [0];
|
|
472
|
+
return new BigNumber(bytes);
|
|
473
|
+
}
|
|
474
|
+
parseLaxChecksigSignature(buf) {
|
|
475
|
+
if (buf.length === 0)
|
|
476
|
+
return TransactionSignature.fromChecksigFormat(buf);
|
|
477
|
+
const scope = buf[buf.length - 1];
|
|
478
|
+
const der = buf.slice(0, -1);
|
|
479
|
+
const position = { value: 0 };
|
|
480
|
+
if (der[position.value++] !== 0x30)
|
|
481
|
+
throw new Error('Signature DER must start with 0x30');
|
|
482
|
+
const sequenceLength = this.readLaxDERLength(der, position);
|
|
483
|
+
const sequenceEnd = Math.min(position.value + sequenceLength, der.length);
|
|
484
|
+
const r = this.parseLaxDERInteger(der, position, sequenceEnd);
|
|
485
|
+
const s = this.parseLaxDERInteger(der, position, sequenceEnd);
|
|
486
|
+
return new TransactionSignature(r, s, scope);
|
|
487
|
+
}
|
|
292
488
|
checkPublicKeyEncoding(buf) {
|
|
489
|
+
if (!this.shouldEnforceStrictEncoding())
|
|
490
|
+
return true;
|
|
293
491
|
if (buf.length === 0) {
|
|
294
492
|
this.scriptEvaluationError('Public key is empty.');
|
|
295
493
|
return false;
|
|
@@ -324,7 +522,7 @@ export default class Spend {
|
|
|
324
522
|
return true;
|
|
325
523
|
}
|
|
326
524
|
verifySignature(sig, pubkey, subscript) {
|
|
327
|
-
const
|
|
525
|
+
const params = {
|
|
328
526
|
sourceTXID: this.sourceTXID,
|
|
329
527
|
sourceOutputIndex: this.sourceOutputIndex,
|
|
330
528
|
sourceSatoshis: this.sourceSatoshis,
|
|
@@ -337,8 +535,10 @@ export default class Spend {
|
|
|
337
535
|
lockTime: this.lockTime,
|
|
338
536
|
scope: sig.scope,
|
|
339
537
|
cache: this.sigHashCache
|
|
340
|
-
}
|
|
341
|
-
const hash =
|
|
538
|
+
};
|
|
539
|
+
const hash = TransactionSignature.usesOtdaSingleBug(params)
|
|
540
|
+
? new BigNumber([1, ...new Array(31).fill(0)])
|
|
541
|
+
: new BigNumber(Hash.hash256(TransactionSignature.formatBytes(params)));
|
|
342
542
|
return verify(hash, sig, pubkey);
|
|
343
543
|
}
|
|
344
544
|
step() {
|
|
@@ -352,6 +552,14 @@ export default class Spend {
|
|
|
352
552
|
}
|
|
353
553
|
if (this.context === 'UnlockingScript' &&
|
|
354
554
|
this.programCounter >= this.unlockingScript.chunks.length) {
|
|
555
|
+
if (this.ifStack.length > 0) {
|
|
556
|
+
this.scriptEvaluationError('Every OP_IF, OP_NOTIF, or OP_ELSE must be terminated with OP_ENDIF prior to the end of the unlocking script.');
|
|
557
|
+
}
|
|
558
|
+
this.clearAltStack();
|
|
559
|
+
this.ifStack = [];
|
|
560
|
+
this.elseStack = [];
|
|
561
|
+
this.returningFromConditional = false;
|
|
562
|
+
this.lastCodeSeparator = null;
|
|
355
563
|
this.context = 'LockingScript';
|
|
356
564
|
this.programCounter = 0;
|
|
357
565
|
}
|
|
@@ -364,12 +572,24 @@ export default class Spend {
|
|
|
364
572
|
if (currentOpcode === undefined) {
|
|
365
573
|
this.scriptEvaluationError(`Missing opcode in ${this.context} at pc=${this.programCounter}.`); // Error thrown
|
|
366
574
|
}
|
|
367
|
-
if (
|
|
368
|
-
this.scriptEvaluationError(`
|
|
575
|
+
if (operation.invalidLength === true) {
|
|
576
|
+
this.scriptEvaluationError(`Malformed push data in ${this.context} at pc=${this.programCounter}.`);
|
|
577
|
+
}
|
|
578
|
+
if (Array.isArray(operation.data) && operation.data.length > this.maxPushSize()) {
|
|
579
|
+
this.scriptEvaluationError(`Data push > ${this.maxPushSize()} bytes (pc=${this.programCounter}).`); // Error thrown
|
|
580
|
+
}
|
|
581
|
+
const isScriptExecuting = !this.returningFromConditional && !this.ifStack.includes(false);
|
|
582
|
+
if (this.hasExplicitFlags() &&
|
|
583
|
+
!this.isAfterGenesis() &&
|
|
584
|
+
!this.isAfterChronicle() &&
|
|
585
|
+
(currentOpcode === OP.OP_2MUL ||
|
|
586
|
+
currentOpcode === OP.OP_2DIV ||
|
|
587
|
+
currentOpcode === OP.OP_VERIF ||
|
|
588
|
+
currentOpcode === OP.OP_VERNOTIF)) {
|
|
589
|
+
this.scriptEvaluationError(`${OP[currentOpcode]} is disabled until Chronicle.`);
|
|
369
590
|
}
|
|
370
|
-
const isScriptExecuting = !this.ifStack.includes(false);
|
|
371
591
|
if (isScriptExecuting && currentOpcode >= 0 && currentOpcode <= OP.OP_PUSHDATA4) {
|
|
372
|
-
if (
|
|
592
|
+
if (this.shouldEnforceMinimalData() && !isChunkMinimalPushHelper(operation)) {
|
|
373
593
|
this.scriptEvaluationError(`This data is not minimally-encoded. (PC: ${this.programCounter})`); // Error thrown
|
|
374
594
|
}
|
|
375
595
|
this.pushStack(Array.isArray(operation.data) ? operation.data : []);
|
|
@@ -382,6 +602,51 @@ export default class Spend {
|
|
|
382
602
|
let bufSig, bufPubkey;
|
|
383
603
|
let sig, pubkey;
|
|
384
604
|
let i, ikey, isig, nKeysCount, nSigsCount, fOk;
|
|
605
|
+
if (isScriptExecuting && currentOpcode > OP.OP_16) {
|
|
606
|
+
this.executedOpCount++;
|
|
607
|
+
if (this.hasExplicitFlags() && !this.isAfterGenesis() && this.executedOpCount > maxOpsBeforeGenesis) {
|
|
608
|
+
this.scriptEvaluationError(`Script executed more than ${maxOpsBeforeGenesis} opcodes.`);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (this.hasExplicitFlags() && !this.isAfterChronicle()) {
|
|
612
|
+
if (isScriptExecuting &&
|
|
613
|
+
(currentOpcode === OP.OP_SUBSTR ||
|
|
614
|
+
currentOpcode === OP.OP_LEFT ||
|
|
615
|
+
currentOpcode === OP.OP_RIGHT ||
|
|
616
|
+
currentOpcode === OP.OP_LSHIFTNUM ||
|
|
617
|
+
currentOpcode === OP.OP_RSHIFTNUM)) {
|
|
618
|
+
if (this.hasFlag('DISCOURAGE_UPGRADABLE_NOPS')) {
|
|
619
|
+
this.scriptEvaluationError(`${OP[currentOpcode]} is discouraged by verification flags.`);
|
|
620
|
+
}
|
|
621
|
+
this.programCounter++;
|
|
622
|
+
return true;
|
|
623
|
+
}
|
|
624
|
+
if ((isScriptExecuting || !this.isAfterGenesis()) &&
|
|
625
|
+
(currentOpcode === OP.OP_2MUL || currentOpcode === OP.OP_2DIV)) {
|
|
626
|
+
this.scriptEvaluationError(`${OP[currentOpcode]} is disabled until Chronicle.`);
|
|
627
|
+
}
|
|
628
|
+
if ((isScriptExecuting || !this.isAfterGenesis()) &&
|
|
629
|
+
(currentOpcode === OP.OP_VER ||
|
|
630
|
+
currentOpcode === OP.OP_VERIF ||
|
|
631
|
+
currentOpcode === OP.OP_VERNOTIF)) {
|
|
632
|
+
this.scriptEvaluationError(`${OP[currentOpcode]} is disabled until Chronicle.`);
|
|
633
|
+
}
|
|
634
|
+
if (!isScriptExecuting &&
|
|
635
|
+
this.isAfterGenesis() &&
|
|
636
|
+
(currentOpcode === OP.OP_VERIF || currentOpcode === OP.OP_VERNOTIF)) {
|
|
637
|
+
this.programCounter++;
|
|
638
|
+
return true;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if (isScriptExecuting &&
|
|
642
|
+
this.hasFlag('DISCOURAGE_UPGRADABLE_NOPS') &&
|
|
643
|
+
(currentOpcode === OP.OP_NOP1 ||
|
|
644
|
+
currentOpcode === OP.OP_CHECKLOCKTIMEVERIFY ||
|
|
645
|
+
currentOpcode === OP.OP_CHECKSEQUENCEVERIFY ||
|
|
646
|
+
currentOpcode === OP.OP_NOP9 ||
|
|
647
|
+
currentOpcode === OP.OP_NOP10)) {
|
|
648
|
+
this.scriptEvaluationError(`${OP[currentOpcode]} is discouraged by verification flags.`);
|
|
649
|
+
}
|
|
385
650
|
switch (currentOpcode) {
|
|
386
651
|
case OP.OP_VER: {
|
|
387
652
|
// Node v1.2.0: pushes tx_version as a 4-byte little-endian integer (to_le encoding)
|
|
@@ -392,8 +657,8 @@ export default class Spend {
|
|
|
392
657
|
case OP.OP_SUBSTR: {
|
|
393
658
|
if (this.stack.length < 3)
|
|
394
659
|
this.scriptEvaluationError('OP_SUBSTR requires at least three items to be on the stack.');
|
|
395
|
-
const len =
|
|
396
|
-
const offset =
|
|
660
|
+
const len = this.readScriptNumber(this.popStack()).toNumber();
|
|
661
|
+
const offset = this.readScriptNumber(this.popStack()).toNumber();
|
|
397
662
|
buf = this.popStack();
|
|
398
663
|
const size = buf.length;
|
|
399
664
|
if (offset < 0 || offset >= size || len < 0 || len > size - offset) {
|
|
@@ -405,7 +670,7 @@ export default class Spend {
|
|
|
405
670
|
case OP.OP_LEFT: {
|
|
406
671
|
if (this.stack.length < 2)
|
|
407
672
|
this.scriptEvaluationError('OP_LEFT requires at least two items to be on the stack.');
|
|
408
|
-
const len =
|
|
673
|
+
const len = this.readScriptNumber(this.popStack()).toNumber();
|
|
409
674
|
buf = this.popStack();
|
|
410
675
|
const size = buf.length;
|
|
411
676
|
if (len < 0 || len > size) {
|
|
@@ -417,7 +682,7 @@ export default class Spend {
|
|
|
417
682
|
case OP.OP_RIGHT: {
|
|
418
683
|
if (this.stack.length < 2)
|
|
419
684
|
this.scriptEvaluationError('OP_RIGHT requires at least two items to be on the stack.');
|
|
420
|
-
const len =
|
|
685
|
+
const len = this.readScriptNumber(this.popStack()).toNumber();
|
|
421
686
|
buf = this.popStack();
|
|
422
687
|
const size = buf.length;
|
|
423
688
|
if (len < 0 || len > size) {
|
|
@@ -429,11 +694,11 @@ export default class Spend {
|
|
|
429
694
|
case OP.OP_LSHIFTNUM: {
|
|
430
695
|
if (this.stack.length < 2)
|
|
431
696
|
this.scriptEvaluationError('OP_LSHIFTNUM requires at least two items to be on the stack.');
|
|
432
|
-
const bits =
|
|
697
|
+
const bits = this.readScriptNumber(this.popStack()).toBigInt();
|
|
433
698
|
if (bits < 0) {
|
|
434
699
|
this.scriptEvaluationError('OP_LSHIFTNUM bits to shift must not be negative.');
|
|
435
700
|
}
|
|
436
|
-
const value =
|
|
701
|
+
const value = this.readScriptNumber(this.popStack()).toBigInt();
|
|
437
702
|
const resultBn = new BigNumber(value << bits);
|
|
438
703
|
this.pushStack(resultBn.toScriptNum());
|
|
439
704
|
break;
|
|
@@ -441,11 +706,11 @@ export default class Spend {
|
|
|
441
706
|
case OP.OP_RSHIFTNUM: {
|
|
442
707
|
if (this.stack.length < 2)
|
|
443
708
|
this.scriptEvaluationError('OP_RSHIFTNUM requires at least two items to be on the stack.');
|
|
444
|
-
const bits =
|
|
709
|
+
const bits = this.readScriptNumber(this.popStack()).toBigInt();
|
|
445
710
|
if (bits < 0) {
|
|
446
711
|
this.scriptEvaluationError('OP_RSHIFTNUM bits to shift must not be negative.');
|
|
447
712
|
}
|
|
448
|
-
const value =
|
|
713
|
+
const value = this.readScriptNumber(this.popStack()).toBigInt();
|
|
449
714
|
let resultBn;
|
|
450
715
|
if (value < 0) {
|
|
451
716
|
resultBn = new BigNumber(-(-value >> bits));
|
|
@@ -489,9 +754,27 @@ export default class Spend {
|
|
|
489
754
|
// OP_NOP2 (0xb1) = OP_CHECKLOCKTIMEVERIFY: on BSV post-genesis treated as NOP
|
|
490
755
|
// falls through
|
|
491
756
|
case OP.OP_CHECKLOCKTIMEVERIFY:
|
|
757
|
+
break;
|
|
492
758
|
// OP_NOP3 (0xb2) = OP_CHECKSEQUENCEVERIFY: on BSV post-genesis treated as NOP
|
|
493
|
-
// falls through
|
|
494
759
|
case OP.OP_CHECKSEQUENCEVERIFY:
|
|
760
|
+
if (this.hasFlag('CHECKSEQUENCEVERIFY')) {
|
|
761
|
+
if (this.stack.length < 1)
|
|
762
|
+
this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY requires at least one item to be on the stack.');
|
|
763
|
+
let sequenceLock = 0n;
|
|
764
|
+
try {
|
|
765
|
+
// BIP112 explicitly permits 5-byte script numbers so the disable flag can be represented.
|
|
766
|
+
sequenceLock = BigNumber.fromScriptNum(this.stackTop(), this.shouldEnforceMinimalData(), 5).toBigInt();
|
|
767
|
+
}
|
|
768
|
+
catch {
|
|
769
|
+
this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY requires a minimally-encoded numeric lock time.');
|
|
770
|
+
}
|
|
771
|
+
if (sequenceLock < 0n)
|
|
772
|
+
this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY requires a non-negative lock time.');
|
|
773
|
+
if ((Number(sequenceLock & BigInt(sequenceLocktimeDisableFlag)) === 0) && this.transactionVersion < 2) {
|
|
774
|
+
this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY lock time is unsatisfied.');
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
break;
|
|
495
778
|
case OP.OP_NOP9:
|
|
496
779
|
case OP.OP_NOP10:
|
|
497
780
|
break;
|
|
@@ -512,6 +795,7 @@ export default class Spend {
|
|
|
512
795
|
fValue = !fValue;
|
|
513
796
|
}
|
|
514
797
|
this.ifStack.push(fValue);
|
|
798
|
+
this.elseStack.push(false);
|
|
515
799
|
break;
|
|
516
800
|
case OP.OP_IF:
|
|
517
801
|
case OP.OP_NOTIF:
|
|
@@ -520,21 +804,30 @@ export default class Spend {
|
|
|
520
804
|
if (this.stack.length < 1)
|
|
521
805
|
this.scriptEvaluationError('OP_IF and OP_NOTIF require at least one item on the stack when they are used!');
|
|
522
806
|
buf = this.popStack();
|
|
807
|
+
if (this.hasFlag('MINIMALIF') && buf.length > 0 && !(buf.length === 1 && buf[0] === 1)) {
|
|
808
|
+
this.scriptEvaluationError('OP_IF and OP_NOTIF require minimal truth values.');
|
|
809
|
+
}
|
|
523
810
|
fValue = this.castToBool(buf);
|
|
524
811
|
if (currentOpcode === OP.OP_NOTIF)
|
|
525
812
|
fValue = !fValue;
|
|
526
813
|
}
|
|
527
814
|
this.ifStack.push(fValue);
|
|
815
|
+
this.elseStack.push(false);
|
|
528
816
|
break;
|
|
529
817
|
case OP.OP_ELSE:
|
|
530
818
|
if (this.ifStack.length === 0)
|
|
531
819
|
this.scriptEvaluationError('OP_ELSE requires a preceeding OP_IF.');
|
|
820
|
+
if (this.hasExplicitFlags() && this.isAfterGenesis() && this.elseStack[this.elseStack.length - 1]) {
|
|
821
|
+
this.scriptEvaluationError('OP_ELSE may only be used once for each OP_IF or OP_NOTIF after Genesis.');
|
|
822
|
+
}
|
|
823
|
+
this.elseStack[this.elseStack.length - 1] = true;
|
|
532
824
|
this.ifStack[this.ifStack.length - 1] = !(this.ifStack[this.ifStack.length - 1]);
|
|
533
825
|
break;
|
|
534
826
|
case OP.OP_ENDIF:
|
|
535
827
|
if (this.ifStack.length === 0)
|
|
536
828
|
this.scriptEvaluationError('OP_ENDIF requires a preceeding OP_IF.');
|
|
537
829
|
this.ifStack.pop();
|
|
830
|
+
this.elseStack.pop();
|
|
538
831
|
break;
|
|
539
832
|
case OP.OP_VERIFY:
|
|
540
833
|
if (this.stack.length < 1)
|
|
@@ -546,12 +839,19 @@ export default class Spend {
|
|
|
546
839
|
this.popStack();
|
|
547
840
|
break;
|
|
548
841
|
case OP.OP_RETURN:
|
|
549
|
-
if (this.
|
|
550
|
-
this.
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
842
|
+
if (this.hasExplicitFlags() && !this.isAfterGenesis()) {
|
|
843
|
+
this.scriptEvaluationError('OP_RETURN is invalid before Genesis.');
|
|
844
|
+
}
|
|
845
|
+
if (this.ifStack.length > 0) {
|
|
846
|
+
this.returningFromConditional = true;
|
|
847
|
+
}
|
|
848
|
+
else {
|
|
849
|
+
if (this.context === 'UnlockingScript')
|
|
850
|
+
this.programCounter = this.unlockingScript.chunks.length;
|
|
851
|
+
else
|
|
852
|
+
this.programCounter = this.lockingScript.chunks.length;
|
|
853
|
+
this.programCounter--; // To counteract the final increment and ensure loop termination
|
|
854
|
+
}
|
|
555
855
|
break;
|
|
556
856
|
case OP.OP_TOALTSTACK:
|
|
557
857
|
if (this.stack.length < 1)
|
|
@@ -662,7 +962,7 @@ export default class Spend {
|
|
|
662
962
|
case OP.OP_ROLL: {
|
|
663
963
|
if (this.stack.length < 2)
|
|
664
964
|
this.scriptEvaluationError(`${OP[currentOpcode]} requires at least two items to be on the stack.`);
|
|
665
|
-
bn =
|
|
965
|
+
bn = this.readScriptNumber(this.popStack());
|
|
666
966
|
const nBigInt = bn.toBigInt();
|
|
667
967
|
if (nBigInt < 0n || nBigInt >= BigInt(this.stack.length)) {
|
|
668
968
|
this.scriptEvaluationError(`${OP[currentOpcode]} requires the top stack element to be 0 or a positive number less than the current size of the stack.`);
|
|
@@ -748,7 +1048,7 @@ export default class Spend {
|
|
|
748
1048
|
case OP.OP_RSHIFT: {
|
|
749
1049
|
if (this.stack.length < 2)
|
|
750
1050
|
this.scriptEvaluationError(`${OP[currentOpcode]} requires at least two items to be on the stack.`);
|
|
751
|
-
bn2 =
|
|
1051
|
+
bn2 = this.readScriptNumber(this.popStack()); // n (shift amount)
|
|
752
1052
|
buf1 = this.popStack(); // value to shift
|
|
753
1053
|
const shiftBits = bn2.toBigInt();
|
|
754
1054
|
if (shiftBits < 0n)
|
|
@@ -796,7 +1096,7 @@ export default class Spend {
|
|
|
796
1096
|
case OP.OP_0NOTEQUAL:
|
|
797
1097
|
if (this.stack.length < 1)
|
|
798
1098
|
this.scriptEvaluationError(`${OP[currentOpcode]} requires at least one item to be on the stack.`);
|
|
799
|
-
bn =
|
|
1099
|
+
bn = this.readScriptNumber(this.popStack());
|
|
800
1100
|
switch (currentOpcode) {
|
|
801
1101
|
case OP.OP_1ADD:
|
|
802
1102
|
bn = bn.add(new BigNumber(1));
|
|
@@ -846,8 +1146,8 @@ export default class Spend {
|
|
|
846
1146
|
this.scriptEvaluationError(`${OP[currentOpcode]} requires at least two items to be on the stack.`);
|
|
847
1147
|
buf2 = this.popStack();
|
|
848
1148
|
buf1 = this.popStack();
|
|
849
|
-
bn2 =
|
|
850
|
-
bn1 =
|
|
1149
|
+
bn2 = this.readScriptNumber(buf2);
|
|
1150
|
+
bn1 = this.readScriptNumber(buf1);
|
|
851
1151
|
let predictedLen = 0;
|
|
852
1152
|
switch (currentOpcode) {
|
|
853
1153
|
case OP.OP_MUL:
|
|
@@ -927,9 +1227,9 @@ export default class Spend {
|
|
|
927
1227
|
case OP.OP_WITHIN:
|
|
928
1228
|
if (this.stack.length < 3)
|
|
929
1229
|
this.scriptEvaluationError('OP_WITHIN requires at least three items to be on the stack.');
|
|
930
|
-
bn3 =
|
|
931
|
-
bn2 =
|
|
932
|
-
bn1 =
|
|
1230
|
+
bn3 = this.readScriptNumber(this.popStack()); // max
|
|
1231
|
+
bn2 = this.readScriptNumber(this.popStack()); // min
|
|
1232
|
+
bn1 = this.readScriptNumber(this.popStack()); // x
|
|
933
1233
|
fValue = bn1.cmp(bn2) >= 0 && bn1.cmp(bn3) < 0;
|
|
934
1234
|
this.pushStack(fValue ? [1] : []);
|
|
935
1235
|
break;
|
|
@@ -971,7 +1271,7 @@ export default class Spend {
|
|
|
971
1271
|
fSuccess = false;
|
|
972
1272
|
if (bufSig.length > 0) {
|
|
973
1273
|
try {
|
|
974
|
-
sig =
|
|
1274
|
+
sig = this.parseChecksigSignature(bufSig);
|
|
975
1275
|
const scriptForChecksig = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript;
|
|
976
1276
|
const scriptCodeChunks = scriptForChecksig.chunks.slice(this.lastCodeSeparator === null ? 0 : this.lastCodeSeparator + 1);
|
|
977
1277
|
subscript = new Script(scriptCodeChunks);
|
|
@@ -983,6 +1283,9 @@ export default class Spend {
|
|
|
983
1283
|
fSuccess = false;
|
|
984
1284
|
}
|
|
985
1285
|
}
|
|
1286
|
+
if (!fSuccess && this.hasFlag('NULLFAIL') && bufSig.length > 0) {
|
|
1287
|
+
this.scriptEvaluationError(`${OP[currentOpcode]} requires failing signatures to be empty.`);
|
|
1288
|
+
}
|
|
986
1289
|
this.pushStack(fSuccess ? [1] : []);
|
|
987
1290
|
if (currentOpcode === OP.OP_CHECKSIGVERIFY) {
|
|
988
1291
|
if (!fSuccess)
|
|
@@ -997,10 +1300,13 @@ export default class Spend {
|
|
|
997
1300
|
if (this.stack.length < i) {
|
|
998
1301
|
this.scriptEvaluationError(`${OP[currentOpcode]} requires at least 1 item for nKeys.`);
|
|
999
1302
|
}
|
|
1000
|
-
const nKeysCountBN =
|
|
1303
|
+
const nKeysCountBN = this.readScriptNumber(this.stackTop(-i));
|
|
1001
1304
|
const nKeysCountBigInt = nKeysCountBN.toBigInt();
|
|
1002
|
-
|
|
1003
|
-
|
|
1305
|
+
const multisigKeyLimitBigInt = this.hasExplicitFlags() && !this.isAfterGenesis()
|
|
1306
|
+
? BigInt(maxMultisigKeyCountBeforeGenesis)
|
|
1307
|
+
: maxMultisigKeyCountBigInt;
|
|
1308
|
+
if (nKeysCountBigInt < 0n || nKeysCountBigInt > multisigKeyLimitBigInt) {
|
|
1309
|
+
this.scriptEvaluationError(`${OP[currentOpcode]} requires a key count between 0 and ${multisigKeyLimitBigInt.toString()}.`);
|
|
1004
1310
|
}
|
|
1005
1311
|
nKeysCount = Number(nKeysCountBigInt);
|
|
1006
1312
|
const declaredKeyCount = nKeysCount;
|
|
@@ -1009,7 +1315,7 @@ export default class Spend {
|
|
|
1009
1315
|
if (this.stack.length < i) {
|
|
1010
1316
|
this.scriptEvaluationError(`${OP[currentOpcode]} stack too small for nKeys and keys. Need ${i}, have ${this.stack.length}.`);
|
|
1011
1317
|
}
|
|
1012
|
-
const nSigsCountBN =
|
|
1318
|
+
const nSigsCountBN = this.readScriptNumber(this.stackTop(-i));
|
|
1013
1319
|
const nSigsCountBigInt = nSigsCountBN.toBigInt();
|
|
1014
1320
|
if (nSigsCountBigInt < 0n || nSigsCountBigInt > BigInt(nKeysCount)) {
|
|
1015
1321
|
this.scriptEvaluationError(`${OP[currentOpcode]} requires the number of signatures to be no greater than the number of keys.`);
|
|
@@ -1024,8 +1330,11 @@ export default class Spend {
|
|
|
1024
1330
|
const baseScriptCMS = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript;
|
|
1025
1331
|
const subscriptChunksCMS = baseScriptCMS.chunks.slice(this.lastCodeSeparator === null ? 0 : this.lastCodeSeparator + 1);
|
|
1026
1332
|
subscript = new Script(subscriptChunksCMS);
|
|
1333
|
+
let hasNonEmptySignature = false;
|
|
1027
1334
|
for (let k = 0; k < nSigsCount; k++) {
|
|
1028
1335
|
bufSig = this.stackTop(-isig - k); // Sigs are closer to top than keys
|
|
1336
|
+
if (bufSig.length > 0)
|
|
1337
|
+
hasNonEmptySignature = true;
|
|
1029
1338
|
subscript.findAndDelete(new Script().writeBin(bufSig));
|
|
1030
1339
|
}
|
|
1031
1340
|
fSuccess = true;
|
|
@@ -1042,7 +1351,7 @@ export default class Spend {
|
|
|
1042
1351
|
fOk = false;
|
|
1043
1352
|
if (bufSig.length > 0) {
|
|
1044
1353
|
try {
|
|
1045
|
-
sig =
|
|
1354
|
+
sig = this.parseChecksigSignature(bufSig);
|
|
1046
1355
|
pubkey = PublicKey.fromDER(bufPubkey);
|
|
1047
1356
|
fOk = this.verifySignature(sig, pubkey, subscript);
|
|
1048
1357
|
}
|
|
@@ -1060,6 +1369,9 @@ export default class Spend {
|
|
|
1060
1369
|
fSuccess = false;
|
|
1061
1370
|
}
|
|
1062
1371
|
}
|
|
1372
|
+
if (!fSuccess && this.hasFlag('NULLFAIL') && hasNonEmptySignature) {
|
|
1373
|
+
this.scriptEvaluationError(`${OP[currentOpcode]} requires failing signatures to be empty.`);
|
|
1374
|
+
}
|
|
1063
1375
|
// Correct total items consumed by op (N_val, keys, M_val, sigs, dummy)
|
|
1064
1376
|
const itemsConsumedByOp = 1 + // N_val
|
|
1065
1377
|
declaredKeyCount + // keys
|
|
@@ -1076,7 +1388,7 @@ export default class Spend {
|
|
|
1076
1388
|
this.scriptEvaluationError(`${OP[currentOpcode]} requires an extra item (dummy) to be on the stack.`);
|
|
1077
1389
|
}
|
|
1078
1390
|
const dummyBuf = this.popStack();
|
|
1079
|
-
if (
|
|
1391
|
+
if (this.shouldEnforceNullDummy() && dummyBuf.length > 0) { // SCRIPT_VERIFY_NULLDUMMY
|
|
1080
1392
|
this.scriptEvaluationError(`${OP[currentOpcode]} requires the extra stack item (dummy) to be empty.`);
|
|
1081
1393
|
}
|
|
1082
1394
|
this.pushStack(fSuccess ? [1] : []);
|
|
@@ -1093,8 +1405,8 @@ export default class Spend {
|
|
|
1093
1405
|
buf2 = this.popStack();
|
|
1094
1406
|
buf1 = this.popStack();
|
|
1095
1407
|
const catResult = (buf1).concat(buf2);
|
|
1096
|
-
if (catResult.length >
|
|
1097
|
-
this.scriptEvaluationError(`It's not currently possible to push data larger than ${
|
|
1408
|
+
if (catResult.length > this.maxPushSize())
|
|
1409
|
+
this.scriptEvaluationError(`It's not currently possible to push data larger than ${this.maxPushSize()} bytes.`);
|
|
1098
1410
|
this.pushStack(catResult);
|
|
1099
1411
|
break;
|
|
1100
1412
|
}
|
|
@@ -1103,7 +1415,7 @@ export default class Spend {
|
|
|
1103
1415
|
this.scriptEvaluationError('OP_SPLIT requires at least two items to be on the stack.');
|
|
1104
1416
|
const posBuf = this.popStack();
|
|
1105
1417
|
const dataToSplit = this.popStack();
|
|
1106
|
-
const splitIndexBigInt =
|
|
1418
|
+
const splitIndexBigInt = this.readScriptNumber(posBuf).toBigInt();
|
|
1107
1419
|
if (splitIndexBigInt < 0n || splitIndexBigInt > BigInt(dataToSplit.length)) {
|
|
1108
1420
|
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.');
|
|
1109
1421
|
}
|
|
@@ -1115,9 +1427,9 @@ export default class Spend {
|
|
|
1115
1427
|
case OP.OP_NUM2BIN: {
|
|
1116
1428
|
if (this.stack.length < 2)
|
|
1117
1429
|
this.scriptEvaluationError('OP_NUM2BIN requires at least two items to be on the stack.');
|
|
1118
|
-
const sizeBigInt =
|
|
1119
|
-
if (sizeBigInt > BigInt(
|
|
1120
|
-
this.scriptEvaluationError(`It's not currently possible to push data larger than ${
|
|
1430
|
+
const sizeBigInt = this.readScriptNumber(this.popStack()).toBigInt();
|
|
1431
|
+
if (sizeBigInt > BigInt(this.maxPushSize()) || sizeBigInt < 0n) { // size can be 0
|
|
1432
|
+
this.scriptEvaluationError(`It's not currently possible to push data larger than ${this.maxPushSize()} bytes or negative size.`);
|
|
1121
1433
|
}
|
|
1122
1434
|
size = Number(sizeBigInt);
|
|
1123
1435
|
let rawnum = this.popStack(); // This is the number to convert
|
|
@@ -1161,7 +1473,17 @@ export default class Spend {
|
|
|
1161
1473
|
this.scriptEvaluationError(`Invalid opcode ${currentOpcode} (pc=${this.programCounter}).`);
|
|
1162
1474
|
}
|
|
1163
1475
|
}
|
|
1164
|
-
this.
|
|
1476
|
+
if (this.returningFromConditional && this.ifStack.length === 0) {
|
|
1477
|
+
this.programCounter = currentScript.chunks.length;
|
|
1478
|
+
}
|
|
1479
|
+
else {
|
|
1480
|
+
this.programCounter++;
|
|
1481
|
+
}
|
|
1482
|
+
if (this.hasExplicitFlags() &&
|
|
1483
|
+
!this.isAfterGenesis() &&
|
|
1484
|
+
this.stack.length + this.altStack.length > maxStackItemsBeforeGenesis) {
|
|
1485
|
+
this.scriptEvaluationError(`Stack item count has exceeded ${maxStackItemsBeforeGenesis}.`);
|
|
1486
|
+
}
|
|
1165
1487
|
return true;
|
|
1166
1488
|
}
|
|
1167
1489
|
/**
|
|
@@ -1176,30 +1498,87 @@ export default class Spend {
|
|
|
1176
1498
|
* }
|
|
1177
1499
|
*/
|
|
1178
1500
|
validate() {
|
|
1179
|
-
if (
|
|
1501
|
+
if (this.shouldEnforceSigPushOnly() && !this.unlockingScript.isPushOnly()) {
|
|
1180
1502
|
this.scriptEvaluationError('Unlocking scripts can only contain push operations, and no other opcodes.');
|
|
1181
1503
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1504
|
+
const originalLockingScript = this.lockingScript;
|
|
1505
|
+
const shouldEvaluateP2SH = this.hasFlag('P2SH') &&
|
|
1506
|
+
!this.isAfterGenesis() &&
|
|
1507
|
+
this.isP2SHLockingScript(this.lockingScript);
|
|
1508
|
+
if (shouldEvaluateP2SH && !this.unlockingScript.isPushOnly()) {
|
|
1509
|
+
this.scriptEvaluationError('P2SH unlocking scripts can only contain push operations.');
|
|
1510
|
+
}
|
|
1511
|
+
this.reset();
|
|
1512
|
+
this.runScript('UnlockingScript');
|
|
1513
|
+
const stackAfterUnlockingScript = this.stack.map(item => item.slice());
|
|
1514
|
+
this.runScript('LockingScript');
|
|
1515
|
+
this.requireTruthyTopStack();
|
|
1516
|
+
try {
|
|
1517
|
+
if (shouldEvaluateP2SH) {
|
|
1518
|
+
if (stackAfterUnlockingScript.length === 0) {
|
|
1519
|
+
this.scriptEvaluationError('P2SH evaluation requires a redeem script on the stack.');
|
|
1520
|
+
}
|
|
1521
|
+
const redeemScriptBytes = stackAfterUnlockingScript.pop();
|
|
1522
|
+
if (redeemScriptBytes === undefined) {
|
|
1523
|
+
this.scriptEvaluationError('P2SH evaluation requires a redeem script on the stack.');
|
|
1524
|
+
return false;
|
|
1525
|
+
}
|
|
1526
|
+
this.setStack(stackAfterUnlockingScript);
|
|
1527
|
+
const redeemScript = Script.fromBinary(redeemScriptBytes);
|
|
1528
|
+
this.lockingScript = new LockingScript(redeemScript.chunks);
|
|
1529
|
+
this.runScript('LockingScript');
|
|
1186
1530
|
}
|
|
1187
1531
|
}
|
|
1532
|
+
finally {
|
|
1533
|
+
this.lockingScript = originalLockingScript;
|
|
1534
|
+
}
|
|
1535
|
+
if (this.shouldEnforceCleanStack() && this.stack.length !== 1) {
|
|
1536
|
+
this.scriptEvaluationError(`The clean stack rule requires exactly one item to be on the stack after script execution, found ${this.stack.length}.`);
|
|
1537
|
+
}
|
|
1538
|
+
this.requireTruthyTopStack();
|
|
1539
|
+
return true;
|
|
1540
|
+
}
|
|
1541
|
+
runScript(context) {
|
|
1542
|
+
this.context = context;
|
|
1543
|
+
this.programCounter = 0;
|
|
1544
|
+
this.ifStack = [];
|
|
1545
|
+
this.elseStack = [];
|
|
1546
|
+
this.returningFromConditional = false;
|
|
1547
|
+
this.clearAltStack();
|
|
1548
|
+
this.lastCodeSeparator = null;
|
|
1549
|
+
const script = context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript;
|
|
1550
|
+
if (this.hasExplicitFlags() &&
|
|
1551
|
+
!this.isAfterGenesis() &&
|
|
1552
|
+
script.toUint8Array().length > maxScriptSizeBeforeGenesis) {
|
|
1553
|
+
this.scriptEvaluationError(`Script size exceeds ${maxScriptSizeBeforeGenesis} bytes.`);
|
|
1554
|
+
}
|
|
1555
|
+
while (this.programCounter < script.chunks.length) {
|
|
1556
|
+
this.step();
|
|
1557
|
+
}
|
|
1188
1558
|
if (this.ifStack.length > 0) {
|
|
1189
1559
|
this.scriptEvaluationError('Every OP_IF, OP_NOTIF, or OP_ELSE must be terminated with OP_ENDIF prior to the end of the script.');
|
|
1190
1560
|
}
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1561
|
+
this.ifStack = [];
|
|
1562
|
+
this.elseStack = [];
|
|
1563
|
+
this.clearAltStack();
|
|
1564
|
+
this.lastCodeSeparator = null;
|
|
1565
|
+
}
|
|
1566
|
+
isP2SHLockingScript(script) {
|
|
1567
|
+
const chunks = script.chunks;
|
|
1568
|
+
return chunks.length === 3 &&
|
|
1569
|
+
chunks[0].op === OP.OP_HASH160 &&
|
|
1570
|
+
chunks[1].op === 20 &&
|
|
1571
|
+
Array.isArray(chunks[1].data) &&
|
|
1572
|
+
chunks[1].data.length === 20 &&
|
|
1573
|
+
chunks[2].op === OP.OP_EQUAL;
|
|
1574
|
+
}
|
|
1575
|
+
requireTruthyTopStack() {
|
|
1196
1576
|
if (this.stack.length === 0) {
|
|
1197
1577
|
this.scriptEvaluationError('The top stack element must be truthy after script evaluation (stack is empty).');
|
|
1198
1578
|
}
|
|
1199
1579
|
else if (!this.castToBool(this.stackTop())) {
|
|
1200
1580
|
this.scriptEvaluationError('The top stack element must be truthy after script evaluation.');
|
|
1201
1581
|
}
|
|
1202
|
-
return true;
|
|
1203
1582
|
}
|
|
1204
1583
|
castToBool(val) {
|
|
1205
1584
|
if (val.length === 0)
|