@bsv/sdk 1.5.3 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/overlay-tools/LookupResolver.js +0 -4
- package/dist/cjs/src/overlay-tools/LookupResolver.js.map +1 -1
- package/dist/cjs/src/overlay-tools/SHIPBroadcaster.js +0 -2
- package/dist/cjs/src/overlay-tools/SHIPBroadcaster.js.map +1 -1
- package/dist/cjs/src/primitives/AESGCM.js +113 -137
- package/dist/cjs/src/primitives/AESGCM.js.map +1 -1
- package/dist/cjs/src/primitives/BigNumber.js +1019 -3947
- package/dist/cjs/src/primitives/BigNumber.js.map +1 -1
- package/dist/cjs/src/primitives/K256.js +53 -37
- package/dist/cjs/src/primitives/K256.js.map +1 -1
- package/dist/cjs/src/primitives/Mersenne.js +16 -21
- package/dist/cjs/src/primitives/Mersenne.js.map +1 -1
- package/dist/cjs/src/primitives/MontgomoryMethod.js.map +1 -1
- package/dist/cjs/src/primitives/utils.js +27 -17
- package/dist/cjs/src/primitives/utils.js.map +1 -1
- package/dist/cjs/src/script/Spend.js +618 -858
- package/dist/cjs/src/script/Spend.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/overlay-tools/LookupResolver.js +0 -4
- package/dist/esm/src/overlay-tools/LookupResolver.js.map +1 -1
- package/dist/esm/src/overlay-tools/SHIPBroadcaster.js +0 -2
- package/dist/esm/src/overlay-tools/SHIPBroadcaster.js.map +1 -1
- package/dist/esm/src/primitives/AESGCM.js +112 -137
- package/dist/esm/src/primitives/AESGCM.js.map +1 -1
- package/dist/esm/src/primitives/BigNumber.js +1011 -3969
- package/dist/esm/src/primitives/BigNumber.js.map +1 -1
- package/dist/esm/src/primitives/K256.js +53 -37
- package/dist/esm/src/primitives/K256.js.map +1 -1
- package/dist/esm/src/primitives/Mersenne.js +16 -21
- package/dist/esm/src/primitives/Mersenne.js.map +1 -1
- package/dist/esm/src/primitives/MontgomoryMethod.js.map +1 -1
- package/dist/esm/src/primitives/utils.js +29 -17
- package/dist/esm/src/primitives/utils.js.map +1 -1
- package/dist/esm/src/script/Spend.js +618 -857
- package/dist/esm/src/script/Spend.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/overlay-tools/LookupResolver.d.ts +1 -3
- package/dist/types/src/overlay-tools/LookupResolver.d.ts.map +1 -1
- package/dist/types/src/overlay-tools/SHIPBroadcaster.d.ts +1 -0
- package/dist/types/src/overlay-tools/SHIPBroadcaster.d.ts.map +1 -1
- package/dist/types/src/primitives/AESGCM.d.ts.map +1 -1
- package/dist/types/src/primitives/BigNumber.d.ts +238 -1705
- package/dist/types/src/primitives/BigNumber.d.ts.map +1 -1
- package/dist/types/src/primitives/K256.d.ts.map +1 -1
- package/dist/types/src/primitives/Mersenne.d.ts +2 -2
- package/dist/types/src/primitives/Mersenne.d.ts.map +1 -1
- package/dist/types/src/primitives/utils.d.ts +2 -0
- package/dist/types/src/primitives/utils.d.ts.map +1 -1
- package/dist/types/src/script/Spend.d.ts +11 -1
- package/dist/types/src/script/Spend.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/docs/performance.md +70 -0
- package/docs/primitives.md +262 -3049
- package/package.json +1 -1
- package/src/auth/__tests/Peer.test.ts +38 -23
- package/src/auth/certificates/__tests/MasterCertificate.test.ts +27 -20
- package/src/auth/certificates/__tests/VerifiableCertificate.test.ts +24 -24
- package/src/overlay-tools/LookupResolver.ts +1 -9
- package/src/overlay-tools/SHIPBroadcaster.ts +1 -2
- package/src/overlay-tools/__tests/LookupResolver.test.ts +0 -112
- package/src/primitives/AESGCM.ts +118 -164
- package/src/primitives/BigNumber.ts +867 -4180
- package/src/primitives/K256.ts +57 -37
- package/src/primitives/Mersenne.ts +16 -20
- package/src/primitives/MontgomoryMethod.ts +2 -2
- package/src/primitives/__tests/ReductionContext.test.ts +6 -1
- package/src/primitives/utils.ts +28 -17
- package/src/script/Spend.ts +634 -1309
- package/src/transaction/__tests/Transaction.test.ts +14 -16
- package/src/transaction/__tests/Transaction.benchmarks.test.ts +0 -237
package/src/script/Spend.ts
CHANGED
|
@@ -13,7 +13,6 @@ import TransactionInput from '../transaction/TransactionInput.js'
|
|
|
13
13
|
import TransactionOutput from '../transaction/TransactionOutput.js'
|
|
14
14
|
|
|
15
15
|
// These constants control the current behavior of the interpreter.
|
|
16
|
-
// In the future, all of them will go away.
|
|
17
16
|
const maxScriptElementSize = 1024 * 1024 * 1024
|
|
18
17
|
const maxMultisigKeyCount = Math.pow(2, 31) - 1
|
|
19
18
|
const requireMinimalPush = true
|
|
@@ -21,6 +20,96 @@ const requirePushOnlyUnlockingScripts = true
|
|
|
21
20
|
const requireLowSSignatures = true
|
|
22
21
|
const requireCleanStack = true
|
|
23
22
|
|
|
23
|
+
// --- Optimization: Pre-computed script numbers ---
|
|
24
|
+
const SCRIPTNUM_NEG_1 = Object.freeze(new BigNumber(-1).toScriptNum())
|
|
25
|
+
const SCRIPTNUMS_0_TO_16: ReadonlyArray<Readonly<number[]>> = Object.freeze(
|
|
26
|
+
Array.from({ length: 17 }, (_, i) => Object.freeze(new BigNumber(i).toScriptNum()))
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
// --- Helper functions ---
|
|
30
|
+
|
|
31
|
+
function compareNumberArrays (a: Readonly<number[]>, b: Readonly<number[]>): boolean {
|
|
32
|
+
if (a.length !== b.length) return false
|
|
33
|
+
for (let i = 0; i < a.length; i++) {
|
|
34
|
+
if (a[i] !== b[i]) return false
|
|
35
|
+
}
|
|
36
|
+
return true
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isMinimallyEncodedHelper (
|
|
40
|
+
buf: Readonly<number[]>,
|
|
41
|
+
maxNumSize: number = Number.MAX_SAFE_INTEGER
|
|
42
|
+
): boolean {
|
|
43
|
+
if (buf.length > maxNumSize) {
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
46
|
+
if (buf.length > 0) {
|
|
47
|
+
if ((buf[buf.length - 1] & 0x7f) === 0) {
|
|
48
|
+
if (buf.length <= 1 || (buf[buf.length - 2] & 0x80) === 0) {
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return true
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isChecksigFormatHelper (buf: Readonly<number[]>): boolean {
|
|
57
|
+
// This is a simplified check. The full DER check is more complex and typically
|
|
58
|
+
// done by TransactionSignature.fromChecksigFormat which can throw.
|
|
59
|
+
// This helper is mostly for early bailout or non-throwing checks if needed.
|
|
60
|
+
if (buf.length < 9 || buf.length > 73) return false
|
|
61
|
+
if (buf[0] !== 0x30) return false // DER SEQUENCE
|
|
62
|
+
if (buf[1] !== buf.length - 3) return false // Total length (excluding type and length byte for sequence, and hash type)
|
|
63
|
+
|
|
64
|
+
const rMarker = buf[2]
|
|
65
|
+
const rLen = buf[3]
|
|
66
|
+
if (rMarker !== 0x02) return false // DER INTEGER
|
|
67
|
+
if (rLen === 0) return false // R length is zero
|
|
68
|
+
if (5 + rLen >= buf.length) return false // S length misplaced or R too long
|
|
69
|
+
|
|
70
|
+
const sMarkerOffset = 4 + rLen
|
|
71
|
+
const sMarker = buf[sMarkerOffset]
|
|
72
|
+
const sLen = buf[sMarkerOffset + 1]
|
|
73
|
+
if (sMarker !== 0x02) return false // DER INTEGER
|
|
74
|
+
if (sLen === 0) return false // S length is zero
|
|
75
|
+
|
|
76
|
+
// Check R value negative or excessively padded
|
|
77
|
+
if ((buf[4] & 0x80) !== 0) return false // R value negative
|
|
78
|
+
if (rLen > 1 && buf[4] === 0x00 && (buf[5] & 0x80) === 0) return false // R value excessively padded
|
|
79
|
+
|
|
80
|
+
// Check S value negative or excessively padded
|
|
81
|
+
const sValueOffset = sMarkerOffset + 2
|
|
82
|
+
if ((buf[sValueOffset] & 0x80) !== 0) return false // S value negative
|
|
83
|
+
if (sLen > 1 && buf[sValueOffset] === 0x00 && (buf[sValueOffset + 1] & 0x80) === 0) return false // S value excessively padded
|
|
84
|
+
|
|
85
|
+
if (rLen + sLen + 7 !== buf.length) return false // Final length check including hash type
|
|
86
|
+
|
|
87
|
+
return true
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isOpcodeDisabledHelper (op: number): boolean {
|
|
91
|
+
return (
|
|
92
|
+
op === OP.OP_2MUL ||
|
|
93
|
+
op === OP.OP_2DIV ||
|
|
94
|
+
op === OP.OP_VERIF ||
|
|
95
|
+
op === OP.OP_VERNOTIF ||
|
|
96
|
+
op === OP.OP_VER
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function isChunkMinimalPushHelper (chunk: ScriptChunk): boolean {
|
|
101
|
+
const data = chunk.data
|
|
102
|
+
const op = chunk.op
|
|
103
|
+
if (!Array.isArray(data)) return true
|
|
104
|
+
if (data.length === 0) return op === OP.OP_0
|
|
105
|
+
if (data.length === 1 && data[0] >= 1 && data[0] <= 16) return op === OP.OP_1 + (data[0] - 1)
|
|
106
|
+
if (data.length === 1 && data[0] === 0x81) return op === OP.OP_1NEGATE
|
|
107
|
+
if (data.length <= 75) return op === data.length
|
|
108
|
+
if (data.length <= 255) return op === OP.OP_PUSHDATA1
|
|
109
|
+
if (data.length <= 65535) return op === OP.OP_PUSHDATA2
|
|
110
|
+
return true
|
|
111
|
+
}
|
|
112
|
+
|
|
24
113
|
/**
|
|
25
114
|
* The Spend class represents a spend action within a Bitcoin SV transaction.
|
|
26
115
|
* It encapsulates all the necessary data required for spending a UTXO (Unspent Transaction Output)
|
|
@@ -52,6 +141,7 @@ export default class Spend {
|
|
|
52
141
|
unlockingScript: UnlockingScript
|
|
53
142
|
inputSequence: number
|
|
54
143
|
lockTime: number
|
|
144
|
+
|
|
55
145
|
context: 'UnlockingScript' | 'LockingScript'
|
|
56
146
|
programCounter: number
|
|
57
147
|
lastCodeSeparator: number | null
|
|
@@ -119,7 +209,10 @@ export default class Spend {
|
|
|
119
209
|
this.unlockingScript = params.unlockingScript
|
|
120
210
|
this.inputSequence = params.inputSequence
|
|
121
211
|
this.lockTime = params.lockTime
|
|
122
|
-
this.memoryLimit = params.memoryLimit ??
|
|
212
|
+
this.memoryLimit = params.memoryLimit ?? 32000000
|
|
213
|
+
this.stack = []
|
|
214
|
+
this.altStack = []
|
|
215
|
+
this.ifStack = []
|
|
123
216
|
this.stackMem = 0
|
|
124
217
|
this.altStackMem = 0
|
|
125
218
|
this.reset()
|
|
@@ -136,377 +229,212 @@ export default class Spend {
|
|
|
136
229
|
this.altStackMem = 0
|
|
137
230
|
}
|
|
138
231
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return false
|
|
145
|
-
}
|
|
146
|
-
if (this.altStackMem > this.memoryLimit) {
|
|
147
|
-
this.scriptEvaluationError('Alt stack memory usage has exceeded ' + String(this.memoryLimit) + ' bytes')
|
|
148
|
-
return false
|
|
232
|
+
private ensureStackMem (additional: number): void {
|
|
233
|
+
if (this.stackMem + additional > this.memoryLimit) {
|
|
234
|
+
this.scriptEvaluationError(
|
|
235
|
+
'Stack memory usage has exceeded ' + String(this.memoryLimit) + ' bytes'
|
|
236
|
+
)
|
|
149
237
|
}
|
|
238
|
+
}
|
|
150
239
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
240
|
+
private ensureAltStackMem (additional: number): void {
|
|
241
|
+
if (this.altStackMem + additional > this.memoryLimit) {
|
|
242
|
+
this.scriptEvaluationError(
|
|
243
|
+
'Alt stack memory usage has exceeded ' + String(this.memoryLimit) + ' bytes'
|
|
244
|
+
)
|
|
154
245
|
}
|
|
246
|
+
}
|
|
155
247
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
) {
|
|
162
|
-
this.context = 'LockingScript'
|
|
163
|
-
this.programCounter = 0
|
|
164
|
-
}
|
|
248
|
+
private pushStack (item: number[]): void {
|
|
249
|
+
this.ensureStackMem(item.length)
|
|
250
|
+
this.stack.push(item)
|
|
251
|
+
this.stackMem += item.length
|
|
252
|
+
}
|
|
165
253
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
254
|
+
private pushStackCopy (item: Readonly<number[]>): void {
|
|
255
|
+
this.ensureStackMem(item.length)
|
|
256
|
+
const copy = item.slice()
|
|
257
|
+
this.stack.push(copy)
|
|
258
|
+
this.stackMem += copy.length
|
|
259
|
+
}
|
|
172
260
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
op === OP.OP_2DIV ||
|
|
177
|
-
op === OP.OP_VERIF ||
|
|
178
|
-
op === OP.OP_VERNOTIF ||
|
|
179
|
-
op === OP.OP_VER
|
|
180
|
-
)
|
|
261
|
+
private popStack (): number[] {
|
|
262
|
+
if (this.stack.length === 0) {
|
|
263
|
+
this.scriptEvaluationError('Attempted to pop from an empty stack.')
|
|
181
264
|
}
|
|
265
|
+
const item = this.stack.pop() as number[]
|
|
266
|
+
this.stackMem -= item.length
|
|
267
|
+
return item
|
|
268
|
+
}
|
|
182
269
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
if (data.length === 0) {
|
|
190
|
-
// Could have used OP_0.
|
|
191
|
-
return op === OP.OP_0
|
|
192
|
-
} else if (data.length === 1 && data[0] >= 1 && data[0] <= 16) {
|
|
193
|
-
// Could have used OP_1 .. OP_16.
|
|
194
|
-
return op === OP.OP_1 + (data[0] - 1)
|
|
195
|
-
} else if (data.length === 1 && data[0] === 0x81) {
|
|
196
|
-
// Could have used OP_1NEGATE.
|
|
197
|
-
return op === OP.OP_1NEGATE
|
|
198
|
-
} else if (data.length <= 75) {
|
|
199
|
-
// Could have used a direct push (opCode indicating number of bytes pushed + those bytes).
|
|
200
|
-
return op === data.length
|
|
201
|
-
} else if (data.length <= 255) {
|
|
202
|
-
// Could have used OP_PUSHDATA.
|
|
203
|
-
return op === OP.OP_PUSHDATA1
|
|
204
|
-
} else if (data.length <= 65535) {
|
|
205
|
-
// Could have used OP_PUSHDATA2.
|
|
206
|
-
return op === OP.OP_PUSHDATA2
|
|
207
|
-
}
|
|
208
|
-
return true
|
|
270
|
+
private stackTop (index: number = -1): number[] {
|
|
271
|
+
// index = -1 for top, -2 for second top, etc.
|
|
272
|
+
// stack.length + index provides 0-based index from start
|
|
273
|
+
if (this.stack.length === 0 || this.stack.length < Math.abs(index) || (index >= 0 && index >= this.stack.length)) {
|
|
274
|
+
this.scriptEvaluationError(`Stack underflow accessing element at index ${index}. Stack length is ${this.stack.length}.`)
|
|
209
275
|
}
|
|
276
|
+
return this.stack[this.stack.length + index]
|
|
277
|
+
}
|
|
210
278
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (buf.length > maxNumSize) {
|
|
217
|
-
return false
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (buf.length > 0) {
|
|
221
|
-
// Check that the number is encoded with the minimum possible number
|
|
222
|
-
// of bytes.
|
|
223
|
-
//
|
|
224
|
-
// If the most-significant-byte - excluding the sign bit - is zero
|
|
225
|
-
// then we're not minimal. Note how this test also rejects the
|
|
226
|
-
// negative-zero encoding, 0x80.
|
|
227
|
-
if ((buf[buf.length - 1] & 0x7f) === 0) {
|
|
228
|
-
// One exception: if there's more than one byte and the most
|
|
229
|
-
// significant bit of the second-most-significant-byte is set it
|
|
230
|
-
// would conflict with the sign bit. An example of this case is
|
|
231
|
-
// +-255, which encode to 0xff00 and 0xff80 respectively.
|
|
232
|
-
// (big-endian).
|
|
233
|
-
if (buf.length <= 1 || (buf[buf.length - 2] & 0x80) === 0) {
|
|
234
|
-
return false
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
return true
|
|
239
|
-
}
|
|
279
|
+
private pushAltStack (item: number[]): void {
|
|
280
|
+
this.ensureAltStackMem(item.length)
|
|
281
|
+
this.altStack.push(item)
|
|
282
|
+
this.altStackMem += item.length
|
|
283
|
+
}
|
|
240
284
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
b.unshift(0)
|
|
245
|
-
}
|
|
246
|
-
return b
|
|
285
|
+
private popAltStack (): number[] {
|
|
286
|
+
if (this.altStack.length === 0) {
|
|
287
|
+
this.scriptEvaluationError('Attempted to pop from an empty alt stack.')
|
|
247
288
|
}
|
|
289
|
+
const item = this.altStack.pop() as number[]
|
|
290
|
+
this.altStackMem -= item.length
|
|
291
|
+
return item
|
|
292
|
+
}
|
|
248
293
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
* the script interpreter. This "DER" format actually includes an extra byte,
|
|
252
|
-
* the nHashType, at the end. It is really the tx format, not DER format.
|
|
253
|
-
*
|
|
254
|
-
* A canonical signature exists of: [30] [total len] [02] [len R] [R] [02] [len S] [S] [hashtype]
|
|
255
|
-
* Where R and S are not negative (their first byte has its highest bit not set), and not
|
|
256
|
-
* excessively padded (do not start with a 0 byte, unless an otherwise negative number follows,
|
|
257
|
-
* in which case a single 0 byte is necessary and even required).
|
|
258
|
-
*
|
|
259
|
-
* See https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623
|
|
260
|
-
*/
|
|
261
|
-
const isChecksigFormat = (buf: number[]): boolean => {
|
|
262
|
-
if (buf.length < 9) {
|
|
263
|
-
// Non-canonical signature: too short
|
|
264
|
-
return false
|
|
265
|
-
}
|
|
266
|
-
if (buf.length > 73) {
|
|
267
|
-
// Non-canonical signature: too long
|
|
268
|
-
return false
|
|
269
|
-
}
|
|
270
|
-
if (buf[0] !== 0x30) {
|
|
271
|
-
// Non-canonical signature: wrong type
|
|
272
|
-
return false
|
|
273
|
-
}
|
|
274
|
-
if (buf[1] !== buf.length - 3) {
|
|
275
|
-
// Non-canonical signature: wrong length marker
|
|
276
|
-
return false
|
|
277
|
-
}
|
|
278
|
-
const nLEnR = buf[3]
|
|
279
|
-
if (5 + nLEnR >= buf.length) {
|
|
280
|
-
// Non-canonical signature: S length misplaced
|
|
281
|
-
return false
|
|
282
|
-
}
|
|
283
|
-
const nLEnS = buf[5 + nLEnR]
|
|
284
|
-
if (nLEnR + nLEnS + 7 !== buf.length) {
|
|
285
|
-
// Non-canonical signature: R+S length mismatch
|
|
286
|
-
return false
|
|
287
|
-
}
|
|
294
|
+
private checkSignatureEncoding (buf: Readonly<number[]>): boolean {
|
|
295
|
+
if (buf.length === 0) return true
|
|
288
296
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
if (nLEnR === 0) {
|
|
299
|
-
// Non-canonical signature: R length is zero
|
|
300
|
-
return false
|
|
301
|
-
}
|
|
302
|
-
if ((R[0] & 0x80) !== 0) {
|
|
303
|
-
// Non-canonical signature: R value negative
|
|
297
|
+
if (!isChecksigFormatHelper(buf)) {
|
|
298
|
+
this.scriptEvaluationError('The signature format is invalid.') // Generic message like original
|
|
299
|
+
return false
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
const sig = TransactionSignature.fromChecksigFormat(buf as number[]) // This can throw for stricter DER rules
|
|
303
|
+
if (requireLowSSignatures && !sig.hasLowS()) {
|
|
304
|
+
this.scriptEvaluationError('The signature must have a low S value.')
|
|
304
305
|
return false
|
|
305
306
|
}
|
|
306
|
-
if (
|
|
307
|
-
|
|
307
|
+
if ((sig.scope & TransactionSignature.SIGHASH_FORKID) === 0) {
|
|
308
|
+
this.scriptEvaluationError('The signature must use SIGHASH_FORKID.')
|
|
308
309
|
return false
|
|
309
310
|
}
|
|
311
|
+
} catch (e) {
|
|
312
|
+
this.scriptEvaluationError('The signature format is invalid.')
|
|
313
|
+
return false
|
|
314
|
+
}
|
|
315
|
+
return true
|
|
316
|
+
}
|
|
310
317
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
324
|
-
if ((S[0] & 0x80) !== 0) {
|
|
325
|
-
// Non-canonical signature: S value negative
|
|
318
|
+
private checkPublicKeyEncoding (buf: Readonly<number[]>): boolean {
|
|
319
|
+
if (buf.length === 0) {
|
|
320
|
+
this.scriptEvaluationError('Public key is empty.')
|
|
321
|
+
return false
|
|
322
|
+
}
|
|
323
|
+
if (buf.length < 33) {
|
|
324
|
+
this.scriptEvaluationError('The public key is too short, it must be at least 33 bytes.')
|
|
325
|
+
return false
|
|
326
|
+
}
|
|
327
|
+
if (buf[0] === 0x04) {
|
|
328
|
+
if (buf.length !== 65) {
|
|
329
|
+
this.scriptEvaluationError('The non-compressed public key must be 65 bytes.')
|
|
326
330
|
return false
|
|
327
331
|
}
|
|
328
|
-
|
|
329
|
-
|
|
332
|
+
} else if (buf[0] === 0x02 || buf[0] === 0x03) {
|
|
333
|
+
if (buf.length !== 33) {
|
|
334
|
+
this.scriptEvaluationError('The compressed public key must be 33 bytes.')
|
|
330
335
|
return false
|
|
331
336
|
}
|
|
332
|
-
|
|
337
|
+
} else {
|
|
338
|
+
this.scriptEvaluationError('The public key is in an unknown format.')
|
|
339
|
+
return false
|
|
333
340
|
}
|
|
341
|
+
try {
|
|
342
|
+
PublicKey.fromDER(buf as number[]) // This can throw for stricter DER rules
|
|
343
|
+
} catch (e) {
|
|
344
|
+
this.scriptEvaluationError('The public key is in an unknown format.')
|
|
345
|
+
return false
|
|
346
|
+
}
|
|
347
|
+
return true
|
|
348
|
+
}
|
|
334
349
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
)
|
|
357
|
-
return false
|
|
358
|
-
}
|
|
350
|
+
private verifySignature (
|
|
351
|
+
sig: TransactionSignature,
|
|
352
|
+
pubkey: PublicKey,
|
|
353
|
+
subscript: Script
|
|
354
|
+
): boolean {
|
|
355
|
+
const preimage = TransactionSignature.format({
|
|
356
|
+
sourceTXID: this.sourceTXID,
|
|
357
|
+
sourceOutputIndex: this.sourceOutputIndex,
|
|
358
|
+
sourceSatoshis: this.sourceSatoshis,
|
|
359
|
+
transactionVersion: this.transactionVersion,
|
|
360
|
+
otherInputs: this.otherInputs,
|
|
361
|
+
outputs: this.outputs,
|
|
362
|
+
inputIndex: this.inputIndex,
|
|
363
|
+
subscript,
|
|
364
|
+
inputSequence: this.inputSequence,
|
|
365
|
+
lockTime: this.lockTime,
|
|
366
|
+
scope: sig.scope
|
|
367
|
+
})
|
|
368
|
+
const hash = new BigNumber(Hash.hash256(preimage))
|
|
369
|
+
return verify(hash, sig, pubkey)
|
|
370
|
+
}
|
|
359
371
|
|
|
360
|
-
|
|
372
|
+
step (): boolean {
|
|
373
|
+
if (this.stackMem > this.memoryLimit) {
|
|
374
|
+
this.scriptEvaluationError('Stack memory usage has exceeded ' + String(this.memoryLimit) + ' bytes')
|
|
375
|
+
return false // Error thrown
|
|
376
|
+
}
|
|
377
|
+
if (this.altStackMem > this.memoryLimit) {
|
|
378
|
+
this.scriptEvaluationError('Alt stack memory usage has exceeded ' + String(this.memoryLimit) + ' bytes')
|
|
379
|
+
return false // Error thrown
|
|
361
380
|
}
|
|
362
381
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
if (buf[0] === 0x04) {
|
|
370
|
-
if (buf.length !== 65) {
|
|
371
|
-
this.scriptEvaluationError(
|
|
372
|
-
'The non-compressed public key must be 65 bytes.'
|
|
373
|
-
)
|
|
374
|
-
}
|
|
375
|
-
} else if (buf[0] === 0x02 || buf[0] === 0x03) {
|
|
376
|
-
if (buf.length !== 33) {
|
|
377
|
-
this.scriptEvaluationError(
|
|
378
|
-
'The compressed public key must be 33 bytes.'
|
|
379
|
-
)
|
|
380
|
-
}
|
|
381
|
-
} else {
|
|
382
|
-
this.scriptEvaluationError(
|
|
383
|
-
'The public key is in an unknown format.'
|
|
384
|
-
)
|
|
385
|
-
}
|
|
386
|
-
return true
|
|
382
|
+
if (
|
|
383
|
+
this.context === 'UnlockingScript' &&
|
|
384
|
+
this.programCounter >= this.unlockingScript.chunks.length
|
|
385
|
+
) {
|
|
386
|
+
this.context = 'LockingScript'
|
|
387
|
+
this.programCounter = 0
|
|
387
388
|
}
|
|
388
389
|
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
subscript: Script
|
|
393
|
-
): boolean => {
|
|
394
|
-
const preimage = TransactionSignature.format({
|
|
395
|
-
sourceTXID: this.sourceTXID,
|
|
396
|
-
sourceOutputIndex: this.sourceOutputIndex,
|
|
397
|
-
sourceSatoshis: this.sourceSatoshis,
|
|
398
|
-
transactionVersion: this.transactionVersion,
|
|
399
|
-
otherInputs: this.otherInputs,
|
|
400
|
-
outputs: this.outputs,
|
|
401
|
-
inputIndex: this.inputIndex,
|
|
402
|
-
subscript,
|
|
403
|
-
inputSequence: this.inputSequence,
|
|
404
|
-
lockTime: this.lockTime,
|
|
405
|
-
scope: sig.scope
|
|
406
|
-
})
|
|
407
|
-
const hash = new BigNumber(Hash.hash256(preimage))
|
|
408
|
-
return verify(hash, sig, pubkey)
|
|
390
|
+
const currentScript = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript
|
|
391
|
+
if (this.programCounter >= currentScript.chunks.length) {
|
|
392
|
+
return false
|
|
409
393
|
}
|
|
394
|
+
const operation = currentScript.chunks[this.programCounter]
|
|
410
395
|
|
|
411
|
-
const isScriptExecuting = !this.ifStack.includes(false)
|
|
412
|
-
let buf: number[],
|
|
413
|
-
buf1: number[],
|
|
414
|
-
buf2: number[],
|
|
415
|
-
buf3: number[],
|
|
416
|
-
spliced: number[][],
|
|
417
|
-
n: number,
|
|
418
|
-
size: number,
|
|
419
|
-
rawnum: number[],
|
|
420
|
-
num: number[],
|
|
421
|
-
signbit: number,
|
|
422
|
-
x1: number[],
|
|
423
|
-
x2: number[],
|
|
424
|
-
x3: number[],
|
|
425
|
-
bn: BigNumber,
|
|
426
|
-
bn1: BigNumber,
|
|
427
|
-
bn2: BigNumber,
|
|
428
|
-
bn3: BigNumber,
|
|
429
|
-
bufSig: number[],
|
|
430
|
-
bufPubkey: number[],
|
|
431
|
-
subscript
|
|
432
|
-
let sig,
|
|
433
|
-
pubkey,
|
|
434
|
-
i: number,
|
|
435
|
-
fOk: boolean,
|
|
436
|
-
nKeysCount: number,
|
|
437
|
-
ikey: number,
|
|
438
|
-
ikey2: number,
|
|
439
|
-
nSigsCount: number,
|
|
440
|
-
isig: number
|
|
441
|
-
let fValue: boolean, fEqual: boolean, fSuccess: boolean
|
|
442
|
-
|
|
443
|
-
// Read instruction
|
|
444
396
|
const currentOpcode = operation.op
|
|
445
397
|
if (typeof currentOpcode === 'undefined') {
|
|
446
|
-
this.scriptEvaluationError(
|
|
447
|
-
`An opcode is missing in this chunk of the ${this.context}!`
|
|
448
|
-
)
|
|
398
|
+
this.scriptEvaluationError(`Missing opcode in ${this.context} at pc=${this.programCounter}.`) // Error thrown
|
|
449
399
|
}
|
|
450
|
-
if (
|
|
451
|
-
|
|
452
|
-
operation.data.length > maxScriptElementSize
|
|
453
|
-
) {
|
|
454
|
-
this.scriptEvaluationError(
|
|
455
|
-
`It's not currently possible to push data larger than ${maxScriptElementSize} bytes.`
|
|
456
|
-
)
|
|
400
|
+
if (Array.isArray(operation.data) && operation.data.length > maxScriptElementSize) {
|
|
401
|
+
this.scriptEvaluationError(`Data push > ${maxScriptElementSize} bytes (pc=${this.programCounter}).`) // Error thrown
|
|
457
402
|
}
|
|
458
403
|
|
|
459
|
-
|
|
460
|
-
|
|
404
|
+
const isScriptExecuting = !this.ifStack.includes(false)
|
|
405
|
+
|
|
406
|
+
if (isScriptExecuting && isOpcodeDisabledHelper(currentOpcode)) {
|
|
407
|
+
this.scriptEvaluationError(`This opcode is currently disabled. (Opcode: ${OP[currentOpcode] as string}, PC: ${this.programCounter})`) // Error thrown
|
|
461
408
|
}
|
|
462
409
|
|
|
463
|
-
if (
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
currentOpcode <= OP.OP_PUSHDATA4
|
|
467
|
-
) {
|
|
468
|
-
if (requireMinimalPush && !isChunkMinimal(operation)) {
|
|
469
|
-
this.scriptEvaluationError('This data is not minimally-encoded.')
|
|
410
|
+
if (isScriptExecuting && currentOpcode >= 0 && currentOpcode <= OP.OP_PUSHDATA4) {
|
|
411
|
+
if (requireMinimalPush && !isChunkMinimalPushHelper(operation)) {
|
|
412
|
+
this.scriptEvaluationError(`This data is not minimally-encoded. (PC: ${this.programCounter})`) // Error thrown
|
|
470
413
|
}
|
|
414
|
+
this.pushStack(Array.isArray(operation.data) ? operation.data : [])
|
|
415
|
+
} else if (isScriptExecuting || (currentOpcode >= OP.OP_IF && currentOpcode <= OP.OP_ENDIF)) {
|
|
416
|
+
let buf: number[], buf1: number[], buf2: number[], buf3: number[]
|
|
417
|
+
let x1: number[], x2: number[], x3: number[]
|
|
418
|
+
let bn: BigNumber, bn1: BigNumber, bn2: BigNumber, bn3: BigNumber
|
|
419
|
+
let n: number, size: number, fValue: boolean, fSuccess: boolean, subscript: Script
|
|
420
|
+
let bufSig: number[], bufPubkey: number[]
|
|
421
|
+
let sig: TransactionSignature, pubkey: PublicKey
|
|
422
|
+
let i: number, ikey: number, isig: number, nKeysCount: number, nSigsCount: number, fOk: boolean
|
|
471
423
|
|
|
472
|
-
if (!Array.isArray(operation.data)) {
|
|
473
|
-
this.stack.push([])
|
|
474
|
-
this.stackMem += 0
|
|
475
|
-
} else {
|
|
476
|
-
this.stack.push(operation.data)
|
|
477
|
-
this.stackMem += operation.data.length
|
|
478
|
-
}
|
|
479
|
-
} else if (
|
|
480
|
-
isScriptExecuting ||
|
|
481
|
-
(OP.OP_IF <= currentOpcode && currentOpcode <= OP.OP_ENDIF)
|
|
482
|
-
) {
|
|
483
424
|
switch (currentOpcode) {
|
|
484
|
-
case OP.OP_1NEGATE:
|
|
485
|
-
case OP.
|
|
486
|
-
case OP.OP_2:
|
|
487
|
-
case OP.
|
|
488
|
-
case OP.
|
|
489
|
-
case OP.
|
|
490
|
-
case OP.OP_6:
|
|
491
|
-
case OP.OP_7:
|
|
492
|
-
case OP.OP_8:
|
|
493
|
-
case OP.OP_9:
|
|
494
|
-
case OP.OP_10:
|
|
495
|
-
case OP.OP_11:
|
|
496
|
-
case OP.OP_12:
|
|
497
|
-
case OP.OP_13:
|
|
498
|
-
case OP.OP_14:
|
|
499
|
-
case OP.OP_15:
|
|
500
|
-
case OP.OP_16:
|
|
425
|
+
case OP.OP_1NEGATE: this.pushStackCopy(SCRIPTNUM_NEG_1); break
|
|
426
|
+
case OP.OP_0: this.pushStackCopy(SCRIPTNUMS_0_TO_16[0]); break
|
|
427
|
+
case OP.OP_1: case OP.OP_2: case OP.OP_3: case OP.OP_4:
|
|
428
|
+
case OP.OP_5: case OP.OP_6: case OP.OP_7: case OP.OP_8:
|
|
429
|
+
case OP.OP_9: case OP.OP_10: case OP.OP_11: case OP.OP_12:
|
|
430
|
+
case OP.OP_13: case OP.OP_14: case OP.OP_15: case OP.OP_16:
|
|
501
431
|
n = currentOpcode - (OP.OP_1 - 1)
|
|
502
|
-
|
|
503
|
-
this.stack.push(buf)
|
|
504
|
-
this.stackMem += buf.length
|
|
432
|
+
this.pushStackCopy(SCRIPTNUMS_0_TO_16[n])
|
|
505
433
|
break
|
|
506
434
|
|
|
507
435
|
case OP.OP_NOP:
|
|
508
|
-
case OP.OP_NOP2:
|
|
509
|
-
case OP.OP_NOP3:
|
|
436
|
+
case OP.OP_NOP2: // Formerly CHECKLOCKTIMEVERIFY
|
|
437
|
+
case OP.OP_NOP3: // Formerly CHECKSEQUENCEVERIFY
|
|
510
438
|
case OP.OP_NOP1:
|
|
511
439
|
case OP.OP_NOP4:
|
|
512
440
|
case OP.OP_NOP5:
|
|
@@ -515,69 +443,22 @@ export default class Spend {
|
|
|
515
443
|
case OP.OP_NOP8:
|
|
516
444
|
case OP.OP_NOP9:
|
|
517
445
|
case OP.OP_NOP10:
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
case OP.OP_NOP14:
|
|
522
|
-
case OP.
|
|
523
|
-
case OP.
|
|
524
|
-
case OP.
|
|
525
|
-
case OP.
|
|
526
|
-
case OP.
|
|
527
|
-
case OP.
|
|
528
|
-
case OP.
|
|
529
|
-
case OP.
|
|
530
|
-
case OP.
|
|
531
|
-
case OP.
|
|
532
|
-
case OP.
|
|
533
|
-
case OP.
|
|
534
|
-
case OP.OP_NOP27:
|
|
535
|
-
case OP.OP_NOP28:
|
|
536
|
-
case OP.OP_NOP29:
|
|
537
|
-
case OP.OP_NOP30:
|
|
538
|
-
case OP.OP_NOP31:
|
|
539
|
-
case OP.OP_NOP32:
|
|
540
|
-
case OP.OP_NOP33:
|
|
541
|
-
case OP.OP_NOP34:
|
|
542
|
-
case OP.OP_NOP35:
|
|
543
|
-
case OP.OP_NOP36:
|
|
544
|
-
case OP.OP_NOP37:
|
|
545
|
-
case OP.OP_NOP38:
|
|
546
|
-
case OP.OP_NOP39:
|
|
547
|
-
case OP.OP_NOP40:
|
|
548
|
-
case OP.OP_NOP41:
|
|
549
|
-
case OP.OP_NOP42:
|
|
550
|
-
case OP.OP_NOP43:
|
|
551
|
-
case OP.OP_NOP44:
|
|
552
|
-
case OP.OP_NOP45:
|
|
553
|
-
case OP.OP_NOP46:
|
|
554
|
-
case OP.OP_NOP47:
|
|
555
|
-
case OP.OP_NOP48:
|
|
556
|
-
case OP.OP_NOP49:
|
|
557
|
-
case OP.OP_NOP50:
|
|
558
|
-
case OP.OP_NOP51:
|
|
559
|
-
case OP.OP_NOP52:
|
|
560
|
-
case OP.OP_NOP53:
|
|
561
|
-
case OP.OP_NOP54:
|
|
562
|
-
case OP.OP_NOP55:
|
|
563
|
-
case OP.OP_NOP56:
|
|
564
|
-
case OP.OP_NOP57:
|
|
565
|
-
case OP.OP_NOP58:
|
|
566
|
-
case OP.OP_NOP59:
|
|
567
|
-
case OP.OP_NOP60:
|
|
568
|
-
case OP.OP_NOP61:
|
|
569
|
-
case OP.OP_NOP62:
|
|
570
|
-
case OP.OP_NOP63:
|
|
571
|
-
case OP.OP_NOP64:
|
|
572
|
-
case OP.OP_NOP65:
|
|
573
|
-
case OP.OP_NOP66:
|
|
574
|
-
case OP.OP_NOP67:
|
|
575
|
-
case OP.OP_NOP68:
|
|
576
|
-
case OP.OP_NOP69:
|
|
577
|
-
case OP.OP_NOP70:
|
|
578
|
-
case OP.OP_NOP71:
|
|
579
|
-
case OP.OP_NOP72:
|
|
580
|
-
case OP.OP_NOP73:
|
|
446
|
+
/* falls through */
|
|
447
|
+
// eslint-disable-next-line no-fallthrough
|
|
448
|
+
// eslint-disable-next-line no-fallthrough
|
|
449
|
+
case OP.OP_NOP11: case OP.OP_NOP12: case OP.OP_NOP13: case OP.OP_NOP14: case OP.OP_NOP15:
|
|
450
|
+
case OP.OP_NOP16: case OP.OP_NOP17: case OP.OP_NOP18: case OP.OP_NOP19: case OP.OP_NOP20:
|
|
451
|
+
case OP.OP_NOP21: case OP.OP_NOP22: case OP.OP_NOP23: case OP.OP_NOP24: case OP.OP_NOP25:
|
|
452
|
+
case OP.OP_NOP26: case OP.OP_NOP27: case OP.OP_NOP28: case OP.OP_NOP29: case OP.OP_NOP30:
|
|
453
|
+
case OP.OP_NOP31: case OP.OP_NOP32: case OP.OP_NOP33: case OP.OP_NOP34: case OP.OP_NOP35:
|
|
454
|
+
case OP.OP_NOP36: case OP.OP_NOP37: case OP.OP_NOP38: case OP.OP_NOP39: case OP.OP_NOP40:
|
|
455
|
+
case OP.OP_NOP41: case OP.OP_NOP42: case OP.OP_NOP43: case OP.OP_NOP44: case OP.OP_NOP45:
|
|
456
|
+
case OP.OP_NOP46: case OP.OP_NOP47: case OP.OP_NOP48: case OP.OP_NOP49: case OP.OP_NOP50:
|
|
457
|
+
case OP.OP_NOP51: case OP.OP_NOP52: case OP.OP_NOP53: case OP.OP_NOP54: case OP.OP_NOP55:
|
|
458
|
+
case OP.OP_NOP56: case OP.OP_NOP57: case OP.OP_NOP58: case OP.OP_NOP59: case OP.OP_NOP60:
|
|
459
|
+
case OP.OP_NOP61: case OP.OP_NOP62: case OP.OP_NOP63: case OP.OP_NOP64: case OP.OP_NOP65:
|
|
460
|
+
case OP.OP_NOP66: case OP.OP_NOP67: case OP.OP_NOP68: case OP.OP_NOP69: case OP.OP_NOP70:
|
|
461
|
+
case OP.OP_NOP71: case OP.OP_NOP72: case OP.OP_NOP73:
|
|
581
462
|
case OP.OP_NOP77:
|
|
582
463
|
break
|
|
583
464
|
|
|
@@ -585,1091 +466,525 @@ export default class Spend {
|
|
|
585
466
|
case OP.OP_NOTIF:
|
|
586
467
|
fValue = false
|
|
587
468
|
if (isScriptExecuting) {
|
|
588
|
-
if (this.stack.length < 1)
|
|
589
|
-
|
|
590
|
-
'OP_IF and OP_NOTIF require at least one item on the stack when they are used!'
|
|
591
|
-
)
|
|
592
|
-
}
|
|
593
|
-
buf = this.stacktop(-1)
|
|
594
|
-
|
|
469
|
+
if (this.stack.length < 1) this.scriptEvaluationError('OP_IF and OP_NOTIF require at least one item on the stack when they are used!')
|
|
470
|
+
buf = this.popStack()
|
|
595
471
|
fValue = this.castToBool(buf)
|
|
596
|
-
if (currentOpcode === OP.OP_NOTIF)
|
|
597
|
-
fValue = !fValue
|
|
598
|
-
}
|
|
599
|
-
poppedValue = this.stack.pop()
|
|
600
|
-
if (poppedValue != null) {
|
|
601
|
-
this.stackMem -= poppedValue.length
|
|
602
|
-
}
|
|
603
|
-
clearPoppedValue()
|
|
472
|
+
if (currentOpcode === OP.OP_NOTIF) fValue = !fValue
|
|
604
473
|
}
|
|
605
474
|
this.ifStack.push(fValue)
|
|
606
475
|
break
|
|
607
|
-
|
|
608
476
|
case OP.OP_ELSE:
|
|
609
|
-
if (this.ifStack.length === 0)
|
|
610
|
-
|
|
611
|
-
}
|
|
612
|
-
this.ifStack[this.ifStack.length - 1] =
|
|
613
|
-
!this.ifStack[this.ifStack.length - 1]
|
|
477
|
+
if (this.ifStack.length === 0) this.scriptEvaluationError('OP_ELSE requires a preceeding OP_IF.')
|
|
478
|
+
this.ifStack[this.ifStack.length - 1] = !this.ifStack[this.ifStack.length - 1]
|
|
614
479
|
break
|
|
615
|
-
|
|
616
480
|
case OP.OP_ENDIF:
|
|
617
|
-
if (this.ifStack.length === 0)
|
|
618
|
-
this.scriptEvaluationError('OP_ENDIF requires a preceeding OP_IF.')
|
|
619
|
-
}
|
|
481
|
+
if (this.ifStack.length === 0) this.scriptEvaluationError('OP_ENDIF requires a preceeding OP_IF.')
|
|
620
482
|
this.ifStack.pop()
|
|
621
483
|
break
|
|
622
|
-
|
|
623
484
|
case OP.OP_VERIFY:
|
|
624
|
-
if (this.stack.length < 1)
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
buf = this.stacktop(-1)
|
|
630
|
-
fValue = this.castToBool(buf)
|
|
631
|
-
poppedValue = this.stack.pop()
|
|
632
|
-
if (poppedValue != null) {
|
|
633
|
-
this.stackMem -= poppedValue.length
|
|
634
|
-
}
|
|
635
|
-
clearPoppedValue()
|
|
636
|
-
if (!fValue) {
|
|
637
|
-
this.scriptEvaluationError(
|
|
638
|
-
'OP_VERIFY requires the top stack value to be truthy.'
|
|
639
|
-
)
|
|
640
|
-
}
|
|
485
|
+
if (this.stack.length < 1) this.scriptEvaluationError('OP_VERIFY requires at least one item to be on the stack.')
|
|
486
|
+
buf1 = this.stackTop()
|
|
487
|
+
fValue = this.castToBool(buf1)
|
|
488
|
+
if (!fValue) this.scriptEvaluationError('OP_VERIFY requires the top stack value to be truthy.')
|
|
489
|
+
this.popStack()
|
|
641
490
|
break
|
|
642
|
-
|
|
643
491
|
case OP.OP_RETURN:
|
|
644
|
-
if (this.context === 'UnlockingScript')
|
|
645
|
-
|
|
646
|
-
} else {
|
|
647
|
-
this.programCounter = this.lockingScript.chunks.length
|
|
648
|
-
}
|
|
492
|
+
if (this.context === 'UnlockingScript') this.programCounter = this.unlockingScript.chunks.length
|
|
493
|
+
else this.programCounter = this.lockingScript.chunks.length
|
|
649
494
|
this.ifStack = []
|
|
495
|
+
this.programCounter-- // To counteract the final increment and ensure loop termination
|
|
650
496
|
break
|
|
651
497
|
|
|
652
498
|
case OP.OP_TOALTSTACK:
|
|
653
|
-
if (this.stack.length < 1)
|
|
654
|
-
|
|
655
|
-
'OP_TOALTSTACK requires at oeast one item to be on the stack.')
|
|
656
|
-
}
|
|
657
|
-
poppedValue = this.stack.pop()
|
|
658
|
-
if (poppedValue != null) {
|
|
659
|
-
this.altStack.push(poppedValue)
|
|
660
|
-
this.altStackMem += poppedValue.length
|
|
661
|
-
this.stackMem -= poppedValue.length
|
|
662
|
-
}
|
|
663
|
-
clearPoppedValue()
|
|
499
|
+
if (this.stack.length < 1) this.scriptEvaluationError('OP_TOALTSTACK requires at oeast one item to be on the stack.')
|
|
500
|
+
this.pushAltStack(this.popStack())
|
|
664
501
|
break
|
|
665
|
-
|
|
666
502
|
case OP.OP_FROMALTSTACK:
|
|
667
|
-
if (this.altStack.length < 1)
|
|
668
|
-
|
|
669
|
-
'OP_FROMALTSTACK requires at least one item to be on the stack.'
|
|
670
|
-
)
|
|
671
|
-
}
|
|
672
|
-
poppedValue = this.altStack.pop()
|
|
673
|
-
if (poppedValue != null) {
|
|
674
|
-
this.stack.push(poppedValue)
|
|
675
|
-
this.stackMem += poppedValue.length
|
|
676
|
-
this.altStackMem -= poppedValue.length
|
|
677
|
-
}
|
|
678
|
-
clearPoppedValue()
|
|
503
|
+
if (this.altStack.length < 1) this.scriptEvaluationError('OP_FROMALTSTACK requires at least one item to be on the stack.') // "stack" here means altstack
|
|
504
|
+
this.pushStack(this.popAltStack())
|
|
679
505
|
break
|
|
680
|
-
|
|
681
506
|
case OP.OP_2DROP:
|
|
682
|
-
if (this.stack.length < 2)
|
|
683
|
-
|
|
684
|
-
'OP_2DROP requires at least two items to be on the stack.'
|
|
685
|
-
)
|
|
686
|
-
}
|
|
687
|
-
poppedValue = this.stack.pop()
|
|
688
|
-
if (poppedValue != null) {
|
|
689
|
-
this.stackMem -= poppedValue.length
|
|
690
|
-
}
|
|
691
|
-
clearPoppedValue()
|
|
692
|
-
poppedValue = this.stack.pop()
|
|
693
|
-
if (poppedValue != null) {
|
|
694
|
-
this.stackMem -= poppedValue.length
|
|
695
|
-
}
|
|
696
|
-
clearPoppedValue()
|
|
507
|
+
if (this.stack.length < 2) this.scriptEvaluationError('OP_2DROP requires at least two items to be on the stack.')
|
|
508
|
+
this.popStack(); this.popStack()
|
|
697
509
|
break
|
|
698
|
-
|
|
699
510
|
case OP.OP_2DUP:
|
|
700
|
-
if (this.stack.length < 2)
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
}
|
|
705
|
-
buf1 = this.stacktop(-2)
|
|
706
|
-
buf2 = this.stacktop(-1)
|
|
707
|
-
this.stack.push([...buf1])
|
|
708
|
-
this.stackMem += buf1.length
|
|
709
|
-
this.stack.push([...buf2])
|
|
710
|
-
this.stackMem += buf2.length
|
|
511
|
+
if (this.stack.length < 2) this.scriptEvaluationError('OP_2DUP requires at least two items to be on the stack.')
|
|
512
|
+
buf1 = this.stackTop(-2)
|
|
513
|
+
buf2 = this.stackTop(-1)
|
|
514
|
+
this.pushStackCopy(buf1); this.pushStackCopy(buf2)
|
|
711
515
|
break
|
|
712
|
-
|
|
713
516
|
case OP.OP_3DUP:
|
|
714
|
-
if (this.stack.length < 3)
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
buf1 = this.stacktop(-3)
|
|
720
|
-
buf2 = this.stacktop(-2)
|
|
721
|
-
buf3 = this.stacktop(-1)
|
|
722
|
-
this.stack.push([...buf1])
|
|
723
|
-
this.stackMem += buf1.length
|
|
724
|
-
this.stack.push([...buf2])
|
|
725
|
-
this.stackMem += buf2.length
|
|
726
|
-
this.stack.push([...buf3])
|
|
727
|
-
this.stackMem += buf3.length
|
|
517
|
+
if (this.stack.length < 3) this.scriptEvaluationError('OP_3DUP requires at least three items to be on the stack.')
|
|
518
|
+
buf1 = this.stackTop(-3)
|
|
519
|
+
buf2 = this.stackTop(-2)
|
|
520
|
+
buf3 = this.stackTop(-1)
|
|
521
|
+
this.pushStackCopy(buf1); this.pushStackCopy(buf2); this.pushStackCopy(buf3)
|
|
728
522
|
break
|
|
729
|
-
|
|
730
523
|
case OP.OP_2OVER:
|
|
731
|
-
if (this.stack.length < 4)
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
}
|
|
736
|
-
buf1 = this.stacktop(-4)
|
|
737
|
-
buf2 = this.stacktop(-3)
|
|
738
|
-
this.stack.push([...buf1])
|
|
739
|
-
this.stackMem += buf1.length
|
|
740
|
-
this.stack.push([...buf2])
|
|
741
|
-
this.stackMem += buf2.length
|
|
524
|
+
if (this.stack.length < 4) this.scriptEvaluationError('OP_2OVER requires at least four items to be on the stack.')
|
|
525
|
+
buf1 = this.stackTop(-4)
|
|
526
|
+
buf2 = this.stackTop(-3)
|
|
527
|
+
this.pushStackCopy(buf1); this.pushStackCopy(buf2)
|
|
742
528
|
break
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
this.stack.push(spliced[0])
|
|
752
|
-
this.stackMem += spliced[0].length
|
|
753
|
-
this.stack.push(spliced[1])
|
|
754
|
-
this.stackMem += spliced[1].length
|
|
529
|
+
case OP.OP_2ROT: {
|
|
530
|
+
if (this.stack.length < 6) this.scriptEvaluationError('OP_2ROT requires at least six items to be on the stack.')
|
|
531
|
+
const rot6 = this.popStack(); const rot5 = this.popStack()
|
|
532
|
+
const rot4 = this.popStack(); const rot3 = this.popStack()
|
|
533
|
+
const rot2 = this.popStack(); const rot1 = this.popStack()
|
|
534
|
+
this.pushStack(rot3); this.pushStack(rot4)
|
|
535
|
+
this.pushStack(rot5); this.pushStack(rot6)
|
|
536
|
+
this.pushStack(rot1); this.pushStack(rot2)
|
|
755
537
|
break
|
|
756
|
-
|
|
757
|
-
case OP.OP_2SWAP:
|
|
758
|
-
if (this.stack.length < 4)
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
spliced = this.stack.splice(this.stack.length - 4, 2)
|
|
764
|
-
this.stack.push(spliced[0])
|
|
765
|
-
this.stackMem += spliced[0].length
|
|
766
|
-
this.stack.push(spliced[1])
|
|
767
|
-
this.stackMem += spliced[1].length
|
|
538
|
+
}
|
|
539
|
+
case OP.OP_2SWAP: {
|
|
540
|
+
if (this.stack.length < 4) this.scriptEvaluationError('OP_2SWAP requires at least four items to be on the stack.')
|
|
541
|
+
const swap4 = this.popStack(); const swap3 = this.popStack()
|
|
542
|
+
const swap2 = this.popStack(); const swap1 = this.popStack()
|
|
543
|
+
this.pushStack(swap3); this.pushStack(swap4)
|
|
544
|
+
this.pushStack(swap1); this.pushStack(swap2)
|
|
768
545
|
break
|
|
769
|
-
|
|
546
|
+
}
|
|
770
547
|
case OP.OP_IFDUP:
|
|
771
|
-
if (this.stack.length < 1)
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
)
|
|
775
|
-
}
|
|
776
|
-
buf = this.stacktop(-1)
|
|
777
|
-
fValue = this.castToBool(buf)
|
|
778
|
-
if (fValue) {
|
|
779
|
-
this.stack.push([...buf])
|
|
780
|
-
this.stackMem += buf.length
|
|
548
|
+
if (this.stack.length < 1) this.scriptEvaluationError('OP_IFDUP requires at least one item to be on the stack.')
|
|
549
|
+
buf1 = this.stackTop()
|
|
550
|
+
if (this.castToBool(buf1)) {
|
|
551
|
+
this.pushStackCopy(buf1)
|
|
781
552
|
}
|
|
782
553
|
break
|
|
783
|
-
|
|
784
554
|
case OP.OP_DEPTH:
|
|
785
|
-
|
|
786
|
-
this.stack.push(buf)
|
|
787
|
-
this.stackMem += buf.length
|
|
555
|
+
this.pushStack(new BigNumber(this.stack.length).toScriptNum())
|
|
788
556
|
break
|
|
789
|
-
|
|
790
557
|
case OP.OP_DROP:
|
|
791
|
-
if (this.stack.length < 1)
|
|
792
|
-
|
|
793
|
-
'OP_DROP requires at least one item to be on the stack.'
|
|
794
|
-
)
|
|
795
|
-
}
|
|
796
|
-
poppedValue = this.stack.pop()
|
|
797
|
-
if (poppedValue != null) {
|
|
798
|
-
this.stackMem -= poppedValue.length
|
|
799
|
-
}
|
|
800
|
-
clearPoppedValue()
|
|
558
|
+
if (this.stack.length < 1) this.scriptEvaluationError('OP_DROP requires at least one item to be on the stack.')
|
|
559
|
+
this.popStack()
|
|
801
560
|
break
|
|
802
|
-
|
|
803
561
|
case OP.OP_DUP:
|
|
804
|
-
if (this.stack.length < 1)
|
|
805
|
-
|
|
806
|
-
'OP_DUP requires at least one item to be on the stack.'
|
|
807
|
-
)
|
|
808
|
-
}
|
|
809
|
-
this.stack.push([...this.stacktop(-1)])
|
|
810
|
-
this.stackMem += this.stacktop(-1).length
|
|
562
|
+
if (this.stack.length < 1) this.scriptEvaluationError('OP_DUP requires at least one item to be on the stack.')
|
|
563
|
+
this.pushStackCopy(this.stackTop())
|
|
811
564
|
break
|
|
812
|
-
|
|
813
565
|
case OP.OP_NIP:
|
|
814
|
-
if (this.stack.length < 2)
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
}
|
|
819
|
-
this.stack.splice(this.stack.length - 2, 1)
|
|
820
|
-
this.stackMem -= this.stacktop(-1).length
|
|
566
|
+
if (this.stack.length < 2) this.scriptEvaluationError('OP_NIP requires at least two items to be on the stack.')
|
|
567
|
+
buf2 = this.popStack()
|
|
568
|
+
this.popStack()
|
|
569
|
+
this.pushStack(buf2)
|
|
821
570
|
break
|
|
822
|
-
|
|
823
571
|
case OP.OP_OVER:
|
|
824
|
-
if (this.stack.length < 2)
|
|
825
|
-
|
|
826
|
-
'OP_OVER requires at least two items to be on the stack.'
|
|
827
|
-
)
|
|
828
|
-
}
|
|
829
|
-
this.stack.push([...this.stacktop(-2)])
|
|
830
|
-
this.stackMem += this.stacktop(-2).length
|
|
572
|
+
if (this.stack.length < 2) this.scriptEvaluationError('OP_OVER requires at least two items to be on the stack.')
|
|
573
|
+
this.pushStackCopy(this.stackTop(-2))
|
|
831
574
|
break
|
|
832
|
-
|
|
833
575
|
case OP.OP_PICK:
|
|
834
|
-
case OP.OP_ROLL:
|
|
835
|
-
if (this.stack.length < 2) {
|
|
836
|
-
|
|
837
|
-
`${OP[currentOpcode] as string} requires at least two items to be on the stack.`
|
|
838
|
-
)
|
|
839
|
-
}
|
|
840
|
-
buf = this.stacktop(-1)
|
|
841
|
-
bn = BigNumber.fromScriptNum(buf, requireMinimalPush)
|
|
576
|
+
case OP.OP_ROLL: {
|
|
577
|
+
if (this.stack.length < 2) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least two items to be on the stack.`)
|
|
578
|
+
bn = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush)
|
|
842
579
|
n = bn.toNumber()
|
|
843
|
-
poppedValue = this.stack.pop()
|
|
844
|
-
if (poppedValue != null) {
|
|
845
|
-
this.stackMem -= poppedValue.length
|
|
846
|
-
}
|
|
847
|
-
clearPoppedValue()
|
|
848
580
|
if (n < 0 || n >= this.stack.length) {
|
|
849
|
-
this.scriptEvaluationError(
|
|
850
|
-
`${OP[currentOpcode] as string} requires the top stack element to be 0 or a positive number less than the current size of the stack.`
|
|
851
|
-
)
|
|
581
|
+
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.`)
|
|
852
582
|
}
|
|
853
|
-
|
|
583
|
+
const itemToMoveOrCopy = this.stack[this.stack.length - 1 - n]
|
|
854
584
|
if (currentOpcode === OP.OP_ROLL) {
|
|
855
|
-
this.stack.splice(this.stack.length -
|
|
856
|
-
this.stackMem -=
|
|
585
|
+
this.stack.splice(this.stack.length - 1 - n, 1)
|
|
586
|
+
this.stackMem -= itemToMoveOrCopy.length
|
|
587
|
+
this.pushStack(itemToMoveOrCopy)
|
|
588
|
+
} else { // OP_PICK
|
|
589
|
+
this.pushStackCopy(itemToMoveOrCopy)
|
|
857
590
|
}
|
|
858
|
-
this.stack.push([...buf])
|
|
859
|
-
this.stackMem += buf.length
|
|
860
591
|
break
|
|
861
|
-
|
|
592
|
+
}
|
|
862
593
|
case OP.OP_ROT:
|
|
863
|
-
if (this.stack.length < 3)
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
x1 = this.stacktop(-3)
|
|
869
|
-
x2 = this.stacktop(-2)
|
|
870
|
-
x3 = this.stacktop(-1)
|
|
871
|
-
this.stack[this.stack.length - 3] = x2
|
|
872
|
-
this.stack[this.stack.length - 2] = x3
|
|
873
|
-
this.stack[this.stack.length - 1] = x1
|
|
594
|
+
if (this.stack.length < 3) this.scriptEvaluationError('OP_ROT requires at least three items to be on the stack.')
|
|
595
|
+
x3 = this.popStack()
|
|
596
|
+
x2 = this.popStack()
|
|
597
|
+
x1 = this.popStack()
|
|
598
|
+
this.pushStack(x2); this.pushStack(x3); this.pushStack(x1)
|
|
874
599
|
break
|
|
875
|
-
|
|
876
600
|
case OP.OP_SWAP:
|
|
877
|
-
if (this.stack.length < 2)
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
}
|
|
882
|
-
x1 = this.stacktop(-2)
|
|
883
|
-
x2 = this.stacktop(-1)
|
|
884
|
-
this.stack[this.stack.length - 2] = x2
|
|
885
|
-
this.stack[this.stack.length - 1] = x1
|
|
601
|
+
if (this.stack.length < 2) this.scriptEvaluationError('OP_SWAP requires at least two items to be on the stack.')
|
|
602
|
+
x2 = this.popStack()
|
|
603
|
+
x1 = this.popStack()
|
|
604
|
+
this.pushStack(x2); this.pushStack(x1)
|
|
886
605
|
break
|
|
887
|
-
|
|
888
606
|
case OP.OP_TUCK:
|
|
889
|
-
if (this.stack.length < 2)
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
this.stack.splice(this.stack.length - 2, 0,
|
|
895
|
-
this.stackMem +=
|
|
607
|
+
if (this.stack.length < 2) this.scriptEvaluationError('OP_TUCK requires at least two items to be on the stack.')
|
|
608
|
+
buf1 = this.stackTop(-1) // Top element (x2)
|
|
609
|
+
// stack is [... rest, x1, x2]
|
|
610
|
+
// We want [... rest, x2_copy, x1, x2]
|
|
611
|
+
this.ensureStackMem(buf1.length)
|
|
612
|
+
this.stack.splice(this.stack.length - 2, 0, buf1.slice()) // Insert copy of x2 before x1
|
|
613
|
+
this.stackMem += buf1.length // Account for the new copy
|
|
896
614
|
break
|
|
897
|
-
|
|
898
615
|
case OP.OP_SIZE:
|
|
899
|
-
if (this.stack.length < 1)
|
|
900
|
-
|
|
901
|
-
'OP_SIZE requires at least one item to be on the stack.'
|
|
902
|
-
)
|
|
903
|
-
}
|
|
904
|
-
bn = new BigNumber(this.stacktop(-1).length)
|
|
905
|
-
this.stack.push(bn.toScriptNum())
|
|
906
|
-
this.stackMem += bn.toScriptNum().length
|
|
616
|
+
if (this.stack.length < 1) this.scriptEvaluationError('OP_SIZE requires at least one item to be on the stack.')
|
|
617
|
+
this.pushStack(new BigNumber(this.stackTop().length).toScriptNum())
|
|
907
618
|
break
|
|
908
619
|
|
|
909
620
|
case OP.OP_AND:
|
|
910
621
|
case OP.OP_OR:
|
|
911
|
-
case OP.OP_XOR:
|
|
912
|
-
if (this.stack.length < 2) {
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
switch (currentOpcode) {
|
|
927
|
-
case OP.OP_AND:
|
|
928
|
-
for (let i = 0; i < buf1.length; i++) {
|
|
929
|
-
buf1[i] &= buf2[i]
|
|
930
|
-
}
|
|
931
|
-
break
|
|
932
|
-
case OP.OP_OR:
|
|
933
|
-
for (let i = 0; i < buf1.length; i++) {
|
|
934
|
-
buf1[i] |= buf2[i]
|
|
935
|
-
}
|
|
936
|
-
break
|
|
937
|
-
case OP.OP_XOR:
|
|
938
|
-
for (let i = 0; i < buf1.length; i++) {
|
|
939
|
-
buf1[i] ^= buf2[i]
|
|
940
|
-
}
|
|
941
|
-
break
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
// And pop vch2.
|
|
945
|
-
poppedValue = this.stack.pop()
|
|
946
|
-
if (poppedValue != null) {
|
|
947
|
-
this.stackMem -= poppedValue.length
|
|
948
|
-
}
|
|
949
|
-
clearPoppedValue()
|
|
622
|
+
case OP.OP_XOR: {
|
|
623
|
+
if (this.stack.length < 2) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least two items on the stack.`)
|
|
624
|
+
buf2 = this.popStack()
|
|
625
|
+
buf1 = this.popStack()
|
|
626
|
+
if (buf1.length !== buf2.length) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires the top two stack items to be the same size.`)
|
|
627
|
+
|
|
628
|
+
const resultBufBitwiseOp = new Array(buf1.length)
|
|
629
|
+
for (let k = 0; k < buf1.length; k++) {
|
|
630
|
+
if (currentOpcode === OP.OP_AND) resultBufBitwiseOp[k] = buf1[k] & buf2[k]
|
|
631
|
+
else if (currentOpcode === OP.OP_OR) resultBufBitwiseOp[k] = buf1[k] | buf2[k]
|
|
632
|
+
else resultBufBitwiseOp[k] = buf1[k] ^ buf2[k]
|
|
633
|
+
}
|
|
634
|
+
this.pushStack(resultBufBitwiseOp)
|
|
950
635
|
break
|
|
951
|
-
|
|
952
|
-
case OP.OP_INVERT:
|
|
953
|
-
if (this.stack.length < 1)
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
buf[i] = ~buf[i]
|
|
961
|
-
}
|
|
636
|
+
}
|
|
637
|
+
case OP.OP_INVERT: {
|
|
638
|
+
if (this.stack.length < 1) this.scriptEvaluationError('OP_INVERT requires at least one item to be on the stack.')
|
|
639
|
+
buf = this.popStack()
|
|
640
|
+
const invertedBufOp = new Array(buf.length)
|
|
641
|
+
for (let k = 0; k < buf.length; k++) {
|
|
642
|
+
invertedBufOp[k] = (~buf[k]) & 0xff
|
|
643
|
+
}
|
|
644
|
+
this.pushStack(invertedBufOp)
|
|
962
645
|
break
|
|
963
|
-
|
|
646
|
+
}
|
|
964
647
|
case OP.OP_LSHIFT:
|
|
965
|
-
case OP.OP_RSHIFT:
|
|
966
|
-
if (this.stack.length < 2) {
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
}
|
|
971
|
-
buf1 = this.stacktop(-2)
|
|
648
|
+
case OP.OP_RSHIFT: {
|
|
649
|
+
if (this.stack.length < 2) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least two items to be on the stack.`)
|
|
650
|
+
bn2 = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush) // n (shift amount)
|
|
651
|
+
buf1 = this.popStack() // value to shift
|
|
652
|
+
n = bn2.toNumber()
|
|
653
|
+
if (n < 0) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires the top item on the stack not to be negative.`)
|
|
972
654
|
if (buf1.length === 0) {
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
this.stackMem -= poppedValue.length
|
|
976
|
-
}
|
|
977
|
-
clearPoppedValue()
|
|
978
|
-
} else {
|
|
979
|
-
bn1 = new BigNumber(buf1)
|
|
980
|
-
bn2 = BigNumber.fromScriptNum(
|
|
981
|
-
this.stacktop(-1),
|
|
982
|
-
requireMinimalPush
|
|
983
|
-
)
|
|
984
|
-
n = bn2.toNumber()
|
|
985
|
-
if (n < 0) {
|
|
986
|
-
this.scriptEvaluationError(
|
|
987
|
-
`${OP[currentOpcode] as string} requires the top item on the stack not to be negative.`
|
|
988
|
-
)
|
|
989
|
-
}
|
|
990
|
-
poppedValue = this.stack.pop()
|
|
991
|
-
if (poppedValue != null) {
|
|
992
|
-
this.stackMem -= poppedValue.length
|
|
993
|
-
}
|
|
994
|
-
clearPoppedValue()
|
|
995
|
-
poppedValue = this.stack.pop()
|
|
996
|
-
if (poppedValue != null) {
|
|
997
|
-
this.stackMem -= poppedValue.length
|
|
998
|
-
}
|
|
999
|
-
clearPoppedValue()
|
|
1000
|
-
let shifted
|
|
1001
|
-
if (currentOpcode === OP.OP_LSHIFT) {
|
|
1002
|
-
shifted = bn1.ushln(n)
|
|
1003
|
-
}
|
|
1004
|
-
if (currentOpcode === OP.OP_RSHIFT) {
|
|
1005
|
-
shifted = bn1.ushrn(n)
|
|
1006
|
-
}
|
|
1007
|
-
const bufShifted = padDataToSize(
|
|
1008
|
-
shifted.toArray().slice(buf1.length * -1),
|
|
1009
|
-
buf1.length
|
|
1010
|
-
)
|
|
1011
|
-
this.stack.push(bufShifted)
|
|
1012
|
-
this.stackMem += bufShifted.length
|
|
655
|
+
this.pushStack([])
|
|
656
|
+
break
|
|
1013
657
|
}
|
|
1014
|
-
|
|
658
|
+
bn1 = new BigNumber(buf1)
|
|
659
|
+
let shiftedBn: BigNumber
|
|
660
|
+
if (currentOpcode === OP.OP_LSHIFT) shiftedBn = bn1.ushln(n)
|
|
661
|
+
else shiftedBn = bn1.ushrn(n)
|
|
1015
662
|
|
|
663
|
+
const shiftedArr = shiftedBn.toArray('le', buf1.length)
|
|
664
|
+
this.pushStack(shiftedArr)
|
|
665
|
+
break
|
|
666
|
+
}
|
|
1016
667
|
case OP.OP_EQUAL:
|
|
1017
668
|
case OP.OP_EQUALVERIFY:
|
|
1018
|
-
if (this.stack.length < 2) {
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
buf1 = this.stacktop(-2)
|
|
1024
|
-
buf2 = this.stacktop(-1)
|
|
1025
|
-
fEqual = toHex(buf1) === toHex(buf2)
|
|
1026
|
-
poppedValue = this.stack.pop()
|
|
1027
|
-
if (poppedValue != null) {
|
|
1028
|
-
this.stackMem -= poppedValue.length
|
|
1029
|
-
}
|
|
1030
|
-
clearPoppedValue()
|
|
1031
|
-
poppedValue = this.stack.pop()
|
|
1032
|
-
if (poppedValue != null) {
|
|
1033
|
-
this.stackMem -= poppedValue.length
|
|
1034
|
-
}
|
|
1035
|
-
clearPoppedValue()
|
|
1036
|
-
this.stack.push(fEqual ? [1] : [])
|
|
1037
|
-
this.stackMem += (fEqual ? 1 : 0)
|
|
669
|
+
if (this.stack.length < 2) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least two items to be on the stack.`)
|
|
670
|
+
buf2 = this.popStack()
|
|
671
|
+
buf1 = this.popStack()
|
|
672
|
+
fValue = compareNumberArrays(buf1, buf2)
|
|
673
|
+
this.pushStack(fValue ? [1] : [])
|
|
1038
674
|
if (currentOpcode === OP.OP_EQUALVERIFY) {
|
|
1039
|
-
if (this.
|
|
1040
|
-
|
|
1041
|
-
if (poppedValue != null) {
|
|
1042
|
-
this.stackMem -= poppedValue.length
|
|
1043
|
-
}
|
|
1044
|
-
clearPoppedValue()
|
|
1045
|
-
} else {
|
|
1046
|
-
this.scriptEvaluationError(
|
|
1047
|
-
'OP_EQUALVERIFY requires the top two stack items to be equal.'
|
|
1048
|
-
)
|
|
1049
|
-
}
|
|
675
|
+
if (!fValue) this.scriptEvaluationError('OP_EQUALVERIFY requires the top two stack items to be equal.')
|
|
676
|
+
this.popStack()
|
|
1050
677
|
}
|
|
1051
678
|
break
|
|
1052
679
|
|
|
1053
|
-
case OP.OP_1ADD:
|
|
1054
|
-
case OP.
|
|
1055
|
-
case OP.
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
case OP.OP_0NOTEQUAL:
|
|
1059
|
-
if (this.stack.length < 1) {
|
|
1060
|
-
this.scriptEvaluationError(
|
|
1061
|
-
`${OP[currentOpcode] as string} requires at least one items to be on the stack.`
|
|
1062
|
-
)
|
|
1063
|
-
}
|
|
1064
|
-
buf = this.stacktop(-1)
|
|
1065
|
-
bn = BigNumber.fromScriptNum(buf, requireMinimalPush)
|
|
680
|
+
case OP.OP_1ADD: case OP.OP_1SUB:
|
|
681
|
+
case OP.OP_NEGATE: case OP.OP_ABS:
|
|
682
|
+
case OP.OP_NOT: case OP.OP_0NOTEQUAL:
|
|
683
|
+
if (this.stack.length < 1) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least one item to be on the stack.`)
|
|
684
|
+
bn = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush)
|
|
1066
685
|
switch (currentOpcode) {
|
|
1067
|
-
case OP.OP_1ADD:
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
case OP.
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
break
|
|
1076
|
-
case OP.OP_ABS:
|
|
1077
|
-
if (bn.cmpn(0) < 0) {
|
|
1078
|
-
bn = bn.neg()
|
|
1079
|
-
}
|
|
1080
|
-
break
|
|
1081
|
-
case OP.OP_NOT:
|
|
1082
|
-
bn = new BigNumber(bn.cmpn(0) === 0 ? 1 : 0 + 0)
|
|
1083
|
-
break
|
|
1084
|
-
case OP.OP_0NOTEQUAL:
|
|
1085
|
-
bn = new BigNumber(bn.cmpn(0) !== 0 ? 1 : 0 + 0)
|
|
1086
|
-
break
|
|
1087
|
-
}
|
|
1088
|
-
poppedValue = this.stack.pop()
|
|
1089
|
-
if (poppedValue != null) {
|
|
1090
|
-
this.stackMem -= poppedValue.length
|
|
1091
|
-
}
|
|
1092
|
-
clearPoppedValue()
|
|
1093
|
-
this.stack.push(bn.toScriptNum())
|
|
1094
|
-
this.stackMem += bn.toScriptNum().length
|
|
686
|
+
case OP.OP_1ADD: bn = bn.add(new BigNumber(1)); break
|
|
687
|
+
case OP.OP_1SUB: bn = bn.sub(new BigNumber(1)); break
|
|
688
|
+
case OP.OP_NEGATE: bn = bn.neg(); break
|
|
689
|
+
case OP.OP_ABS: if (bn.isNeg()) bn = bn.neg(); break
|
|
690
|
+
case OP.OP_NOT: bn = new BigNumber(bn.cmpn(0) === 0 ? 1 : 0); break
|
|
691
|
+
case OP.OP_0NOTEQUAL: bn = new BigNumber(bn.cmpn(0) !== 0 ? 1 : 0); break
|
|
692
|
+
}
|
|
693
|
+
this.pushStack(bn.toScriptNum())
|
|
1095
694
|
break
|
|
1096
|
-
|
|
1097
|
-
case OP.
|
|
1098
|
-
case OP.
|
|
1099
|
-
case OP.
|
|
1100
|
-
case OP.
|
|
1101
|
-
case OP.
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
case OP.OP_GREATERTHAN:
|
|
1109
|
-
case OP.OP_LESSTHANOREQUAL:
|
|
1110
|
-
case OP.OP_GREATERTHANOREQUAL:
|
|
1111
|
-
case OP.OP_MIN:
|
|
1112
|
-
case OP.OP_MAX:
|
|
1113
|
-
if (this.stack.length < 2) {
|
|
1114
|
-
this.scriptEvaluationError(
|
|
1115
|
-
`${OP[currentOpcode] as string} requires at least two items to be on the stack.`
|
|
1116
|
-
)
|
|
1117
|
-
}
|
|
1118
|
-
bn1 = BigNumber.fromScriptNum(this.stacktop(-2), requireMinimalPush)
|
|
1119
|
-
bn2 = BigNumber.fromScriptNum(this.stacktop(-1), requireMinimalPush)
|
|
1120
|
-
bn = new BigNumber(0)
|
|
1121
|
-
|
|
695
|
+
case OP.OP_ADD: case OP.OP_SUB: case OP.OP_MUL: case OP.OP_DIV: case OP.OP_MOD:
|
|
696
|
+
case OP.OP_BOOLAND: case OP.OP_BOOLOR:
|
|
697
|
+
case OP.OP_NUMEQUAL: case OP.OP_NUMEQUALVERIFY: case OP.OP_NUMNOTEQUAL:
|
|
698
|
+
case OP.OP_LESSTHAN: case OP.OP_GREATERTHAN:
|
|
699
|
+
case OP.OP_LESSTHANOREQUAL: case OP.OP_GREATERTHANOREQUAL:
|
|
700
|
+
case OP.OP_MIN: case OP.OP_MAX: {
|
|
701
|
+
if (this.stack.length < 2) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least two items to be on the stack.`)
|
|
702
|
+
buf2 = this.popStack()
|
|
703
|
+
buf1 = this.popStack()
|
|
704
|
+
bn2 = BigNumber.fromScriptNum(buf2, requireMinimalPush)
|
|
705
|
+
bn1 = BigNumber.fromScriptNum(buf1, requireMinimalPush)
|
|
706
|
+
let predictedLen = 0
|
|
1122
707
|
switch (currentOpcode) {
|
|
1123
|
-
case OP.
|
|
1124
|
-
|
|
708
|
+
case OP.OP_MUL:
|
|
709
|
+
predictedLen = bn1.byteLength() + bn2.byteLength()
|
|
1125
710
|
break
|
|
711
|
+
case OP.OP_ADD:
|
|
1126
712
|
case OP.OP_SUB:
|
|
1127
|
-
|
|
1128
|
-
break
|
|
1129
|
-
case OP.OP_MUL:
|
|
1130
|
-
bn = bn1.mul(bn2)
|
|
713
|
+
predictedLen = Math.max(bn1.byteLength(), bn2.byteLength()) + 1
|
|
1131
714
|
break
|
|
715
|
+
default:
|
|
716
|
+
predictedLen = Math.max(bn1.byteLength(), bn2.byteLength())
|
|
717
|
+
}
|
|
718
|
+
this.ensureStackMem(predictedLen)
|
|
719
|
+
let resultBnArithmetic: BigNumber = new BigNumber(0)
|
|
720
|
+
switch (currentOpcode) {
|
|
721
|
+
case OP.OP_ADD: resultBnArithmetic = bn1.add(bn2); break
|
|
722
|
+
case OP.OP_SUB: resultBnArithmetic = bn1.sub(bn2); break
|
|
723
|
+
case OP.OP_MUL: resultBnArithmetic = bn1.mul(bn2); break
|
|
1132
724
|
case OP.OP_DIV:
|
|
1133
|
-
if (bn2.cmpn(0) === 0)
|
|
1134
|
-
|
|
1135
|
-
}
|
|
1136
|
-
bn = bn1.div(bn2)
|
|
1137
|
-
break
|
|
725
|
+
if (bn2.cmpn(0) === 0) this.scriptEvaluationError('OP_DIV cannot divide by zero!')
|
|
726
|
+
resultBnArithmetic = bn1.div(bn2); break
|
|
1138
727
|
case OP.OP_MOD:
|
|
1139
|
-
if (bn2.cmpn(0) === 0)
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
case OP.
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
case OP.
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
case OP.OP_NUMEQUAL:
|
|
1155
|
-
bn = new BigNumber(bn1.cmp(bn2) === 0 ? 1 : 0 + 0)
|
|
1156
|
-
break
|
|
1157
|
-
case OP.OP_NUMEQUALVERIFY:
|
|
1158
|
-
bn = new BigNumber(bn1.cmp(bn2) === 0 ? 1 : 0 + 0)
|
|
1159
|
-
break
|
|
1160
|
-
case OP.OP_NUMNOTEQUAL:
|
|
1161
|
-
bn = new BigNumber(bn1.cmp(bn2) !== 0 ? 1 : 0 + 0)
|
|
1162
|
-
break
|
|
1163
|
-
case OP.OP_LESSTHAN:
|
|
1164
|
-
bn = new BigNumber(bn1.cmp(bn2) < 0 ? 1 : 0 + 0)
|
|
1165
|
-
break
|
|
1166
|
-
case OP.OP_GREATERTHAN:
|
|
1167
|
-
bn = new BigNumber(bn1.cmp(bn2) > 0 ? 1 : 0 + 0)
|
|
1168
|
-
break
|
|
1169
|
-
case OP.OP_LESSTHANOREQUAL:
|
|
1170
|
-
bn = new BigNumber(bn1.cmp(bn2) <= 0 ? 1 : 0 + 0)
|
|
1171
|
-
break
|
|
1172
|
-
case OP.OP_GREATERTHANOREQUAL:
|
|
1173
|
-
bn = new BigNumber(bn1.cmp(bn2) >= 0 ? 1 : 0 + 0)
|
|
1174
|
-
break
|
|
1175
|
-
case OP.OP_MIN:
|
|
1176
|
-
bn = bn1.cmp(bn2) < 0 ? bn1 : bn2
|
|
1177
|
-
break
|
|
1178
|
-
case OP.OP_MAX:
|
|
1179
|
-
bn = bn1.cmp(bn2) > 0 ? bn1 : bn2
|
|
1180
|
-
break
|
|
1181
|
-
}
|
|
1182
|
-
poppedValue = this.stack.pop()
|
|
1183
|
-
if (poppedValue != null) {
|
|
1184
|
-
this.stackMem -= poppedValue.length
|
|
1185
|
-
}
|
|
1186
|
-
clearPoppedValue()
|
|
1187
|
-
poppedValue = this.stack.pop()
|
|
1188
|
-
if (poppedValue != null) {
|
|
1189
|
-
this.stackMem -= poppedValue.length
|
|
1190
|
-
}
|
|
1191
|
-
clearPoppedValue()
|
|
1192
|
-
this.stack.push(bn.toScriptNum())
|
|
1193
|
-
this.stackMem += bn.toScriptNum().length
|
|
1194
|
-
|
|
728
|
+
if (bn2.cmpn(0) === 0) this.scriptEvaluationError('OP_MOD cannot divide by zero!')
|
|
729
|
+
resultBnArithmetic = bn1.mod(bn2); break
|
|
730
|
+
case OP.OP_BOOLAND: resultBnArithmetic = new BigNumber((bn1.cmpn(0) !== 0 && bn2.cmpn(0) !== 0) ? 1 : 0); break
|
|
731
|
+
case OP.OP_BOOLOR: resultBnArithmetic = new BigNumber((bn1.cmpn(0) !== 0 || bn2.cmpn(0) !== 0) ? 1 : 0); break
|
|
732
|
+
case OP.OP_NUMEQUAL: resultBnArithmetic = new BigNumber(bn1.cmp(bn2) === 0 ? 1 : 0); break
|
|
733
|
+
case OP.OP_NUMEQUALVERIFY: resultBnArithmetic = new BigNumber(bn1.cmp(bn2) === 0 ? 1 : 0); break
|
|
734
|
+
case OP.OP_NUMNOTEQUAL: resultBnArithmetic = new BigNumber(bn1.cmp(bn2) !== 0 ? 1 : 0); break
|
|
735
|
+
case OP.OP_LESSTHAN: resultBnArithmetic = new BigNumber(bn1.cmp(bn2) < 0 ? 1 : 0); break
|
|
736
|
+
case OP.OP_GREATERTHAN: resultBnArithmetic = new BigNumber(bn1.cmp(bn2) > 0 ? 1 : 0); break
|
|
737
|
+
case OP.OP_LESSTHANOREQUAL: resultBnArithmetic = new BigNumber(bn1.cmp(bn2) <= 0 ? 1 : 0); break
|
|
738
|
+
case OP.OP_GREATERTHANOREQUAL: resultBnArithmetic = new BigNumber(bn1.cmp(bn2) >= 0 ? 1 : 0); break
|
|
739
|
+
case OP.OP_MIN: resultBnArithmetic = bn1.cmp(bn2) < 0 ? bn1 : bn2; break
|
|
740
|
+
case OP.OP_MAX: resultBnArithmetic = bn1.cmp(bn2) > 0 ? bn1 : bn2; break
|
|
741
|
+
}
|
|
742
|
+
this.pushStack(resultBnArithmetic.toScriptNum())
|
|
1195
743
|
if (currentOpcode === OP.OP_NUMEQUALVERIFY) {
|
|
1196
|
-
if (this.castToBool(this.
|
|
1197
|
-
|
|
1198
|
-
if (poppedValue != null) {
|
|
1199
|
-
this.stackMem -= poppedValue.length
|
|
1200
|
-
}
|
|
1201
|
-
clearPoppedValue()
|
|
1202
|
-
} else {
|
|
1203
|
-
this.scriptEvaluationError(
|
|
1204
|
-
'OP_NUMEQUALVERIFY requires the top stack item to be truthy.'
|
|
1205
|
-
)
|
|
1206
|
-
}
|
|
744
|
+
if (!this.castToBool(this.stackTop())) this.scriptEvaluationError('OP_NUMEQUALVERIFY requires the top stack item to be truthy.')
|
|
745
|
+
this.popStack()
|
|
1207
746
|
}
|
|
1208
747
|
break
|
|
1209
|
-
|
|
748
|
+
}
|
|
1210
749
|
case OP.OP_WITHIN:
|
|
1211
|
-
if (this.stack.length < 3)
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
bn2 = BigNumber.fromScriptNum(this.stacktop(-2), requireMinimalPush)
|
|
1218
|
-
bn3 = BigNumber.fromScriptNum(this.stacktop(-1), requireMinimalPush)
|
|
1219
|
-
fValue = bn2.cmp(bn1) <= 0 && bn1.cmp(bn3) < 0
|
|
1220
|
-
poppedValue = this.stack.pop()
|
|
1221
|
-
if (poppedValue != null) {
|
|
1222
|
-
this.stackMem -= poppedValue.length
|
|
1223
|
-
}
|
|
1224
|
-
clearPoppedValue()
|
|
1225
|
-
poppedValue = this.stack.pop()
|
|
1226
|
-
if (poppedValue != null) {
|
|
1227
|
-
this.stackMem -= poppedValue.length
|
|
1228
|
-
}
|
|
1229
|
-
clearPoppedValue()
|
|
1230
|
-
poppedValue = this.stack.pop()
|
|
1231
|
-
if (poppedValue != null) {
|
|
1232
|
-
this.stackMem -= poppedValue.length
|
|
1233
|
-
}
|
|
1234
|
-
clearPoppedValue()
|
|
1235
|
-
this.stack.push(fValue ? [1] : [])
|
|
1236
|
-
this.stackMem += (fValue ? 1 : 0)
|
|
750
|
+
if (this.stack.length < 3) this.scriptEvaluationError('OP_WITHIN requires at least three items to be on the stack.')
|
|
751
|
+
bn3 = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush) // max
|
|
752
|
+
bn2 = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush) // min
|
|
753
|
+
bn1 = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush) // x
|
|
754
|
+
fValue = bn1.cmp(bn2) >= 0 && bn1.cmp(bn3) < 0
|
|
755
|
+
this.pushStack(fValue ? [1] : [])
|
|
1237
756
|
break
|
|
1238
757
|
|
|
1239
|
-
case OP.OP_RIPEMD160:
|
|
1240
|
-
case OP.
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
if (
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
let bufHash: number[] = [] // Initialize bufHash to an empty array
|
|
1251
|
-
buf = this.stacktop(-1)
|
|
1252
|
-
if (currentOpcode === OP.OP_RIPEMD160) {
|
|
1253
|
-
bufHash = Hash.ripemd160(buf)
|
|
1254
|
-
} else if (currentOpcode === OP.OP_SHA1) {
|
|
1255
|
-
bufHash = Hash.sha1(buf)
|
|
1256
|
-
} else if (currentOpcode === OP.OP_SHA256) {
|
|
1257
|
-
bufHash = Hash.sha256(buf)
|
|
1258
|
-
} else if (currentOpcode === OP.OP_HASH160) {
|
|
1259
|
-
bufHash = Hash.hash160(buf)
|
|
1260
|
-
} else if (currentOpcode === OP.OP_HASH256) {
|
|
1261
|
-
bufHash = Hash.hash256(buf)
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
poppedValue = this.stack.pop()
|
|
1265
|
-
if (poppedValue != null) {
|
|
1266
|
-
this.stackMem -= poppedValue.length
|
|
1267
|
-
}
|
|
1268
|
-
clearPoppedValue()
|
|
1269
|
-
this.stack.push(bufHash)
|
|
1270
|
-
this.stackMem += bufHash.length
|
|
758
|
+
case OP.OP_RIPEMD160: case OP.OP_SHA1: case OP.OP_SHA256:
|
|
759
|
+
case OP.OP_HASH160: case OP.OP_HASH256: {
|
|
760
|
+
if (this.stack.length < 1) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least one item to be on the stack.`)
|
|
761
|
+
buf = this.popStack()
|
|
762
|
+
let hashResult: number[] = [] // Initialize to empty, to satisfy TS compiler
|
|
763
|
+
if (currentOpcode === OP.OP_RIPEMD160) hashResult = Hash.ripemd160(buf)
|
|
764
|
+
else if (currentOpcode === OP.OP_SHA1) hashResult = Hash.sha1(buf)
|
|
765
|
+
else if (currentOpcode === OP.OP_SHA256) hashResult = Hash.sha256(buf)
|
|
766
|
+
else if (currentOpcode === OP.OP_HASH160) hashResult = Hash.hash160(buf)
|
|
767
|
+
else if (currentOpcode === OP.OP_HASH256) hashResult = Hash.hash256(buf)
|
|
768
|
+
this.pushStack(hashResult)
|
|
1271
769
|
break
|
|
1272
770
|
}
|
|
1273
|
-
|
|
1274
771
|
case OP.OP_CODESEPARATOR:
|
|
1275
772
|
this.lastCodeSeparator = this.programCounter
|
|
1276
773
|
break
|
|
1277
|
-
|
|
1278
774
|
case OP.OP_CHECKSIG:
|
|
1279
|
-
case OP.OP_CHECKSIGVERIFY:
|
|
1280
|
-
if (this.stack.length < 2) {
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
)
|
|
1284
|
-
}
|
|
775
|
+
case OP.OP_CHECKSIGVERIFY: {
|
|
776
|
+
if (this.stack.length < 2) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least two items to be on the stack.`)
|
|
777
|
+
bufPubkey = this.popStack()
|
|
778
|
+
bufSig = this.popStack()
|
|
1285
779
|
|
|
1286
|
-
bufSig
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
if (
|
|
1290
|
-
!checkSignatureEncoding(bufSig) ||
|
|
1291
|
-
!checkPublicKeyEncoding(bufPubkey)
|
|
1292
|
-
) {
|
|
1293
|
-
this.scriptEvaluationError(
|
|
1294
|
-
`${OP[currentOpcode] as string} requires correct encoding for the public key and signature.`
|
|
1295
|
-
)
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
// Subset of script starting at the most recent codeseparator
|
|
1299
|
-
// CScript scriptCode(pbegincodehash, pend);
|
|
1300
|
-
if (this.context === 'UnlockingScript') {
|
|
1301
|
-
subscript = new Script(
|
|
1302
|
-
this.unlockingScript.chunks.slice(this.lastCodeSeparator ?? 0)
|
|
1303
|
-
)
|
|
1304
|
-
} else {
|
|
1305
|
-
subscript = new Script(
|
|
1306
|
-
this.lockingScript.chunks.slice(this.lastCodeSeparator ?? 0)
|
|
1307
|
-
)
|
|
780
|
+
if (!this.checkSignatureEncoding(bufSig) || !this.checkPublicKeyEncoding(bufPubkey)) {
|
|
781
|
+
// Error already thrown by helpers
|
|
782
|
+
this.scriptEvaluationError(`${OP[currentOpcode] as string} requires correct encoding for the public key and signature.`) // Fallback, should be unreachable
|
|
1308
783
|
}
|
|
1309
784
|
|
|
1310
|
-
|
|
785
|
+
const scriptForChecksig = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript
|
|
786
|
+
const scriptCodeChunks = scriptForChecksig.chunks.slice(this.lastCodeSeparator === null ? 0 : this.lastCodeSeparator + 1)
|
|
787
|
+
subscript = new Script(scriptCodeChunks)
|
|
1311
788
|
subscript.findAndDelete(new Script().writeBin(bufSig))
|
|
1312
789
|
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
if (!fSuccess && bufSig.length > 0) {
|
|
1323
|
-
this.scriptEvaluationError(
|
|
1324
|
-
`${OP[currentOpcode] as string} failed to verify the signature, and requires an empty signature when verification fails.`
|
|
1325
|
-
)
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
poppedValue = this.stack.pop()
|
|
1329
|
-
if (poppedValue != null) {
|
|
1330
|
-
this.stackMem -= poppedValue.length
|
|
1331
|
-
}
|
|
1332
|
-
clearPoppedValue()
|
|
1333
|
-
poppedValue = this.stack.pop()
|
|
1334
|
-
if (poppedValue != null) {
|
|
1335
|
-
this.stackMem -= poppedValue.length
|
|
790
|
+
fSuccess = false
|
|
791
|
+
if (bufSig.length > 0) {
|
|
792
|
+
try {
|
|
793
|
+
sig = TransactionSignature.fromChecksigFormat(bufSig)
|
|
794
|
+
pubkey = PublicKey.fromDER(bufPubkey)
|
|
795
|
+
fSuccess = this.verifySignature(sig, pubkey, subscript)
|
|
796
|
+
} catch (e) {
|
|
797
|
+
fSuccess = false
|
|
798
|
+
}
|
|
1336
799
|
}
|
|
1337
|
-
clearPoppedValue()
|
|
1338
800
|
|
|
1339
|
-
|
|
1340
|
-
this.stack.push(fSuccess ? [1] : [])
|
|
1341
|
-
this.stackMem += (fSuccess ? 1 : 0)
|
|
801
|
+
this.pushStack(fSuccess ? [1] : [])
|
|
1342
802
|
if (currentOpcode === OP.OP_CHECKSIGVERIFY) {
|
|
1343
|
-
if (fSuccess)
|
|
1344
|
-
|
|
1345
|
-
if (poppedValue != null) {
|
|
1346
|
-
this.stackMem -= poppedValue.length
|
|
1347
|
-
}
|
|
1348
|
-
clearPoppedValue()
|
|
1349
|
-
} else {
|
|
1350
|
-
this.scriptEvaluationError(
|
|
1351
|
-
'OP_CHECKSIGVERIFY requires that a valid signature is provided.'
|
|
1352
|
-
)
|
|
1353
|
-
}
|
|
803
|
+
if (!fSuccess) this.scriptEvaluationError('OP_CHECKSIGVERIFY requires that a valid signature is provided.')
|
|
804
|
+
this.popStack()
|
|
1354
805
|
}
|
|
1355
806
|
break
|
|
1356
|
-
|
|
807
|
+
}
|
|
1357
808
|
case OP.OP_CHECKMULTISIG:
|
|
1358
|
-
case OP.OP_CHECKMULTISIGVERIFY:
|
|
809
|
+
case OP.OP_CHECKMULTISIGVERIFY: {
|
|
1359
810
|
i = 1
|
|
1360
811
|
if (this.stack.length < i) {
|
|
1361
|
-
this.scriptEvaluationError(
|
|
1362
|
-
`${OP[currentOpcode] as string} requires at least 1 item to be on the stack.`
|
|
1363
|
-
)
|
|
812
|
+
this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least 1 item for nKeys.`)
|
|
1364
813
|
}
|
|
1365
814
|
|
|
1366
|
-
nKeysCount = BigNumber.fromScriptNum(
|
|
1367
|
-
this.stacktop(-i),
|
|
1368
|
-
requireMinimalPush
|
|
1369
|
-
).toNumber()
|
|
1370
|
-
// TODO: Keys and opcount are parameterized in client. No magic numbers!
|
|
815
|
+
nKeysCount = BigNumber.fromScriptNum(this.stackTop(-i), requireMinimalPush).toNumber()
|
|
1371
816
|
if (nKeysCount < 0 || nKeysCount > maxMultisigKeyCount) {
|
|
1372
|
-
this.scriptEvaluationError(
|
|
1373
|
-
`${OP[currentOpcode] as string} requires a key count between 0 and ${maxMultisigKeyCount}.`
|
|
1374
|
-
)
|
|
817
|
+
this.scriptEvaluationError(`${OP[currentOpcode] as string} requires a key count between 0 and ${maxMultisigKeyCount}.`)
|
|
1375
818
|
}
|
|
1376
819
|
ikey = ++i
|
|
1377
820
|
i += nKeysCount
|
|
1378
821
|
|
|
1379
|
-
// ikey2 is the position of last non-signature item in
|
|
1380
|
-
// the stack. Top stack item = 1. With
|
|
1381
|
-
// SCRIPT_VERIFY_NULLFAIL, this is used for cleanup if
|
|
1382
|
-
// operation fails.
|
|
1383
|
-
ikey2 = nKeysCount + 2
|
|
1384
|
-
|
|
1385
822
|
if (this.stack.length < i) {
|
|
1386
|
-
this.scriptEvaluationError(
|
|
1387
|
-
`${OP[currentOpcode] as string} requires the number of stack items not to be less than the number of keys used.`
|
|
1388
|
-
)
|
|
823
|
+
this.scriptEvaluationError(`${OP[currentOpcode] as string} stack too small for nKeys and keys. Need ${i}, have ${this.stack.length}.`)
|
|
1389
824
|
}
|
|
1390
825
|
|
|
1391
|
-
nSigsCount = BigNumber.fromScriptNum(
|
|
1392
|
-
this.stacktop(-i),
|
|
1393
|
-
requireMinimalPush
|
|
1394
|
-
).toNumber()
|
|
826
|
+
nSigsCount = BigNumber.fromScriptNum(this.stackTop(-i), requireMinimalPush).toNumber()
|
|
1395
827
|
if (nSigsCount < 0 || nSigsCount > nKeysCount) {
|
|
1396
|
-
this.scriptEvaluationError(
|
|
1397
|
-
`${OP[currentOpcode] as string} requires the number of signatures to be no greater than the number of keys.`
|
|
1398
|
-
)
|
|
828
|
+
this.scriptEvaluationError(`${OP[currentOpcode] as string} requires the number of signatures to be no greater than the number of keys.`)
|
|
1399
829
|
}
|
|
1400
830
|
isig = ++i
|
|
1401
831
|
i += nSigsCount
|
|
1402
832
|
if (this.stack.length < i) {
|
|
1403
|
-
this.scriptEvaluationError(
|
|
1404
|
-
`${OP[currentOpcode] as string} requires the number of stack items not to be less than the number of signatures provided.`
|
|
1405
|
-
)
|
|
833
|
+
this.scriptEvaluationError(`${OP[currentOpcode] as string} stack too small for N, keys, M, sigs, and dummy. Need ${i}, have ${this.stack.length}.`)
|
|
1406
834
|
}
|
|
1407
835
|
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
this.unlockingScript.chunks.slice(this.lastCodeSeparator ?? 0)
|
|
1412
|
-
)
|
|
1413
|
-
} else {
|
|
1414
|
-
subscript = new Script(
|
|
1415
|
-
this.lockingScript.chunks.slice(this.lastCodeSeparator ?? 0)
|
|
1416
|
-
)
|
|
1417
|
-
}
|
|
836
|
+
const baseScriptCMS = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript
|
|
837
|
+
const subscriptChunksCMS = baseScriptCMS.chunks.slice(this.lastCodeSeparator === null ? 0 : this.lastCodeSeparator + 1)
|
|
838
|
+
subscript = new Script(subscriptChunksCMS)
|
|
1418
839
|
|
|
1419
|
-
// Drop the signatures, since there's no way for a signature to sign itself
|
|
1420
840
|
for (let k = 0; k < nSigsCount; k++) {
|
|
1421
|
-
bufSig = this.
|
|
841
|
+
bufSig = this.stackTop(-isig - k) // Sigs are closer to top than keys
|
|
1422
842
|
subscript.findAndDelete(new Script().writeBin(bufSig))
|
|
1423
843
|
}
|
|
1424
844
|
|
|
1425
845
|
fSuccess = true
|
|
1426
846
|
while (fSuccess && nSigsCount > 0) {
|
|
1427
|
-
//
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
bufPubkey = this.stacktop(-ikey)
|
|
1431
|
-
|
|
1432
|
-
if (
|
|
1433
|
-
!checkSignatureEncoding(bufSig) ||
|
|
1434
|
-
!checkPublicKeyEncoding(bufPubkey)
|
|
1435
|
-
) {
|
|
1436
|
-
this.scriptEvaluationError(
|
|
1437
|
-
`${OP[currentOpcode] as string} requires correct encoding for the public key and signature.`
|
|
1438
|
-
)
|
|
847
|
+
if (nKeysCount === 0) { // No more keys to check against but still sigs left
|
|
848
|
+
fSuccess = false
|
|
849
|
+
break
|
|
1439
850
|
}
|
|
851
|
+
bufSig = this.stackTop(-isig)
|
|
852
|
+
bufPubkey = this.stackTop(-ikey)
|
|
1440
853
|
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
854
|
+
if (!this.checkSignatureEncoding(bufSig) || !this.checkPublicKeyEncoding(bufPubkey)) {
|
|
855
|
+
this.scriptEvaluationError(`${OP[currentOpcode] as string} requires correct encoding for the public key and signature.`)
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
fOk = false
|
|
859
|
+
if (bufSig.length > 0) {
|
|
860
|
+
try {
|
|
861
|
+
sig = TransactionSignature.fromChecksigFormat(bufSig)
|
|
862
|
+
pubkey = PublicKey.fromDER(bufPubkey)
|
|
863
|
+
fOk = this.verifySignature(sig, pubkey, subscript)
|
|
864
|
+
} catch (e) {
|
|
865
|
+
fOk = false
|
|
866
|
+
}
|
|
1448
867
|
}
|
|
1449
868
|
|
|
1450
869
|
if (fOk) {
|
|
1451
|
-
isig
|
|
1452
|
-
nSigsCount--
|
|
870
|
+
isig++; nSigsCount--
|
|
1453
871
|
}
|
|
1454
|
-
ikey
|
|
1455
|
-
nKeysCount--
|
|
872
|
+
ikey++; nKeysCount--
|
|
1456
873
|
|
|
1457
|
-
// If there are more signatures left than keys left,
|
|
1458
|
-
// then too many signatures have failed
|
|
1459
874
|
if (nSigsCount > nKeysCount) {
|
|
1460
875
|
fSuccess = false
|
|
1461
876
|
}
|
|
1462
877
|
}
|
|
1463
878
|
|
|
1464
|
-
//
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
if (ikey2 > 0) {
|
|
1473
|
-
ikey2--
|
|
1474
|
-
}
|
|
879
|
+
// Correct total items consumed by op (N_val, keys, M_val, sigs, dummy)
|
|
880
|
+
const itemsConsumedByOp = 1 + // N_val
|
|
881
|
+
BigNumber.fromScriptNum(this.stackTop(-1), false).toNumber() + // keys
|
|
882
|
+
1 + // M_val
|
|
883
|
+
BigNumber.fromScriptNum(this.stackTop(-(1 + BigNumber.fromScriptNum(this.stackTop(-1), false).toNumber() + 1)), false).toNumber() + // sigs
|
|
884
|
+
1 // dummy
|
|
1475
885
|
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
clearPoppedValue()
|
|
886
|
+
let popCount = itemsConsumedByOp - 1 // Pop all except dummy
|
|
887
|
+
while (popCount > 0) {
|
|
888
|
+
this.popStack()
|
|
889
|
+
popCount--
|
|
1481
890
|
}
|
|
1482
891
|
|
|
1483
|
-
//
|
|
1484
|
-
// whose contents were not checked in any way.
|
|
1485
|
-
//
|
|
1486
|
-
// Unfortunately this is a potential source of mutability,
|
|
1487
|
-
// so optionally verify it is exactly equal to zero prior
|
|
1488
|
-
// to removing it from the stack.
|
|
892
|
+
// Check and pop dummy
|
|
1489
893
|
if (this.stack.length < 1) {
|
|
1490
|
-
this.scriptEvaluationError(
|
|
1491
|
-
`${OP[currentOpcode] as string} requires an extra item to be on the stack.`
|
|
1492
|
-
)
|
|
1493
|
-
}
|
|
1494
|
-
if (this.stacktop(-1).length > 0) {
|
|
1495
|
-
// NOTE: Is this necessary? We don't care about malleability.
|
|
1496
|
-
this.scriptEvaluationError(
|
|
1497
|
-
`${OP[currentOpcode] as string} requires the extra stack item to be empty.`
|
|
1498
|
-
)
|
|
894
|
+
this.scriptEvaluationError(`${OP[currentOpcode] as string} requires an extra item (dummy) to be on the stack.`)
|
|
1499
895
|
}
|
|
1500
|
-
|
|
1501
|
-
if (
|
|
1502
|
-
this.
|
|
896
|
+
const dummyBuf = this.popStack()
|
|
897
|
+
if (dummyBuf.length > 0) { // SCRIPT_VERIFY_NULLDUMMY
|
|
898
|
+
this.scriptEvaluationError(`${OP[currentOpcode] as string} requires the extra stack item (dummy) to be empty.`)
|
|
1503
899
|
}
|
|
1504
|
-
clearPoppedValue()
|
|
1505
|
-
|
|
1506
|
-
this.stack.push(fSuccess ? [1] : [])
|
|
1507
|
-
this.stackMem += (fSuccess ? 1 : 0)
|
|
1508
900
|
|
|
901
|
+
this.pushStack(fSuccess ? [1] : [])
|
|
1509
902
|
if (currentOpcode === OP.OP_CHECKMULTISIGVERIFY) {
|
|
1510
|
-
if (fSuccess)
|
|
1511
|
-
|
|
1512
|
-
if (poppedValue != null) {
|
|
1513
|
-
this.stackMem -= poppedValue.length
|
|
1514
|
-
}
|
|
1515
|
-
clearPoppedValue()
|
|
1516
|
-
} else {
|
|
1517
|
-
this.scriptEvaluationError(
|
|
1518
|
-
'OP_CHECKMULTISIGVERIFY requires that a sufficient number of valid signatures are provided.'
|
|
1519
|
-
)
|
|
1520
|
-
}
|
|
903
|
+
if (!fSuccess) this.scriptEvaluationError('OP_CHECKMULTISIGVERIFY requires that a sufficient number of valid signatures are provided.')
|
|
904
|
+
this.popStack()
|
|
1521
905
|
}
|
|
1522
906
|
break
|
|
907
|
+
}
|
|
1523
908
|
|
|
1524
|
-
case OP.OP_CAT:
|
|
1525
|
-
if (this.stack.length < 2)
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
}
|
|
1530
|
-
|
|
1531
|
-
buf1 = this.stacktop(-2)
|
|
1532
|
-
buf2 = this.stacktop(-1)
|
|
1533
|
-
if (buf1.length + buf2.length > maxScriptElementSize) {
|
|
1534
|
-
this.scriptEvaluationError(
|
|
1535
|
-
`It's not currently possible to push data larger than ${maxScriptElementSize} bytes.`
|
|
1536
|
-
)
|
|
1537
|
-
}
|
|
1538
|
-
this.stack[this.stack.length - 2] = [...buf1, ...buf2]
|
|
1539
|
-
this.stackMem -= buf1.length
|
|
1540
|
-
this.stackMem -= buf2.length
|
|
1541
|
-
this.stackMem += buf1.length + buf2.length
|
|
1542
|
-
poppedValue = this.stack.pop()
|
|
1543
|
-
if (poppedValue != null) {
|
|
1544
|
-
this.stackMem -= poppedValue.length
|
|
1545
|
-
}
|
|
1546
|
-
clearPoppedValue()
|
|
909
|
+
case OP.OP_CAT: {
|
|
910
|
+
if (this.stack.length < 2) this.scriptEvaluationError('OP_CAT requires at least two items to be on the stack.')
|
|
911
|
+
buf2 = this.popStack()
|
|
912
|
+
buf1 = this.popStack()
|
|
913
|
+
const catResult = (buf1).concat(buf2)
|
|
914
|
+
if (catResult.length > maxScriptElementSize) this.scriptEvaluationError(`It's not currently possible to push data larger than ${maxScriptElementSize} bytes.`)
|
|
915
|
+
this.pushStack(catResult)
|
|
1547
916
|
break
|
|
917
|
+
}
|
|
918
|
+
case OP.OP_SPLIT: {
|
|
919
|
+
if (this.stack.length < 2) this.scriptEvaluationError('OP_SPLIT requires at least two items to be on the stack.')
|
|
920
|
+
const posBuf = this.popStack()
|
|
921
|
+
const dataToSplit = this.popStack()
|
|
1548
922
|
|
|
1549
|
-
|
|
1550
|
-
if (
|
|
1551
|
-
this.scriptEvaluationError(
|
|
1552
|
-
'OP_SPLIT requires at least two items to be on the stack.'
|
|
1553
|
-
)
|
|
1554
|
-
}
|
|
1555
|
-
buf1 = this.stacktop(-2)
|
|
1556
|
-
|
|
1557
|
-
// Make sure the split point is apropriate.
|
|
1558
|
-
n = BigNumber.fromScriptNum(
|
|
1559
|
-
this.stacktop(-1),
|
|
1560
|
-
requireMinimalPush
|
|
1561
|
-
).toNumber()
|
|
1562
|
-
if (n < 0 || n > buf1.length) {
|
|
1563
|
-
this.scriptEvaluationError(
|
|
1564
|
-
'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.'
|
|
1565
|
-
)
|
|
923
|
+
n = BigNumber.fromScriptNum(posBuf, requireMinimalPush).toNumber()
|
|
924
|
+
if (n < 0 || n > dataToSplit.length) {
|
|
925
|
+
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.')
|
|
1566
926
|
}
|
|
1567
927
|
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
// Copy buffer data, to slice it before
|
|
1571
|
-
buf2 = [...buf1]
|
|
1572
|
-
|
|
1573
|
-
// Replace existing stack values by the new values.
|
|
1574
|
-
this.stack[this.stack.length - 2] = buf2.slice(0, n)
|
|
1575
|
-
this.stackMem -= buf1.length
|
|
1576
|
-
this.stackMem += n
|
|
1577
|
-
this.stack[this.stack.length - 1] = buf2.slice(n)
|
|
1578
|
-
this.stackMem += buf1.length - n
|
|
928
|
+
this.pushStack(dataToSplit.slice(0, n))
|
|
929
|
+
this.pushStack(dataToSplit.slice(n))
|
|
1579
930
|
break
|
|
931
|
+
}
|
|
932
|
+
case OP.OP_NUM2BIN: {
|
|
933
|
+
if (this.stack.length < 2) this.scriptEvaluationError('OP_NUM2BIN requires at least two items to be on the stack.')
|
|
1580
934
|
|
|
1581
|
-
|
|
1582
|
-
if (
|
|
1583
|
-
this.scriptEvaluationError(
|
|
1584
|
-
'OP_NUM2BIN requires at least two items to be on the stack.'
|
|
1585
|
-
)
|
|
1586
|
-
}
|
|
1587
|
-
|
|
1588
|
-
size = BigNumber.fromScriptNum(
|
|
1589
|
-
this.stacktop(-1),
|
|
1590
|
-
requireMinimalPush
|
|
1591
|
-
).toNumber()
|
|
1592
|
-
if (size > maxScriptElementSize) {
|
|
1593
|
-
this.scriptEvaluationError(
|
|
1594
|
-
`It's not currently possible to push data larger than ${maxScriptElementSize} bytes.`
|
|
1595
|
-
)
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
poppedValue = this.stack.pop()
|
|
1599
|
-
if (poppedValue != null) {
|
|
1600
|
-
this.stackMem -= poppedValue.length
|
|
935
|
+
size = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush).toNumber()
|
|
936
|
+
if (size > maxScriptElementSize || size < 0) { // size can be 0
|
|
937
|
+
this.scriptEvaluationError(`It's not currently possible to push data larger than ${maxScriptElementSize} bytes or negative size.`)
|
|
1601
938
|
}
|
|
1602
|
-
clearPoppedValue()
|
|
1603
|
-
rawnum = this.stacktop(-1)
|
|
1604
939
|
|
|
1605
|
-
|
|
1606
|
-
//
|
|
1607
|
-
rawnum = minimallyEncode(rawnum)
|
|
940
|
+
let rawnum = this.popStack() // This is the number to convert
|
|
941
|
+
rawnum = minimallyEncode(rawnum) // Get its minimal scriptnum form
|
|
1608
942
|
|
|
1609
943
|
if (rawnum.length > size) {
|
|
1610
|
-
this.scriptEvaluationError(
|
|
1611
|
-
'OP_NUM2BIN requires that the size expressed in the top stack item is large enough to hold the value expressed in the second-from-top stack item.'
|
|
1612
|
-
)
|
|
944
|
+
this.scriptEvaluationError('OP_NUM2BIN requires that the size expressed in the top stack item is large enough to hold the value expressed in the second-from-top stack item.')
|
|
1613
945
|
}
|
|
1614
946
|
|
|
1615
|
-
// We already have an element of the right size, we
|
|
1616
|
-
// don't need to do anything.
|
|
1617
947
|
if (rawnum.length === size) {
|
|
1618
|
-
this.
|
|
948
|
+
this.pushStack(rawnum)
|
|
1619
949
|
break
|
|
1620
950
|
}
|
|
1621
951
|
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
signbit = rawnum[rawnum.length - 1] & 0x80
|
|
1625
|
-
rawnum[rawnum.length - 1] &= 0x7f
|
|
1626
|
-
}
|
|
952
|
+
const resultN2B = new Array(size).fill(0x00)
|
|
953
|
+
let signbit = 0x00
|
|
1627
954
|
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
num[n] = rawnum[n]
|
|
1632
|
-
}
|
|
1633
|
-
n = rawnum.length - 1
|
|
1634
|
-
while (n++ < size - 2) {
|
|
1635
|
-
num[n] = 0x00
|
|
955
|
+
if (rawnum.length > 0) {
|
|
956
|
+
signbit = rawnum[rawnum.length - 1] & 0x80 // Store sign bit
|
|
957
|
+
rawnum[rawnum.length - 1] &= 0x7f // Remove sign bit for padding
|
|
1636
958
|
}
|
|
1637
959
|
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
this.stackMem -= rawnum.length
|
|
1642
|
-
this.stackMem += size
|
|
1643
|
-
break
|
|
1644
|
-
|
|
1645
|
-
case OP.OP_BIN2NUM:
|
|
1646
|
-
if (this.stack.length < 1) {
|
|
1647
|
-
this.scriptEvaluationError(
|
|
1648
|
-
'OP_BIN2NUM requires at least one item to be on the stack.'
|
|
1649
|
-
)
|
|
960
|
+
// Copy rawnum (now positive magnitude) into the result
|
|
961
|
+
for (let k = 0; k < rawnum.length; k++) {
|
|
962
|
+
resultN2B[k] = rawnum[k]
|
|
1650
963
|
}
|
|
1651
964
|
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
this.stack[this.stack.length - 1] = buf2
|
|
1656
|
-
this.stackMem -= buf1.length
|
|
1657
|
-
this.stackMem += buf2.length
|
|
1658
|
-
|
|
1659
|
-
// The resulting number must be a valid number.
|
|
1660
|
-
if (!isMinimallyEncoded(buf2)) {
|
|
1661
|
-
this.scriptEvaluationError(
|
|
1662
|
-
'OP_BIN2NUM requires that the resulting number is valid.'
|
|
1663
|
-
)
|
|
965
|
+
// If the original number was negative, the sign bit must be set on the new MSB
|
|
966
|
+
if (signbit !== 0) {
|
|
967
|
+
resultN2B[size - 1] |= 0x80
|
|
1664
968
|
}
|
|
969
|
+
this.pushStack(resultN2B)
|
|
1665
970
|
break
|
|
971
|
+
}
|
|
972
|
+
case OP.OP_BIN2NUM: {
|
|
973
|
+
if (this.stack.length < 1) this.scriptEvaluationError('OP_BIN2NUM requires at least one item to be on the stack.')
|
|
974
|
+
buf1 = this.popStack()
|
|
975
|
+
const b2nResult = minimallyEncode(buf1)
|
|
976
|
+
if (!isMinimallyEncodedHelper(b2nResult)) {
|
|
977
|
+
this.scriptEvaluationError('OP_BIN2NUM requires that the resulting number is valid.')
|
|
978
|
+
}
|
|
979
|
+
this.pushStack(b2nResult)
|
|
980
|
+
break
|
|
981
|
+
}
|
|
1666
982
|
|
|
1667
983
|
default:
|
|
1668
|
-
this.scriptEvaluationError(
|
|
984
|
+
this.scriptEvaluationError(`Invalid opcode ${currentOpcode} (pc=${this.programCounter}).`)
|
|
1669
985
|
}
|
|
1670
986
|
}
|
|
1671
987
|
|
|
1672
|
-
// Finally, increment the program counter
|
|
1673
988
|
this.programCounter++
|
|
1674
989
|
return true
|
|
1675
990
|
}
|
|
@@ -1691,6 +1006,7 @@ export default class Spend {
|
|
|
1691
1006
|
'Unlocking scripts can only contain push operations, and no other opcodes.'
|
|
1692
1007
|
)
|
|
1693
1008
|
}
|
|
1009
|
+
|
|
1694
1010
|
while (this.step()) {
|
|
1695
1011
|
if (
|
|
1696
1012
|
this.context === 'LockingScript' &&
|
|
@@ -1699,46 +1015,55 @@ export default class Spend {
|
|
|
1699
1015
|
break
|
|
1700
1016
|
}
|
|
1701
1017
|
}
|
|
1018
|
+
|
|
1702
1019
|
if (this.ifStack.length > 0) {
|
|
1703
1020
|
this.scriptEvaluationError(
|
|
1704
|
-
'Every OP_IF must be terminated prior to the end of the script.'
|
|
1021
|
+
'Every OP_IF, OP_NOTIF, or OP_ELSE must be terminated with OP_ENDIF prior to the end of the script.'
|
|
1705
1022
|
)
|
|
1706
1023
|
}
|
|
1024
|
+
|
|
1707
1025
|
if (requireCleanStack) {
|
|
1708
1026
|
if (this.stack.length !== 1) {
|
|
1709
1027
|
this.scriptEvaluationError(
|
|
1710
|
-
|
|
1028
|
+
`The clean stack rule requires exactly one item to be on the stack after script execution, found ${this.stack.length}.`
|
|
1711
1029
|
)
|
|
1712
1030
|
}
|
|
1713
1031
|
}
|
|
1714
|
-
|
|
1032
|
+
|
|
1033
|
+
if (this.stack.length === 0) {
|
|
1034
|
+
this.scriptEvaluationError(
|
|
1035
|
+
'The top stack element must be truthy after script evaluation (stack is empty).'
|
|
1036
|
+
)
|
|
1037
|
+
} else if (!this.castToBool(this.stackTop())) {
|
|
1715
1038
|
this.scriptEvaluationError(
|
|
1716
1039
|
'The top stack element must be truthy after script evaluation.'
|
|
1717
1040
|
)
|
|
1718
1041
|
}
|
|
1719
|
-
return true
|
|
1720
|
-
}
|
|
1721
1042
|
|
|
1722
|
-
|
|
1723
|
-
return this.stack[this.stack.length + i]
|
|
1043
|
+
return true
|
|
1724
1044
|
}
|
|
1725
1045
|
|
|
1726
|
-
private castToBool (val: number[]): boolean {
|
|
1046
|
+
private castToBool (val: Readonly<number[]>): boolean {
|
|
1047
|
+
if (val.length === 0) return false
|
|
1727
1048
|
for (let i = 0; i < val.length; i++) {
|
|
1728
1049
|
if (val[i] !== 0) {
|
|
1729
|
-
|
|
1730
|
-
if (i === val.length - 1 && val[i] === 0x80) {
|
|
1731
|
-
return false
|
|
1732
|
-
}
|
|
1733
|
-
return true
|
|
1050
|
+
return !(i === val.length - 1 && val[i] === 0x80)
|
|
1734
1051
|
}
|
|
1735
1052
|
}
|
|
1736
1053
|
return false
|
|
1737
1054
|
}
|
|
1738
1055
|
|
|
1739
1056
|
private scriptEvaluationError (str: string): void {
|
|
1057
|
+
const pcInfo = `Context: ${this.context}, PC: ${this.programCounter}`
|
|
1058
|
+
const stackHex = this.stack.map(s => (s != null && typeof s.length !== 'undefined') ? toHex(s) : (s === null || s === undefined ? 'null/undef' : 'INVALID_STACK_ITEM')).join(', ')
|
|
1059
|
+
const altStackHex = this.altStack.map(s => (s != null && typeof s.length !== 'undefined') ? toHex(s) : (s === null || s === undefined ? 'null/undef' : 'INVALID_STACK_ITEM')).join(', ')
|
|
1060
|
+
|
|
1061
|
+
const stackInfo = `Stack: [${stackHex}] (len: ${this.stack.length}, mem: ${this.stackMem})`
|
|
1062
|
+
const altStackInfo = `AltStack: [${altStackHex}] (len: ${this.altStack.length}, mem: ${this.altStackMem})`
|
|
1063
|
+
const ifStackInfo = `IfStack: [${this.ifStack.join(', ')}]`
|
|
1064
|
+
|
|
1740
1065
|
throw new Error(
|
|
1741
|
-
`Script evaluation error: ${str}\
|
|
1066
|
+
`Script evaluation error: ${str}\nTXID: ${this.sourceTXID}, OutputIdx: ${this.sourceOutputIndex}\n${pcInfo}\n${stackInfo}\n${altStackInfo}\n${ifStackInfo}`
|
|
1742
1067
|
)
|
|
1743
1068
|
}
|
|
1744
1069
|
}
|