@bsv/sdk 1.6.19 → 1.6.20

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.
Files changed (40) hide show
  1. package/dist/cjs/package.json +3 -3
  2. package/dist/cjs/src/primitives/ECDSA.js +43 -42
  3. package/dist/cjs/src/primitives/ECDSA.js.map +1 -1
  4. package/dist/cjs/src/primitives/Point.js +70 -91
  5. package/dist/cjs/src/primitives/Point.js.map +1 -1
  6. package/dist/cjs/src/script/ScriptEvaluationError.js +27 -0
  7. package/dist/cjs/src/script/ScriptEvaluationError.js.map +1 -0
  8. package/dist/cjs/src/script/Spend.js +13 -7
  9. package/dist/cjs/src/script/Spend.js.map +1 -1
  10. package/dist/cjs/src/script/index.js +3 -1
  11. package/dist/cjs/src/script/index.js.map +1 -1
  12. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  13. package/dist/esm/src/primitives/ECDSA.js +43 -42
  14. package/dist/esm/src/primitives/ECDSA.js.map +1 -1
  15. package/dist/esm/src/primitives/Point.js +67 -90
  16. package/dist/esm/src/primitives/Point.js.map +1 -1
  17. package/dist/esm/src/script/ScriptEvaluationError.js +33 -0
  18. package/dist/esm/src/script/ScriptEvaluationError.js.map +1 -0
  19. package/dist/esm/src/script/Spend.js +14 -8
  20. package/dist/esm/src/script/Spend.js.map +1 -1
  21. package/dist/esm/src/script/index.js +1 -0
  22. package/dist/esm/src/script/index.js.map +1 -1
  23. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  24. package/dist/types/src/primitives/ECDSA.d.ts.map +1 -1
  25. package/dist/types/src/primitives/Point.d.ts +3 -5
  26. package/dist/types/src/primitives/Point.d.ts.map +1 -1
  27. package/dist/types/src/script/ScriptEvaluationError.d.ts +24 -0
  28. package/dist/types/src/script/ScriptEvaluationError.d.ts.map +1 -0
  29. package/dist/types/src/script/Spend.d.ts.map +1 -1
  30. package/dist/types/src/script/index.d.ts +1 -0
  31. package/dist/types/src/script/index.d.ts.map +1 -1
  32. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  33. package/dist/umd/bundle.js +5 -5
  34. package/dist/umd/bundle.js.map +1 -1
  35. package/package.json +3 -3
  36. package/src/primitives/ECDSA.ts +50 -49
  37. package/src/primitives/Point.ts +65 -99
  38. package/src/script/ScriptEvaluationError.ts +44 -0
  39. package/src/script/Spend.ts +14 -12
  40. package/src/script/index.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.6.19",
3
+ "version": "1.6.20",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -243,6 +243,7 @@
243
243
  "devDependencies": {
244
244
  "@eslint/js": "^9.23.0",
245
245
  "@jest/globals": "^29.7.0",
246
+ "@rspack/cli": "^1.4.9",
246
247
  "@types/jest": "^29.5.14",
247
248
  "@types/node": "^22.13.14",
248
249
  "eslint": "^9.23.0",
@@ -255,8 +256,7 @@
255
256
  "ts2md": "^0.2.8",
256
257
  "tsconfig-to-dual-package": "^1.2.0",
257
258
  "typescript": "5.1",
258
- "typescript-eslint": "^8.29.0",
259
- "@rspack/cli": "^0.4.4"
259
+ "typescript-eslint": "^8.29.0"
260
260
  },
261
261
  "ts-standard": {
262
262
  "project": "tsconfig.eslint.json",
@@ -39,6 +39,11 @@ function truncateToN (
39
39
  }
40
40
  }
41
41
 
42
+ const curve = new Curve()
43
+ const bytes = curve.n.byteLength()
44
+ const ns1 = curve.n.subn(1)
45
+ const halfN = N_BIGINT >> 1n
46
+
42
47
  /**
43
48
  * Generates a digital signature for a given message.
44
49
  *
@@ -60,84 +65,80 @@ export const sign = (
60
65
  forceLowS: boolean = false,
61
66
  customK?: BigNumber | ((iter: number) => BigNumber)
62
67
  ): Signature => {
63
- const curve = new Curve()
68
+ // —— prepare inputs ────────────────────────────────────────────────────────
64
69
  msg = truncateToN(msg)
70
+ const msgBig = BigInt('0x' + msg.toString(16))
71
+ const keyBig = BigInt('0x' + key.toString(16))
65
72
 
66
- // Zero-extend key to provide enough entropy
67
- const bytes = curve.n.byteLength()
73
+ // DRBG seeding identical to previous implementation
68
74
  const bkey = key.toArray('be', bytes)
69
-
70
- // Zero-extend nonce to have the same byte size as N
71
75
  const nonce = msg.toArray('be', bytes)
72
-
73
- // Instantiate Hmac_DRBG
74
76
  const drbg = new DRBG(bkey, nonce)
75
77
 
76
- // Number of bytes to generate
77
- const ns1 = curve.n.subn(1)
78
-
79
78
  for (let iter = 0; ; iter++) {
80
- // Compute the k-value
81
- let k =
79
+ // —— k generation & basic validity checks ───────────────────────────────
80
+ let kBN =
82
81
  typeof customK === 'function'
83
82
  ? customK(iter)
84
83
  : BigNumber.isBN(customK)
85
84
  ? customK
86
85
  : new BigNumber(drbg.generate(bytes), 16)
87
- if (k != null) {
88
- k = truncateToN(k, true)
89
- } else {
90
- throw new Error('k is undefined')
91
- }
92
- if (k.cmpn(1) <= 0 || k.cmp(ns1) >= 0) {
86
+
87
+ if (kBN == null) throw new Error('k is undefined')
88
+ kBN = truncateToN(kBN, true)
89
+
90
+ if (kBN.cmpn(1) <= 0 || kBN.cmp(ns1) >= 0) {
93
91
  if (BigNumber.isBN(customK)) {
94
- throw new Error(
95
- 'Invalid fixed custom K value (must be more than 1 and less than N-1)'
96
- )
97
- } else {
98
- continue
92
+ throw new Error('Invalid fixed custom K value (must be >1 and <N‑1)')
99
93
  }
94
+ continue
100
95
  }
101
96
 
102
- const kp = curve.g.mul(k)
103
- if (kp.isInfinity()) {
97
+ const kBig = BigInt('0x' + kBN.toString(16))
98
+
99
+ // —— R = k·G (Jacobian, window‑NAF) ──────────────────────────────────────
100
+ const R = scalarMultiplyWNAF(kBig, { x: GX_BIGINT, y: GY_BIGINT })
101
+ if (R.Z === 0n) { // point at infinity – should never happen for valid k
104
102
  if (BigNumber.isBN(customK)) {
105
- throw new Error(
106
- 'Invalid fixed custom K value (must not create a point at infinity when multiplied by the generator point)'
107
- )
108
- } else {
109
- continue
103
+ throw new Error('Invalid fixed custom K value (k·G at infinity)')
110
104
  }
105
+ continue
111
106
  }
112
107
 
113
- const kpX = kp.getX()
114
- const r = kpX.umod(curve.n)
115
- if (r.cmpn(0) === 0) {
108
+ // affine X coordinate of R
109
+ const zInv = biModInv(R.Z)
110
+ const zInv2 = biModMul(zInv, zInv)
111
+ const xAff = biModMul(R.X, zInv2)
112
+ const rBig = modN(xAff)
113
+
114
+ if (rBig === 0n) {
116
115
  if (BigNumber.isBN(customK)) {
117
- throw new Error(
118
- 'Invalid fixed custom K value (when multiplied by G, the resulting x coordinate mod N must not be zero)'
119
- )
120
- } else {
121
- continue
116
+ throw new Error('Invalid fixed custom K value (r == 0)')
122
117
  }
118
+ continue
123
119
  }
124
120
 
125
- let s = k.invm(curve.n).mul(r.mul(key).iadd(msg))
126
- s = s.umod(curve.n)
127
- if (s.cmpn(0) === 0) {
121
+ // —— s = k⁻¹ · (msg + r·key) mod n ─────────────────────────────────────
122
+ const kInv = modInvN(kBig)
123
+ const rTimesKey = modMulN(rBig, keyBig)
124
+ const sum = modN(msgBig + rTimesKey)
125
+ let sBig = modMulN(kInv, sum)
126
+
127
+ if (sBig === 0n) {
128
128
  if (BigNumber.isBN(customK)) {
129
- throw new Error(
130
- 'Invalid fixed custom K value (when used with the key, it cannot create a zero value for S)'
131
- )
132
- } else {
133
- continue
129
+ throw new Error('Invalid fixed custom K value (s == 0)')
134
130
  }
131
+ continue
135
132
  }
136
133
 
137
- // Use complement of `s`, if it is > `n / 2`
138
- if (forceLowS && s.cmp(curve.n.ushrn(1)) > 0) {
139
- s = curve.n.sub(s)
134
+ // low‑S mitigation (BIP‑62/BIP‑340 style)
135
+ if (forceLowS && sBig > halfN) {
136
+ sBig = N_BIGINT - sBig
140
137
  }
138
+
139
+ // —— convert back to BigNumber & return ─────────────────────────────────
140
+ const r = new BigNumber(rBig.toString(16), 16)
141
+ const s = new BigNumber(sBig.toString(16), 16)
141
142
  return new Signature(r, s)
142
143
  }
143
144
  }
@@ -2,7 +2,6 @@ import BasePoint from './BasePoint.js'
2
2
  import JPoint from './JacobianPoint.js'
3
3
  import BigNumber from './BigNumber.js'
4
4
  import { toArray, toHex } from './utils.js'
5
- import ReductionContext from './ReductionContext.js'
6
5
 
7
6
  // -----------------------------------------------------------------------------
8
7
  // BigInt helpers & constants (secp256k1) – hoisted so we don't recreate them on
@@ -44,6 +43,32 @@ export const biModInv = (a: bigint): bigint => { // binary‑ext GCD
44
43
  }
45
44
  export const biModSqr = (a: bigint): bigint => biModMul(a, a)
46
45
 
46
+ export const biModPow = (base: bigint, exp: bigint): bigint => {
47
+ let result = BI_ONE
48
+ base = biMod(base)
49
+ let e = exp
50
+ while (e > BI_ZERO) {
51
+ if ((e & BI_ONE) === BI_ONE) result = biModMul(result, base)
52
+ base = biModMul(base, base)
53
+ e >>= BI_ONE
54
+ }
55
+ return result
56
+ }
57
+
58
+ export const P_PLUS1_DIV4 = (P_BIGINT + 1n) >> 2n
59
+
60
+ export const biModSqrt = (a: bigint): bigint | null => {
61
+ const r = biModPow(a, P_PLUS1_DIV4)
62
+ return biModMul(r, r) === biMod(a) ? r : null
63
+ }
64
+
65
+ const toBigInt = (x: BigNumber | number | number[] | string): bigint => {
66
+ if (BigNumber.isBN(x)) return BigInt('0x' + (x as BigNumber).toString(16))
67
+ if (typeof x === 'string') return BigInt('0x' + x)
68
+ if (Array.isArray(x)) return BigInt('0x' + toHex(x))
69
+ return BigInt(x as number)
70
+ }
71
+
47
72
  // Generator point coordinates as bigint constants
48
73
  export const GX_BIGINT = BigInt('0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')
49
74
  export const GY_BIGINT = BigInt('0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8')
@@ -191,10 +216,6 @@ export const modInvN = (a: bigint): bigint => {
191
216
  * @property inf - Flag to record if the point is at infinity in the Elliptic Curve.
192
217
  */
193
218
  export default class Point extends BasePoint {
194
- private static readonly red: any = new ReductionContext('k256')
195
- private static readonly a: BigNumber = new BigNumber(0).toRed(Point.red)
196
- private static readonly b: BigNumber = new BigNumber(7).toRed(Point.red)
197
- private static readonly zero: BigNumber = new BigNumber(0).toRed(Point.red)
198
219
  x: BigNumber | null
199
220
  y: BigNumber | null
200
221
  inf: boolean
@@ -269,13 +290,6 @@ export default class Point extends BasePoint {
269
290
  return Point.fromDER(bytes)
270
291
  }
271
292
 
272
- static redSqrtOptimized (y2: BigNumber): BigNumber {
273
- const red = Point.red
274
- const p = red.m // The modulus
275
- const exponent = p.addn(1).iushrn(2) // (p + 1) / 4
276
- return y2.redPow(exponent)
277
- }
278
-
279
293
  /**
280
294
  * Generates a point from an x coordinate and a boolean indicating whether the corresponding
281
295
  * y coordinate is odd.
@@ -292,66 +306,17 @@ export default class Point extends BasePoint {
292
306
  * const point = Point.fromX(xCoordinate, true);
293
307
  */
294
308
  static fromX (x: BigNumber | number | number[] | string, odd: boolean): Point {
295
- function mod (a: bigint, n: bigint): bigint {
296
- return ((a % n) + n) % n
297
- }
298
- function modPow (base: bigint, exponent: bigint, modulus: bigint): bigint {
299
- let result = BigInt(1)
300
- base = mod(base, modulus)
301
- while (exponent > BigInt(0)) {
302
- if ((exponent & BigInt(1)) === BigInt(1)) {
303
- result = mod(result * base, modulus)
304
- }
305
- exponent >>= BigInt(1)
306
- base = mod(base * base, modulus)
307
- }
308
- return result
309
- }
310
- function sqrtMod (a: bigint, p: bigint): bigint | null {
311
- const exponent = (p + BigInt(1)) >> BigInt(2)
312
- const sqrtCandidate = modPow(a, exponent, p)
313
- if (mod(sqrtCandidate * sqrtCandidate, p) === mod(a, p)) {
314
- return sqrtCandidate
315
- } else {
316
- return null
317
- }
318
- }
319
-
320
- // Curve parameters for secp256k1
321
- const p = BigInt(
322
- '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F'
323
- )
324
- const b = BigInt(7)
325
-
326
- let xBigInt: bigint
327
- if (x instanceof BigNumber) {
328
- xBigInt = BigInt('0x' + x.toString(16))
329
- } else if (typeof x === 'string') {
330
- xBigInt = BigInt('0x' + x)
331
- } else if (Array.isArray(x)) {
332
- xBigInt = BigInt('0x' + toHex(x).padStart(64, '0'))
333
- } else if (typeof x === 'number') {
334
- xBigInt = BigInt(x)
335
- } else {
336
- throw new Error('Invalid x-coordinate type')
337
- }
338
-
339
- xBigInt = mod(xBigInt, p)
340
-
341
- const y2 = mod(modPow(xBigInt, BigInt(3), p) + b, p)
342
-
343
- let y = sqrtMod(y2, p)
344
- if (y === null) {
345
- throw new Error('Invalid point')
346
- }
347
-
348
- const isYOdd = y % BigInt(2) === BigInt(1)
349
- if ((odd && !isYOdd) || (!odd && isYOdd)) {
350
- y = p - y
309
+ let xBigInt = toBigInt(x)
310
+ xBigInt = biMod(xBigInt)
311
+ const y2 = biModAdd(biModMul(biModSqr(xBigInt), xBigInt), 7n)
312
+ const y = biModSqrt(y2)
313
+ if (y === null) throw new Error('Invalid point')
314
+ let yBig = y
315
+ if ((yBig & BI_ONE) !== (odd ? BI_ONE : BI_ZERO)) {
316
+ yBig = biModSub(P_BIGINT, yBig)
351
317
  }
352
-
353
318
  const xBN = new BigNumber(xBigInt.toString(16), 16)
354
- const yBN = new BigNumber(y.toString(16), 16)
319
+ const yBN = new BigNumber(yBig.toString(16), 16)
355
320
  return new Point(xBN, yBN)
356
321
  }
357
322
 
@@ -622,26 +587,31 @@ export default class Point extends BasePoint {
622
587
 
623
588
  // P + (-P) = O
624
589
  if (this.neg().eq(p)) {
625
- return new Point(new BigNumber(0), new BigNumber(0))
590
+ return new Point(null, null)
626
591
  }
627
592
 
628
593
  // P + Q = O
629
594
  if (this.x?.cmp(p.x ?? new BigNumber(0)) === 0) {
630
- return new Point(new BigNumber(0), new BigNumber(0))
595
+ return new Point(null, null)
631
596
  }
632
597
 
633
- const q = {
634
- x: BigInt('0x' + (p.x as BigNumber).fromRed().toString(16)),
635
- y: BigInt('0x' + (p.y as BigNumber).fromRed().toString(16))
598
+ const P1 = {
599
+ X: BigInt('0x' + (this.x as BigNumber).fromRed().toString(16)),
600
+ Y: BigInt('0x' + (this.y as BigNumber).fromRed().toString(16)),
601
+ Z: BI_ONE
636
602
  }
637
- const t = {
638
- x: BigInt('0x' + (this.x as BigNumber).fromRed().toString(16)),
639
- y: BigInt('0x' + (this.y as BigNumber).fromRed().toString(16))
603
+ const Q1 = {
604
+ X: BigInt('0x' + (p.x as BigNumber).fromRed().toString(16)),
605
+ Y: BigInt('0x' + (p.y as BigNumber).fromRed().toString(16)),
606
+ Z: BI_ONE
640
607
  }
641
- const λ = biModMul(biModSub(q.y, t.y), biModInv(biModSub(q.x, t.x)))
642
- const rx = biModSub(biModSub(biModSqr(λ), t.x), q.x)
643
- const ry = biModSub(biModMul(λ, biModSub(t.x, rx)), t.y)
644
- return new Point(rx.toString(16), ry.toString(16))
608
+ const R = jpAdd(P1, Q1)
609
+ if (R.Z === BI_ZERO) return new Point(null, null)
610
+ const zInv = biModInv(R.Z)
611
+ const zInv2 = biModMul(zInv, zInv)
612
+ const xRes = biModMul(R.X, zInv2)
613
+ const yRes = biModMul(R.Y, biModMul(zInv2, zInv))
614
+ return new Point(xRes.toString(16), yRes.toString(16))
645
615
  }
646
616
 
647
617
  /**
@@ -654,25 +624,21 @@ export default class Point extends BasePoint {
654
624
  * const result = P.dbl();
655
625
  * */
656
626
  dbl (): Point {
657
- if (this.inf) {
658
- return this
659
- }
660
-
661
- // 2P = O
662
- const ys1 = (this.y ?? new BigNumber(0)).redAdd(this.y ?? new BigNumber(0))
663
- if (ys1.cmpn(0) === 0) {
664
- return new Point(new BigNumber(0), new BigNumber(0))
627
+ if (this.inf) return this
628
+ if (this.x === null || this.y === null) {
629
+ throw new Error('Point coordinates cannot be null')
665
630
  }
666
631
 
667
- const a = this.curve.a
668
- const x2 = (this.x ?? new BigNumber(0)).redSqr()
669
- const dyinv = ys1.redInvm()
670
- const c = x2.redAdd(x2).redIAdd(x2).redIAdd(a).redMul(dyinv)
632
+ const X = BigInt('0x' + this.x.fromRed().toString(16))
633
+ const Y = BigInt('0x' + this.y.fromRed().toString(16))
634
+ if (Y === BI_ZERO) return new Point(null, null)
671
635
 
672
- const nx = c.redSqr().redISub((this.x ?? new BigNumber(0)).redAdd(this.x ?? new BigNumber(0)))
673
- const ny = c.redMul((this.x ?? new BigNumber(0)).redSub(nx)).redISub(this.y ?? new BigNumber(0))
674
-
675
- return new Point(nx, ny)
636
+ const R = jpDouble({ X, Y, Z: BI_ONE })
637
+ const zInv = biModInv(R.Z)
638
+ const zInv2 = biModMul(zInv, zInv)
639
+ const xRes = biModMul(R.X, zInv2)
640
+ const yRes = biModMul(R.Y, biModMul(zInv2, zInv))
641
+ return new Point(xRes.toString(16), yRes.toString(16))
676
642
  }
677
643
 
678
644
  /**
@@ -1134,7 +1100,7 @@ export default class Point extends BasePoint {
1134
1100
  for (i = 0; i < points.length; i++) {
1135
1101
  const split = this.curve._endoSplit(coeffs[i])
1136
1102
  let p = points[i]
1137
- let beta: Point = p._getBeta() ?? new Point(new BigNumber(0), new BigNumber(0))
1103
+ let beta: Point = p._getBeta() ?? new Point(null, null)
1138
1104
 
1139
1105
  if (split.k1.negative !== 0) {
1140
1106
  split.k1.ineg()
@@ -0,0 +1,44 @@
1
+ import { toHex } from '../primitives/utils.js'
2
+ export default class ScriptEvaluationError extends Error {
3
+ txid: string
4
+ outputIndex: number
5
+ context: 'UnlockingScript' | 'LockingScript'
6
+ programCounter: number
7
+ stackState: number[][]
8
+ altStackState: number[][]
9
+ ifStackState: boolean[]
10
+ stackMem: number
11
+ altStackMem: number
12
+
13
+ constructor (params: {
14
+ message: string
15
+ txid: string
16
+ outputIndex: number
17
+ context: 'UnlockingScript' | 'LockingScript'
18
+ programCounter: number
19
+ stackState: number[][]
20
+ altStackState: number[][]
21
+ ifStackState: boolean[]
22
+ stackMem: number
23
+ altStackMem: number
24
+ }) {
25
+ const stackHex = params.stackState.map(s => s != null && typeof s.length !== 'undefined' ? toHex(s) : (s === null || s === undefined ? 'null/undef' : 'INVALID_STACK_ITEM')).join(', ')
26
+ const altStackHex = params.altStackState.map(s => s != null && typeof s.length !== 'undefined' ? toHex(s) : (s === null || s === undefined ? 'null/undef' : 'INVALID_STACK_ITEM')).join(', ')
27
+ const pcInfo = `Context: ${params.context}, PC: ${params.programCounter}`
28
+ const stackInfo = `Stack: [${stackHex}] (len: ${params.stackState.length}, mem: ${params.stackMem})`
29
+ const altStackInfo = `AltStack: [${altStackHex}] (len: ${params.altStackState.length}, mem: ${params.altStackMem})`
30
+ const ifStackInfo = `IfStack: [${params.ifStackState.join(', ')}]`
31
+ const fullMessage = `Script evaluation error: ${params.message}\nTXID: ${params.txid}, OutputIdx: ${params.outputIndex}\n${pcInfo}\n${stackInfo}\n${altStackInfo}\n${ifStackInfo}`
32
+ super(fullMessage)
33
+ this.name = this.constructor.name
34
+ this.txid = params.txid
35
+ this.outputIndex = params.outputIndex
36
+ this.context = params.context
37
+ this.programCounter = params.programCounter
38
+ this.stackState = params.stackState.map(s => s.slice())
39
+ this.altStackState = params.altStackState.map(s => s.slice())
40
+ this.ifStackState = params.ifStackState.slice()
41
+ this.stackMem = params.stackMem
42
+ this.altStackMem = params.altStackMem
43
+ }
44
+ }
@@ -4,7 +4,8 @@ import Script from './Script.js'
4
4
  import BigNumber from '../primitives/BigNumber.js'
5
5
  import OP from './OP.js'
6
6
  import ScriptChunk from './ScriptChunk.js'
7
- import { toHex, minimallyEncode } from '../primitives/utils.js'
7
+ import { minimallyEncode } from '../primitives/utils.js'
8
+ import ScriptEvaluationError from './ScriptEvaluationError.js'
8
9
  import * as Hash from '../primitives/Hash.js'
9
10
  import TransactionSignature from '../primitives/TransactionSignature.js'
10
11
  import PublicKey from '../primitives/PublicKey.js'
@@ -1054,16 +1055,17 @@ export default class Spend {
1054
1055
  }
1055
1056
 
1056
1057
  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
-
1065
- throw new Error(
1066
- `Script evaluation error: ${str}\nTXID: ${this.sourceTXID}, OutputIdx: ${this.sourceOutputIndex}\n${pcInfo}\n${stackInfo}\n${altStackInfo}\n${ifStackInfo}`
1067
- )
1058
+ throw new ScriptEvaluationError({
1059
+ message: str,
1060
+ txid: this.sourceTXID,
1061
+ outputIndex: this.sourceOutputIndex,
1062
+ context: this.context,
1063
+ programCounter: this.programCounter,
1064
+ stackState: this.stack,
1065
+ altStackState: this.altStack,
1066
+ ifStackState: this.ifStack,
1067
+ stackMem: this.stackMem,
1068
+ altStackMem: this.altStackMem
1069
+ })
1068
1070
  }
1069
1071
  }
@@ -4,6 +4,7 @@ export { default as LockingScript } from './LockingScript.js'
4
4
  export { default as UnlockingScript } from './UnlockingScript.js'
5
5
  export { default as Spend } from './Spend.js'
6
6
  export type { default as ScriptTemplateUnlock } from './ScriptTemplateUnlock.js'
7
+ export { default as ScriptEvaluationError } from './ScriptEvaluationError.js'
7
8
  export type { default as ScriptTemplate } from './ScriptTemplate.js'
8
9
  export * from './templates/index.js'
9
10
  export type { default as ScriptChunk } from './ScriptChunk.js'