@bsv/sdk 1.10.4 → 2.0.0

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 (156) hide show
  1. package/dist/cjs/mod.js +1 -0
  2. package/dist/cjs/mod.js.map +1 -1
  3. package/dist/cjs/package.json +2 -3
  4. package/dist/cjs/src/auth/Peer.js +18 -20
  5. package/dist/cjs/src/auth/Peer.js.map +1 -1
  6. package/dist/cjs/src/identity/IdentityClient.js +20 -124
  7. package/dist/cjs/src/identity/IdentityClient.js.map +1 -1
  8. package/dist/cjs/src/primitives/TransactionSignature.js +115 -10
  9. package/dist/cjs/src/primitives/TransactionSignature.js.map +1 -1
  10. package/dist/cjs/src/primitives/utils.js +13 -112
  11. package/dist/cjs/src/primitives/utils.js.map +1 -1
  12. package/dist/cjs/src/remittance/CommsLayer.js +3 -0
  13. package/dist/cjs/src/remittance/CommsLayer.js.map +1 -0
  14. package/dist/cjs/src/remittance/IdentityLayer.js +3 -0
  15. package/dist/cjs/src/remittance/IdentityLayer.js.map +1 -0
  16. package/dist/cjs/src/remittance/RemittanceManager.js +1245 -0
  17. package/dist/cjs/src/remittance/RemittanceManager.js.map +1 -0
  18. package/dist/cjs/src/remittance/RemittanceModule.js +3 -0
  19. package/dist/cjs/src/remittance/RemittanceModule.js.map +1 -0
  20. package/dist/cjs/src/remittance/index.js +23 -0
  21. package/dist/cjs/src/remittance/index.js.map +1 -0
  22. package/dist/cjs/src/remittance/modules/BasicBRC29.js +225 -0
  23. package/dist/cjs/src/remittance/modules/BasicBRC29.js.map +1 -0
  24. package/dist/cjs/src/remittance/modules/index.js +18 -0
  25. package/dist/cjs/src/remittance/modules/index.js.map +1 -0
  26. package/dist/cjs/src/remittance/types.js +22 -0
  27. package/dist/cjs/src/remittance/types.js.map +1 -0
  28. package/dist/cjs/src/script/OP.js +15 -13
  29. package/dist/cjs/src/script/OP.js.map +1 -1
  30. package/dist/cjs/src/script/Script.js +4 -1
  31. package/dist/cjs/src/script/Script.js.map +1 -1
  32. package/dist/cjs/src/script/Spend.js +128 -46
  33. package/dist/cjs/src/script/Spend.js.map +1 -1
  34. package/dist/cjs/src/transaction/BeefTx.js +2 -2
  35. package/dist/cjs/src/transaction/Transaction.js +160 -0
  36. package/dist/cjs/src/transaction/Transaction.js.map +1 -1
  37. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  38. package/dist/esm/mod.js +1 -0
  39. package/dist/esm/mod.js.map +1 -1
  40. package/dist/esm/src/auth/Peer.js +18 -20
  41. package/dist/esm/src/auth/Peer.js.map +1 -1
  42. package/dist/esm/src/identity/IdentityClient.js +20 -124
  43. package/dist/esm/src/identity/IdentityClient.js.map +1 -1
  44. package/dist/esm/src/primitives/TransactionSignature.js +115 -10
  45. package/dist/esm/src/primitives/TransactionSignature.js.map +1 -1
  46. package/dist/esm/src/primitives/utils.js +13 -112
  47. package/dist/esm/src/primitives/utils.js.map +1 -1
  48. package/dist/esm/src/remittance/CommsLayer.js +2 -0
  49. package/dist/esm/src/remittance/CommsLayer.js.map +1 -0
  50. package/dist/esm/src/remittance/IdentityLayer.js +2 -0
  51. package/dist/esm/src/remittance/IdentityLayer.js.map +1 -0
  52. package/dist/esm/src/remittance/RemittanceManager.js +1254 -0
  53. package/dist/esm/src/remittance/RemittanceManager.js.map +1 -0
  54. package/dist/esm/src/remittance/RemittanceModule.js +2 -0
  55. package/dist/esm/src/remittance/RemittanceModule.js.map +1 -0
  56. package/dist/esm/src/remittance/index.js +7 -0
  57. package/dist/esm/src/remittance/index.js.map +1 -0
  58. package/dist/esm/src/remittance/modules/BasicBRC29.js +227 -0
  59. package/dist/esm/src/remittance/modules/BasicBRC29.js.map +1 -0
  60. package/dist/esm/src/remittance/modules/index.js +2 -0
  61. package/dist/esm/src/remittance/modules/index.js.map +1 -0
  62. package/dist/esm/src/remittance/types.js +19 -0
  63. package/dist/esm/src/remittance/types.js.map +1 -0
  64. package/dist/esm/src/script/OP.js +15 -13
  65. package/dist/esm/src/script/OP.js.map +1 -1
  66. package/dist/esm/src/script/Script.js +4 -1
  67. package/dist/esm/src/script/Script.js.map +1 -1
  68. package/dist/esm/src/script/Spend.js +129 -46
  69. package/dist/esm/src/script/Spend.js.map +1 -1
  70. package/dist/esm/src/transaction/BeefTx.js +3 -3
  71. package/dist/esm/src/transaction/BeefTx.js.map +1 -1
  72. package/dist/esm/src/transaction/Transaction.js +160 -0
  73. package/dist/esm/src/transaction/Transaction.js.map +1 -1
  74. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  75. package/dist/types/mod.d.ts +1 -0
  76. package/dist/types/mod.d.ts.map +1 -1
  77. package/dist/types/src/auth/Peer.d.ts +3 -7
  78. package/dist/types/src/auth/Peer.d.ts.map +1 -1
  79. package/dist/types/src/identity/IdentityClient.d.ts +0 -8
  80. package/dist/types/src/identity/IdentityClient.d.ts.map +1 -1
  81. package/dist/types/src/primitives/TransactionSignature.d.ts +16 -4
  82. package/dist/types/src/primitives/TransactionSignature.d.ts.map +1 -1
  83. package/dist/types/src/primitives/utils.d.ts +1 -0
  84. package/dist/types/src/primitives/utils.d.ts.map +1 -1
  85. package/dist/types/src/remittance/CommsLayer.d.ts +50 -0
  86. package/dist/types/src/remittance/CommsLayer.d.ts.map +1 -0
  87. package/dist/types/src/remittance/IdentityLayer.d.ts +35 -0
  88. package/dist/types/src/remittance/IdentityLayer.d.ts.map +1 -0
  89. package/dist/types/src/remittance/RemittanceManager.d.ts +452 -0
  90. package/dist/types/src/remittance/RemittanceManager.d.ts.map +1 -0
  91. package/dist/types/src/remittance/RemittanceModule.d.ts +106 -0
  92. package/dist/types/src/remittance/RemittanceModule.d.ts.map +1 -0
  93. package/dist/types/src/remittance/index.d.ts +7 -0
  94. package/dist/types/src/remittance/index.d.ts.map +1 -0
  95. package/dist/types/src/remittance/modules/BasicBRC29.d.ts +133 -0
  96. package/dist/types/src/remittance/modules/BasicBRC29.d.ts.map +1 -0
  97. package/dist/types/src/remittance/modules/index.d.ts +2 -0
  98. package/dist/types/src/remittance/modules/index.d.ts.map +1 -0
  99. package/dist/types/src/remittance/types.d.ts +238 -0
  100. package/dist/types/src/remittance/types.d.ts.map +1 -0
  101. package/dist/types/src/script/OP.d.ts +5 -3
  102. package/dist/types/src/script/OP.d.ts.map +1 -1
  103. package/dist/types/src/script/Script.d.ts.map +1 -1
  104. package/dist/types/src/script/Spend.d.ts +7 -0
  105. package/dist/types/src/script/Spend.d.ts.map +1 -1
  106. package/dist/types/src/transaction/BeefTx.d.ts +2 -2
  107. package/dist/types/src/transaction/Transaction.d.ts +14 -0
  108. package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
  109. package/dist/types/src/wallet/Wallet.interfaces.d.ts +5 -5
  110. package/dist/types/src/wallet/Wallet.interfaces.d.ts.map +1 -1
  111. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  112. package/dist/umd/bundle.js +13 -13
  113. package/dist/umd/bundle.js.map +1 -1
  114. package/docs/index.md +2 -14
  115. package/docs/reference/auth.md +6 -12
  116. package/docs/reference/primitives.md +20 -78
  117. package/docs/reference/remittance.md +2166 -0
  118. package/docs/reference/script.md +11 -3
  119. package/docs/reference/transaction.md +27 -1
  120. package/docs/reference/wallet.md +6 -5
  121. package/docs/remittance-getting-started.md +138 -0
  122. package/mod.ts +1 -0
  123. package/package.json +12 -3
  124. package/src/auth/Peer.ts +18 -29
  125. package/src/auth/__tests/Peer.test.ts +253 -1
  126. package/src/identity/IdentityClient.ts +29 -153
  127. package/src/identity/__tests/IdentityClient.test.ts +1 -289
  128. package/src/overlay-tools/__tests/SHIPBroadcaster.test.ts +7 -9
  129. package/src/primitives/TransactionSignature.ts +129 -10
  130. package/src/primitives/__tests/utils.test.ts +30 -7
  131. package/src/primitives/utils.ts +13 -129
  132. package/src/remittance/CommsLayer.ts +41 -0
  133. package/src/remittance/IdentityLayer.ts +32 -0
  134. package/src/remittance/RemittanceManager.ts +1672 -0
  135. package/src/remittance/RemittanceModule.ts +92 -0
  136. package/src/remittance/__tests/BasicBRC29.test.ts +188 -0
  137. package/src/remittance/__tests/RemittanceManager.test.ts +493 -0
  138. package/src/remittance/__tests/examples.ts +130 -0
  139. package/src/remittance/index.ts +6 -0
  140. package/src/remittance/modules/BasicBRC29.ts +361 -0
  141. package/src/remittance/modules/index.ts +1 -0
  142. package/src/remittance/types.ts +284 -0
  143. package/src/script/OP.ts +15 -13
  144. package/src/script/Script.ts +3 -1
  145. package/src/script/Spend.ts +128 -52
  146. package/src/script/__tests/Chronicle.test.ts +186 -0
  147. package/src/script/__tests/Spend.test.ts +1 -1
  148. package/src/script/__tests/SpendValildVectors.test.ts +63 -0
  149. package/src/script/__tests/lrshiftnum.test.ts +185 -0
  150. package/src/script/__tests/sighashTestData.ts +1031 -0
  151. package/src/script/__tests/spend.valid.vectors.ts +9 -16
  152. package/src/transaction/BeefTx.ts +3 -3
  153. package/src/transaction/Transaction.ts +186 -0
  154. package/src/transaction/__tests/Beef.test.ts +2 -0
  155. package/src/transaction/__tests/Transaction.test.ts +641 -3
  156. package/src/wallet/Wallet.interfaces.ts +5 -5
@@ -356,11 +356,13 @@ export default class Script {
356
356
  findAndDelete (script: Script): Script {
357
357
  this.invalidateSerializationCaches()
358
358
  const buf = script.toHex()
359
- for (let i = 0; i < this.chunks.length; i++) {
359
+ for (let i = 0; i < this.chunks.length;) {
360
360
  const script2 = new Script([this.chunks[i]])
361
361
  const buf2 = script2.toHex()
362
362
  if (buf === buf2) {
363
363
  this.chunks.splice(i, 1)
364
+ } else {
365
+ i++
364
366
  }
365
367
  }
366
368
  return this
@@ -17,10 +17,6 @@ import TransactionOutput from '../transaction/TransactionOutput.js'
17
17
  const maxScriptElementSize = 1024 * 1024 * 1024
18
18
  const maxMultisigKeyCount = Math.pow(2, 31) - 1
19
19
  const maxMultisigKeyCountBigInt = BigInt(maxMultisigKeyCount)
20
- const requireMinimalPush = true
21
- const requirePushOnlyUnlockingScripts = true
22
- const requireLowSSignatures = true
23
- const requireCleanStack = true
24
20
 
25
21
  // --- Optimization: Pre-computed script numbers ---
26
22
  const SCRIPTNUM_NEG_1 = Object.freeze(new BigNumber(-1).toScriptNum())
@@ -89,16 +85,6 @@ function isChecksigFormatHelper (buf: Readonly<number[]>): boolean {
89
85
  return true
90
86
  }
91
87
 
92
- function isOpcodeDisabledHelper (op: number): boolean {
93
- return (
94
- op === OP.OP_2MUL ||
95
- op === OP.OP_2DIV ||
96
- op === OP.OP_VERIF ||
97
- op === OP.OP_VERNOTIF ||
98
- op === OP.OP_VER
99
- )
100
- }
101
-
102
88
  function isChunkMinimalPushHelper (chunk: ScriptChunk): boolean {
103
89
  const data = chunk.data
104
90
  const op = chunk.op
@@ -130,6 +116,8 @@ function isChunkMinimalPushHelper (chunk: ScriptChunk): boolean {
130
116
  * @property {UnlockingScript} unlockingScript - The unlocking script that unlocks the UTXO for spending.
131
117
  * @property {number} inputSequence - The sequence number of this input.
132
118
  * @property {number} lockTime - The lock time of the transaction.
119
+ * @property {number} memoryLimit - Control over script interpreter memory usage.
120
+ * @property {boolean} isRelaxed - Optional. If true, disables all the unlocking script maleability restrictions consitent with Chronicle release. Maleability restrictions are neve appliced to locking scripts.
133
121
  */
134
122
  export default class Spend {
135
123
  sourceTXID: string
@@ -153,6 +141,8 @@ export default class Spend {
153
141
  memoryLimit: number
154
142
  stackMem: number
155
143
  altStackMem: number
144
+ isRelaxedOverride: boolean
145
+
156
146
  private sigHashCache: SignatureHashCache
157
147
 
158
148
  /**
@@ -171,6 +161,8 @@ export default class Spend {
171
161
  * @param {UnlockingScript} params.unlockingScript - The unlocking script for this spend.
172
162
  * @param {number} params.inputSequence - The sequence number of this input.
173
163
  * @param {number} params.lockTime - The lock time of the transaction.
164
+ * @param {number} params.memoryLimit - Optional control over script interpreter memory usage.
165
+ * @param {boolean} params.isRelaxed - Optional. If true, disables all the unlocking script maleability restrictions consitent with Chronicle release. Maleability restrictions are neve appliced to locking scripts.
174
166
  *
175
167
  * @example
176
168
  * const spend = new Spend({
@@ -200,6 +192,7 @@ export default class Spend {
200
192
  inputIndex: number
201
193
  lockTime: number
202
194
  memoryLimit?: number
195
+ isRelaxed?: boolean
203
196
  }) {
204
197
  this.sourceTXID = params.sourceTXID
205
198
  this.sourceOutputIndex = params.sourceOutputIndex
@@ -213,6 +206,7 @@ export default class Spend {
213
206
  this.inputSequence = params.inputSequence
214
207
  this.lockTime = params.lockTime
215
208
  this.memoryLimit = params.memoryLimit ?? 32000000
209
+ this.isRelaxedOverride = params.isRelaxed === true
216
210
  this.stack = []
217
211
  this.altStack = []
218
212
  this.ifStack = []
@@ -222,6 +216,11 @@ export default class Spend {
222
216
  this.reset()
223
217
  }
224
218
 
219
+ private isRelaxed (): boolean {
220
+ return this.isRelaxedOverride ||
221
+ (this.transactionVersion > 1)
222
+ }
223
+
225
224
  reset (): void {
226
225
  this.context = 'UnlockingScript'
227
226
  this.programCounter = 0
@@ -305,14 +304,10 @@ export default class Spend {
305
304
  }
306
305
  try {
307
306
  const sig = TransactionSignature.fromChecksigFormat(buf as number[]) // This can throw for stricter DER rules
308
- if (requireLowSSignatures && !sig.hasLowS()) {
307
+ if (!this.isRelaxed() && !sig.hasLowS()) {
309
308
  this.scriptEvaluationError('The signature must have a low S value.')
310
309
  return false
311
310
  }
312
- if ((sig.scope & TransactionSignature.SIGHASH_FORKID) === 0) {
313
- this.scriptEvaluationError('The signature must use SIGHASH_FORKID.')
314
- return false
315
- }
316
311
  } catch (e) {
317
312
  this.scriptEvaluationError('The signature format is invalid.')
318
313
  return false
@@ -409,12 +404,8 @@ export default class Spend {
409
404
 
410
405
  const isScriptExecuting = !this.ifStack.includes(false)
411
406
 
412
- if (isScriptExecuting && isOpcodeDisabledHelper(currentOpcode)) {
413
- this.scriptEvaluationError(`This opcode is currently disabled. (Opcode: ${OP[currentOpcode] as string}, PC: ${this.programCounter})`) // Error thrown
414
- }
415
-
416
407
  if (isScriptExecuting && currentOpcode >= 0 && currentOpcode <= OP.OP_PUSHDATA4) {
417
- if (requireMinimalPush && !isChunkMinimalPushHelper(operation)) {
408
+ if (!this.isRelaxed() && !isChunkMinimalPushHelper(operation)) {
418
409
  this.scriptEvaluationError(`This data is not minimally-encoded. (PC: ${this.programCounter})`) // Error thrown
419
410
  }
420
411
  this.pushStack(Array.isArray(operation.data) ? operation.data : [])
@@ -428,6 +419,77 @@ export default class Spend {
428
419
  let i: number, ikey: number, isig: number, nKeysCount: number, nSigsCount: number, fOk: boolean
429
420
 
430
421
  switch (currentOpcode) {
422
+ case OP.OP_VER:
423
+ this.pushStackCopy(new BigNumber(this.transactionVersion).toScriptNum())
424
+ break
425
+ case OP.OP_SUBSTR: {
426
+ if (this.stack.length < 3) this.scriptEvaluationError('OP_SUBSTR requires at least three items to be on the stack.')
427
+ const len = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toNumber()
428
+ const offset = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toNumber()
429
+ buf = this.popStack()
430
+ const size = buf.length
431
+
432
+ if (offset < 0 || offset >= size || len < 0 || len > size - offset) {
433
+ this.scriptEvaluationError(`OP_SUBSTR offset (${offset}) must be in range [0, ${size}) and length (${len}) must be in range [0, ${size - offset}]`)
434
+ }
435
+
436
+ this.pushStack(buf.slice(offset, offset + len))
437
+ break
438
+ }
439
+ case OP.OP_LEFT: {
440
+ if (this.stack.length < 2) this.scriptEvaluationError('OP_LEFT requires at least two items to be on the stack.')
441
+ const len = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toNumber()
442
+ buf = this.popStack()
443
+ const size = buf.length
444
+
445
+ if (len < 0 || len > size) {
446
+ this.scriptEvaluationError(`OP_LEFT length (${len}) must be in range [0, ${size}]`)
447
+ }
448
+
449
+ this.pushStack(buf.slice(0, len))
450
+ break
451
+ }
452
+ case OP.OP_RIGHT: {
453
+ if (this.stack.length < 2) this.scriptEvaluationError('OP_RIGHT requires at least two items to be on the stack.')
454
+ const len = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toNumber()
455
+ buf = this.popStack()
456
+ const size = buf.length
457
+
458
+ if (len < 0 || len > size) {
459
+ this.scriptEvaluationError(`OP_RIGHT length (${len}) must be in range [0, ${size}]`)
460
+ }
461
+
462
+ this.pushStack(buf.slice(size - len, len))
463
+ break
464
+ }
465
+ case OP.OP_LSHIFTNUM: {
466
+ if (this.stack.length < 2) this.scriptEvaluationError('OP_LSHIFTNUM requires at least two items to be on the stack.')
467
+ const bits = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt()
468
+ if (bits < 0) {
469
+ this.scriptEvaluationError('OP_LSHIFTNUM bits to shift must not be negative.')
470
+ }
471
+ const value = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt()
472
+ const resultBn = new BigNumber(value << bits)
473
+ this.pushStack(resultBn.toScriptNum())
474
+ break
475
+ }
476
+ case OP.OP_RSHIFTNUM: {
477
+ if (this.stack.length < 2) this.scriptEvaluationError('OP_RSHIFTNUM requires at least two items to be on the stack.')
478
+ const bits = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt()
479
+ if (bits < 0) {
480
+ this.scriptEvaluationError('OP_RSHIFTNUM bits to shift must not be negative.')
481
+ }
482
+ const value = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt()
483
+ let resultBn: BigNumber
484
+ if (value < 0) {
485
+ resultBn = new BigNumber(-(-value >> bits))
486
+ } else {
487
+ resultBn = new BigNumber(value >> bits)
488
+ }
489
+ this.pushStack(resultBn.toScriptNum())
490
+ break
491
+ }
492
+
431
493
  case OP.OP_1NEGATE: this.pushStackCopy(SCRIPTNUM_NEG_1); break
432
494
  case OP.OP_0: this.pushStackCopy(SCRIPTNUMS_0_TO_16[0]); break
433
495
  case OP.OP_1: case OP.OP_2: case OP.OP_3: case OP.OP_4:
@@ -439,18 +501,17 @@ export default class Spend {
439
501
  break
440
502
 
441
503
  case OP.OP_NOP:
442
- case OP.OP_NOP2: // Formerly CHECKLOCKTIMEVERIFY
443
- case OP.OP_NOP3: // Formerly CHECKSEQUENCEVERIFY
444
504
  case OP.OP_NOP1:
445
- case OP.OP_NOP4:
446
- case OP.OP_NOP5:
447
- case OP.OP_NOP6:
505
+ case OP.OP_NOP2:
506
+ case OP.OP_NOP3:
507
+ // case OP.OP_NOP4: // Allocated to OP_SUBSTR
508
+ // case OP.OP_NOP5: // Allocated to OP_LEFT
509
+ // case OP.OP_NOP6: // Allocated to OP_RIGHT
510
+ // eslint-disable-next-line no-fallthrough
448
511
  case OP.OP_NOP7:
449
512
  case OP.OP_NOP8:
450
513
  case OP.OP_NOP9:
451
514
  case OP.OP_NOP10:
452
- /* falls through */
453
- // eslint-disable-next-line no-fallthrough
454
515
  // eslint-disable-next-line no-fallthrough
455
516
  case OP.OP_NOP11: case OP.OP_NOP12: case OP.OP_NOP13: case OP.OP_NOP14: case OP.OP_NOP15:
456
517
  case OP.OP_NOP16: case OP.OP_NOP17: case OP.OP_NOP18: case OP.OP_NOP19: case OP.OP_NOP20:
@@ -468,6 +529,18 @@ export default class Spend {
468
529
  case OP.OP_NOP77:
469
530
  break
470
531
 
532
+ case OP.OP_VERIF:
533
+ case OP.OP_VERNOTIF:
534
+ fValue = false
535
+ if (isScriptExecuting) {
536
+ if (this.stack.length < 1) this.scriptEvaluationError('OP_VERIF and OP_VERNOTIF require at least one item on the stack when they are used!')
537
+ buf2 = new BigNumber(this.transactionVersion).toScriptNum()
538
+ buf1 = this.popStack()
539
+ fValue = compareNumberArrays(buf1, buf2)
540
+ if (currentOpcode === OP.OP_VERNOTIF) fValue = !fValue
541
+ }
542
+ this.ifStack.push(fValue)
543
+ break
471
544
  case OP.OP_IF:
472
545
  case OP.OP_NOTIF:
473
546
  fValue = false
@@ -581,7 +654,7 @@ export default class Spend {
581
654
  case OP.OP_PICK:
582
655
  case OP.OP_ROLL: {
583
656
  if (this.stack.length < 2) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least two items to be on the stack.`)
584
- bn = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush)
657
+ bn = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed())
585
658
  const nBigInt = bn.toBigInt()
586
659
  if (nBigInt < 0n || nBigInt >= BigInt(this.stack.length)) {
587
660
  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.`)
@@ -654,7 +727,7 @@ export default class Spend {
654
727
  case OP.OP_LSHIFT:
655
728
  case OP.OP_RSHIFT: {
656
729
  if (this.stack.length < 2) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least two items to be on the stack.`)
657
- bn2 = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush) // n (shift amount)
730
+ bn2 = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()) // n (shift amount)
658
731
  buf1 = this.popStack() // value to shift
659
732
  const shiftBits = bn2.toBigInt()
660
733
  if (shiftBits < 0n) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires the top item on the stack not to be negative.`)
@@ -684,14 +757,16 @@ export default class Spend {
684
757
  }
685
758
  break
686
759
 
687
- case OP.OP_1ADD: case OP.OP_1SUB:
760
+ case OP.OP_1ADD: case OP.OP_1SUB: case OP.OP_2MUL: case OP.OP_2DIV:
688
761
  case OP.OP_NEGATE: case OP.OP_ABS:
689
762
  case OP.OP_NOT: case OP.OP_0NOTEQUAL:
690
763
  if (this.stack.length < 1) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least one item to be on the stack.`)
691
- bn = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush)
764
+ bn = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed())
692
765
  switch (currentOpcode) {
693
766
  case OP.OP_1ADD: bn = bn.add(new BigNumber(1)); break
694
767
  case OP.OP_1SUB: bn = bn.sub(new BigNumber(1)); break
768
+ case OP.OP_2MUL: bn = bn.mul(new BigNumber(2)); break
769
+ case OP.OP_2DIV: bn = bn.div(new BigNumber(2)); break
695
770
  case OP.OP_NEGATE: bn = bn.neg(); break
696
771
  case OP.OP_ABS: if (bn.isNeg()) bn = bn.neg(); break
697
772
  case OP.OP_NOT: bn = new BigNumber(bn.cmpn(0) === 0 ? 1 : 0); break
@@ -708,8 +783,8 @@ export default class Spend {
708
783
  if (this.stack.length < 2) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least two items to be on the stack.`)
709
784
  buf2 = this.popStack()
710
785
  buf1 = this.popStack()
711
- bn2 = BigNumber.fromScriptNum(buf2, requireMinimalPush)
712
- bn1 = BigNumber.fromScriptNum(buf1, requireMinimalPush)
786
+ bn2 = BigNumber.fromScriptNum(buf2, !this.isRelaxed())
787
+ bn1 = BigNumber.fromScriptNum(buf1, !this.isRelaxed())
713
788
  let predictedLen = 0
714
789
  switch (currentOpcode) {
715
790
  case OP.OP_MUL:
@@ -755,9 +830,9 @@ export default class Spend {
755
830
  }
756
831
  case OP.OP_WITHIN:
757
832
  if (this.stack.length < 3) this.scriptEvaluationError('OP_WITHIN requires at least three items to be on the stack.')
758
- bn3 = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush) // max
759
- bn2 = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush) // min
760
- bn1 = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush) // x
833
+ bn3 = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()) // max
834
+ bn2 = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()) // min
835
+ bn1 = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()) // x
761
836
  fValue = bn1.cmp(bn2) >= 0 && bn1.cmp(bn3) < 0
762
837
  this.pushStack(fValue ? [1] : [])
763
838
  break
@@ -789,15 +864,16 @@ export default class Spend {
789
864
  this.scriptEvaluationError(`${OP[currentOpcode] as string} requires correct encoding for the public key and signature.`) // Fallback, should be unreachable
790
865
  }
791
866
 
792
- const scriptForChecksig = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript
793
- const scriptCodeChunks = scriptForChecksig.chunks.slice(this.lastCodeSeparator === null ? 0 : this.lastCodeSeparator + 1)
794
- subscript = new Script(scriptCodeChunks)
795
- subscript.findAndDelete(new Script().writeBin(bufSig))
796
-
797
867
  fSuccess = false
798
868
  if (bufSig.length > 0) {
799
869
  try {
800
870
  sig = TransactionSignature.fromChecksigFormat(bufSig)
871
+
872
+ const scriptForChecksig: Script = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript
873
+ const scriptCodeChunks = scriptForChecksig.chunks.slice(this.lastCodeSeparator === null ? 0 : this.lastCodeSeparator + 1)
874
+ subscript = new Script(scriptCodeChunks)
875
+ subscript.findAndDelete(new Script().writeBin(bufSig))
876
+
801
877
  pubkey = PublicKey.fromDER(bufPubkey)
802
878
  fSuccess = this.verifySignature(sig, pubkey, subscript)
803
879
  } catch (e) {
@@ -819,7 +895,7 @@ export default class Spend {
819
895
  this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least 1 item for nKeys.`)
820
896
  }
821
897
 
822
- const nKeysCountBN = BigNumber.fromScriptNum(this.stackTop(-i), requireMinimalPush)
898
+ const nKeysCountBN = BigNumber.fromScriptNum(this.stackTop(-i), !this.isRelaxed())
823
899
  const nKeysCountBigInt = nKeysCountBN.toBigInt()
824
900
  if (nKeysCountBigInt < 0n || nKeysCountBigInt > maxMultisigKeyCountBigInt) {
825
901
  this.scriptEvaluationError(`${OP[currentOpcode] as string} requires a key count between 0 and ${maxMultisigKeyCount}.`)
@@ -833,7 +909,7 @@ export default class Spend {
833
909
  this.scriptEvaluationError(`${OP[currentOpcode] as string} stack too small for nKeys and keys. Need ${i}, have ${this.stack.length}.`)
834
910
  }
835
911
 
836
- const nSigsCountBN = BigNumber.fromScriptNum(this.stackTop(-i), requireMinimalPush)
912
+ const nSigsCountBN = BigNumber.fromScriptNum(this.stackTop(-i), !this.isRelaxed())
837
913
  const nSigsCountBigInt = nSigsCountBN.toBigInt()
838
914
  if (nSigsCountBigInt < 0n || nSigsCountBigInt > BigInt(nKeysCount)) {
839
915
  this.scriptEvaluationError(`${OP[currentOpcode] as string} requires the number of signatures to be no greater than the number of keys.`)
@@ -907,7 +983,7 @@ export default class Spend {
907
983
  this.scriptEvaluationError(`${OP[currentOpcode] as string} requires an extra item (dummy) to be on the stack.`)
908
984
  }
909
985
  const dummyBuf = this.popStack()
910
- if (dummyBuf.length > 0) { // SCRIPT_VERIFY_NULLDUMMY
986
+ if (!this.isRelaxed() && dummyBuf.length > 0) { // SCRIPT_VERIFY_NULLDUMMY
911
987
  this.scriptEvaluationError(`${OP[currentOpcode] as string} requires the extra stack item (dummy) to be empty.`)
912
988
  }
913
989
 
@@ -933,7 +1009,7 @@ export default class Spend {
933
1009
  const posBuf = this.popStack()
934
1010
  const dataToSplit = this.popStack()
935
1011
 
936
- const splitIndexBigInt = BigNumber.fromScriptNum(posBuf, requireMinimalPush).toBigInt()
1012
+ const splitIndexBigInt = BigNumber.fromScriptNum(posBuf, !this.isRelaxed()).toBigInt()
937
1013
  if (splitIndexBigInt < 0n || splitIndexBigInt > BigInt(dataToSplit.length)) {
938
1014
  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.')
939
1015
  }
@@ -946,7 +1022,7 @@ export default class Spend {
946
1022
  case OP.OP_NUM2BIN: {
947
1023
  if (this.stack.length < 2) this.scriptEvaluationError('OP_NUM2BIN requires at least two items to be on the stack.')
948
1024
 
949
- const sizeBigInt = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush).toBigInt()
1025
+ const sizeBigInt = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt()
950
1026
  if (sizeBigInt > BigInt(maxScriptElementSize) || sizeBigInt < 0n) { // size can be 0
951
1027
  this.scriptEvaluationError(`It's not currently possible to push data larger than ${maxScriptElementSize} bytes or negative size.`)
952
1028
  }
@@ -1016,7 +1092,7 @@ export default class Spend {
1016
1092
  * }
1017
1093
  */
1018
1094
  validate (): boolean {
1019
- if (requirePushOnlyUnlockingScripts && !this.unlockingScript.isPushOnly()) {
1095
+ if (!this.isRelaxed() && !this.unlockingScript.isPushOnly()) {
1020
1096
  this.scriptEvaluationError(
1021
1097
  'Unlocking scripts can only contain push operations, and no other opcodes.'
1022
1098
  )
@@ -1037,7 +1113,7 @@ export default class Spend {
1037
1113
  )
1038
1114
  }
1039
1115
 
1040
- if (requireCleanStack) {
1116
+ if (!this.isRelaxed()) {
1041
1117
  if (this.stack.length !== 1) {
1042
1118
  this.scriptEvaluationError(
1043
1119
  `The clean stack rule requires exactly one item to be on the stack after script execution, found ${this.stack.length}.`
@@ -0,0 +1,186 @@
1
+ import TransactionSignature from '../../primitives/TransactionSignature.js'
2
+ import { Hash, Utils } from '../../primitives/index.js'
3
+ import Script from '../Script.js'
4
+ import Spend from '../Spend.js'
5
+ import Transaction from '../../transaction/Transaction.js'
6
+ import { sighashTestData, sighashTestDataWip } from './sighashTestData.js'
7
+
8
+ describe('Chronicle Tests', () => {
9
+
10
+ it('spend bip143 input', () => {
11
+ const rawTx =
12
+ '0100000001cc5988c346027ca1f6870422616d022caf216f537f3d4994f7a7d46a8bf0758a020000006b483045022100f1c0bfe5e7c9c5559ede2b1e2b98aeaac295218406df1d66af397f615350599202201ad5d39c6706e1cc5cdc297ee5cd5d5f79d237a1c4b16c5b4646c284f2eeeabe41210245c6e32afad67f6177b02cfc2878fce2a28e77ad9ecbc6356960c020c592d867ffffffff0302000000000000001976a914a4429da7462800dedc7b03a4fc77c363b8de40f588ac000000000000000020006a4c1c507573682d5468652d427574746f6e2e617070207c204c616d6264610a32f201000000001976a914296b03a4dd56b3b0fe5706c845f2edff22e84d7388ac00000000'
13
+ const lockingScript =
14
+ '76a914296b03a4dd56b3b0fe5706c845f2edff22e84d7388ac'
15
+ const ok = validateUnlockScript(rawTx, 0, lockingScript, 32649741)
16
+ expect(ok).toBe(true)
17
+ // preimage 182 byes
18
+ // "010000002987eea6ff44b0a0c38a4c81cc92c052f6bcb7e3cfa2f59e3af1f7a73433a6fc3bb13029ce7b1f559ef5e747fcac439f1455a2ec7c5f09b72290795e70665044cc5988c346027ca1f6870422616d022caf216f537f3d4994f7a7d46a8bf0758a020000001976a914296b03a4dd56b3b0fe5706c845f2edff22e84d7388ac0d32f20100000000ffffffffa4cd3e7fef24c9653d7a12c404e428b5bb9ac1c5d3a9ddc5a4b9331536df94d50000000041000000"
19
+ // sighash
20
+ // "f40a770ed6f1952973387579d7c7d14719d2bdce871fb449b6fa3d5cca35fbcf"
21
+ })
22
+
23
+ it('spend OTDA', () => {
24
+ const rawTx =
25
+ // block 300000, txid 7301b595279ece985f0c415e420e425451fcf7f684fcce087ba14d10ffec1121
26
+ '01000000014dff4050dcee16672e48d755c6dd25d324492b5ea306f85a3ab23b4df26e16e9000000008c493046022100cb6dc911ef0bae0ab0e6265a45f25e081fc7ea4975517c9f848f82bc2b80a909022100e30fb6bb4fb64f414c351ed3abaed7491b8f0b1b9bcd75286036df8bfabc3ea5014104b70574006425b61867d2cbb8de7c26095fbc00ba4041b061cf75b85699cb2b449c6758741f640adffa356406632610efb267cb1efa0442c207059dd7fd652eeaffffffff020049d971020000001976a91461cf5af7bb84348df3fd695672e53c7d5b3f3db988ac30601c0c060000001976a914fd4ed114ef85d350d6d40ed3f6dc23743f8f99c488ac00000000'
27
+ const lockingScript =
28
+ // input sourceTxid e9166ef24d3bb23a5af806a35e2b4924d325ddc655d7482e6716eedc5040ff4d vout 0
29
+ // 'OP_DUP OP_HASH160 5478d152bb557ac994c9793cece77d4295ed37e3 OP_EQUALVERIFY OP_CHECKSIG'
30
+ '76a9145478d152bb557ac994c9793cece77d4295ed37e388ac'
31
+ const ok = validateUnlockScript(rawTx, 0, lockingScript, 36473000000, true)
32
+ expect(ok).toBe(true)
33
+ // preimage 148 bytes
34
+ // "01000000014dff4050dcee16672e48d755c6dd25d324492b5ea306f85a3ab23b4df26e16e9000000001976a9145478d152bb557ac994c9793cece77d4295ed37e388acffffffff020049d971020000001976a91461cf5af7bb84348df3fd695672e53c7d5b3f3db988ac30601c0c060000001976a914fd4ed114ef85d350d6d40ed3f6dc23743f8f99c488ac0000000001000000"
35
+ // sighash
36
+ // "bdf9e0f5600d64bf3754919bd181f5e3848440ef7b351150abcb1ca3a54d3eb9",
37
+ })
38
+
39
+ /**
40
+ * At present only the first two test vectors added as real transaction (pre and post fork) compute a sighash known to match a valid signagure.
41
+ * The remaining test vectors come from bitcoin-sv-staging repo as of 2025-06-23, but appear to be at least somewhat randomly generated.
42
+ * The "scope" (SigHashType) values are all over the place.
43
+ * Working with Teranode team to determine correct preimage values...
44
+ */
45
+ it('sighashTestData', () => {
46
+ let log = ''
47
+ let i = -1
48
+ for (const t of sighashTestData) {
49
+ i++
50
+ const tx = Transaction.fromHex(t.rawTxHex)
51
+ const script = Script.fromHex(t.scriptHex)
52
+ const input = tx.inputs[t.inputIndex]
53
+ if (input.unlockingScript?.chunks.length != 2)
54
+ continue;
55
+ const otherInputs = [...tx.inputs]
56
+ otherInputs.splice(t.inputIndex, 1)
57
+ const params = {
58
+ sourceTXID: input.sourceTXID!,
59
+ sourceOutputIndex: input.sourceOutputIndex,
60
+ sourceSatoshis: t.satoshis,
61
+ transactionVersion: tx.version,
62
+ otherInputs,
63
+ outputs: tx.outputs,
64
+ inputIndex: t.inputIndex,
65
+ subscript: Script.fromHex(t.scriptHex),
66
+ inputSequence: input.sequence ?? 0xffffffff, // Default to max sequence number
67
+ lockTime: tx.lockTime,
68
+ scope: t.hashType,
69
+ }
70
+ let ok = false
71
+ let okReg = false
72
+ let okOTDA = false
73
+ {
74
+ const sighash = t.sighashReg
75
+ const buf = TransactionSignature.format(params)
76
+ const ret = Utils.toHex(Hash.hash256(buf).reverse())
77
+ if (ret === sighash) {
78
+ ok = true
79
+ okReg = true
80
+ }
81
+ if (!okReg)
82
+ log += `${i+1} Reg ${okReg} ${!okReg ? ret : ''}\n`
83
+ }
84
+ {
85
+ const sighash = t.sighashOTDA
86
+ const buf = TransactionSignature.formatOTDA(params)
87
+ const ret = Utils.toHex(Hash.hash256(buf).reverse())
88
+ if (ret === sighash) {
89
+ ok = true
90
+ okOTDA = true
91
+ }
92
+ if (!okOTDA)
93
+ log += `${i+1} OTDA ${okOTDA} ${!okOTDA ? ret : ''}\n`
94
+ }
95
+ }
96
+ //console.log(log)
97
+ //expect(log).toBe('')
98
+ })
99
+
100
+ it('wip sighashTestData', () => {
101
+ let log = ''
102
+ let i = -1
103
+ for (const t of sighashTestDataWip) {
104
+ i++
105
+ const tx = Transaction.fromHex(t.rawTxHex)
106
+ const script = Script.fromHex(t.scriptHex)
107
+ const input = tx.inputs[t.inputIndex]
108
+ const otherInputs = [...tx.inputs]
109
+ otherInputs.splice(t.inputIndex, 1)
110
+ const params = {
111
+ sourceTXID: input.sourceTXID!,
112
+ sourceOutputIndex: input.sourceOutputIndex,
113
+ sourceSatoshis: t.satoshis,
114
+ transactionVersion: tx.version,
115
+ otherInputs,
116
+ outputs: tx.outputs,
117
+ inputIndex: t.inputIndex,
118
+ subscript: Script.fromHex(t.scriptHex),
119
+ inputSequence: input.sequence ?? 0xffffffff, // Default to max sequence number
120
+ lockTime: tx.lockTime,
121
+ scope: t.hashType,
122
+ }
123
+ let ok = false
124
+ let okReg = false
125
+ let okOTDA = false
126
+ {
127
+ const sighash = t.sighashReg
128
+ const buf = TransactionSignature.format(params)
129
+ const ret = Utils.toHex(Hash.hash256(buf).reverse())
130
+ if (ret === sighash) {
131
+ ok = true
132
+ okReg = true
133
+ }
134
+ if (!okReg)
135
+ log += `${i+1} Reg ${okReg} ${!okReg ? ret : ''}\n`
136
+ }
137
+ {
138
+ const sighash = t.sighashOTDA
139
+ const buf = TransactionSignature.formatOTDA(params)
140
+ const ret = Utils.toHex(Hash.hash256(buf).reverse())
141
+ if (ret === sighash) {
142
+ ok = true
143
+ okOTDA = true
144
+ }
145
+ if (!okOTDA)
146
+ log += `${i+1} OTDA ${okOTDA} ${!okOTDA ? ret : ''}\n`
147
+ }
148
+ }
149
+ console.log(log)
150
+ expect(log).toBe('')
151
+ })
152
+ })
153
+
154
+ function verifyTruthy<T> (v: T | undefined): T {
155
+ if (v == null) throw new Error('must have value')
156
+ return v
157
+ }
158
+
159
+ function validateUnlockScript (
160
+ spendingRawTx: string,
161
+ vin: number,
162
+ lockingScript: string,
163
+ amount: number,
164
+ isRelaxed?: boolean
165
+ ): boolean {
166
+ const spendingTx = Transaction.fromHex(spendingRawTx)
167
+ const ls = Script.fromHex(lockingScript)
168
+
169
+ const spend = new Spend({
170
+ sourceTXID: verifyTruthy(spendingTx.inputs[vin].sourceTXID),
171
+ sourceOutputIndex: verifyTruthy(spendingTx.inputs[vin].sourceOutputIndex),
172
+ sourceSatoshis: amount,
173
+ lockingScript: ls,
174
+ transactionVersion: spendingTx.version,
175
+ otherInputs: spendingTx.inputs.filter((v, i) => i !== vin),
176
+ inputIndex: vin,
177
+ unlockingScript: verifyTruthy(spendingTx.inputs[vin].unlockingScript),
178
+ outputs: spendingTx.outputs,
179
+ inputSequence: verifyTruthy(spendingTx.inputs[vin].sequence),
180
+ lockTime: spendingTx.lockTime,
181
+ isRelaxed
182
+ })
183
+
184
+ const valid = spend.validate()
185
+ return valid
186
+ }
@@ -9,7 +9,7 @@ import LockingScript from '../../script/LockingScript'
9
9
  import UnlockingScript from '../../script/UnlockingScript'
10
10
  import MerklePath from '../../transaction/MerklePath'
11
11
  import ChainTracker from '../../transaction/ChainTracker'
12
- import spendValid from './spend.valid.vectors'
12
+ import { preChronicle as spendValid } from './spend.valid.vectors'
13
13
  import Script from '../../script/Script'
14
14
  import BigNumber from '../../primitives/BigNumber'
15
15
  import ScriptChunk from '../../script/ScriptChunk'
@@ -0,0 +1,63 @@
1
+ import PrivateKey from '../../primitives/PrivateKey'
2
+ import { hash160, hash256 } from '../../primitives/Hash'
3
+ import Curve from '../../primitives/Curve'
4
+ import Spend from '../Spend'
5
+ import P2PKH from '../templates/P2PKH'
6
+ import RPuzzle from '../templates/RPuzzle'
7
+ import Transaction from '../../transaction/Transaction'
8
+ import LockingScript from '../LockingScript'
9
+ import UnlockingScript from '../UnlockingScript'
10
+
11
+ import { preChronicle as spendValid1, chronicle as spendValid2 } from './spend.valid.vectors'
12
+
13
+ describe('Spend Valid Vectors 1', () => {
14
+ for (let i = 0; i < spendValid1.length; i++) {
15
+ const a = spendValid1[i]
16
+ if (a.length === 1) {
17
+ continue
18
+ }
19
+ it(`${i} ${a[2]}`, () => {
20
+ const spend = new Spend({
21
+ sourceTXID:
22
+ '0000000000000000000000000000000000000000000000000000000000000000',
23
+ sourceOutputIndex: 0,
24
+ sourceSatoshis: 1,
25
+ lockingScript: LockingScript.fromHex(a[1]),
26
+ transactionVersion: 1,
27
+ otherInputs: [],
28
+ outputs: [],
29
+ inputIndex: 0,
30
+ unlockingScript: UnlockingScript.fromHex(a[0]),
31
+ inputSequence: 0xffffffff,
32
+ lockTime: 0
33
+ })
34
+ expect(spend.validate()).toBe(true)
35
+ })
36
+ }
37
+ })
38
+
39
+ describe('Spend Valid Vectors 2', () => {
40
+ for (let i = 0; i < spendValid2.length; i++) {
41
+ const a = spendValid2[i]
42
+ if (a.length === 1) {
43
+ continue
44
+ }
45
+ it(`${i} ${a[2]}`, () => {
46
+ const spend = new Spend({
47
+ sourceTXID:
48
+ '0000000000000000000000000000000000000000000000000000000000000000',
49
+ sourceOutputIndex: 0,
50
+ sourceSatoshis: 1,
51
+ lockingScript: LockingScript.fromHex(a[1]),
52
+ transactionVersion: 1,
53
+ otherInputs: [],
54
+ outputs: [],
55
+ inputIndex: 0,
56
+ unlockingScript: UnlockingScript.fromHex(a[0]),
57
+ inputSequence: 0xffffffff,
58
+ lockTime: 0
59
+ })
60
+ expect(spend.validate()).toBe(true)
61
+ })
62
+ }
63
+ })