@bsv/sdk 1.8.13 → 1.9.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/auth/Peer.js +35 -16
- package/dist/cjs/src/auth/Peer.js.map +1 -1
- package/dist/cjs/src/kvstore/LocalKVStore.js +7 -7
- package/dist/cjs/src/kvstore/LocalKVStore.js.map +1 -1
- package/dist/cjs/src/primitives/BigNumber.js +43 -31
- package/dist/cjs/src/primitives/BigNumber.js.map +1 -1
- package/dist/cjs/src/primitives/Hash.js +11 -5
- package/dist/cjs/src/primitives/Hash.js.map +1 -1
- package/dist/cjs/src/primitives/SymmetricKey.js +15 -6
- package/dist/cjs/src/primitives/SymmetricKey.js.map +1 -1
- package/dist/cjs/src/primitives/TransactionSignature.js +60 -18
- package/dist/cjs/src/primitives/TransactionSignature.js.map +1 -1
- package/dist/cjs/src/primitives/utils.js +74 -28
- package/dist/cjs/src/primitives/utils.js.map +1 -1
- package/dist/cjs/src/script/Script.js +217 -108
- package/dist/cjs/src/script/Script.js.map +1 -1
- package/dist/cjs/src/script/Spend.js +5 -2
- package/dist/cjs/src/script/Spend.js.map +1 -1
- package/dist/cjs/src/transaction/Beef.js +62 -7
- package/dist/cjs/src/transaction/Beef.js.map +1 -1
- package/dist/cjs/src/transaction/BeefTx.js +1 -1
- package/dist/cjs/src/transaction/BeefTx.js.map +1 -1
- package/dist/cjs/src/transaction/Transaction.js +67 -35
- package/dist/cjs/src/transaction/Transaction.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/auth/Peer.js +36 -16
- package/dist/esm/src/auth/Peer.js.map +1 -1
- package/dist/esm/src/kvstore/LocalKVStore.js +7 -7
- package/dist/esm/src/kvstore/LocalKVStore.js.map +1 -1
- package/dist/esm/src/primitives/BigNumber.js +43 -31
- package/dist/esm/src/primitives/BigNumber.js.map +1 -1
- package/dist/esm/src/primitives/Hash.js +11 -5
- package/dist/esm/src/primitives/Hash.js.map +1 -1
- package/dist/esm/src/primitives/SymmetricKey.js +15 -6
- package/dist/esm/src/primitives/SymmetricKey.js.map +1 -1
- package/dist/esm/src/primitives/TransactionSignature.js +60 -18
- package/dist/esm/src/primitives/TransactionSignature.js.map +1 -1
- package/dist/esm/src/primitives/utils.js +74 -28
- package/dist/esm/src/primitives/utils.js.map +1 -1
- package/dist/esm/src/script/Script.js +222 -110
- package/dist/esm/src/script/Script.js.map +1 -1
- package/dist/esm/src/script/Spend.js +6 -2
- package/dist/esm/src/script/Spend.js.map +1 -1
- package/dist/esm/src/transaction/Beef.js +64 -7
- package/dist/esm/src/transaction/Beef.js.map +1 -1
- package/dist/esm/src/transaction/BeefTx.js +1 -1
- package/dist/esm/src/transaction/BeefTx.js.map +1 -1
- package/dist/esm/src/transaction/Transaction.js +69 -35
- package/dist/esm/src/transaction/Transaction.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/auth/Peer.d.ts +4 -0
- package/dist/types/src/auth/Peer.d.ts.map +1 -1
- package/dist/types/src/primitives/BigNumber.d.ts.map +1 -1
- package/dist/types/src/primitives/Hash.d.ts +10 -10
- package/dist/types/src/primitives/Hash.d.ts.map +1 -1
- package/dist/types/src/primitives/SymmetricKey.d.ts.map +1 -1
- package/dist/types/src/primitives/TransactionSignature.d.ts +34 -13
- package/dist/types/src/primitives/TransactionSignature.d.ts.map +1 -1
- package/dist/types/src/primitives/utils.d.ts +6 -8
- package/dist/types/src/primitives/utils.d.ts.map +1 -1
- package/dist/types/src/script/Script.d.ts +18 -9
- package/dist/types/src/script/Script.d.ts.map +1 -1
- package/dist/types/src/script/Spend.d.ts +1 -0
- package/dist/types/src/script/Spend.d.ts.map +1 -1
- package/dist/types/src/transaction/Beef.d.ts +9 -0
- package/dist/types/src/transaction/Beef.d.ts.map +1 -1
- package/dist/types/src/transaction/Transaction.d.ts +7 -0
- package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +3 -3
- package/dist/umd/bundle.js.map +1 -1
- package/docs/reference/primitives.md +87 -37
- package/docs/reference/script.md +11 -7
- package/docs/reference/transaction.md +2 -0
- package/package.json +1 -1
- package/src/auth/Peer.ts +44 -18
- package/src/kvstore/LocalKVStore.ts +7 -7
- package/src/kvstore/__tests/LocalKVStore.test.ts +17 -17
- package/src/primitives/BigNumber.ts +44 -23
- package/src/primitives/Hash.ts +41 -17
- package/src/primitives/SymmetricKey.ts +15 -6
- package/src/primitives/TransactionSignature.ts +77 -31
- package/src/primitives/utils.ts +80 -30
- package/src/script/Script.ts +238 -104
- package/src/script/Spend.ts +7 -3
- package/src/transaction/Beef.ts +74 -7
- package/src/transaction/BeefTx.ts +1 -1
- package/src/transaction/Transaction.ts +77 -34
package/src/script/Script.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import ScriptChunk from './ScriptChunk.js'
|
|
2
2
|
import OP from './OP.js'
|
|
3
|
-
import { encode, toHex,
|
|
3
|
+
import { encode, toHex, toArray } from '../primitives/utils.js'
|
|
4
4
|
import BigNumber from '../primitives/BigNumber.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -10,8 +10,14 @@ import BigNumber from '../primitives/BigNumber.js'
|
|
|
10
10
|
*
|
|
11
11
|
* @property {ScriptChunk[]} chunks - An array of script chunks that make up the script.
|
|
12
12
|
*/
|
|
13
|
+
const BufferCtor =
|
|
14
|
+
typeof globalThis !== 'undefined' ? (globalThis as any).Buffer : undefined
|
|
15
|
+
|
|
13
16
|
export default class Script {
|
|
14
|
-
|
|
17
|
+
private _chunks: ScriptChunk[]
|
|
18
|
+
private parsed: boolean
|
|
19
|
+
private rawBytesCache?: Uint8Array
|
|
20
|
+
private hexCache?: string
|
|
15
21
|
|
|
16
22
|
/**
|
|
17
23
|
* @method fromASM
|
|
@@ -110,7 +116,9 @@ export default class Script {
|
|
|
110
116
|
if (!/^[0-9a-fA-F]+$/.test(hex)) {
|
|
111
117
|
throw new Error('Some elements in this string are not hex encoded.')
|
|
112
118
|
}
|
|
113
|
-
|
|
119
|
+
const bin = toArray(hex, 'hex')
|
|
120
|
+
const rawBytes = Uint8Array.from(bin)
|
|
121
|
+
return new Script([], rawBytes, hex.toLowerCase(), false)
|
|
114
122
|
}
|
|
115
123
|
|
|
116
124
|
/**
|
|
@@ -122,89 +130,44 @@ export default class Script {
|
|
|
122
130
|
* const script = Script.fromBinary([0x76, 0xa9, ...])
|
|
123
131
|
*/
|
|
124
132
|
static fromBinary (bin: number[]): Script {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
let inConditionalBlock: number = 0
|
|
129
|
-
|
|
130
|
-
const br = new Reader(bin)
|
|
131
|
-
while (!br.eof()) {
|
|
132
|
-
const op = br.readUInt8()
|
|
133
|
-
|
|
134
|
-
// if OP_RETURN and not in a conditional block, do not parse the rest of the data,
|
|
135
|
-
// rather just return the last chunk as data without prefixing with data length.
|
|
136
|
-
if (op === OP.OP_RETURN && inConditionalBlock === 0) {
|
|
137
|
-
chunks.push({
|
|
138
|
-
op,
|
|
139
|
-
data: br.read()
|
|
140
|
-
})
|
|
141
|
-
break
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (op === OP.OP_IF || op === OP.OP_NOTIF || op === OP.OP_VERIF || op === OP.OP_VERNOTIF) {
|
|
145
|
-
inConditionalBlock++
|
|
146
|
-
} else if (op === OP.OP_ENDIF) {
|
|
147
|
-
inConditionalBlock--
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
let len = 0
|
|
151
|
-
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
152
|
-
let data: number[] = []
|
|
153
|
-
if (op > 0 && op < OP.OP_PUSHDATA1) {
|
|
154
|
-
len = op
|
|
155
|
-
chunks.push({
|
|
156
|
-
data: br.read(len),
|
|
157
|
-
op
|
|
158
|
-
})
|
|
159
|
-
} else if (op === OP.OP_PUSHDATA1) {
|
|
160
|
-
try {
|
|
161
|
-
len = br.readUInt8()
|
|
162
|
-
data = br.read(len)
|
|
163
|
-
} catch {
|
|
164
|
-
br.read()
|
|
165
|
-
}
|
|
166
|
-
chunks.push({
|
|
167
|
-
data,
|
|
168
|
-
op
|
|
169
|
-
})
|
|
170
|
-
} else if (op === OP.OP_PUSHDATA2) {
|
|
171
|
-
try {
|
|
172
|
-
len = br.readUInt16LE()
|
|
173
|
-
data = br.read(len)
|
|
174
|
-
} catch {
|
|
175
|
-
br.read()
|
|
176
|
-
}
|
|
177
|
-
chunks.push({
|
|
178
|
-
data,
|
|
179
|
-
op
|
|
180
|
-
})
|
|
181
|
-
} else if (op === OP.OP_PUSHDATA4) {
|
|
182
|
-
try {
|
|
183
|
-
len = br.readUInt32LE()
|
|
184
|
-
data = br.read(len)
|
|
185
|
-
} catch {
|
|
186
|
-
br.read()
|
|
187
|
-
}
|
|
188
|
-
chunks.push({
|
|
189
|
-
data,
|
|
190
|
-
op
|
|
191
|
-
})
|
|
192
|
-
} else {
|
|
193
|
-
chunks.push({
|
|
194
|
-
op
|
|
195
|
-
})
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
return new Script(chunks)
|
|
133
|
+
const rawBytes = Uint8Array.from(bin)
|
|
134
|
+
return new Script([], rawBytes, undefined, false)
|
|
199
135
|
}
|
|
200
136
|
|
|
201
137
|
/**
|
|
202
138
|
* @constructor
|
|
203
139
|
* Constructs a new Script object.
|
|
204
140
|
* @param chunks=[] - An array of script chunks to directly initialize the script.
|
|
141
|
+
* @param rawBytesCache - Optional serialized bytes that can be reused instead of reserializing `chunks`.
|
|
142
|
+
* @param hexCache - Optional lowercase hex string that matches the serialized bytes, used to satisfy `toHex` quickly.
|
|
143
|
+
* @param parsed - When false the script defers parsing `rawBytesCache` until `chunks` is accessed; defaults to true.
|
|
205
144
|
*/
|
|
206
|
-
constructor (chunks: ScriptChunk[] = []) {
|
|
207
|
-
this.
|
|
145
|
+
constructor (chunks: ScriptChunk[] = [], rawBytesCache?: Uint8Array, hexCache?: string, parsed: boolean = true) {
|
|
146
|
+
this._chunks = chunks
|
|
147
|
+
this.parsed = parsed
|
|
148
|
+
this.rawBytesCache = rawBytesCache
|
|
149
|
+
this.hexCache = hexCache
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
get chunks (): ScriptChunk[] {
|
|
153
|
+
this.ensureParsed()
|
|
154
|
+
return this._chunks
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
set chunks (value: ScriptChunk[]) {
|
|
158
|
+
this._chunks = value
|
|
159
|
+
this.parsed = true
|
|
160
|
+
this.invalidateSerializationCaches()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private ensureParsed (): void {
|
|
164
|
+
if (this.parsed) return
|
|
165
|
+
if (this.rawBytesCache != null) {
|
|
166
|
+
this._chunks = Script.parseChunks(this.rawBytesCache)
|
|
167
|
+
} else {
|
|
168
|
+
this._chunks = []
|
|
169
|
+
}
|
|
170
|
+
this.parsed = true
|
|
208
171
|
}
|
|
209
172
|
|
|
210
173
|
/**
|
|
@@ -228,7 +191,18 @@ export default class Script {
|
|
|
228
191
|
* @returns The script in hexadecimal format.
|
|
229
192
|
*/
|
|
230
193
|
toHex (): string {
|
|
231
|
-
|
|
194
|
+
if (this.hexCache != null) {
|
|
195
|
+
return this.hexCache
|
|
196
|
+
}
|
|
197
|
+
if (this.rawBytesCache == null) {
|
|
198
|
+
this.rawBytesCache = this.serializeChunksToBytes()
|
|
199
|
+
}
|
|
200
|
+
const hex =
|
|
201
|
+
BufferCtor != null
|
|
202
|
+
? BufferCtor.from(this.rawBytesCache).toString('hex')
|
|
203
|
+
: (encode(Array.from(this.rawBytesCache), 'hex') as string)
|
|
204
|
+
this.hexCache = hex
|
|
205
|
+
return hex
|
|
232
206
|
}
|
|
233
207
|
|
|
234
208
|
/**
|
|
@@ -237,32 +211,14 @@ export default class Script {
|
|
|
237
211
|
* @returns The script in binary array format.
|
|
238
212
|
*/
|
|
239
213
|
toBinary (): number[] {
|
|
240
|
-
|
|
214
|
+
return Array.from(this.toUint8Array())
|
|
215
|
+
}
|
|
241
216
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
writer.writeUInt8(op)
|
|
246
|
-
if (op === OP.OP_RETURN && chunk.data != null) { // special case for unformatted data
|
|
247
|
-
writer.write(chunk.data)
|
|
248
|
-
break
|
|
249
|
-
} else if (chunk.data != null) {
|
|
250
|
-
if (op < OP.OP_PUSHDATA1) {
|
|
251
|
-
writer.write(chunk.data)
|
|
252
|
-
} else if (op === OP.OP_PUSHDATA1) {
|
|
253
|
-
writer.writeUInt8(chunk.data.length)
|
|
254
|
-
writer.write(chunk.data)
|
|
255
|
-
} else if (op === OP.OP_PUSHDATA2) {
|
|
256
|
-
writer.writeUInt16LE(chunk.data.length)
|
|
257
|
-
writer.write(chunk.data)
|
|
258
|
-
} else if (op === OP.OP_PUSHDATA4) {
|
|
259
|
-
writer.writeUInt32LE(chunk.data.length)
|
|
260
|
-
writer.write(chunk.data)
|
|
261
|
-
}
|
|
262
|
-
}
|
|
217
|
+
toUint8Array (): Uint8Array {
|
|
218
|
+
if (this.rawBytesCache == null) {
|
|
219
|
+
this.rawBytesCache = this.serializeChunksToBytes()
|
|
263
220
|
}
|
|
264
|
-
|
|
265
|
-
return writer.toArray()
|
|
221
|
+
return this.rawBytesCache
|
|
266
222
|
}
|
|
267
223
|
|
|
268
224
|
/**
|
|
@@ -272,6 +228,7 @@ export default class Script {
|
|
|
272
228
|
* @returns This script instance for chaining.
|
|
273
229
|
*/
|
|
274
230
|
writeScript (script: Script): Script {
|
|
231
|
+
this.invalidateSerializationCaches()
|
|
275
232
|
this.chunks = this.chunks.concat(script.chunks)
|
|
276
233
|
return this
|
|
277
234
|
}
|
|
@@ -283,6 +240,7 @@ export default class Script {
|
|
|
283
240
|
* @returns This script instance for chaining.
|
|
284
241
|
*/
|
|
285
242
|
writeOpCode (op: number): Script {
|
|
243
|
+
this.invalidateSerializationCaches()
|
|
286
244
|
this.chunks.push({ op })
|
|
287
245
|
return this
|
|
288
246
|
}
|
|
@@ -295,6 +253,7 @@ export default class Script {
|
|
|
295
253
|
* @returns This script instance for chaining.
|
|
296
254
|
*/
|
|
297
255
|
setChunkOpCode (i: number, op: number): Script {
|
|
256
|
+
this.invalidateSerializationCaches()
|
|
298
257
|
this.chunks[i] = { op }
|
|
299
258
|
return this
|
|
300
259
|
}
|
|
@@ -306,6 +265,7 @@ export default class Script {
|
|
|
306
265
|
* @returns This script instance for chaining.
|
|
307
266
|
*/
|
|
308
267
|
writeBn (bn: BigNumber): Script {
|
|
268
|
+
this.invalidateSerializationCaches()
|
|
309
269
|
if (bn.cmpn(0) === OP.OP_0) {
|
|
310
270
|
this.chunks.push({
|
|
311
271
|
op: OP.OP_0
|
|
@@ -334,6 +294,7 @@ export default class Script {
|
|
|
334
294
|
* @throws {Error} Throws an error if the data is too large to be pushed.
|
|
335
295
|
*/
|
|
336
296
|
writeBin (bin: number[]): Script {
|
|
297
|
+
this.invalidateSerializationCaches()
|
|
337
298
|
let op: number
|
|
338
299
|
const data = bin.length > 0 ? bin : undefined
|
|
339
300
|
if (bin.length > 0 && bin.length < OP.OP_PUSHDATA1) {
|
|
@@ -363,6 +324,7 @@ export default class Script {
|
|
|
363
324
|
* @returns This script instance for chaining.
|
|
364
325
|
*/
|
|
365
326
|
writeNumber (num: number): Script {
|
|
327
|
+
this.invalidateSerializationCaches()
|
|
366
328
|
this.writeBn(new BigNumber(num))
|
|
367
329
|
return this
|
|
368
330
|
}
|
|
@@ -373,6 +335,7 @@ export default class Script {
|
|
|
373
335
|
* @returns This script instance for chaining.
|
|
374
336
|
*/
|
|
375
337
|
removeCodeseparators (): Script {
|
|
338
|
+
this.invalidateSerializationCaches()
|
|
376
339
|
const chunks: ScriptChunk[] = []
|
|
377
340
|
for (let i = 0; i < this.chunks.length; i++) {
|
|
378
341
|
if (this.chunks[i].op !== OP.OP_CODESEPARATOR) {
|
|
@@ -391,6 +354,7 @@ export default class Script {
|
|
|
391
354
|
* @returns This script instance for chaining.
|
|
392
355
|
*/
|
|
393
356
|
findAndDelete (script: Script): Script {
|
|
357
|
+
this.invalidateSerializationCaches()
|
|
394
358
|
const buf = script.toHex()
|
|
395
359
|
for (let i = 0; i < this.chunks.length; i++) {
|
|
396
360
|
const script2 = new Script([this.chunks[i]])
|
|
@@ -443,6 +407,176 @@ export default class Script {
|
|
|
443
407
|
* @param chunk - The script chunk.
|
|
444
408
|
* @returns The string representation of the chunk.
|
|
445
409
|
*/
|
|
410
|
+
private static computeSerializedLength (chunks: ScriptChunk[]): number {
|
|
411
|
+
let total = 0
|
|
412
|
+
for (const chunk of chunks) {
|
|
413
|
+
total += 1
|
|
414
|
+
if (chunk.data == null) continue
|
|
415
|
+
const len = chunk.data.length
|
|
416
|
+
if (chunk.op === OP.OP_RETURN) {
|
|
417
|
+
total += len
|
|
418
|
+
break
|
|
419
|
+
}
|
|
420
|
+
if (chunk.op < OP.OP_PUSHDATA1) {
|
|
421
|
+
total += len
|
|
422
|
+
} else if (chunk.op === OP.OP_PUSHDATA1) {
|
|
423
|
+
total += 1 + len
|
|
424
|
+
} else if (chunk.op === OP.OP_PUSHDATA2) {
|
|
425
|
+
total += 2 + len
|
|
426
|
+
} else if (chunk.op === OP.OP_PUSHDATA4) {
|
|
427
|
+
total += 4 + len
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return total
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
private serializeChunksToBytes (): Uint8Array {
|
|
434
|
+
const chunks = this.chunks
|
|
435
|
+
const totalLength = Script.computeSerializedLength(chunks)
|
|
436
|
+
const bytes = new Uint8Array(totalLength)
|
|
437
|
+
let offset = 0
|
|
438
|
+
|
|
439
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
440
|
+
const chunk = chunks[i]
|
|
441
|
+
bytes[offset++] = chunk.op
|
|
442
|
+
if (chunk.data == null) continue
|
|
443
|
+
if (chunk.op === OP.OP_RETURN) {
|
|
444
|
+
bytes.set(chunk.data, offset)
|
|
445
|
+
offset += chunk.data.length
|
|
446
|
+
break
|
|
447
|
+
}
|
|
448
|
+
offset = Script.writeChunkData(bytes, offset, chunk.op, chunk.data)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return bytes
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
private invalidateSerializationCaches (): void {
|
|
455
|
+
this.rawBytesCache = undefined
|
|
456
|
+
this.hexCache = undefined
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
private static writeChunkData (
|
|
460
|
+
target: Uint8Array,
|
|
461
|
+
offset: number,
|
|
462
|
+
op: number,
|
|
463
|
+
data: number[]
|
|
464
|
+
): number {
|
|
465
|
+
const len = data.length
|
|
466
|
+
if (op < OP.OP_PUSHDATA1) {
|
|
467
|
+
target.set(data, offset)
|
|
468
|
+
return offset + len
|
|
469
|
+
} else if (op === OP.OP_PUSHDATA1) {
|
|
470
|
+
target[offset++] = len & 0xff
|
|
471
|
+
target.set(data, offset)
|
|
472
|
+
return offset + len
|
|
473
|
+
} else if (op === OP.OP_PUSHDATA2) {
|
|
474
|
+
target[offset++] = len & 0xff
|
|
475
|
+
target[offset++] = (len >> 8) & 0xff
|
|
476
|
+
target.set(data, offset)
|
|
477
|
+
return offset + len
|
|
478
|
+
} else if (op === OP.OP_PUSHDATA4) {
|
|
479
|
+
const size = len >>> 0
|
|
480
|
+
target[offset++] = size & 0xff
|
|
481
|
+
target[offset++] = (size >> 8) & 0xff
|
|
482
|
+
target[offset++] = (size >> 16) & 0xff
|
|
483
|
+
target[offset++] = (size >> 24) & 0xff
|
|
484
|
+
target.set(data, offset)
|
|
485
|
+
return offset + len
|
|
486
|
+
}
|
|
487
|
+
return offset
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
private static parseChunks (bytes: ArrayLike<number>): ScriptChunk[] {
|
|
491
|
+
const chunks: ScriptChunk[] = []
|
|
492
|
+
const length = bytes.length
|
|
493
|
+
let pos = 0
|
|
494
|
+
let inConditionalBlock = 0
|
|
495
|
+
|
|
496
|
+
while (pos < length) {
|
|
497
|
+
const op = bytes[pos++] ?? 0
|
|
498
|
+
|
|
499
|
+
if (op === OP.OP_RETURN && inConditionalBlock === 0) {
|
|
500
|
+
chunks.push({
|
|
501
|
+
op,
|
|
502
|
+
data: Script.copyRange(bytes, pos, length)
|
|
503
|
+
})
|
|
504
|
+
break
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (
|
|
508
|
+
op === OP.OP_IF ||
|
|
509
|
+
op === OP.OP_NOTIF ||
|
|
510
|
+
op === OP.OP_VERIF ||
|
|
511
|
+
op === OP.OP_VERNOTIF
|
|
512
|
+
) {
|
|
513
|
+
inConditionalBlock++
|
|
514
|
+
} else if (op === OP.OP_ENDIF) {
|
|
515
|
+
inConditionalBlock--
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (op > 0 && op < OP.OP_PUSHDATA1) {
|
|
519
|
+
const len = op
|
|
520
|
+
const end = Math.min(pos + len, length)
|
|
521
|
+
chunks.push({
|
|
522
|
+
data: Script.copyRange(bytes, pos, end),
|
|
523
|
+
op
|
|
524
|
+
})
|
|
525
|
+
pos = end
|
|
526
|
+
} else if (op === OP.OP_PUSHDATA1) {
|
|
527
|
+
const len = pos < length ? bytes[pos++] ?? 0 : 0
|
|
528
|
+
const end = Math.min(pos + len, length)
|
|
529
|
+
chunks.push({
|
|
530
|
+
data: Script.copyRange(bytes, pos, end),
|
|
531
|
+
op
|
|
532
|
+
})
|
|
533
|
+
pos = end
|
|
534
|
+
} else if (op === OP.OP_PUSHDATA2) {
|
|
535
|
+
const b0 = bytes[pos] ?? 0
|
|
536
|
+
const b1 = bytes[pos + 1] ?? 0
|
|
537
|
+
const len = b0 | (b1 << 8)
|
|
538
|
+
pos = Math.min(pos + 2, length)
|
|
539
|
+
const end = Math.min(pos + len, length)
|
|
540
|
+
chunks.push({
|
|
541
|
+
data: Script.copyRange(bytes, pos, end),
|
|
542
|
+
op
|
|
543
|
+
})
|
|
544
|
+
pos = end
|
|
545
|
+
} else if (op === OP.OP_PUSHDATA4) {
|
|
546
|
+
const len =
|
|
547
|
+
((bytes[pos] ?? 0) |
|
|
548
|
+
((bytes[pos + 1] ?? 0) << 8) |
|
|
549
|
+
((bytes[pos + 2] ?? 0) << 16) |
|
|
550
|
+
((bytes[pos + 3] ?? 0) << 24)) >>>
|
|
551
|
+
0
|
|
552
|
+
pos = Math.min(pos + 4, length)
|
|
553
|
+
const end = Math.min(pos + len, length)
|
|
554
|
+
chunks.push({
|
|
555
|
+
data: Script.copyRange(bytes, pos, end),
|
|
556
|
+
op
|
|
557
|
+
})
|
|
558
|
+
pos = end
|
|
559
|
+
} else {
|
|
560
|
+
chunks.push({ op })
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return chunks
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
private static copyRange (
|
|
568
|
+
bytes: ArrayLike<number>,
|
|
569
|
+
start: number,
|
|
570
|
+
end: number
|
|
571
|
+
): number[] {
|
|
572
|
+
const size = Math.max(end - start, 0)
|
|
573
|
+
const data = new Array(size)
|
|
574
|
+
for (let i = 0; i < size; i++) {
|
|
575
|
+
data[i] = bytes[start + i] ?? 0
|
|
576
|
+
}
|
|
577
|
+
return data
|
|
578
|
+
}
|
|
579
|
+
|
|
446
580
|
private _chunkToString (chunk: ScriptChunk): string {
|
|
447
581
|
const op = chunk.op
|
|
448
582
|
let str = ''
|
package/src/script/Spend.ts
CHANGED
|
@@ -7,7 +7,7 @@ import ScriptChunk from './ScriptChunk.js'
|
|
|
7
7
|
import { minimallyEncode } from '../primitives/utils.js'
|
|
8
8
|
import ScriptEvaluationError from './ScriptEvaluationError.js'
|
|
9
9
|
import * as Hash from '../primitives/Hash.js'
|
|
10
|
-
import TransactionSignature from '../primitives/TransactionSignature.js'
|
|
10
|
+
import TransactionSignature, { SignatureHashCache } from '../primitives/TransactionSignature.js'
|
|
11
11
|
import PublicKey from '../primitives/PublicKey.js'
|
|
12
12
|
import { verify } from '../primitives/ECDSA.js'
|
|
13
13
|
import TransactionInput from '../transaction/TransactionInput.js'
|
|
@@ -152,6 +152,7 @@ export default class Spend {
|
|
|
152
152
|
memoryLimit: number
|
|
153
153
|
stackMem: number
|
|
154
154
|
altStackMem: number
|
|
155
|
+
private sigHashCache: SignatureHashCache
|
|
155
156
|
|
|
156
157
|
/**
|
|
157
158
|
* @constructor
|
|
@@ -216,6 +217,7 @@ export default class Spend {
|
|
|
216
217
|
this.ifStack = []
|
|
217
218
|
this.stackMem = 0
|
|
218
219
|
this.altStackMem = 0
|
|
220
|
+
this.sigHashCache = { hashOutputsSingle: new Map() }
|
|
219
221
|
this.reset()
|
|
220
222
|
}
|
|
221
223
|
|
|
@@ -228,6 +230,7 @@ export default class Spend {
|
|
|
228
230
|
this.ifStack = []
|
|
229
231
|
this.stackMem = 0
|
|
230
232
|
this.altStackMem = 0
|
|
233
|
+
this.sigHashCache = { hashOutputsSingle: new Map() }
|
|
231
234
|
}
|
|
232
235
|
|
|
233
236
|
private ensureStackMem (additional: number): void {
|
|
@@ -353,7 +356,7 @@ export default class Spend {
|
|
|
353
356
|
pubkey: PublicKey,
|
|
354
357
|
subscript: Script
|
|
355
358
|
): boolean {
|
|
356
|
-
const preimage = TransactionSignature.
|
|
359
|
+
const preimage = TransactionSignature.formatBytes({
|
|
357
360
|
sourceTXID: this.sourceTXID,
|
|
358
361
|
sourceOutputIndex: this.sourceOutputIndex,
|
|
359
362
|
sourceSatoshis: this.sourceSatoshis,
|
|
@@ -364,7 +367,8 @@ export default class Spend {
|
|
|
364
367
|
subscript,
|
|
365
368
|
inputSequence: this.inputSequence,
|
|
366
369
|
lockTime: this.lockTime,
|
|
367
|
-
scope: sig.scope
|
|
370
|
+
scope: sig.scope,
|
|
371
|
+
cache: this.sigHashCache
|
|
368
372
|
})
|
|
369
373
|
const hash = new BigNumber(Hash.hash256(preimage))
|
|
370
374
|
return verify(hash, sig, pubkey)
|
package/src/transaction/Beef.ts
CHANGED
|
@@ -5,6 +5,9 @@ import BeefTx from './BeefTx.js'
|
|
|
5
5
|
import { Reader, Writer, toHex, toArray, verifyNotNull } from '../primitives/utils.js'
|
|
6
6
|
import { hash256 } from '../primitives/Hash.js'
|
|
7
7
|
|
|
8
|
+
const BufferCtor =
|
|
9
|
+
typeof globalThis !== 'undefined' ? (globalThis as any).Buffer : undefined
|
|
10
|
+
|
|
8
11
|
export const BEEF_V1 = 4022206465 // 0100BEEF in LE order
|
|
9
12
|
export const BEEF_V2 = 4022206466 // 0200BEEF in LE order
|
|
10
13
|
export const ATOMIC_BEEF = 0x01010101 // 01010101
|
|
@@ -73,11 +76,49 @@ export class Beef {
|
|
|
73
76
|
version: number = BEEF_V2
|
|
74
77
|
atomicTxid: string | undefined = undefined
|
|
75
78
|
private txidIndex: Map<string, BeefTx> | undefined = undefined
|
|
79
|
+
private rawBytesCache?: Uint8Array
|
|
80
|
+
private hexCache?: string
|
|
81
|
+
private needsSort: boolean = true
|
|
76
82
|
|
|
77
83
|
constructor (version: number = BEEF_V2) {
|
|
78
84
|
this.version = version
|
|
79
85
|
}
|
|
80
86
|
|
|
87
|
+
private invalidateSerializationCaches (): void {
|
|
88
|
+
this.rawBytesCache = undefined
|
|
89
|
+
this.hexCache = undefined
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private markMutated (requiresSort: boolean = true): void {
|
|
93
|
+
this.invalidateSerializationCaches()
|
|
94
|
+
if (requiresSort) {
|
|
95
|
+
this.needsSort = true
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private ensureSerializableState (): void {
|
|
100
|
+
for (const tx of this.txs) {
|
|
101
|
+
void tx.txid
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private ensureSortedForSerialization (): void {
|
|
106
|
+
if (this.needsSort) {
|
|
107
|
+
this.sortTxs()
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private getSerializedBytes (): Uint8Array {
|
|
112
|
+
this.ensureSerializableState()
|
|
113
|
+
if (this.rawBytesCache == null) {
|
|
114
|
+
this.ensureSortedForSerialization()
|
|
115
|
+
const writer = new Writer()
|
|
116
|
+
this.toWriter(writer)
|
|
117
|
+
this.rawBytesCache = writer.toUint8Array()
|
|
118
|
+
}
|
|
119
|
+
return this.rawBytesCache
|
|
120
|
+
}
|
|
121
|
+
|
|
81
122
|
/**
|
|
82
123
|
* @param txid of `beefTx` to find
|
|
83
124
|
* @returns `BeefTx` in `txs` with `txid`.
|
|
@@ -123,6 +164,7 @@ export class Beef {
|
|
|
123
164
|
}
|
|
124
165
|
this.deleteFromIndex(txid)
|
|
125
166
|
this.txs.splice(i, 1)
|
|
167
|
+
this.markMutated(true)
|
|
126
168
|
btx = this.mergeTxidOnly(txid)
|
|
127
169
|
return btx
|
|
128
170
|
}
|
|
@@ -208,6 +250,7 @@ export class Beef {
|
|
|
208
250
|
* @returns index of merged bump
|
|
209
251
|
*/
|
|
210
252
|
mergeBump (bump: MerklePath): number {
|
|
253
|
+
this.markMutated(false)
|
|
211
254
|
let bumpIndex: number | undefined
|
|
212
255
|
// If this proof is identical to another one previously added, we use that first. Otherwise, we try to merge it with proofs from the same block.
|
|
213
256
|
for (let i = 0; i < this.bumps.length; i++) {
|
|
@@ -266,6 +309,7 @@ export class Beef {
|
|
|
266
309
|
* @returns txid of rawTx
|
|
267
310
|
*/
|
|
268
311
|
mergeRawTx (rawTx: number[], bumpIndex?: number): BeefTx {
|
|
312
|
+
this.markMutated(true)
|
|
269
313
|
const newTx: BeefTx = new BeefTx(rawTx, bumpIndex)
|
|
270
314
|
this.removeExistingTxid(newTx.txid)
|
|
271
315
|
this.txs.push(newTx)
|
|
@@ -285,6 +329,7 @@ export class Beef {
|
|
|
285
329
|
* @returns txid of tx
|
|
286
330
|
*/
|
|
287
331
|
mergeTransaction (tx: Transaction): BeefTx {
|
|
332
|
+
this.markMutated(true)
|
|
288
333
|
const txid = tx.id('hex')
|
|
289
334
|
this.removeExistingTxid(txid)
|
|
290
335
|
let bumpIndex: number | undefined
|
|
@@ -315,6 +360,7 @@ export class Beef {
|
|
|
315
360
|
if (existingTxIndex >= 0) {
|
|
316
361
|
this.deleteFromIndex(txid)
|
|
317
362
|
this.txs.splice(existingTxIndex, 1)
|
|
363
|
+
this.markMutated(true)
|
|
318
364
|
}
|
|
319
365
|
}
|
|
320
366
|
|
|
@@ -325,6 +371,7 @@ export class Beef {
|
|
|
325
371
|
this.txs.push(tx)
|
|
326
372
|
this.addToIndex(tx)
|
|
327
373
|
this.tryToValidateBumpIndex(tx)
|
|
374
|
+
this.markMutated(true)
|
|
328
375
|
}
|
|
329
376
|
return tx
|
|
330
377
|
}
|
|
@@ -521,11 +568,11 @@ export class Beef {
|
|
|
521
568
|
* @returns A binary array representing the BEEF
|
|
522
569
|
*/
|
|
523
570
|
toBinary (): number[] {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
return
|
|
571
|
+
return Array.from(this.getSerializedBytes())
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
toUint8Array (): Uint8Array {
|
|
575
|
+
return this.getSerializedBytes()
|
|
529
576
|
}
|
|
530
577
|
|
|
531
578
|
/**
|
|
@@ -539,7 +586,9 @@ export class Beef {
|
|
|
539
586
|
* @returns serialized contents of this Beef with AtomicBEEF prefix.
|
|
540
587
|
*/
|
|
541
588
|
toBinaryAtomic (txid: string): number[] {
|
|
542
|
-
this.
|
|
589
|
+
if (this.needsSort) {
|
|
590
|
+
this.sortTxs()
|
|
591
|
+
}
|
|
543
592
|
const tx = this.findTxid(txid)
|
|
544
593
|
if (tx == null) {
|
|
545
594
|
throw new Error(`${txid} does not exist in this Beef`)
|
|
@@ -566,7 +615,16 @@ export class Beef {
|
|
|
566
615
|
* @returns A hex string representing the BEEF
|
|
567
616
|
*/
|
|
568
617
|
toHex (): string {
|
|
569
|
-
|
|
618
|
+
if (this.hexCache != null) {
|
|
619
|
+
return this.hexCache
|
|
620
|
+
}
|
|
621
|
+
const bytes = this.getSerializedBytes()
|
|
622
|
+
const hex =
|
|
623
|
+
BufferCtor != null
|
|
624
|
+
? BufferCtor.from(bytes).toString('hex')
|
|
625
|
+
: toHex(Array.from(bytes))
|
|
626
|
+
this.hexCache = hex
|
|
627
|
+
return hex
|
|
570
628
|
}
|
|
571
629
|
|
|
572
630
|
static fromReader (br: Reader): Beef {
|
|
@@ -745,6 +803,9 @@ export class Beef {
|
|
|
745
803
|
.concat(txidOnly)
|
|
746
804
|
.concat(result)
|
|
747
805
|
|
|
806
|
+
this.needsSort = false
|
|
807
|
+
this.invalidateSerializationCaches()
|
|
808
|
+
|
|
748
809
|
return {
|
|
749
810
|
missingInputs: Object.keys(missingInputs),
|
|
750
811
|
notValid: txsNotValid.map((tx) => tx.txid),
|
|
@@ -763,6 +824,7 @@ export class Beef {
|
|
|
763
824
|
c.bumps = Array.from(this.bumps)
|
|
764
825
|
c.txs = Array.from(this.txs)
|
|
765
826
|
c.txidIndex = undefined
|
|
827
|
+
c.needsSort = this.needsSort
|
|
766
828
|
return c
|
|
767
829
|
}
|
|
768
830
|
|
|
@@ -771,16 +833,21 @@ export class Beef {
|
|
|
771
833
|
* @param knownTxids
|
|
772
834
|
*/
|
|
773
835
|
trimKnownTxids (knownTxids: string[]): void {
|
|
836
|
+
let mutated = false
|
|
774
837
|
for (let i = 0; i < this.txs.length;) {
|
|
775
838
|
const tx = this.txs[i]
|
|
776
839
|
if (tx.isTxidOnly && knownTxids.includes(tx.txid)) {
|
|
777
840
|
this.deleteFromIndex(tx.txid)
|
|
778
841
|
this.txs.splice(i, 1)
|
|
842
|
+
mutated = true
|
|
779
843
|
} else {
|
|
780
844
|
i++
|
|
781
845
|
}
|
|
782
846
|
}
|
|
783
847
|
// TODO: bumps could be trimmed to eliminate unreferenced proofs.
|
|
848
|
+
if (mutated) {
|
|
849
|
+
this.markMutated(true)
|
|
850
|
+
}
|
|
784
851
|
}
|
|
785
852
|
|
|
786
853
|
/**
|
|
@@ -133,7 +133,7 @@ export default class BeefTx {
|
|
|
133
133
|
if (this._rawTx != null) {
|
|
134
134
|
writer.write(this._rawTx)
|
|
135
135
|
} else if (this._tx != null) {
|
|
136
|
-
writer.write(this._tx.
|
|
136
|
+
writer.write(this._tx.toUint8Array())
|
|
137
137
|
} else {
|
|
138
138
|
throw new Error('a valid serialized Transaction is expected')
|
|
139
139
|
}
|