@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
|
@@ -36,6 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const LockingScript_js_1 = __importDefault(require("./LockingScript.js"));
|
|
39
40
|
const Script_js_1 = __importDefault(require("./Script.js"));
|
|
40
41
|
const BigNumber_js_1 = __importDefault(require("../primitives/BigNumber.js"));
|
|
41
42
|
const OP_js_1 = __importDefault(require("./OP.js"));
|
|
@@ -47,8 +48,14 @@ const PublicKey_js_1 = __importDefault(require("../primitives/PublicKey.js"));
|
|
|
47
48
|
const ECDSA_js_1 = require("../primitives/ECDSA.js");
|
|
48
49
|
// These constants control the current behavior of the interpreter.
|
|
49
50
|
const maxScriptElementSize = 1024 * 1024 * 1024;
|
|
51
|
+
const maxScriptElementSizeBeforeGenesis = 520;
|
|
52
|
+
const maxScriptSizeBeforeGenesis = 10000;
|
|
53
|
+
const maxOpsBeforeGenesis = 500;
|
|
54
|
+
const maxStackItemsBeforeGenesis = 1000;
|
|
50
55
|
const maxMultisigKeyCount = Math.pow(2, 31) - 1;
|
|
51
56
|
const maxMultisigKeyCountBigInt = BigInt(maxMultisigKeyCount);
|
|
57
|
+
const maxMultisigKeyCountBeforeGenesis = 20;
|
|
58
|
+
const sequenceLocktimeDisableFlag = 0x80000000;
|
|
52
59
|
// --- Optimization: Pre-computed script numbers ---
|
|
53
60
|
const SCRIPTNUM_NEG_1 = Object.freeze(new BigNumber_js_1.default(-1).toScriptNum());
|
|
54
61
|
const SCRIPTNUMS_0_TO_16 = Object.freeze(Array.from({ length: 17 }, (_, i) => Object.freeze(new BigNumber_js_1.default(i).toScriptNum())));
|
|
@@ -204,11 +211,21 @@ class Spend {
|
|
|
204
211
|
this.lockTime = params.lockTime;
|
|
205
212
|
this.memoryLimit = params.memoryLimit ?? 32000000;
|
|
206
213
|
this.isRelaxedOverride = params.isRelaxed === true;
|
|
214
|
+
this.verifyFlags = params.verifyFlags === undefined
|
|
215
|
+
? undefined
|
|
216
|
+
: new Set((Array.isArray(params.verifyFlags)
|
|
217
|
+
? params.verifyFlags
|
|
218
|
+
: params.verifyFlags.split(','))
|
|
219
|
+
.map(flag => flag.trim())
|
|
220
|
+
.filter(flag => flag.length > 0));
|
|
207
221
|
this.stack = [];
|
|
208
222
|
this.altStack = [];
|
|
209
223
|
this.ifStack = [];
|
|
224
|
+
this.elseStack = [];
|
|
210
225
|
this.stackMem = 0;
|
|
211
226
|
this.altStackMem = 0;
|
|
227
|
+
this.executedOpCount = 0;
|
|
228
|
+
this.returningFromConditional = false;
|
|
212
229
|
this.sigHashCache = { hashOutputsSingle: new Map() };
|
|
213
230
|
this.reset();
|
|
214
231
|
}
|
|
@@ -216,6 +233,75 @@ class Spend {
|
|
|
216
233
|
return this.isRelaxedOverride ||
|
|
217
234
|
(this.transactionVersion > 1);
|
|
218
235
|
}
|
|
236
|
+
hasExplicitFlags() {
|
|
237
|
+
return this.verifyFlags !== undefined;
|
|
238
|
+
}
|
|
239
|
+
hasFlag(flag) {
|
|
240
|
+
return this.verifyFlags?.has(flag) === true;
|
|
241
|
+
}
|
|
242
|
+
isAfterGenesis() {
|
|
243
|
+
if (this.hasExplicitFlags()) {
|
|
244
|
+
return this.hasFlag('GENESIS') ||
|
|
245
|
+
this.hasFlag('UTXO_AFTER_GENESIS') ||
|
|
246
|
+
this.hasFlag('UTXO_AFTER_CHRONICLE');
|
|
247
|
+
}
|
|
248
|
+
return this.isRelaxed();
|
|
249
|
+
}
|
|
250
|
+
isAfterChronicle() {
|
|
251
|
+
if (this.hasExplicitFlags())
|
|
252
|
+
return this.hasFlag('UTXO_AFTER_CHRONICLE');
|
|
253
|
+
return this.isRelaxed();
|
|
254
|
+
}
|
|
255
|
+
shouldEnforceMinimalData() {
|
|
256
|
+
if (this.hasExplicitFlags())
|
|
257
|
+
return this.hasFlag('MINIMALDATA');
|
|
258
|
+
return !this.isRelaxed();
|
|
259
|
+
}
|
|
260
|
+
shouldEnforceLowS() {
|
|
261
|
+
if (this.hasExplicitFlags())
|
|
262
|
+
return this.hasFlag('LOW_S');
|
|
263
|
+
return !this.isRelaxed();
|
|
264
|
+
}
|
|
265
|
+
shouldEnforceNullDummy() {
|
|
266
|
+
if (this.hasExplicitFlags())
|
|
267
|
+
return this.hasFlag('NULLDUMMY');
|
|
268
|
+
return !this.isRelaxed();
|
|
269
|
+
}
|
|
270
|
+
shouldEnforceSigPushOnly() {
|
|
271
|
+
if (this.hasExplicitFlags())
|
|
272
|
+
return this.hasFlag('SIGPUSHONLY');
|
|
273
|
+
return !this.isRelaxed();
|
|
274
|
+
}
|
|
275
|
+
shouldEnforceCleanStack() {
|
|
276
|
+
if (this.hasExplicitFlags())
|
|
277
|
+
return this.hasFlag('CLEANSTACK');
|
|
278
|
+
return !this.isRelaxed();
|
|
279
|
+
}
|
|
280
|
+
shouldEnforceDerSignatures() {
|
|
281
|
+
if (this.hasExplicitFlags()) {
|
|
282
|
+
return this.hasFlag('DERSIG') ||
|
|
283
|
+
this.hasFlag('STRICTENC') ||
|
|
284
|
+
this.hasFlag('LOW_S') ||
|
|
285
|
+
this.hasFlag('SIGHASH_FORKID');
|
|
286
|
+
}
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
shouldEnforceStrictEncoding() {
|
|
290
|
+
if (this.hasExplicitFlags()) {
|
|
291
|
+
return this.hasFlag('STRICTENC') || this.hasFlag('SIGHASH_FORKID');
|
|
292
|
+
}
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
scriptNumMaxSize() {
|
|
296
|
+
if (this.hasExplicitFlags() && !this.isAfterGenesis())
|
|
297
|
+
return 4;
|
|
298
|
+
return undefined;
|
|
299
|
+
}
|
|
300
|
+
maxPushSize() {
|
|
301
|
+
if (this.hasExplicitFlags() && !this.isAfterGenesis())
|
|
302
|
+
return maxScriptElementSizeBeforeGenesis;
|
|
303
|
+
return maxScriptElementSize;
|
|
304
|
+
}
|
|
219
305
|
reset() {
|
|
220
306
|
this.context = 'UnlockingScript';
|
|
221
307
|
this.programCounter = 0;
|
|
@@ -223,8 +309,11 @@ class Spend {
|
|
|
223
309
|
this.stack = [];
|
|
224
310
|
this.altStack = [];
|
|
225
311
|
this.ifStack = [];
|
|
312
|
+
this.elseStack = [];
|
|
226
313
|
this.stackMem = 0;
|
|
227
314
|
this.altStackMem = 0;
|
|
315
|
+
this.executedOpCount = 0;
|
|
316
|
+
this.returningFromConditional = false;
|
|
228
317
|
this.sigHashCache = { hashOutputsSingle: new Map() };
|
|
229
318
|
}
|
|
230
319
|
ensureStackMem(additional) {
|
|
@@ -268,6 +357,14 @@ class Spend {
|
|
|
268
357
|
}
|
|
269
358
|
return this.stack[this.stack.length + index];
|
|
270
359
|
}
|
|
360
|
+
setStack(items) {
|
|
361
|
+
this.stack = items.map(item => item.slice());
|
|
362
|
+
this.stackMem = this.stack.reduce((total, item) => total + item.length, 0);
|
|
363
|
+
}
|
|
364
|
+
clearAltStack() {
|
|
365
|
+
this.altStack = [];
|
|
366
|
+
this.altStackMem = 0;
|
|
367
|
+
}
|
|
271
368
|
pushAltStack(item) {
|
|
272
369
|
this.ensureAltStackMem(item.length);
|
|
273
370
|
this.altStack.push(item);
|
|
@@ -285,27 +382,124 @@ class Spend {
|
|
|
285
382
|
this.altStackMem -= item.length;
|
|
286
383
|
return item;
|
|
287
384
|
}
|
|
385
|
+
readScriptNumber(buf) {
|
|
386
|
+
try {
|
|
387
|
+
return BigNumber_js_1.default.fromScriptNum(buf, this.shouldEnforceMinimalData(), this.scriptNumMaxSize());
|
|
388
|
+
}
|
|
389
|
+
catch (e) {
|
|
390
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
391
|
+
this.scriptEvaluationError(message);
|
|
392
|
+
}
|
|
393
|
+
return new BigNumber_js_1.default(0);
|
|
394
|
+
}
|
|
395
|
+
isDefinedHashType(scope) {
|
|
396
|
+
const baseType = scope & 0x1f;
|
|
397
|
+
return baseType >= TransactionSignature_js_1.default.SIGHASH_ALL &&
|
|
398
|
+
baseType <= TransactionSignature_js_1.default.SIGHASH_SINGLE;
|
|
399
|
+
}
|
|
288
400
|
checkSignatureEncoding(buf) {
|
|
289
401
|
if (buf.length === 0)
|
|
290
402
|
return true;
|
|
291
|
-
|
|
403
|
+
const enforceDer = this.shouldEnforceDerSignatures();
|
|
404
|
+
if (enforceDer && !isChecksigFormatHelper(buf)) {
|
|
292
405
|
this.scriptEvaluationError('The signature format is invalid.'); // Generic message like original
|
|
293
406
|
return false;
|
|
294
407
|
}
|
|
295
408
|
try {
|
|
296
409
|
const sig = TransactionSignature_js_1.default.fromChecksigFormat(buf); // This can throw for stricter DER rules
|
|
297
|
-
if (
|
|
410
|
+
if (this.shouldEnforceStrictEncoding() && !this.isDefinedHashType(sig.scope)) {
|
|
411
|
+
this.scriptEvaluationError('The signature hash type is invalid.');
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
if (this.shouldEnforceStrictEncoding() &&
|
|
415
|
+
(sig.scope & TransactionSignature_js_1.default.SIGHASH_CHRONICLE) !== 0 &&
|
|
416
|
+
!this.isAfterChronicle()) {
|
|
417
|
+
this.scriptEvaluationError('The signature hash type is invalid before Chronicle.');
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
const hasForkId = (sig.scope & TransactionSignature_js_1.default.SIGHASH_FORKID) !== 0;
|
|
421
|
+
if (this.hasExplicitFlags()) {
|
|
422
|
+
if (this.hasFlag('SIGHASH_FORKID') && !hasForkId) {
|
|
423
|
+
this.scriptEvaluationError('The signature must use SIGHASH_FORKID.');
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
if (!this.hasFlag('SIGHASH_FORKID') && !this.isAfterGenesis() && hasForkId) {
|
|
427
|
+
this.scriptEvaluationError('The signature must not use SIGHASH_FORKID.');
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (this.shouldEnforceLowS() && !sig.hasLowS()) {
|
|
298
432
|
this.scriptEvaluationError('The signature must have a low S value.');
|
|
299
433
|
return false;
|
|
300
434
|
}
|
|
301
435
|
}
|
|
302
436
|
catch {
|
|
303
|
-
|
|
304
|
-
|
|
437
|
+
if (enforceDer) {
|
|
438
|
+
this.scriptEvaluationError('The signature format is invalid.');
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
305
441
|
}
|
|
306
442
|
return true;
|
|
307
443
|
}
|
|
444
|
+
parseChecksigSignature(buf) {
|
|
445
|
+
try {
|
|
446
|
+
return TransactionSignature_js_1.default.fromChecksigFormat(buf);
|
|
447
|
+
}
|
|
448
|
+
catch (e) {
|
|
449
|
+
if (this.shouldEnforceDerSignatures())
|
|
450
|
+
throw e;
|
|
451
|
+
return this.parseLaxChecksigSignature(buf);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
readLaxDERLength(buf, position) {
|
|
455
|
+
const first = buf[position.value++];
|
|
456
|
+
if (first === undefined)
|
|
457
|
+
throw new Error('Invalid DER length');
|
|
458
|
+
if ((first & 0x80) === 0)
|
|
459
|
+
return first;
|
|
460
|
+
const lengthBytes = first & 0x7f;
|
|
461
|
+
if (lengthBytes === 0 || position.value + lengthBytes > buf.length) {
|
|
462
|
+
throw new Error('Invalid DER length');
|
|
463
|
+
}
|
|
464
|
+
let length = 0;
|
|
465
|
+
for (let i = 0; i < lengthBytes; i++) {
|
|
466
|
+
length = (length << 8) | (buf[position.value++] ?? 0);
|
|
467
|
+
}
|
|
468
|
+
return length;
|
|
469
|
+
}
|
|
470
|
+
parseLaxDERInteger(buf, position, sequenceEnd) {
|
|
471
|
+
if (position.value >= sequenceEnd || buf[position.value++] !== 0x02) {
|
|
472
|
+
throw new Error('Invalid DER integer');
|
|
473
|
+
}
|
|
474
|
+
const length = this.readLaxDERLength(buf, position);
|
|
475
|
+
if (position.value + length > sequenceEnd) {
|
|
476
|
+
throw new Error('Invalid DER integer length');
|
|
477
|
+
}
|
|
478
|
+
let bytes = buf.slice(position.value, position.value + length);
|
|
479
|
+
position.value += length;
|
|
480
|
+
while (bytes.length > 1 && bytes[0] === 0)
|
|
481
|
+
bytes = bytes.slice(1);
|
|
482
|
+
if (bytes.length === 0)
|
|
483
|
+
bytes = [0];
|
|
484
|
+
return new BigNumber_js_1.default(bytes);
|
|
485
|
+
}
|
|
486
|
+
parseLaxChecksigSignature(buf) {
|
|
487
|
+
if (buf.length === 0)
|
|
488
|
+
return TransactionSignature_js_1.default.fromChecksigFormat(buf);
|
|
489
|
+
const scope = buf[buf.length - 1];
|
|
490
|
+
const der = buf.slice(0, -1);
|
|
491
|
+
const position = { value: 0 };
|
|
492
|
+
if (der[position.value++] !== 0x30)
|
|
493
|
+
throw new Error('Signature DER must start with 0x30');
|
|
494
|
+
const sequenceLength = this.readLaxDERLength(der, position);
|
|
495
|
+
const sequenceEnd = Math.min(position.value + sequenceLength, der.length);
|
|
496
|
+
const r = this.parseLaxDERInteger(der, position, sequenceEnd);
|
|
497
|
+
const s = this.parseLaxDERInteger(der, position, sequenceEnd);
|
|
498
|
+
return new TransactionSignature_js_1.default(r, s, scope);
|
|
499
|
+
}
|
|
308
500
|
checkPublicKeyEncoding(buf) {
|
|
501
|
+
if (!this.shouldEnforceStrictEncoding())
|
|
502
|
+
return true;
|
|
309
503
|
if (buf.length === 0) {
|
|
310
504
|
this.scriptEvaluationError('Public key is empty.');
|
|
311
505
|
return false;
|
|
@@ -340,7 +534,7 @@ class Spend {
|
|
|
340
534
|
return true;
|
|
341
535
|
}
|
|
342
536
|
verifySignature(sig, pubkey, subscript) {
|
|
343
|
-
const
|
|
537
|
+
const params = {
|
|
344
538
|
sourceTXID: this.sourceTXID,
|
|
345
539
|
sourceOutputIndex: this.sourceOutputIndex,
|
|
346
540
|
sourceSatoshis: this.sourceSatoshis,
|
|
@@ -353,8 +547,10 @@ class Spend {
|
|
|
353
547
|
lockTime: this.lockTime,
|
|
354
548
|
scope: sig.scope,
|
|
355
549
|
cache: this.sigHashCache
|
|
356
|
-
}
|
|
357
|
-
const hash =
|
|
550
|
+
};
|
|
551
|
+
const hash = TransactionSignature_js_1.default.usesOtdaSingleBug(params)
|
|
552
|
+
? new BigNumber_js_1.default([1, ...new Array(31).fill(0)])
|
|
553
|
+
: new BigNumber_js_1.default(Hash.hash256(TransactionSignature_js_1.default.formatBytes(params)));
|
|
358
554
|
return (0, ECDSA_js_1.verify)(hash, sig, pubkey);
|
|
359
555
|
}
|
|
360
556
|
step() {
|
|
@@ -368,6 +564,14 @@ class Spend {
|
|
|
368
564
|
}
|
|
369
565
|
if (this.context === 'UnlockingScript' &&
|
|
370
566
|
this.programCounter >= this.unlockingScript.chunks.length) {
|
|
567
|
+
if (this.ifStack.length > 0) {
|
|
568
|
+
this.scriptEvaluationError('Every OP_IF, OP_NOTIF, or OP_ELSE must be terminated with OP_ENDIF prior to the end of the unlocking script.');
|
|
569
|
+
}
|
|
570
|
+
this.clearAltStack();
|
|
571
|
+
this.ifStack = [];
|
|
572
|
+
this.elseStack = [];
|
|
573
|
+
this.returningFromConditional = false;
|
|
574
|
+
this.lastCodeSeparator = null;
|
|
371
575
|
this.context = 'LockingScript';
|
|
372
576
|
this.programCounter = 0;
|
|
373
577
|
}
|
|
@@ -380,12 +584,24 @@ class Spend {
|
|
|
380
584
|
if (currentOpcode === undefined) {
|
|
381
585
|
this.scriptEvaluationError(`Missing opcode in ${this.context} at pc=${this.programCounter}.`); // Error thrown
|
|
382
586
|
}
|
|
383
|
-
if (
|
|
384
|
-
this.scriptEvaluationError(`
|
|
587
|
+
if (operation.invalidLength === true) {
|
|
588
|
+
this.scriptEvaluationError(`Malformed push data in ${this.context} at pc=${this.programCounter}.`);
|
|
589
|
+
}
|
|
590
|
+
if (Array.isArray(operation.data) && operation.data.length > this.maxPushSize()) {
|
|
591
|
+
this.scriptEvaluationError(`Data push > ${this.maxPushSize()} bytes (pc=${this.programCounter}).`); // Error thrown
|
|
592
|
+
}
|
|
593
|
+
const isScriptExecuting = !this.returningFromConditional && !this.ifStack.includes(false);
|
|
594
|
+
if (this.hasExplicitFlags() &&
|
|
595
|
+
!this.isAfterGenesis() &&
|
|
596
|
+
!this.isAfterChronicle() &&
|
|
597
|
+
(currentOpcode === OP_js_1.default.OP_2MUL ||
|
|
598
|
+
currentOpcode === OP_js_1.default.OP_2DIV ||
|
|
599
|
+
currentOpcode === OP_js_1.default.OP_VERIF ||
|
|
600
|
+
currentOpcode === OP_js_1.default.OP_VERNOTIF)) {
|
|
601
|
+
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} is disabled until Chronicle.`);
|
|
385
602
|
}
|
|
386
|
-
const isScriptExecuting = !this.ifStack.includes(false);
|
|
387
603
|
if (isScriptExecuting && currentOpcode >= 0 && currentOpcode <= OP_js_1.default.OP_PUSHDATA4) {
|
|
388
|
-
if (
|
|
604
|
+
if (this.shouldEnforceMinimalData() && !isChunkMinimalPushHelper(operation)) {
|
|
389
605
|
this.scriptEvaluationError(`This data is not minimally-encoded. (PC: ${this.programCounter})`); // Error thrown
|
|
390
606
|
}
|
|
391
607
|
this.pushStack(Array.isArray(operation.data) ? operation.data : []);
|
|
@@ -398,6 +614,51 @@ class Spend {
|
|
|
398
614
|
let bufSig, bufPubkey;
|
|
399
615
|
let sig, pubkey;
|
|
400
616
|
let i, ikey, isig, nKeysCount, nSigsCount, fOk;
|
|
617
|
+
if (isScriptExecuting && currentOpcode > OP_js_1.default.OP_16) {
|
|
618
|
+
this.executedOpCount++;
|
|
619
|
+
if (this.hasExplicitFlags() && !this.isAfterGenesis() && this.executedOpCount > maxOpsBeforeGenesis) {
|
|
620
|
+
this.scriptEvaluationError(`Script executed more than ${maxOpsBeforeGenesis} opcodes.`);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (this.hasExplicitFlags() && !this.isAfterChronicle()) {
|
|
624
|
+
if (isScriptExecuting &&
|
|
625
|
+
(currentOpcode === OP_js_1.default.OP_SUBSTR ||
|
|
626
|
+
currentOpcode === OP_js_1.default.OP_LEFT ||
|
|
627
|
+
currentOpcode === OP_js_1.default.OP_RIGHT ||
|
|
628
|
+
currentOpcode === OP_js_1.default.OP_LSHIFTNUM ||
|
|
629
|
+
currentOpcode === OP_js_1.default.OP_RSHIFTNUM)) {
|
|
630
|
+
if (this.hasFlag('DISCOURAGE_UPGRADABLE_NOPS')) {
|
|
631
|
+
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} is discouraged by verification flags.`);
|
|
632
|
+
}
|
|
633
|
+
this.programCounter++;
|
|
634
|
+
return true;
|
|
635
|
+
}
|
|
636
|
+
if ((isScriptExecuting || !this.isAfterGenesis()) &&
|
|
637
|
+
(currentOpcode === OP_js_1.default.OP_2MUL || currentOpcode === OP_js_1.default.OP_2DIV)) {
|
|
638
|
+
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} is disabled until Chronicle.`);
|
|
639
|
+
}
|
|
640
|
+
if ((isScriptExecuting || !this.isAfterGenesis()) &&
|
|
641
|
+
(currentOpcode === OP_js_1.default.OP_VER ||
|
|
642
|
+
currentOpcode === OP_js_1.default.OP_VERIF ||
|
|
643
|
+
currentOpcode === OP_js_1.default.OP_VERNOTIF)) {
|
|
644
|
+
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} is disabled until Chronicle.`);
|
|
645
|
+
}
|
|
646
|
+
if (!isScriptExecuting &&
|
|
647
|
+
this.isAfterGenesis() &&
|
|
648
|
+
(currentOpcode === OP_js_1.default.OP_VERIF || currentOpcode === OP_js_1.default.OP_VERNOTIF)) {
|
|
649
|
+
this.programCounter++;
|
|
650
|
+
return true;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (isScriptExecuting &&
|
|
654
|
+
this.hasFlag('DISCOURAGE_UPGRADABLE_NOPS') &&
|
|
655
|
+
(currentOpcode === OP_js_1.default.OP_NOP1 ||
|
|
656
|
+
currentOpcode === OP_js_1.default.OP_CHECKLOCKTIMEVERIFY ||
|
|
657
|
+
currentOpcode === OP_js_1.default.OP_CHECKSEQUENCEVERIFY ||
|
|
658
|
+
currentOpcode === OP_js_1.default.OP_NOP9 ||
|
|
659
|
+
currentOpcode === OP_js_1.default.OP_NOP10)) {
|
|
660
|
+
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} is discouraged by verification flags.`);
|
|
661
|
+
}
|
|
401
662
|
switch (currentOpcode) {
|
|
402
663
|
case OP_js_1.default.OP_VER: {
|
|
403
664
|
// Node v1.2.0: pushes tx_version as a 4-byte little-endian integer (to_le encoding)
|
|
@@ -408,8 +669,8 @@ class Spend {
|
|
|
408
669
|
case OP_js_1.default.OP_SUBSTR: {
|
|
409
670
|
if (this.stack.length < 3)
|
|
410
671
|
this.scriptEvaluationError('OP_SUBSTR requires at least three items to be on the stack.');
|
|
411
|
-
const len =
|
|
412
|
-
const offset =
|
|
672
|
+
const len = this.readScriptNumber(this.popStack()).toNumber();
|
|
673
|
+
const offset = this.readScriptNumber(this.popStack()).toNumber();
|
|
413
674
|
buf = this.popStack();
|
|
414
675
|
const size = buf.length;
|
|
415
676
|
if (offset < 0 || offset >= size || len < 0 || len > size - offset) {
|
|
@@ -421,7 +682,7 @@ class Spend {
|
|
|
421
682
|
case OP_js_1.default.OP_LEFT: {
|
|
422
683
|
if (this.stack.length < 2)
|
|
423
684
|
this.scriptEvaluationError('OP_LEFT requires at least two items to be on the stack.');
|
|
424
|
-
const len =
|
|
685
|
+
const len = this.readScriptNumber(this.popStack()).toNumber();
|
|
425
686
|
buf = this.popStack();
|
|
426
687
|
const size = buf.length;
|
|
427
688
|
if (len < 0 || len > size) {
|
|
@@ -433,7 +694,7 @@ class Spend {
|
|
|
433
694
|
case OP_js_1.default.OP_RIGHT: {
|
|
434
695
|
if (this.stack.length < 2)
|
|
435
696
|
this.scriptEvaluationError('OP_RIGHT requires at least two items to be on the stack.');
|
|
436
|
-
const len =
|
|
697
|
+
const len = this.readScriptNumber(this.popStack()).toNumber();
|
|
437
698
|
buf = this.popStack();
|
|
438
699
|
const size = buf.length;
|
|
439
700
|
if (len < 0 || len > size) {
|
|
@@ -445,11 +706,11 @@ class Spend {
|
|
|
445
706
|
case OP_js_1.default.OP_LSHIFTNUM: {
|
|
446
707
|
if (this.stack.length < 2)
|
|
447
708
|
this.scriptEvaluationError('OP_LSHIFTNUM requires at least two items to be on the stack.');
|
|
448
|
-
const bits =
|
|
709
|
+
const bits = this.readScriptNumber(this.popStack()).toBigInt();
|
|
449
710
|
if (bits < 0) {
|
|
450
711
|
this.scriptEvaluationError('OP_LSHIFTNUM bits to shift must not be negative.');
|
|
451
712
|
}
|
|
452
|
-
const value =
|
|
713
|
+
const value = this.readScriptNumber(this.popStack()).toBigInt();
|
|
453
714
|
const resultBn = new BigNumber_js_1.default(value << bits);
|
|
454
715
|
this.pushStack(resultBn.toScriptNum());
|
|
455
716
|
break;
|
|
@@ -457,11 +718,11 @@ class Spend {
|
|
|
457
718
|
case OP_js_1.default.OP_RSHIFTNUM: {
|
|
458
719
|
if (this.stack.length < 2)
|
|
459
720
|
this.scriptEvaluationError('OP_RSHIFTNUM requires at least two items to be on the stack.');
|
|
460
|
-
const bits =
|
|
721
|
+
const bits = this.readScriptNumber(this.popStack()).toBigInt();
|
|
461
722
|
if (bits < 0) {
|
|
462
723
|
this.scriptEvaluationError('OP_RSHIFTNUM bits to shift must not be negative.');
|
|
463
724
|
}
|
|
464
|
-
const value =
|
|
725
|
+
const value = this.readScriptNumber(this.popStack()).toBigInt();
|
|
465
726
|
let resultBn;
|
|
466
727
|
if (value < 0) {
|
|
467
728
|
resultBn = new BigNumber_js_1.default(-(-value >> bits));
|
|
@@ -505,9 +766,27 @@ class Spend {
|
|
|
505
766
|
// OP_NOP2 (0xb1) = OP_CHECKLOCKTIMEVERIFY: on BSV post-genesis treated as NOP
|
|
506
767
|
// falls through
|
|
507
768
|
case OP_js_1.default.OP_CHECKLOCKTIMEVERIFY:
|
|
769
|
+
break;
|
|
508
770
|
// OP_NOP3 (0xb2) = OP_CHECKSEQUENCEVERIFY: on BSV post-genesis treated as NOP
|
|
509
|
-
// falls through
|
|
510
771
|
case OP_js_1.default.OP_CHECKSEQUENCEVERIFY:
|
|
772
|
+
if (this.hasFlag('CHECKSEQUENCEVERIFY')) {
|
|
773
|
+
if (this.stack.length < 1)
|
|
774
|
+
this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY requires at least one item to be on the stack.');
|
|
775
|
+
let sequenceLock = 0n;
|
|
776
|
+
try {
|
|
777
|
+
// BIP112 explicitly permits 5-byte script numbers so the disable flag can be represented.
|
|
778
|
+
sequenceLock = BigNumber_js_1.default.fromScriptNum(this.stackTop(), this.shouldEnforceMinimalData(), 5).toBigInt();
|
|
779
|
+
}
|
|
780
|
+
catch {
|
|
781
|
+
this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY requires a minimally-encoded numeric lock time.');
|
|
782
|
+
}
|
|
783
|
+
if (sequenceLock < 0n)
|
|
784
|
+
this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY requires a non-negative lock time.');
|
|
785
|
+
if ((Number(sequenceLock & BigInt(sequenceLocktimeDisableFlag)) === 0) && this.transactionVersion < 2) {
|
|
786
|
+
this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY lock time is unsatisfied.');
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
break;
|
|
511
790
|
case OP_js_1.default.OP_NOP9:
|
|
512
791
|
case OP_js_1.default.OP_NOP10:
|
|
513
792
|
break;
|
|
@@ -528,6 +807,7 @@ class Spend {
|
|
|
528
807
|
fValue = !fValue;
|
|
529
808
|
}
|
|
530
809
|
this.ifStack.push(fValue);
|
|
810
|
+
this.elseStack.push(false);
|
|
531
811
|
break;
|
|
532
812
|
case OP_js_1.default.OP_IF:
|
|
533
813
|
case OP_js_1.default.OP_NOTIF:
|
|
@@ -536,21 +816,30 @@ class Spend {
|
|
|
536
816
|
if (this.stack.length < 1)
|
|
537
817
|
this.scriptEvaluationError('OP_IF and OP_NOTIF require at least one item on the stack when they are used!');
|
|
538
818
|
buf = this.popStack();
|
|
819
|
+
if (this.hasFlag('MINIMALIF') && buf.length > 0 && !(buf.length === 1 && buf[0] === 1)) {
|
|
820
|
+
this.scriptEvaluationError('OP_IF and OP_NOTIF require minimal truth values.');
|
|
821
|
+
}
|
|
539
822
|
fValue = this.castToBool(buf);
|
|
540
823
|
if (currentOpcode === OP_js_1.default.OP_NOTIF)
|
|
541
824
|
fValue = !fValue;
|
|
542
825
|
}
|
|
543
826
|
this.ifStack.push(fValue);
|
|
827
|
+
this.elseStack.push(false);
|
|
544
828
|
break;
|
|
545
829
|
case OP_js_1.default.OP_ELSE:
|
|
546
830
|
if (this.ifStack.length === 0)
|
|
547
831
|
this.scriptEvaluationError('OP_ELSE requires a preceeding OP_IF.');
|
|
832
|
+
if (this.hasExplicitFlags() && this.isAfterGenesis() && this.elseStack[this.elseStack.length - 1]) {
|
|
833
|
+
this.scriptEvaluationError('OP_ELSE may only be used once for each OP_IF or OP_NOTIF after Genesis.');
|
|
834
|
+
}
|
|
835
|
+
this.elseStack[this.elseStack.length - 1] = true;
|
|
548
836
|
this.ifStack[this.ifStack.length - 1] = !(this.ifStack[this.ifStack.length - 1]);
|
|
549
837
|
break;
|
|
550
838
|
case OP_js_1.default.OP_ENDIF:
|
|
551
839
|
if (this.ifStack.length === 0)
|
|
552
840
|
this.scriptEvaluationError('OP_ENDIF requires a preceeding OP_IF.');
|
|
553
841
|
this.ifStack.pop();
|
|
842
|
+
this.elseStack.pop();
|
|
554
843
|
break;
|
|
555
844
|
case OP_js_1.default.OP_VERIFY:
|
|
556
845
|
if (this.stack.length < 1)
|
|
@@ -562,12 +851,19 @@ class Spend {
|
|
|
562
851
|
this.popStack();
|
|
563
852
|
break;
|
|
564
853
|
case OP_js_1.default.OP_RETURN:
|
|
565
|
-
if (this.
|
|
566
|
-
this.
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
854
|
+
if (this.hasExplicitFlags() && !this.isAfterGenesis()) {
|
|
855
|
+
this.scriptEvaluationError('OP_RETURN is invalid before Genesis.');
|
|
856
|
+
}
|
|
857
|
+
if (this.ifStack.length > 0) {
|
|
858
|
+
this.returningFromConditional = true;
|
|
859
|
+
}
|
|
860
|
+
else {
|
|
861
|
+
if (this.context === 'UnlockingScript')
|
|
862
|
+
this.programCounter = this.unlockingScript.chunks.length;
|
|
863
|
+
else
|
|
864
|
+
this.programCounter = this.lockingScript.chunks.length;
|
|
865
|
+
this.programCounter--; // To counteract the final increment and ensure loop termination
|
|
866
|
+
}
|
|
571
867
|
break;
|
|
572
868
|
case OP_js_1.default.OP_TOALTSTACK:
|
|
573
869
|
if (this.stack.length < 1)
|
|
@@ -678,7 +974,7 @@ class Spend {
|
|
|
678
974
|
case OP_js_1.default.OP_ROLL: {
|
|
679
975
|
if (this.stack.length < 2)
|
|
680
976
|
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires at least two items to be on the stack.`);
|
|
681
|
-
bn =
|
|
977
|
+
bn = this.readScriptNumber(this.popStack());
|
|
682
978
|
const nBigInt = bn.toBigInt();
|
|
683
979
|
if (nBigInt < 0n || nBigInt >= BigInt(this.stack.length)) {
|
|
684
980
|
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires the top stack element to be 0 or a positive number less than the current size of the stack.`);
|
|
@@ -764,7 +1060,7 @@ class Spend {
|
|
|
764
1060
|
case OP_js_1.default.OP_RSHIFT: {
|
|
765
1061
|
if (this.stack.length < 2)
|
|
766
1062
|
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires at least two items to be on the stack.`);
|
|
767
|
-
bn2 =
|
|
1063
|
+
bn2 = this.readScriptNumber(this.popStack()); // n (shift amount)
|
|
768
1064
|
buf1 = this.popStack(); // value to shift
|
|
769
1065
|
const shiftBits = bn2.toBigInt();
|
|
770
1066
|
if (shiftBits < 0n)
|
|
@@ -812,7 +1108,7 @@ class Spend {
|
|
|
812
1108
|
case OP_js_1.default.OP_0NOTEQUAL:
|
|
813
1109
|
if (this.stack.length < 1)
|
|
814
1110
|
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires at least one item to be on the stack.`);
|
|
815
|
-
bn =
|
|
1111
|
+
bn = this.readScriptNumber(this.popStack());
|
|
816
1112
|
switch (currentOpcode) {
|
|
817
1113
|
case OP_js_1.default.OP_1ADD:
|
|
818
1114
|
bn = bn.add(new BigNumber_js_1.default(1));
|
|
@@ -862,8 +1158,8 @@ class Spend {
|
|
|
862
1158
|
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires at least two items to be on the stack.`);
|
|
863
1159
|
buf2 = this.popStack();
|
|
864
1160
|
buf1 = this.popStack();
|
|
865
|
-
bn2 =
|
|
866
|
-
bn1 =
|
|
1161
|
+
bn2 = this.readScriptNumber(buf2);
|
|
1162
|
+
bn1 = this.readScriptNumber(buf1);
|
|
867
1163
|
let predictedLen = 0;
|
|
868
1164
|
switch (currentOpcode) {
|
|
869
1165
|
case OP_js_1.default.OP_MUL:
|
|
@@ -943,9 +1239,9 @@ class Spend {
|
|
|
943
1239
|
case OP_js_1.default.OP_WITHIN:
|
|
944
1240
|
if (this.stack.length < 3)
|
|
945
1241
|
this.scriptEvaluationError('OP_WITHIN requires at least three items to be on the stack.');
|
|
946
|
-
bn3 =
|
|
947
|
-
bn2 =
|
|
948
|
-
bn1 =
|
|
1242
|
+
bn3 = this.readScriptNumber(this.popStack()); // max
|
|
1243
|
+
bn2 = this.readScriptNumber(this.popStack()); // min
|
|
1244
|
+
bn1 = this.readScriptNumber(this.popStack()); // x
|
|
949
1245
|
fValue = bn1.cmp(bn2) >= 0 && bn1.cmp(bn3) < 0;
|
|
950
1246
|
this.pushStack(fValue ? [1] : []);
|
|
951
1247
|
break;
|
|
@@ -987,7 +1283,7 @@ class Spend {
|
|
|
987
1283
|
fSuccess = false;
|
|
988
1284
|
if (bufSig.length > 0) {
|
|
989
1285
|
try {
|
|
990
|
-
sig =
|
|
1286
|
+
sig = this.parseChecksigSignature(bufSig);
|
|
991
1287
|
const scriptForChecksig = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript;
|
|
992
1288
|
const scriptCodeChunks = scriptForChecksig.chunks.slice(this.lastCodeSeparator === null ? 0 : this.lastCodeSeparator + 1);
|
|
993
1289
|
subscript = new Script_js_1.default(scriptCodeChunks);
|
|
@@ -999,6 +1295,9 @@ class Spend {
|
|
|
999
1295
|
fSuccess = false;
|
|
1000
1296
|
}
|
|
1001
1297
|
}
|
|
1298
|
+
if (!fSuccess && this.hasFlag('NULLFAIL') && bufSig.length > 0) {
|
|
1299
|
+
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires failing signatures to be empty.`);
|
|
1300
|
+
}
|
|
1002
1301
|
this.pushStack(fSuccess ? [1] : []);
|
|
1003
1302
|
if (currentOpcode === OP_js_1.default.OP_CHECKSIGVERIFY) {
|
|
1004
1303
|
if (!fSuccess)
|
|
@@ -1013,10 +1312,13 @@ class Spend {
|
|
|
1013
1312
|
if (this.stack.length < i) {
|
|
1014
1313
|
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires at least 1 item for nKeys.`);
|
|
1015
1314
|
}
|
|
1016
|
-
const nKeysCountBN =
|
|
1315
|
+
const nKeysCountBN = this.readScriptNumber(this.stackTop(-i));
|
|
1017
1316
|
const nKeysCountBigInt = nKeysCountBN.toBigInt();
|
|
1018
|
-
|
|
1019
|
-
|
|
1317
|
+
const multisigKeyLimitBigInt = this.hasExplicitFlags() && !this.isAfterGenesis()
|
|
1318
|
+
? BigInt(maxMultisigKeyCountBeforeGenesis)
|
|
1319
|
+
: maxMultisigKeyCountBigInt;
|
|
1320
|
+
if (nKeysCountBigInt < 0n || nKeysCountBigInt > multisigKeyLimitBigInt) {
|
|
1321
|
+
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires a key count between 0 and ${multisigKeyLimitBigInt.toString()}.`);
|
|
1020
1322
|
}
|
|
1021
1323
|
nKeysCount = Number(nKeysCountBigInt);
|
|
1022
1324
|
const declaredKeyCount = nKeysCount;
|
|
@@ -1025,7 +1327,7 @@ class Spend {
|
|
|
1025
1327
|
if (this.stack.length < i) {
|
|
1026
1328
|
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} stack too small for nKeys and keys. Need ${i}, have ${this.stack.length}.`);
|
|
1027
1329
|
}
|
|
1028
|
-
const nSigsCountBN =
|
|
1330
|
+
const nSigsCountBN = this.readScriptNumber(this.stackTop(-i));
|
|
1029
1331
|
const nSigsCountBigInt = nSigsCountBN.toBigInt();
|
|
1030
1332
|
if (nSigsCountBigInt < 0n || nSigsCountBigInt > BigInt(nKeysCount)) {
|
|
1031
1333
|
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires the number of signatures to be no greater than the number of keys.`);
|
|
@@ -1040,8 +1342,11 @@ class Spend {
|
|
|
1040
1342
|
const baseScriptCMS = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript;
|
|
1041
1343
|
const subscriptChunksCMS = baseScriptCMS.chunks.slice(this.lastCodeSeparator === null ? 0 : this.lastCodeSeparator + 1);
|
|
1042
1344
|
subscript = new Script_js_1.default(subscriptChunksCMS);
|
|
1345
|
+
let hasNonEmptySignature = false;
|
|
1043
1346
|
for (let k = 0; k < nSigsCount; k++) {
|
|
1044
1347
|
bufSig = this.stackTop(-isig - k); // Sigs are closer to top than keys
|
|
1348
|
+
if (bufSig.length > 0)
|
|
1349
|
+
hasNonEmptySignature = true;
|
|
1045
1350
|
subscript.findAndDelete(new Script_js_1.default().writeBin(bufSig));
|
|
1046
1351
|
}
|
|
1047
1352
|
fSuccess = true;
|
|
@@ -1058,7 +1363,7 @@ class Spend {
|
|
|
1058
1363
|
fOk = false;
|
|
1059
1364
|
if (bufSig.length > 0) {
|
|
1060
1365
|
try {
|
|
1061
|
-
sig =
|
|
1366
|
+
sig = this.parseChecksigSignature(bufSig);
|
|
1062
1367
|
pubkey = PublicKey_js_1.default.fromDER(bufPubkey);
|
|
1063
1368
|
fOk = this.verifySignature(sig, pubkey, subscript);
|
|
1064
1369
|
}
|
|
@@ -1076,6 +1381,9 @@ class Spend {
|
|
|
1076
1381
|
fSuccess = false;
|
|
1077
1382
|
}
|
|
1078
1383
|
}
|
|
1384
|
+
if (!fSuccess && this.hasFlag('NULLFAIL') && hasNonEmptySignature) {
|
|
1385
|
+
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires failing signatures to be empty.`);
|
|
1386
|
+
}
|
|
1079
1387
|
// Correct total items consumed by op (N_val, keys, M_val, sigs, dummy)
|
|
1080
1388
|
const itemsConsumedByOp = 1 + // N_val
|
|
1081
1389
|
declaredKeyCount + // keys
|
|
@@ -1092,7 +1400,7 @@ class Spend {
|
|
|
1092
1400
|
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires an extra item (dummy) to be on the stack.`);
|
|
1093
1401
|
}
|
|
1094
1402
|
const dummyBuf = this.popStack();
|
|
1095
|
-
if (
|
|
1403
|
+
if (this.shouldEnforceNullDummy() && dummyBuf.length > 0) { // SCRIPT_VERIFY_NULLDUMMY
|
|
1096
1404
|
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires the extra stack item (dummy) to be empty.`);
|
|
1097
1405
|
}
|
|
1098
1406
|
this.pushStack(fSuccess ? [1] : []);
|
|
@@ -1109,8 +1417,8 @@ class Spend {
|
|
|
1109
1417
|
buf2 = this.popStack();
|
|
1110
1418
|
buf1 = this.popStack();
|
|
1111
1419
|
const catResult = (buf1).concat(buf2);
|
|
1112
|
-
if (catResult.length >
|
|
1113
|
-
this.scriptEvaluationError(`It's not currently possible to push data larger than ${
|
|
1420
|
+
if (catResult.length > this.maxPushSize())
|
|
1421
|
+
this.scriptEvaluationError(`It's not currently possible to push data larger than ${this.maxPushSize()} bytes.`);
|
|
1114
1422
|
this.pushStack(catResult);
|
|
1115
1423
|
break;
|
|
1116
1424
|
}
|
|
@@ -1119,7 +1427,7 @@ class Spend {
|
|
|
1119
1427
|
this.scriptEvaluationError('OP_SPLIT requires at least two items to be on the stack.');
|
|
1120
1428
|
const posBuf = this.popStack();
|
|
1121
1429
|
const dataToSplit = this.popStack();
|
|
1122
|
-
const splitIndexBigInt =
|
|
1430
|
+
const splitIndexBigInt = this.readScriptNumber(posBuf).toBigInt();
|
|
1123
1431
|
if (splitIndexBigInt < 0n || splitIndexBigInt > BigInt(dataToSplit.length)) {
|
|
1124
1432
|
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.');
|
|
1125
1433
|
}
|
|
@@ -1131,9 +1439,9 @@ class Spend {
|
|
|
1131
1439
|
case OP_js_1.default.OP_NUM2BIN: {
|
|
1132
1440
|
if (this.stack.length < 2)
|
|
1133
1441
|
this.scriptEvaluationError('OP_NUM2BIN requires at least two items to be on the stack.');
|
|
1134
|
-
const sizeBigInt =
|
|
1135
|
-
if (sizeBigInt > BigInt(
|
|
1136
|
-
this.scriptEvaluationError(`It's not currently possible to push data larger than ${
|
|
1442
|
+
const sizeBigInt = this.readScriptNumber(this.popStack()).toBigInt();
|
|
1443
|
+
if (sizeBigInt > BigInt(this.maxPushSize()) || sizeBigInt < 0n) { // size can be 0
|
|
1444
|
+
this.scriptEvaluationError(`It's not currently possible to push data larger than ${this.maxPushSize()} bytes or negative size.`);
|
|
1137
1445
|
}
|
|
1138
1446
|
size = Number(sizeBigInt);
|
|
1139
1447
|
let rawnum = this.popStack(); // This is the number to convert
|
|
@@ -1177,7 +1485,17 @@ class Spend {
|
|
|
1177
1485
|
this.scriptEvaluationError(`Invalid opcode ${currentOpcode} (pc=${this.programCounter}).`);
|
|
1178
1486
|
}
|
|
1179
1487
|
}
|
|
1180
|
-
this.
|
|
1488
|
+
if (this.returningFromConditional && this.ifStack.length === 0) {
|
|
1489
|
+
this.programCounter = currentScript.chunks.length;
|
|
1490
|
+
}
|
|
1491
|
+
else {
|
|
1492
|
+
this.programCounter++;
|
|
1493
|
+
}
|
|
1494
|
+
if (this.hasExplicitFlags() &&
|
|
1495
|
+
!this.isAfterGenesis() &&
|
|
1496
|
+
this.stack.length + this.altStack.length > maxStackItemsBeforeGenesis) {
|
|
1497
|
+
this.scriptEvaluationError(`Stack item count has exceeded ${maxStackItemsBeforeGenesis}.`);
|
|
1498
|
+
}
|
|
1181
1499
|
return true;
|
|
1182
1500
|
}
|
|
1183
1501
|
/**
|
|
@@ -1192,30 +1510,87 @@ class Spend {
|
|
|
1192
1510
|
* }
|
|
1193
1511
|
*/
|
|
1194
1512
|
validate() {
|
|
1195
|
-
if (
|
|
1513
|
+
if (this.shouldEnforceSigPushOnly() && !this.unlockingScript.isPushOnly()) {
|
|
1196
1514
|
this.scriptEvaluationError('Unlocking scripts can only contain push operations, and no other opcodes.');
|
|
1197
1515
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1516
|
+
const originalLockingScript = this.lockingScript;
|
|
1517
|
+
const shouldEvaluateP2SH = this.hasFlag('P2SH') &&
|
|
1518
|
+
!this.isAfterGenesis() &&
|
|
1519
|
+
this.isP2SHLockingScript(this.lockingScript);
|
|
1520
|
+
if (shouldEvaluateP2SH && !this.unlockingScript.isPushOnly()) {
|
|
1521
|
+
this.scriptEvaluationError('P2SH unlocking scripts can only contain push operations.');
|
|
1522
|
+
}
|
|
1523
|
+
this.reset();
|
|
1524
|
+
this.runScript('UnlockingScript');
|
|
1525
|
+
const stackAfterUnlockingScript = this.stack.map(item => item.slice());
|
|
1526
|
+
this.runScript('LockingScript');
|
|
1527
|
+
this.requireTruthyTopStack();
|
|
1528
|
+
try {
|
|
1529
|
+
if (shouldEvaluateP2SH) {
|
|
1530
|
+
if (stackAfterUnlockingScript.length === 0) {
|
|
1531
|
+
this.scriptEvaluationError('P2SH evaluation requires a redeem script on the stack.');
|
|
1532
|
+
}
|
|
1533
|
+
const redeemScriptBytes = stackAfterUnlockingScript.pop();
|
|
1534
|
+
if (redeemScriptBytes === undefined) {
|
|
1535
|
+
this.scriptEvaluationError('P2SH evaluation requires a redeem script on the stack.');
|
|
1536
|
+
return false;
|
|
1537
|
+
}
|
|
1538
|
+
this.setStack(stackAfterUnlockingScript);
|
|
1539
|
+
const redeemScript = Script_js_1.default.fromBinary(redeemScriptBytes);
|
|
1540
|
+
this.lockingScript = new LockingScript_js_1.default(redeemScript.chunks);
|
|
1541
|
+
this.runScript('LockingScript');
|
|
1202
1542
|
}
|
|
1203
1543
|
}
|
|
1544
|
+
finally {
|
|
1545
|
+
this.lockingScript = originalLockingScript;
|
|
1546
|
+
}
|
|
1547
|
+
if (this.shouldEnforceCleanStack() && this.stack.length !== 1) {
|
|
1548
|
+
this.scriptEvaluationError(`The clean stack rule requires exactly one item to be on the stack after script execution, found ${this.stack.length}.`);
|
|
1549
|
+
}
|
|
1550
|
+
this.requireTruthyTopStack();
|
|
1551
|
+
return true;
|
|
1552
|
+
}
|
|
1553
|
+
runScript(context) {
|
|
1554
|
+
this.context = context;
|
|
1555
|
+
this.programCounter = 0;
|
|
1556
|
+
this.ifStack = [];
|
|
1557
|
+
this.elseStack = [];
|
|
1558
|
+
this.returningFromConditional = false;
|
|
1559
|
+
this.clearAltStack();
|
|
1560
|
+
this.lastCodeSeparator = null;
|
|
1561
|
+
const script = context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript;
|
|
1562
|
+
if (this.hasExplicitFlags() &&
|
|
1563
|
+
!this.isAfterGenesis() &&
|
|
1564
|
+
script.toUint8Array().length > maxScriptSizeBeforeGenesis) {
|
|
1565
|
+
this.scriptEvaluationError(`Script size exceeds ${maxScriptSizeBeforeGenesis} bytes.`);
|
|
1566
|
+
}
|
|
1567
|
+
while (this.programCounter < script.chunks.length) {
|
|
1568
|
+
this.step();
|
|
1569
|
+
}
|
|
1204
1570
|
if (this.ifStack.length > 0) {
|
|
1205
1571
|
this.scriptEvaluationError('Every OP_IF, OP_NOTIF, or OP_ELSE must be terminated with OP_ENDIF prior to the end of the script.');
|
|
1206
1572
|
}
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1573
|
+
this.ifStack = [];
|
|
1574
|
+
this.elseStack = [];
|
|
1575
|
+
this.clearAltStack();
|
|
1576
|
+
this.lastCodeSeparator = null;
|
|
1577
|
+
}
|
|
1578
|
+
isP2SHLockingScript(script) {
|
|
1579
|
+
const chunks = script.chunks;
|
|
1580
|
+
return chunks.length === 3 &&
|
|
1581
|
+
chunks[0].op === OP_js_1.default.OP_HASH160 &&
|
|
1582
|
+
chunks[1].op === 20 &&
|
|
1583
|
+
Array.isArray(chunks[1].data) &&
|
|
1584
|
+
chunks[1].data.length === 20 &&
|
|
1585
|
+
chunks[2].op === OP_js_1.default.OP_EQUAL;
|
|
1586
|
+
}
|
|
1587
|
+
requireTruthyTopStack() {
|
|
1212
1588
|
if (this.stack.length === 0) {
|
|
1213
1589
|
this.scriptEvaluationError('The top stack element must be truthy after script evaluation (stack is empty).');
|
|
1214
1590
|
}
|
|
1215
1591
|
else if (!this.castToBool(this.stackTop())) {
|
|
1216
1592
|
this.scriptEvaluationError('The top stack element must be truthy after script evaluation.');
|
|
1217
1593
|
}
|
|
1218
|
-
return true;
|
|
1219
1594
|
}
|
|
1220
1595
|
castToBool(val) {
|
|
1221
1596
|
if (val.length === 0)
|