@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
package/src/script/Spend.ts
CHANGED
|
@@ -15,8 +15,14 @@ import TransactionOutput from '../transaction/TransactionOutput.js'
|
|
|
15
15
|
|
|
16
16
|
// These constants control the current behavior of the interpreter.
|
|
17
17
|
const maxScriptElementSize = 1024 * 1024 * 1024
|
|
18
|
+
const maxScriptElementSizeBeforeGenesis = 520
|
|
19
|
+
const maxScriptSizeBeforeGenesis = 10000
|
|
20
|
+
const maxOpsBeforeGenesis = 500
|
|
21
|
+
const maxStackItemsBeforeGenesis = 1000
|
|
18
22
|
const maxMultisigKeyCount = Math.pow(2, 31) - 1
|
|
19
23
|
const maxMultisigKeyCountBigInt = BigInt(maxMultisigKeyCount)
|
|
24
|
+
const maxMultisigKeyCountBeforeGenesis = 20
|
|
25
|
+
const sequenceLocktimeDisableFlag = 0x80000000
|
|
20
26
|
|
|
21
27
|
// --- Optimization: Pre-computed script numbers ---
|
|
22
28
|
const SCRIPTNUM_NEG_1 = Object.freeze(new BigNumber(-1).toScriptNum())
|
|
@@ -138,10 +144,14 @@ export default class Spend {
|
|
|
138
144
|
stack: number[][]
|
|
139
145
|
altStack: number[][]
|
|
140
146
|
ifStack: boolean[]
|
|
147
|
+
elseStack: boolean[]
|
|
141
148
|
memoryLimit: number
|
|
142
149
|
stackMem: number
|
|
143
150
|
altStackMem: number
|
|
144
151
|
isRelaxedOverride: boolean
|
|
152
|
+
verifyFlags?: Set<string>
|
|
153
|
+
executedOpCount: number
|
|
154
|
+
returningFromConditional: boolean
|
|
145
155
|
|
|
146
156
|
private sigHashCache: SignatureHashCache
|
|
147
157
|
|
|
@@ -193,6 +203,7 @@ export default class Spend {
|
|
|
193
203
|
lockTime: number
|
|
194
204
|
memoryLimit?: number
|
|
195
205
|
isRelaxed?: boolean
|
|
206
|
+
verifyFlags?: string | string[]
|
|
196
207
|
}) {
|
|
197
208
|
this.sourceTXID = params.sourceTXID
|
|
198
209
|
this.sourceOutputIndex = params.sourceOutputIndex
|
|
@@ -207,11 +218,23 @@ export default class Spend {
|
|
|
207
218
|
this.lockTime = params.lockTime
|
|
208
219
|
this.memoryLimit = params.memoryLimit ?? 32000000
|
|
209
220
|
this.isRelaxedOverride = params.isRelaxed === true
|
|
221
|
+
this.verifyFlags = params.verifyFlags === undefined
|
|
222
|
+
? undefined
|
|
223
|
+
: new Set(
|
|
224
|
+
(Array.isArray(params.verifyFlags)
|
|
225
|
+
? params.verifyFlags
|
|
226
|
+
: params.verifyFlags.split(','))
|
|
227
|
+
.map(flag => flag.trim())
|
|
228
|
+
.filter(flag => flag.length > 0)
|
|
229
|
+
)
|
|
210
230
|
this.stack = []
|
|
211
231
|
this.altStack = []
|
|
212
232
|
this.ifStack = []
|
|
233
|
+
this.elseStack = []
|
|
213
234
|
this.stackMem = 0
|
|
214
235
|
this.altStackMem = 0
|
|
236
|
+
this.executedOpCount = 0
|
|
237
|
+
this.returningFromConditional = false
|
|
215
238
|
this.sigHashCache = { hashOutputsSingle: new Map() }
|
|
216
239
|
this.reset()
|
|
217
240
|
}
|
|
@@ -221,6 +244,80 @@ export default class Spend {
|
|
|
221
244
|
(this.transactionVersion > 1)
|
|
222
245
|
}
|
|
223
246
|
|
|
247
|
+
private hasExplicitFlags (): boolean {
|
|
248
|
+
return this.verifyFlags !== undefined
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private hasFlag (flag: string): boolean {
|
|
252
|
+
return this.verifyFlags?.has(flag) === true
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private isAfterGenesis (): boolean {
|
|
256
|
+
if (this.hasExplicitFlags()) {
|
|
257
|
+
return this.hasFlag('GENESIS') ||
|
|
258
|
+
this.hasFlag('UTXO_AFTER_GENESIS') ||
|
|
259
|
+
this.hasFlag('UTXO_AFTER_CHRONICLE')
|
|
260
|
+
}
|
|
261
|
+
return this.isRelaxed()
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private isAfterChronicle (): boolean {
|
|
265
|
+
if (this.hasExplicitFlags()) return this.hasFlag('UTXO_AFTER_CHRONICLE')
|
|
266
|
+
return this.isRelaxed()
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private shouldEnforceMinimalData (): boolean {
|
|
270
|
+
if (this.hasExplicitFlags()) return this.hasFlag('MINIMALDATA')
|
|
271
|
+
return !this.isRelaxed()
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private shouldEnforceLowS (): boolean {
|
|
275
|
+
if (this.hasExplicitFlags()) return this.hasFlag('LOW_S')
|
|
276
|
+
return !this.isRelaxed()
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private shouldEnforceNullDummy (): boolean {
|
|
280
|
+
if (this.hasExplicitFlags()) return this.hasFlag('NULLDUMMY')
|
|
281
|
+
return !this.isRelaxed()
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private shouldEnforceSigPushOnly (): boolean {
|
|
285
|
+
if (this.hasExplicitFlags()) return this.hasFlag('SIGPUSHONLY')
|
|
286
|
+
return !this.isRelaxed()
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private shouldEnforceCleanStack (): boolean {
|
|
290
|
+
if (this.hasExplicitFlags()) return this.hasFlag('CLEANSTACK')
|
|
291
|
+
return !this.isRelaxed()
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private shouldEnforceDerSignatures (): boolean {
|
|
295
|
+
if (this.hasExplicitFlags()) {
|
|
296
|
+
return this.hasFlag('DERSIG') ||
|
|
297
|
+
this.hasFlag('STRICTENC') ||
|
|
298
|
+
this.hasFlag('LOW_S') ||
|
|
299
|
+
this.hasFlag('SIGHASH_FORKID')
|
|
300
|
+
}
|
|
301
|
+
return true
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private shouldEnforceStrictEncoding (): boolean {
|
|
305
|
+
if (this.hasExplicitFlags()) {
|
|
306
|
+
return this.hasFlag('STRICTENC') || this.hasFlag('SIGHASH_FORKID')
|
|
307
|
+
}
|
|
308
|
+
return true
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private scriptNumMaxSize (): number | undefined {
|
|
312
|
+
if (this.hasExplicitFlags() && !this.isAfterGenesis()) return 4
|
|
313
|
+
return undefined
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private maxPushSize (): number {
|
|
317
|
+
if (this.hasExplicitFlags() && !this.isAfterGenesis()) return maxScriptElementSizeBeforeGenesis
|
|
318
|
+
return maxScriptElementSize
|
|
319
|
+
}
|
|
320
|
+
|
|
224
321
|
reset (): void {
|
|
225
322
|
this.context = 'UnlockingScript'
|
|
226
323
|
this.programCounter = 0
|
|
@@ -228,8 +325,11 @@ export default class Spend {
|
|
|
228
325
|
this.stack = []
|
|
229
326
|
this.altStack = []
|
|
230
327
|
this.ifStack = []
|
|
328
|
+
this.elseStack = []
|
|
231
329
|
this.stackMem = 0
|
|
232
330
|
this.altStackMem = 0
|
|
331
|
+
this.executedOpCount = 0
|
|
332
|
+
this.returningFromConditional = false
|
|
233
333
|
this.sigHashCache = { hashOutputsSingle: new Map() }
|
|
234
334
|
}
|
|
235
335
|
|
|
@@ -284,6 +384,16 @@ export default class Spend {
|
|
|
284
384
|
return this.stack[this.stack.length + index]
|
|
285
385
|
}
|
|
286
386
|
|
|
387
|
+
private setStack (items: number[][]): void {
|
|
388
|
+
this.stack = items.map(item => item.slice())
|
|
389
|
+
this.stackMem = this.stack.reduce((total, item) => total + item.length, 0)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private clearAltStack (): void {
|
|
393
|
+
this.altStack = []
|
|
394
|
+
this.altStackMem = 0
|
|
395
|
+
}
|
|
396
|
+
|
|
287
397
|
private pushAltStack (item: number[]): void {
|
|
288
398
|
this.ensureAltStackMem(item.length)
|
|
289
399
|
this.altStack.push(item)
|
|
@@ -303,27 +413,130 @@ export default class Spend {
|
|
|
303
413
|
return item
|
|
304
414
|
}
|
|
305
415
|
|
|
416
|
+
private readScriptNumber (buf: number[]): BigNumber {
|
|
417
|
+
try {
|
|
418
|
+
return BigNumber.fromScriptNum(
|
|
419
|
+
buf,
|
|
420
|
+
this.shouldEnforceMinimalData(),
|
|
421
|
+
this.scriptNumMaxSize()
|
|
422
|
+
)
|
|
423
|
+
} catch (e) {
|
|
424
|
+
const message = e instanceof Error ? e.message : String(e)
|
|
425
|
+
this.scriptEvaluationError(message)
|
|
426
|
+
}
|
|
427
|
+
return new BigNumber(0)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
private isDefinedHashType (scope: number): boolean {
|
|
431
|
+
const baseType = scope & 0x1f
|
|
432
|
+
return baseType >= TransactionSignature.SIGHASH_ALL &&
|
|
433
|
+
baseType <= TransactionSignature.SIGHASH_SINGLE
|
|
434
|
+
}
|
|
435
|
+
|
|
306
436
|
private checkSignatureEncoding (buf: Readonly<number[]>): boolean {
|
|
307
437
|
if (buf.length === 0) return true
|
|
308
438
|
|
|
309
|
-
|
|
439
|
+
const enforceDer = this.shouldEnforceDerSignatures()
|
|
440
|
+
if (enforceDer && !isChecksigFormatHelper(buf)) {
|
|
310
441
|
this.scriptEvaluationError('The signature format is invalid.') // Generic message like original
|
|
311
442
|
return false
|
|
312
443
|
}
|
|
313
444
|
try {
|
|
314
445
|
const sig = TransactionSignature.fromChecksigFormat(buf as number[]) // This can throw for stricter DER rules
|
|
315
|
-
if (
|
|
446
|
+
if (this.shouldEnforceStrictEncoding() && !this.isDefinedHashType(sig.scope)) {
|
|
447
|
+
this.scriptEvaluationError('The signature hash type is invalid.')
|
|
448
|
+
return false
|
|
449
|
+
}
|
|
450
|
+
if (
|
|
451
|
+
this.shouldEnforceStrictEncoding() &&
|
|
452
|
+
(sig.scope & TransactionSignature.SIGHASH_CHRONICLE) !== 0 &&
|
|
453
|
+
!this.isAfterChronicle()
|
|
454
|
+
) {
|
|
455
|
+
this.scriptEvaluationError('The signature hash type is invalid before Chronicle.')
|
|
456
|
+
return false
|
|
457
|
+
}
|
|
458
|
+
const hasForkId = (sig.scope & TransactionSignature.SIGHASH_FORKID) !== 0
|
|
459
|
+
if (this.hasExplicitFlags()) {
|
|
460
|
+
if (this.hasFlag('SIGHASH_FORKID') && !hasForkId) {
|
|
461
|
+
this.scriptEvaluationError('The signature must use SIGHASH_FORKID.')
|
|
462
|
+
return false
|
|
463
|
+
}
|
|
464
|
+
if (!this.hasFlag('SIGHASH_FORKID') && !this.isAfterGenesis() && hasForkId) {
|
|
465
|
+
this.scriptEvaluationError('The signature must not use SIGHASH_FORKID.')
|
|
466
|
+
return false
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (this.shouldEnforceLowS() && !sig.hasLowS()) {
|
|
316
470
|
this.scriptEvaluationError('The signature must have a low S value.')
|
|
317
471
|
return false
|
|
318
472
|
}
|
|
319
473
|
} catch {
|
|
320
|
-
|
|
321
|
-
|
|
474
|
+
if (enforceDer) {
|
|
475
|
+
this.scriptEvaluationError('The signature format is invalid.')
|
|
476
|
+
return false
|
|
477
|
+
}
|
|
322
478
|
}
|
|
323
479
|
return true
|
|
324
480
|
}
|
|
325
481
|
|
|
482
|
+
private parseChecksigSignature (buf: number[]): TransactionSignature {
|
|
483
|
+
try {
|
|
484
|
+
return TransactionSignature.fromChecksigFormat(buf)
|
|
485
|
+
} catch (e) {
|
|
486
|
+
if (this.shouldEnforceDerSignatures()) throw e
|
|
487
|
+
return this.parseLaxChecksigSignature(buf)
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
private readLaxDERLength (buf: number[], position: { value: number }): number {
|
|
492
|
+
const first = buf[position.value++]
|
|
493
|
+
if (first === undefined) throw new Error('Invalid DER length')
|
|
494
|
+
if ((first & 0x80) === 0) return first
|
|
495
|
+
|
|
496
|
+
const lengthBytes = first & 0x7f
|
|
497
|
+
if (lengthBytes === 0 || position.value + lengthBytes > buf.length) {
|
|
498
|
+
throw new Error('Invalid DER length')
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
let length = 0
|
|
502
|
+
for (let i = 0; i < lengthBytes; i++) {
|
|
503
|
+
length = (length << 8) | (buf[position.value++] ?? 0)
|
|
504
|
+
}
|
|
505
|
+
return length
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
private parseLaxDERInteger (buf: number[], position: { value: number }, sequenceEnd: number): BigNumber {
|
|
509
|
+
if (position.value >= sequenceEnd || buf[position.value++] !== 0x02) {
|
|
510
|
+
throw new Error('Invalid DER integer')
|
|
511
|
+
}
|
|
512
|
+
const length = this.readLaxDERLength(buf, position)
|
|
513
|
+
if (position.value + length > sequenceEnd) {
|
|
514
|
+
throw new Error('Invalid DER integer length')
|
|
515
|
+
}
|
|
516
|
+
let bytes = buf.slice(position.value, position.value + length)
|
|
517
|
+
position.value += length
|
|
518
|
+
|
|
519
|
+
while (bytes.length > 1 && bytes[0] === 0) bytes = bytes.slice(1)
|
|
520
|
+
if (bytes.length === 0) bytes = [0]
|
|
521
|
+
return new BigNumber(bytes)
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
private parseLaxChecksigSignature (buf: number[]): TransactionSignature {
|
|
525
|
+
if (buf.length === 0) return TransactionSignature.fromChecksigFormat(buf)
|
|
526
|
+
|
|
527
|
+
const scope = buf[buf.length - 1]
|
|
528
|
+
const der = buf.slice(0, -1)
|
|
529
|
+
const position = { value: 0 }
|
|
530
|
+
if (der[position.value++] !== 0x30) throw new Error('Signature DER must start with 0x30')
|
|
531
|
+
const sequenceLength = this.readLaxDERLength(der, position)
|
|
532
|
+
const sequenceEnd = Math.min(position.value + sequenceLength, der.length)
|
|
533
|
+
const r = this.parseLaxDERInteger(der, position, sequenceEnd)
|
|
534
|
+
const s = this.parseLaxDERInteger(der, position, sequenceEnd)
|
|
535
|
+
return new TransactionSignature(r, s, scope)
|
|
536
|
+
}
|
|
537
|
+
|
|
326
538
|
private checkPublicKeyEncoding (buf: Readonly<number[]>): boolean {
|
|
539
|
+
if (!this.shouldEnforceStrictEncoding()) return true
|
|
327
540
|
if (buf.length === 0) {
|
|
328
541
|
this.scriptEvaluationError('Public key is empty.')
|
|
329
542
|
return false
|
|
@@ -360,7 +573,7 @@ export default class Spend {
|
|
|
360
573
|
pubkey: PublicKey,
|
|
361
574
|
subscript: Script
|
|
362
575
|
): boolean {
|
|
363
|
-
const
|
|
576
|
+
const params = {
|
|
364
577
|
sourceTXID: this.sourceTXID,
|
|
365
578
|
sourceOutputIndex: this.sourceOutputIndex,
|
|
366
579
|
sourceSatoshis: this.sourceSatoshis,
|
|
@@ -373,8 +586,10 @@ export default class Spend {
|
|
|
373
586
|
lockTime: this.lockTime,
|
|
374
587
|
scope: sig.scope,
|
|
375
588
|
cache: this.sigHashCache
|
|
376
|
-
}
|
|
377
|
-
const hash =
|
|
589
|
+
}
|
|
590
|
+
const hash = TransactionSignature.usesOtdaSingleBug(params)
|
|
591
|
+
? new BigNumber([1, ...new Array(31).fill(0)])
|
|
592
|
+
: new BigNumber(Hash.hash256(TransactionSignature.formatBytes(params)))
|
|
378
593
|
return verify(hash, sig, pubkey)
|
|
379
594
|
}
|
|
380
595
|
|
|
@@ -392,6 +607,16 @@ export default class Spend {
|
|
|
392
607
|
this.context === 'UnlockingScript' &&
|
|
393
608
|
this.programCounter >= this.unlockingScript.chunks.length
|
|
394
609
|
) {
|
|
610
|
+
if (this.ifStack.length > 0) {
|
|
611
|
+
this.scriptEvaluationError(
|
|
612
|
+
'Every OP_IF, OP_NOTIF, or OP_ELSE must be terminated with OP_ENDIF prior to the end of the unlocking script.'
|
|
613
|
+
)
|
|
614
|
+
}
|
|
615
|
+
this.clearAltStack()
|
|
616
|
+
this.ifStack = []
|
|
617
|
+
this.elseStack = []
|
|
618
|
+
this.returningFromConditional = false
|
|
619
|
+
this.lastCodeSeparator = null
|
|
395
620
|
this.context = 'LockingScript'
|
|
396
621
|
this.programCounter = 0
|
|
397
622
|
}
|
|
@@ -406,14 +631,31 @@ export default class Spend {
|
|
|
406
631
|
if (currentOpcode === undefined) {
|
|
407
632
|
this.scriptEvaluationError(`Missing opcode in ${this.context} at pc=${this.programCounter}.`) // Error thrown
|
|
408
633
|
}
|
|
409
|
-
if (
|
|
410
|
-
this.scriptEvaluationError(`
|
|
634
|
+
if (operation.invalidLength === true) {
|
|
635
|
+
this.scriptEvaluationError(`Malformed push data in ${this.context} at pc=${this.programCounter}.`)
|
|
636
|
+
}
|
|
637
|
+
if (Array.isArray(operation.data) && operation.data.length > this.maxPushSize()) {
|
|
638
|
+
this.scriptEvaluationError(`Data push > ${this.maxPushSize()} bytes (pc=${this.programCounter}).`) // Error thrown
|
|
411
639
|
}
|
|
412
640
|
|
|
413
|
-
const isScriptExecuting = !this.ifStack.includes(false)
|
|
641
|
+
const isScriptExecuting = !this.returningFromConditional && !this.ifStack.includes(false)
|
|
642
|
+
|
|
643
|
+
if (
|
|
644
|
+
this.hasExplicitFlags() &&
|
|
645
|
+
!this.isAfterGenesis() &&
|
|
646
|
+
!this.isAfterChronicle() &&
|
|
647
|
+
(
|
|
648
|
+
currentOpcode === OP.OP_2MUL ||
|
|
649
|
+
currentOpcode === OP.OP_2DIV ||
|
|
650
|
+
currentOpcode === OP.OP_VERIF ||
|
|
651
|
+
currentOpcode === OP.OP_VERNOTIF
|
|
652
|
+
)
|
|
653
|
+
) {
|
|
654
|
+
this.scriptEvaluationError(`${OP[currentOpcode] as string} is disabled until Chronicle.`)
|
|
655
|
+
}
|
|
414
656
|
|
|
415
657
|
if (isScriptExecuting && currentOpcode >= 0 && currentOpcode <= OP.OP_PUSHDATA4) {
|
|
416
|
-
if (
|
|
658
|
+
if (this.shouldEnforceMinimalData() && !isChunkMinimalPushHelper(operation)) {
|
|
417
659
|
this.scriptEvaluationError(`This data is not minimally-encoded. (PC: ${this.programCounter})`) // Error thrown
|
|
418
660
|
}
|
|
419
661
|
this.pushStack(Array.isArray(operation.data) ? operation.data : [])
|
|
@@ -426,6 +668,70 @@ export default class Spend {
|
|
|
426
668
|
let sig: TransactionSignature, pubkey: PublicKey
|
|
427
669
|
let i: number, ikey: number, isig: number, nKeysCount: number, nSigsCount: number, fOk: boolean
|
|
428
670
|
|
|
671
|
+
if (isScriptExecuting && currentOpcode > OP.OP_16) {
|
|
672
|
+
this.executedOpCount++
|
|
673
|
+
if (this.hasExplicitFlags() && !this.isAfterGenesis() && this.executedOpCount > maxOpsBeforeGenesis) {
|
|
674
|
+
this.scriptEvaluationError(`Script executed more than ${maxOpsBeforeGenesis} opcodes.`)
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
if (this.hasExplicitFlags() && !this.isAfterChronicle()) {
|
|
679
|
+
if (
|
|
680
|
+
isScriptExecuting &&
|
|
681
|
+
(
|
|
682
|
+
currentOpcode === OP.OP_SUBSTR ||
|
|
683
|
+
currentOpcode === OP.OP_LEFT ||
|
|
684
|
+
currentOpcode === OP.OP_RIGHT ||
|
|
685
|
+
currentOpcode === OP.OP_LSHIFTNUM ||
|
|
686
|
+
currentOpcode === OP.OP_RSHIFTNUM
|
|
687
|
+
)
|
|
688
|
+
) {
|
|
689
|
+
if (this.hasFlag('DISCOURAGE_UPGRADABLE_NOPS')) {
|
|
690
|
+
this.scriptEvaluationError(`${OP[currentOpcode] as string} is discouraged by verification flags.`)
|
|
691
|
+
}
|
|
692
|
+
this.programCounter++
|
|
693
|
+
return true
|
|
694
|
+
}
|
|
695
|
+
if (
|
|
696
|
+
(isScriptExecuting || !this.isAfterGenesis()) &&
|
|
697
|
+
(currentOpcode === OP.OP_2MUL || currentOpcode === OP.OP_2DIV)
|
|
698
|
+
) {
|
|
699
|
+
this.scriptEvaluationError(`${OP[currentOpcode] as string} is disabled until Chronicle.`)
|
|
700
|
+
}
|
|
701
|
+
if (
|
|
702
|
+
(isScriptExecuting || !this.isAfterGenesis()) &&
|
|
703
|
+
(
|
|
704
|
+
currentOpcode === OP.OP_VER ||
|
|
705
|
+
currentOpcode === OP.OP_VERIF ||
|
|
706
|
+
currentOpcode === OP.OP_VERNOTIF
|
|
707
|
+
)
|
|
708
|
+
) {
|
|
709
|
+
this.scriptEvaluationError(`${OP[currentOpcode] as string} is disabled until Chronicle.`)
|
|
710
|
+
}
|
|
711
|
+
if (
|
|
712
|
+
!isScriptExecuting &&
|
|
713
|
+
this.isAfterGenesis() &&
|
|
714
|
+
(currentOpcode === OP.OP_VERIF || currentOpcode === OP.OP_VERNOTIF)
|
|
715
|
+
) {
|
|
716
|
+
this.programCounter++
|
|
717
|
+
return true
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (
|
|
722
|
+
isScriptExecuting &&
|
|
723
|
+
this.hasFlag('DISCOURAGE_UPGRADABLE_NOPS') &&
|
|
724
|
+
(
|
|
725
|
+
currentOpcode === OP.OP_NOP1 ||
|
|
726
|
+
currentOpcode === OP.OP_CHECKLOCKTIMEVERIFY ||
|
|
727
|
+
currentOpcode === OP.OP_CHECKSEQUENCEVERIFY ||
|
|
728
|
+
currentOpcode === OP.OP_NOP9 ||
|
|
729
|
+
currentOpcode === OP.OP_NOP10
|
|
730
|
+
)
|
|
731
|
+
) {
|
|
732
|
+
this.scriptEvaluationError(`${OP[currentOpcode] as string} is discouraged by verification flags.`)
|
|
733
|
+
}
|
|
734
|
+
|
|
429
735
|
switch (currentOpcode) {
|
|
430
736
|
case OP.OP_VER: {
|
|
431
737
|
// Node v1.2.0: pushes tx_version as a 4-byte little-endian integer (to_le encoding)
|
|
@@ -435,8 +741,8 @@ export default class Spend {
|
|
|
435
741
|
}
|
|
436
742
|
case OP.OP_SUBSTR: {
|
|
437
743
|
if (this.stack.length < 3) this.scriptEvaluationError('OP_SUBSTR requires at least three items to be on the stack.')
|
|
438
|
-
const len =
|
|
439
|
-
const offset =
|
|
744
|
+
const len = this.readScriptNumber(this.popStack()).toNumber()
|
|
745
|
+
const offset = this.readScriptNumber(this.popStack()).toNumber()
|
|
440
746
|
buf = this.popStack()
|
|
441
747
|
const size = buf.length
|
|
442
748
|
|
|
@@ -449,7 +755,7 @@ export default class Spend {
|
|
|
449
755
|
}
|
|
450
756
|
case OP.OP_LEFT: {
|
|
451
757
|
if (this.stack.length < 2) this.scriptEvaluationError('OP_LEFT requires at least two items to be on the stack.')
|
|
452
|
-
const len =
|
|
758
|
+
const len = this.readScriptNumber(this.popStack()).toNumber()
|
|
453
759
|
buf = this.popStack()
|
|
454
760
|
const size = buf.length
|
|
455
761
|
|
|
@@ -462,7 +768,7 @@ export default class Spend {
|
|
|
462
768
|
}
|
|
463
769
|
case OP.OP_RIGHT: {
|
|
464
770
|
if (this.stack.length < 2) this.scriptEvaluationError('OP_RIGHT requires at least two items to be on the stack.')
|
|
465
|
-
const len =
|
|
771
|
+
const len = this.readScriptNumber(this.popStack()).toNumber()
|
|
466
772
|
buf = this.popStack()
|
|
467
773
|
const size = buf.length
|
|
468
774
|
|
|
@@ -475,22 +781,22 @@ export default class Spend {
|
|
|
475
781
|
}
|
|
476
782
|
case OP.OP_LSHIFTNUM: {
|
|
477
783
|
if (this.stack.length < 2) this.scriptEvaluationError('OP_LSHIFTNUM requires at least two items to be on the stack.')
|
|
478
|
-
const bits =
|
|
784
|
+
const bits = this.readScriptNumber(this.popStack()).toBigInt()
|
|
479
785
|
if (bits < 0) {
|
|
480
786
|
this.scriptEvaluationError('OP_LSHIFTNUM bits to shift must not be negative.')
|
|
481
787
|
}
|
|
482
|
-
const value =
|
|
788
|
+
const value = this.readScriptNumber(this.popStack()).toBigInt()
|
|
483
789
|
const resultBn = new BigNumber(value << bits)
|
|
484
790
|
this.pushStack(resultBn.toScriptNum())
|
|
485
791
|
break
|
|
486
792
|
}
|
|
487
793
|
case OP.OP_RSHIFTNUM: {
|
|
488
794
|
if (this.stack.length < 2) this.scriptEvaluationError('OP_RSHIFTNUM requires at least two items to be on the stack.')
|
|
489
|
-
const bits =
|
|
795
|
+
const bits = this.readScriptNumber(this.popStack()).toBigInt()
|
|
490
796
|
if (bits < 0) {
|
|
491
797
|
this.scriptEvaluationError('OP_RSHIFTNUM bits to shift must not be negative.')
|
|
492
798
|
}
|
|
493
|
-
const value =
|
|
799
|
+
const value = this.readScriptNumber(this.popStack()).toBigInt()
|
|
494
800
|
let resultBn: BigNumber
|
|
495
801
|
if (value < 0) {
|
|
496
802
|
resultBn = new BigNumber(-(-value >> bits))
|
|
@@ -519,9 +825,24 @@ export default class Spend {
|
|
|
519
825
|
// OP_NOP2 (0xb1) = OP_CHECKLOCKTIMEVERIFY: on BSV post-genesis treated as NOP
|
|
520
826
|
// falls through
|
|
521
827
|
case OP.OP_CHECKLOCKTIMEVERIFY:
|
|
828
|
+
break
|
|
522
829
|
// OP_NOP3 (0xb2) = OP_CHECKSEQUENCEVERIFY: on BSV post-genesis treated as NOP
|
|
523
|
-
// falls through
|
|
524
830
|
case OP.OP_CHECKSEQUENCEVERIFY:
|
|
831
|
+
if (this.hasFlag('CHECKSEQUENCEVERIFY')) {
|
|
832
|
+
if (this.stack.length < 1) this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY requires at least one item to be on the stack.')
|
|
833
|
+
let sequenceLock = 0n
|
|
834
|
+
try {
|
|
835
|
+
// BIP112 explicitly permits 5-byte script numbers so the disable flag can be represented.
|
|
836
|
+
sequenceLock = BigNumber.fromScriptNum(this.stackTop(), this.shouldEnforceMinimalData(), 5).toBigInt()
|
|
837
|
+
} catch {
|
|
838
|
+
this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY requires a minimally-encoded numeric lock time.')
|
|
839
|
+
}
|
|
840
|
+
if (sequenceLock < 0n) this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY requires a non-negative lock time.')
|
|
841
|
+
if ((Number(sequenceLock & BigInt(sequenceLocktimeDisableFlag)) === 0) && this.transactionVersion < 2) {
|
|
842
|
+
this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY lock time is unsatisfied.')
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
break
|
|
525
846
|
case OP.OP_NOP9:
|
|
526
847
|
case OP.OP_NOP10:
|
|
527
848
|
break
|
|
@@ -541,6 +862,7 @@ export default class Spend {
|
|
|
541
862
|
if (currentOpcode === OP.OP_VERNOTIF) fValue = !fValue
|
|
542
863
|
}
|
|
543
864
|
this.ifStack.push(fValue)
|
|
865
|
+
this.elseStack.push(false)
|
|
544
866
|
break
|
|
545
867
|
case OP.OP_IF:
|
|
546
868
|
case OP.OP_NOTIF:
|
|
@@ -548,18 +870,27 @@ export default class Spend {
|
|
|
548
870
|
if (isScriptExecuting) {
|
|
549
871
|
if (this.stack.length < 1) this.scriptEvaluationError('OP_IF and OP_NOTIF require at least one item on the stack when they are used!')
|
|
550
872
|
buf = this.popStack()
|
|
873
|
+
if (this.hasFlag('MINIMALIF') && buf.length > 0 && !(buf.length === 1 && buf[0] === 1)) {
|
|
874
|
+
this.scriptEvaluationError('OP_IF and OP_NOTIF require minimal truth values.')
|
|
875
|
+
}
|
|
551
876
|
fValue = this.castToBool(buf)
|
|
552
877
|
if (currentOpcode === OP.OP_NOTIF) fValue = !fValue
|
|
553
878
|
}
|
|
554
879
|
this.ifStack.push(fValue)
|
|
880
|
+
this.elseStack.push(false)
|
|
555
881
|
break
|
|
556
882
|
case OP.OP_ELSE:
|
|
557
883
|
if (this.ifStack.length === 0) this.scriptEvaluationError('OP_ELSE requires a preceeding OP_IF.')
|
|
884
|
+
if (this.hasExplicitFlags() && this.isAfterGenesis() && this.elseStack[this.elseStack.length - 1]) {
|
|
885
|
+
this.scriptEvaluationError('OP_ELSE may only be used once for each OP_IF or OP_NOTIF after Genesis.')
|
|
886
|
+
}
|
|
887
|
+
this.elseStack[this.elseStack.length - 1] = true
|
|
558
888
|
this.ifStack[this.ifStack.length - 1] = !(this.ifStack[this.ifStack.length - 1])
|
|
559
889
|
break
|
|
560
890
|
case OP.OP_ENDIF:
|
|
561
891
|
if (this.ifStack.length === 0) this.scriptEvaluationError('OP_ENDIF requires a preceeding OP_IF.')
|
|
562
892
|
this.ifStack.pop()
|
|
893
|
+
this.elseStack.pop()
|
|
563
894
|
break
|
|
564
895
|
case OP.OP_VERIFY:
|
|
565
896
|
if (this.stack.length < 1) this.scriptEvaluationError('OP_VERIFY requires at least one item to be on the stack.')
|
|
@@ -569,10 +900,16 @@ export default class Spend {
|
|
|
569
900
|
this.popStack()
|
|
570
901
|
break
|
|
571
902
|
case OP.OP_RETURN:
|
|
572
|
-
if (this.
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
this.
|
|
903
|
+
if (this.hasExplicitFlags() && !this.isAfterGenesis()) {
|
|
904
|
+
this.scriptEvaluationError('OP_RETURN is invalid before Genesis.')
|
|
905
|
+
}
|
|
906
|
+
if (this.ifStack.length > 0) {
|
|
907
|
+
this.returningFromConditional = true
|
|
908
|
+
} else {
|
|
909
|
+
if (this.context === 'UnlockingScript') this.programCounter = this.unlockingScript.chunks.length
|
|
910
|
+
else this.programCounter = this.lockingScript.chunks.length
|
|
911
|
+
this.programCounter-- // To counteract the final increment and ensure loop termination
|
|
912
|
+
}
|
|
576
913
|
break
|
|
577
914
|
|
|
578
915
|
case OP.OP_TOALTSTACK:
|
|
@@ -655,7 +992,7 @@ export default class Spend {
|
|
|
655
992
|
case OP.OP_PICK:
|
|
656
993
|
case OP.OP_ROLL: {
|
|
657
994
|
if (this.stack.length < 2) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least two items to be on the stack.`)
|
|
658
|
-
bn =
|
|
995
|
+
bn = this.readScriptNumber(this.popStack())
|
|
659
996
|
const nBigInt = bn.toBigInt()
|
|
660
997
|
if (nBigInt < 0n || nBigInt >= BigInt(this.stack.length)) {
|
|
661
998
|
this.scriptEvaluationError(`${OP[currentOpcode] as string} requires the top stack element to be 0 or a positive number less than the current size of the stack.`)
|
|
@@ -728,7 +1065,7 @@ export default class Spend {
|
|
|
728
1065
|
case OP.OP_LSHIFT:
|
|
729
1066
|
case OP.OP_RSHIFT: {
|
|
730
1067
|
if (this.stack.length < 2) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least two items to be on the stack.`)
|
|
731
|
-
bn2 =
|
|
1068
|
+
bn2 = this.readScriptNumber(this.popStack()) // n (shift amount)
|
|
732
1069
|
buf1 = this.popStack() // value to shift
|
|
733
1070
|
const shiftBits = bn2.toBigInt()
|
|
734
1071
|
if (shiftBits < 0n) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires the top item on the stack not to be negative.`)
|
|
@@ -768,7 +1105,7 @@ export default class Spend {
|
|
|
768
1105
|
case OP.OP_NEGATE: case OP.OP_ABS:
|
|
769
1106
|
case OP.OP_NOT: case OP.OP_0NOTEQUAL:
|
|
770
1107
|
if (this.stack.length < 1) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least one item to be on the stack.`)
|
|
771
|
-
bn =
|
|
1108
|
+
bn = this.readScriptNumber(this.popStack())
|
|
772
1109
|
switch (currentOpcode) {
|
|
773
1110
|
case OP.OP_1ADD: bn = bn.add(new BigNumber(1)); break
|
|
774
1111
|
case OP.OP_1SUB: bn = bn.sub(new BigNumber(1)); break
|
|
@@ -790,8 +1127,8 @@ export default class Spend {
|
|
|
790
1127
|
if (this.stack.length < 2) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least two items to be on the stack.`)
|
|
791
1128
|
buf2 = this.popStack()
|
|
792
1129
|
buf1 = this.popStack()
|
|
793
|
-
bn2 =
|
|
794
|
-
bn1 =
|
|
1130
|
+
bn2 = this.readScriptNumber(buf2)
|
|
1131
|
+
bn1 = this.readScriptNumber(buf1)
|
|
795
1132
|
let predictedLen = 0
|
|
796
1133
|
switch (currentOpcode) {
|
|
797
1134
|
case OP.OP_MUL:
|
|
@@ -837,9 +1174,9 @@ export default class Spend {
|
|
|
837
1174
|
}
|
|
838
1175
|
case OP.OP_WITHIN:
|
|
839
1176
|
if (this.stack.length < 3) this.scriptEvaluationError('OP_WITHIN requires at least three items to be on the stack.')
|
|
840
|
-
bn3 =
|
|
841
|
-
bn2 =
|
|
842
|
-
bn1 =
|
|
1177
|
+
bn3 = this.readScriptNumber(this.popStack()) // max
|
|
1178
|
+
bn2 = this.readScriptNumber(this.popStack()) // min
|
|
1179
|
+
bn1 = this.readScriptNumber(this.popStack()) // x
|
|
843
1180
|
fValue = bn1.cmp(bn2) >= 0 && bn1.cmp(bn3) < 0
|
|
844
1181
|
this.pushStack(fValue ? [1] : [])
|
|
845
1182
|
break
|
|
@@ -874,7 +1211,7 @@ export default class Spend {
|
|
|
874
1211
|
fSuccess = false
|
|
875
1212
|
if (bufSig.length > 0) {
|
|
876
1213
|
try {
|
|
877
|
-
sig =
|
|
1214
|
+
sig = this.parseChecksigSignature(bufSig)
|
|
878
1215
|
|
|
879
1216
|
const scriptForChecksig: Script = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript
|
|
880
1217
|
const scriptCodeChunks = scriptForChecksig.chunks.slice(this.lastCodeSeparator === null ? 0 : this.lastCodeSeparator + 1)
|
|
@@ -888,6 +1225,9 @@ export default class Spend {
|
|
|
888
1225
|
}
|
|
889
1226
|
}
|
|
890
1227
|
|
|
1228
|
+
if (!fSuccess && this.hasFlag('NULLFAIL') && bufSig.length > 0) {
|
|
1229
|
+
this.scriptEvaluationError(`${OP[currentOpcode] as string} requires failing signatures to be empty.`)
|
|
1230
|
+
}
|
|
891
1231
|
this.pushStack(fSuccess ? [1] : [])
|
|
892
1232
|
if (currentOpcode === OP.OP_CHECKSIGVERIFY) {
|
|
893
1233
|
if (!fSuccess) this.scriptEvaluationError('OP_CHECKSIGVERIFY requires that a valid signature is provided.')
|
|
@@ -902,10 +1242,13 @@ export default class Spend {
|
|
|
902
1242
|
this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least 1 item for nKeys.`)
|
|
903
1243
|
}
|
|
904
1244
|
|
|
905
|
-
const nKeysCountBN =
|
|
1245
|
+
const nKeysCountBN = this.readScriptNumber(this.stackTop(-i))
|
|
906
1246
|
const nKeysCountBigInt = nKeysCountBN.toBigInt()
|
|
907
|
-
|
|
908
|
-
|
|
1247
|
+
const multisigKeyLimitBigInt = this.hasExplicitFlags() && !this.isAfterGenesis()
|
|
1248
|
+
? BigInt(maxMultisigKeyCountBeforeGenesis)
|
|
1249
|
+
: maxMultisigKeyCountBigInt
|
|
1250
|
+
if (nKeysCountBigInt < 0n || nKeysCountBigInt > multisigKeyLimitBigInt) {
|
|
1251
|
+
this.scriptEvaluationError(`${OP[currentOpcode] as string} requires a key count between 0 and ${multisigKeyLimitBigInt.toString()}.`)
|
|
909
1252
|
}
|
|
910
1253
|
nKeysCount = Number(nKeysCountBigInt)
|
|
911
1254
|
const declaredKeyCount = nKeysCount
|
|
@@ -916,7 +1259,7 @@ export default class Spend {
|
|
|
916
1259
|
this.scriptEvaluationError(`${OP[currentOpcode] as string} stack too small for nKeys and keys. Need ${i}, have ${this.stack.length}.`)
|
|
917
1260
|
}
|
|
918
1261
|
|
|
919
|
-
const nSigsCountBN =
|
|
1262
|
+
const nSigsCountBN = this.readScriptNumber(this.stackTop(-i))
|
|
920
1263
|
const nSigsCountBigInt = nSigsCountBN.toBigInt()
|
|
921
1264
|
if (nSigsCountBigInt < 0n || nSigsCountBigInt > BigInt(nKeysCount)) {
|
|
922
1265
|
this.scriptEvaluationError(`${OP[currentOpcode] as string} requires the number of signatures to be no greater than the number of keys.`)
|
|
@@ -933,8 +1276,10 @@ export default class Spend {
|
|
|
933
1276
|
const subscriptChunksCMS = baseScriptCMS.chunks.slice(this.lastCodeSeparator === null ? 0 : this.lastCodeSeparator + 1)
|
|
934
1277
|
subscript = new Script(subscriptChunksCMS)
|
|
935
1278
|
|
|
1279
|
+
let hasNonEmptySignature = false
|
|
936
1280
|
for (let k = 0; k < nSigsCount; k++) {
|
|
937
1281
|
bufSig = this.stackTop(-isig - k) // Sigs are closer to top than keys
|
|
1282
|
+
if (bufSig.length > 0) hasNonEmptySignature = true
|
|
938
1283
|
subscript.findAndDelete(new Script().writeBin(bufSig))
|
|
939
1284
|
}
|
|
940
1285
|
|
|
@@ -954,7 +1299,7 @@ export default class Spend {
|
|
|
954
1299
|
fOk = false
|
|
955
1300
|
if (bufSig.length > 0) {
|
|
956
1301
|
try {
|
|
957
|
-
sig =
|
|
1302
|
+
sig = this.parseChecksigSignature(bufSig)
|
|
958
1303
|
pubkey = PublicKey.fromDER(bufPubkey)
|
|
959
1304
|
fOk = this.verifySignature(sig, pubkey, subscript)
|
|
960
1305
|
} catch {
|
|
@@ -972,6 +1317,10 @@ export default class Spend {
|
|
|
972
1317
|
}
|
|
973
1318
|
}
|
|
974
1319
|
|
|
1320
|
+
if (!fSuccess && this.hasFlag('NULLFAIL') && hasNonEmptySignature) {
|
|
1321
|
+
this.scriptEvaluationError(`${OP[currentOpcode] as string} requires failing signatures to be empty.`)
|
|
1322
|
+
}
|
|
1323
|
+
|
|
975
1324
|
// Correct total items consumed by op (N_val, keys, M_val, sigs, dummy)
|
|
976
1325
|
const itemsConsumedByOp = 1 + // N_val
|
|
977
1326
|
declaredKeyCount + // keys
|
|
@@ -990,7 +1339,7 @@ export default class Spend {
|
|
|
990
1339
|
this.scriptEvaluationError(`${OP[currentOpcode] as string} requires an extra item (dummy) to be on the stack.`)
|
|
991
1340
|
}
|
|
992
1341
|
const dummyBuf = this.popStack()
|
|
993
|
-
if (
|
|
1342
|
+
if (this.shouldEnforceNullDummy() && dummyBuf.length > 0) { // SCRIPT_VERIFY_NULLDUMMY
|
|
994
1343
|
this.scriptEvaluationError(`${OP[currentOpcode] as string} requires the extra stack item (dummy) to be empty.`)
|
|
995
1344
|
}
|
|
996
1345
|
|
|
@@ -1007,7 +1356,7 @@ export default class Spend {
|
|
|
1007
1356
|
buf2 = this.popStack()
|
|
1008
1357
|
buf1 = this.popStack()
|
|
1009
1358
|
const catResult = (buf1).concat(buf2)
|
|
1010
|
-
if (catResult.length >
|
|
1359
|
+
if (catResult.length > this.maxPushSize()) this.scriptEvaluationError(`It's not currently possible to push data larger than ${this.maxPushSize()} bytes.`)
|
|
1011
1360
|
this.pushStack(catResult)
|
|
1012
1361
|
break
|
|
1013
1362
|
}
|
|
@@ -1016,7 +1365,7 @@ export default class Spend {
|
|
|
1016
1365
|
const posBuf = this.popStack()
|
|
1017
1366
|
const dataToSplit = this.popStack()
|
|
1018
1367
|
|
|
1019
|
-
const splitIndexBigInt =
|
|
1368
|
+
const splitIndexBigInt = this.readScriptNumber(posBuf).toBigInt()
|
|
1020
1369
|
if (splitIndexBigInt < 0n || splitIndexBigInt > BigInt(dataToSplit.length)) {
|
|
1021
1370
|
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.')
|
|
1022
1371
|
}
|
|
@@ -1029,9 +1378,9 @@ export default class Spend {
|
|
|
1029
1378
|
case OP.OP_NUM2BIN: {
|
|
1030
1379
|
if (this.stack.length < 2) this.scriptEvaluationError('OP_NUM2BIN requires at least two items to be on the stack.')
|
|
1031
1380
|
|
|
1032
|
-
const sizeBigInt =
|
|
1033
|
-
if (sizeBigInt > BigInt(
|
|
1034
|
-
this.scriptEvaluationError(`It's not currently possible to push data larger than ${
|
|
1381
|
+
const sizeBigInt = this.readScriptNumber(this.popStack()).toBigInt()
|
|
1382
|
+
if (sizeBigInt > BigInt(this.maxPushSize()) || sizeBigInt < 0n) { // size can be 0
|
|
1383
|
+
this.scriptEvaluationError(`It's not currently possible to push data larger than ${this.maxPushSize()} bytes or negative size.`)
|
|
1035
1384
|
}
|
|
1036
1385
|
size = Number(sizeBigInt)
|
|
1037
1386
|
|
|
@@ -1083,7 +1432,18 @@ export default class Spend {
|
|
|
1083
1432
|
}
|
|
1084
1433
|
}
|
|
1085
1434
|
|
|
1086
|
-
this.
|
|
1435
|
+
if (this.returningFromConditional && this.ifStack.length === 0) {
|
|
1436
|
+
this.programCounter = currentScript.chunks.length
|
|
1437
|
+
} else {
|
|
1438
|
+
this.programCounter++
|
|
1439
|
+
}
|
|
1440
|
+
if (
|
|
1441
|
+
this.hasExplicitFlags() &&
|
|
1442
|
+
!this.isAfterGenesis() &&
|
|
1443
|
+
this.stack.length + this.altStack.length > maxStackItemsBeforeGenesis
|
|
1444
|
+
) {
|
|
1445
|
+
this.scriptEvaluationError(`Stack item count has exceeded ${maxStackItemsBeforeGenesis}.`)
|
|
1446
|
+
}
|
|
1087
1447
|
return true
|
|
1088
1448
|
}
|
|
1089
1449
|
|
|
@@ -1099,35 +1459,99 @@ export default class Spend {
|
|
|
1099
1459
|
* }
|
|
1100
1460
|
*/
|
|
1101
1461
|
validate (): boolean {
|
|
1102
|
-
if (
|
|
1462
|
+
if (this.shouldEnforceSigPushOnly() && !this.unlockingScript.isPushOnly()) {
|
|
1103
1463
|
this.scriptEvaluationError(
|
|
1104
1464
|
'Unlocking scripts can only contain push operations, and no other opcodes.'
|
|
1105
1465
|
)
|
|
1106
1466
|
}
|
|
1107
1467
|
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
)
|
|
1113
|
-
|
|
1468
|
+
const originalLockingScript = this.lockingScript
|
|
1469
|
+
const shouldEvaluateP2SH =
|
|
1470
|
+
this.hasFlag('P2SH') &&
|
|
1471
|
+
!this.isAfterGenesis() &&
|
|
1472
|
+
this.isP2SHLockingScript(this.lockingScript)
|
|
1473
|
+
|
|
1474
|
+
if (shouldEvaluateP2SH && !this.unlockingScript.isPushOnly()) {
|
|
1475
|
+
this.scriptEvaluationError('P2SH unlocking scripts can only contain push operations.')
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
this.reset()
|
|
1479
|
+
this.runScript('UnlockingScript')
|
|
1480
|
+
const stackAfterUnlockingScript = this.stack.map(item => item.slice())
|
|
1481
|
+
|
|
1482
|
+
this.runScript('LockingScript')
|
|
1483
|
+
this.requireTruthyTopStack()
|
|
1484
|
+
|
|
1485
|
+
try {
|
|
1486
|
+
if (shouldEvaluateP2SH) {
|
|
1487
|
+
if (stackAfterUnlockingScript.length === 0) {
|
|
1488
|
+
this.scriptEvaluationError('P2SH evaluation requires a redeem script on the stack.')
|
|
1489
|
+
}
|
|
1490
|
+
const redeemScriptBytes = stackAfterUnlockingScript.pop()
|
|
1491
|
+
if (redeemScriptBytes === undefined) {
|
|
1492
|
+
this.scriptEvaluationError('P2SH evaluation requires a redeem script on the stack.')
|
|
1493
|
+
return false
|
|
1494
|
+
}
|
|
1495
|
+
this.setStack(stackAfterUnlockingScript)
|
|
1496
|
+
const redeemScript = Script.fromBinary(redeemScriptBytes)
|
|
1497
|
+
this.lockingScript = new LockingScript(redeemScript.chunks)
|
|
1498
|
+
this.runScript('LockingScript')
|
|
1114
1499
|
}
|
|
1500
|
+
} finally {
|
|
1501
|
+
this.lockingScript = originalLockingScript
|
|
1115
1502
|
}
|
|
1116
1503
|
|
|
1504
|
+
if (this.shouldEnforceCleanStack() && this.stack.length !== 1) {
|
|
1505
|
+
this.scriptEvaluationError(
|
|
1506
|
+
`The clean stack rule requires exactly one item to be on the stack after script execution, found ${this.stack.length}.`
|
|
1507
|
+
)
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
this.requireTruthyTopStack()
|
|
1511
|
+
return true
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
private runScript (context: 'UnlockingScript' | 'LockingScript'): void {
|
|
1515
|
+
this.context = context
|
|
1516
|
+
this.programCounter = 0
|
|
1517
|
+
this.ifStack = []
|
|
1518
|
+
this.elseStack = []
|
|
1519
|
+
this.returningFromConditional = false
|
|
1520
|
+
this.clearAltStack()
|
|
1521
|
+
this.lastCodeSeparator = null
|
|
1522
|
+
const script = context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript
|
|
1523
|
+
if (
|
|
1524
|
+
this.hasExplicitFlags() &&
|
|
1525
|
+
!this.isAfterGenesis() &&
|
|
1526
|
+
script.toUint8Array().length > maxScriptSizeBeforeGenesis
|
|
1527
|
+
) {
|
|
1528
|
+
this.scriptEvaluationError(`Script size exceeds ${maxScriptSizeBeforeGenesis} bytes.`)
|
|
1529
|
+
}
|
|
1530
|
+
while (this.programCounter < script.chunks.length) {
|
|
1531
|
+
this.step()
|
|
1532
|
+
}
|
|
1117
1533
|
if (this.ifStack.length > 0) {
|
|
1118
1534
|
this.scriptEvaluationError(
|
|
1119
1535
|
'Every OP_IF, OP_NOTIF, or OP_ELSE must be terminated with OP_ENDIF prior to the end of the script.'
|
|
1120
1536
|
)
|
|
1121
1537
|
}
|
|
1538
|
+
this.ifStack = []
|
|
1539
|
+
this.elseStack = []
|
|
1540
|
+
this.clearAltStack()
|
|
1541
|
+
this.lastCodeSeparator = null
|
|
1542
|
+
}
|
|
1122
1543
|
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1544
|
+
private isP2SHLockingScript (script: LockingScript): boolean {
|
|
1545
|
+
const chunks = script.chunks
|
|
1546
|
+
return chunks.length === 3 &&
|
|
1547
|
+
chunks[0].op === OP.OP_HASH160 &&
|
|
1548
|
+
chunks[1].op === 20 &&
|
|
1549
|
+
Array.isArray(chunks[1].data) &&
|
|
1550
|
+
chunks[1].data.length === 20 &&
|
|
1551
|
+
chunks[2].op === OP.OP_EQUAL
|
|
1552
|
+
}
|
|
1130
1553
|
|
|
1554
|
+
private requireTruthyTopStack (): void {
|
|
1131
1555
|
if (this.stack.length === 0) {
|
|
1132
1556
|
this.scriptEvaluationError(
|
|
1133
1557
|
'The top stack element must be truthy after script evaluation (stack is empty).'
|
|
@@ -1137,8 +1561,6 @@ export default class Spend {
|
|
|
1137
1561
|
'The top stack element must be truthy after script evaluation.'
|
|
1138
1562
|
)
|
|
1139
1563
|
}
|
|
1140
|
-
|
|
1141
|
-
return true
|
|
1142
1564
|
}
|
|
1143
1565
|
|
|
1144
1566
|
private castToBool (val: Readonly<number[]>): boolean {
|