@bsv/sdk 2.0.7 → 2.0.9

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 (89) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/primitives/index.js.map +1 -1
  3. package/dist/cjs/src/script/templates/P2PKH.js +9 -43
  4. package/dist/cjs/src/script/templates/P2PKH.js.map +1 -1
  5. package/dist/cjs/src/script/templates/PushDrop.js +10 -40
  6. package/dist/cjs/src/script/templates/PushDrop.js.map +1 -1
  7. package/dist/cjs/src/script/templates/SignatureUtils.js +77 -0
  8. package/dist/cjs/src/script/templates/SignatureUtils.js.map +1 -0
  9. package/dist/cjs/src/transaction/Transaction.js +45 -0
  10. package/dist/cjs/src/transaction/Transaction.js.map +1 -1
  11. package/dist/cjs/src/transaction/http/BinaryFetchClient.js +2 -28
  12. package/dist/cjs/src/transaction/http/BinaryFetchClient.js.map +1 -1
  13. package/dist/cjs/src/transaction/http/NodejsHttpClient.js +2 -28
  14. package/dist/cjs/src/transaction/http/NodejsHttpClient.js.map +1 -1
  15. package/dist/cjs/src/transaction/http/NodejsHttpRequestUtils.js +43 -0
  16. package/dist/cjs/src/transaction/http/NodejsHttpRequestUtils.js.map +1 -0
  17. package/dist/cjs/src/wallet/substrates/InvokableWalletBase.js +98 -0
  18. package/dist/cjs/src/wallet/substrates/InvokableWalletBase.js.map +1 -0
  19. package/dist/cjs/src/wallet/substrates/ReactNativeWebView.js +3 -85
  20. package/dist/cjs/src/wallet/substrates/ReactNativeWebView.js.map +1 -1
  21. package/dist/cjs/src/wallet/substrates/XDM.js +3 -85
  22. package/dist/cjs/src/wallet/substrates/XDM.js.map +1 -1
  23. package/dist/cjs/src/wallet/substrates/index.js +3 -1
  24. package/dist/cjs/src/wallet/substrates/index.js.map +1 -1
  25. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  26. package/dist/esm/src/primitives/index.js.map +1 -1
  27. package/dist/esm/src/script/templates/P2PKH.js +10 -46
  28. package/dist/esm/src/script/templates/P2PKH.js.map +1 -1
  29. package/dist/esm/src/script/templates/PushDrop.js +10 -42
  30. package/dist/esm/src/script/templates/PushDrop.js.map +1 -1
  31. package/dist/esm/src/script/templates/SignatureUtils.js +69 -0
  32. package/dist/esm/src/script/templates/SignatureUtils.js.map +1 -0
  33. package/dist/esm/src/transaction/Transaction.js +45 -0
  34. package/dist/esm/src/transaction/Transaction.js.map +1 -1
  35. package/dist/esm/src/transaction/http/BinaryFetchClient.js +2 -28
  36. package/dist/esm/src/transaction/http/BinaryFetchClient.js.map +1 -1
  37. package/dist/esm/src/transaction/http/NodejsHttpClient.js +2 -28
  38. package/dist/esm/src/transaction/http/NodejsHttpClient.js.map +1 -1
  39. package/dist/esm/src/transaction/http/NodejsHttpRequestUtils.js +40 -0
  40. package/dist/esm/src/transaction/http/NodejsHttpRequestUtils.js.map +1 -0
  41. package/dist/esm/src/wallet/substrates/InvokableWalletBase.js +94 -0
  42. package/dist/esm/src/wallet/substrates/InvokableWalletBase.js.map +1 -0
  43. package/dist/esm/src/wallet/substrates/ReactNativeWebView.js +3 -85
  44. package/dist/esm/src/wallet/substrates/ReactNativeWebView.js.map +1 -1
  45. package/dist/esm/src/wallet/substrates/XDM.js +3 -85
  46. package/dist/esm/src/wallet/substrates/XDM.js.map +1 -1
  47. package/dist/esm/src/wallet/substrates/index.js +1 -0
  48. package/dist/esm/src/wallet/substrates/index.js.map +1 -1
  49. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  50. package/dist/types/src/primitives/index.d.ts +1 -1
  51. package/dist/types/src/primitives/index.d.ts.map +1 -1
  52. package/dist/types/src/script/templates/P2PKH.d.ts.map +1 -1
  53. package/dist/types/src/script/templates/PushDrop.d.ts.map +1 -1
  54. package/dist/types/src/script/templates/SignatureUtils.d.ts +32 -0
  55. package/dist/types/src/script/templates/SignatureUtils.d.ts.map +1 -0
  56. package/dist/types/src/transaction/Transaction.d.ts +10 -0
  57. package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
  58. package/dist/types/src/transaction/http/BinaryFetchClient.d.ts +1 -1
  59. package/dist/types/src/transaction/http/BinaryFetchClient.d.ts.map +1 -1
  60. package/dist/types/src/transaction/http/NodejsHttpClient.d.ts +1 -1
  61. package/dist/types/src/transaction/http/NodejsHttpClient.d.ts.map +1 -1
  62. package/dist/types/src/transaction/http/NodejsHttpRequestUtils.d.ts +22 -0
  63. package/dist/types/src/transaction/http/NodejsHttpRequestUtils.d.ts.map +1 -0
  64. package/dist/types/src/wallet/substrates/InvokableWalletBase.d.ts +41 -0
  65. package/dist/types/src/wallet/substrates/InvokableWalletBase.d.ts.map +1 -0
  66. package/dist/types/src/wallet/substrates/ReactNativeWebView.d.ts +2 -403
  67. package/dist/types/src/wallet/substrates/ReactNativeWebView.d.ts.map +1 -1
  68. package/dist/types/src/wallet/substrates/XDM.d.ts +2 -403
  69. package/dist/types/src/wallet/substrates/XDM.d.ts.map +1 -1
  70. package/dist/types/src/wallet/substrates/index.d.ts +1 -0
  71. package/dist/types/src/wallet/substrates/index.d.ts.map +1 -1
  72. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  73. package/dist/umd/bundle.js +3 -3
  74. package/dist/umd/bundle.js.map +1 -1
  75. package/docs/reference/transaction.md +24 -1
  76. package/package.json +1 -1
  77. package/src/primitives/index.ts +1 -1
  78. package/src/script/templates/P2PKH.ts +10 -59
  79. package/src/script/templates/PushDrop.ts +11 -54
  80. package/src/script/templates/SignatureUtils.ts +111 -0
  81. package/src/transaction/Transaction.ts +47 -0
  82. package/src/transaction/__tests/Transaction.test.ts +179 -9
  83. package/src/transaction/http/BinaryFetchClient.ts +8 -32
  84. package/src/transaction/http/NodejsHttpClient.ts +8 -32
  85. package/src/transaction/http/NodejsHttpRequestUtils.ts +68 -0
  86. package/src/wallet/substrates/InvokableWalletBase.ts +176 -0
  87. package/src/wallet/substrates/ReactNativeWebView.ts +3 -491
  88. package/src/wallet/substrates/XDM.ts +3 -491
  89. package/src/wallet/substrates/index.ts +1 -0
@@ -1673,10 +1673,11 @@ export default class Transaction {
1673
1673
  toAtomicBEEF(allowPartial?: boolean): number[]
1674
1674
  toAtomicBEEFUint8Array(allowPartial?: boolean): Uint8Array
1675
1675
  async completeWithWallet(wallet: WalletInterface, actionDescription?: DescriptionString5to50Bytes, originator?: string, options?: CreateActionOptions): Promise<void>
1676
+ preimage(inputIndex?: number, signatureScope?: number, subscript?: LockingScript): number[]
1676
1677
  }
1677
1678
  ```
1678
1679
 
1679
- See also: [BroadcastFailure](./transaction.md#interface-broadcastfailure), [BroadcastResponse](./transaction.md#interface-broadcastresponse), [Broadcaster](./transaction.md#interface-broadcaster), [ChainTracker](./transaction.md#interface-chaintracker), [CreateActionOptions](./wallet.md#interface-createactionoptions), [DescriptionString5to50Bytes](./wallet.md#type-descriptionstring5to50bytes), [FeeModel](./transaction.md#interface-feemodel), [LivePolicy](./transaction.md#class-livepolicy), [MerklePath](./transaction.md#class-merklepath), [Reader](./primitives.md#class-reader), [ReaderUint8Array](./primitives.md#class-readeruint8array), [TransactionInput](./transaction.md#interface-transactioninput), [TransactionOutput](./transaction.md#interface-transactionoutput), [WalletInterface](./wallet.md#interface-walletinterface), [Writer](./primitives.md#class-writer), [WriterUint8Array](./primitives.md#class-writeruint8array), [defaultBroadcaster](./transaction.md#function-defaultbroadcaster), [defaultChainTracker](./transaction.md#function-defaultchaintracker), [sign](./compat.md#variable-sign), [toHex](./primitives.md#variable-tohex), [toUint8Array](./primitives.md#variable-touint8array), [verify](./compat.md#variable-verify)
1680
+ See also: [BroadcastFailure](./transaction.md#interface-broadcastfailure), [BroadcastResponse](./transaction.md#interface-broadcastresponse), [Broadcaster](./transaction.md#interface-broadcaster), [ChainTracker](./transaction.md#interface-chaintracker), [CreateActionOptions](./wallet.md#interface-createactionoptions), [DescriptionString5to50Bytes](./wallet.md#type-descriptionstring5to50bytes), [FeeModel](./transaction.md#interface-feemodel), [LivePolicy](./transaction.md#class-livepolicy), [LockingScript](./script.md#class-lockingscript), [MerklePath](./transaction.md#class-merklepath), [Reader](./primitives.md#class-reader), [ReaderUint8Array](./primitives.md#class-readeruint8array), [TransactionInput](./transaction.md#interface-transactioninput), [TransactionOutput](./transaction.md#interface-transactionoutput), [WalletInterface](./wallet.md#interface-walletinterface), [Writer](./primitives.md#class-writer), [WriterUint8Array](./primitives.md#class-writeruint8array), [defaultBroadcaster](./transaction.md#function-defaultbroadcaster), [defaultChainTracker](./transaction.md#function-defaultchaintracker), [sign](./compat.md#variable-sign), [toHex](./primitives.md#variable-tohex), [toUint8Array](./primitives.md#variable-touint8array), [verify](./compat.md#variable-verify)
1680
1681
 
1681
1682
  #### Method addInput
1682
1683
 
@@ -2033,6 +2034,28 @@ Argument Details
2033
2034
  + **bin**
2034
2035
  + binary transaction data
2035
2036
 
2037
+ #### Method preimage
2038
+
2039
+ Returns the formatted preimage of a transaction for the requested input index, signature scope (default SIGHASH_FORKID | SIGHASH_ALL), and optional subscript.
2040
+
2041
+ ```ts
2042
+ preimage(inputIndex?: number, signatureScope?: number, subscript?: LockingScript): number[]
2043
+ ```
2044
+ See also: [LockingScript](./script.md#class-lockingscript)
2045
+
2046
+ Returns
2047
+
2048
+ The formatted preimage
2049
+
2050
+ Argument Details
2051
+
2052
+ + **inputIndex**
2053
+ + The index of the input to generate the preimage for
2054
+ + **signatureScope**
2055
+ + The signature scope to use for the preimage
2056
+ + **subscript**
2057
+ + The subscript to use for the preimage (optional)
2058
+
2036
2059
  #### Method sign
2037
2060
 
2038
2061
  Signs a transaction, hydrating all its unlocking scripts based on the provided script templates where they are available.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "2.0.7",
3
+ "version": "2.0.9",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -9,7 +9,7 @@ export * as ECDSA from './ECDSA.js'
9
9
  export * as Utils from './utils.js'
10
10
  export * as Hash from './Hash.js'
11
11
  export { default as Random } from './Random.js'
12
- export { default as TransactionSignature } from './TransactionSignature.js'
12
+ export { default as TransactionSignature, type SignatureHashCache } from './TransactionSignature.js'
13
13
  export { default as Polynomial, PointInFiniteField } from './Polynomial.js'
14
14
  export { default as Schnorr } from './Schnorr.js'
15
15
  export { default as Secp256r1 } from './Secp256r1.js'
@@ -1,6 +1,6 @@
1
1
  import OP from '../OP.js'
2
2
  import ScriptTemplate from '../ScriptTemplate.js'
3
- import { fromBase58Check, verifyNotNull } from '../../primitives/utils.js'
3
+ import { fromBase58Check } from '../../primitives/utils.js'
4
4
  import LockingScript from '../LockingScript.js'
5
5
  import UnlockingScript from '../UnlockingScript.js'
6
6
  import Transaction from '../../transaction/Transaction.js'
@@ -8,6 +8,7 @@ import PrivateKey from '../../primitives/PrivateKey.js'
8
8
  import TransactionSignature from '../../primitives/TransactionSignature.js'
9
9
  import { sha256 } from '../../primitives/Hash.js'
10
10
  import Script from '../Script.js'
11
+ import { computeSignatureScope, resolveSourceDetails, formatPreimage } from './SignatureUtils.js'
11
12
 
12
13
  /**
13
14
  * P2PKH (Pay To Public Key Hash) class implementing ScriptTemplate.
@@ -71,65 +72,15 @@ export default class P2PKH implements ScriptTemplate {
71
72
  } {
72
73
  return {
73
74
  sign: async (tx: Transaction, inputIndex: number) => {
74
- let signatureScope = TransactionSignature.SIGHASH_FORKID
75
- if (signOutputs === 'all') {
76
- signatureScope |= TransactionSignature.SIGHASH_ALL
77
- }
78
- if (signOutputs === 'none') {
79
- signatureScope |= TransactionSignature.SIGHASH_NONE
80
- }
81
- if (signOutputs === 'single') {
82
- signatureScope |= TransactionSignature.SIGHASH_SINGLE
83
- }
84
- if (anyoneCanPay) {
85
- signatureScope |= TransactionSignature.SIGHASH_ANYONECANPAY
86
- }
75
+ const signatureScope = computeSignatureScope(signOutputs, anyoneCanPay)
76
+ const resolved = resolveSourceDetails(tx, inputIndex, sourceSatoshis, lockingScript)
77
+ sourceSatoshis = resolved.sourceSatoshis
78
+ lockingScript = resolved.lockingScript
87
79
 
88
- const input = tx.inputs[inputIndex]
89
-
90
- const otherInputs = tx.inputs.filter(
91
- (_, index) => index !== inputIndex
92
- )
93
-
94
- const sourceTXID = input.sourceTXID ?? input.sourceTransaction?.id('hex')
95
- if (sourceTXID == null || sourceTXID === undefined) {
96
- throw new Error(
97
- 'The input sourceTXID or sourceTransaction is required for transaction signing.'
98
- )
99
- }
100
- if (sourceTXID === '') {
101
- throw new Error(
102
- 'The input sourceTXID or sourceTransaction is required for transaction signing.'
103
- )
104
- }
105
- sourceSatoshis ||=
106
- input.sourceTransaction?.outputs[input.sourceOutputIndex].satoshis
107
- if (sourceSatoshis == null || sourceSatoshis === undefined) {
108
- throw new Error(
109
- 'The sourceSatoshis or input sourceTransaction is required for transaction signing.'
110
- )
111
- }
112
- lockingScript ||=
113
- input.sourceTransaction?.outputs[input.sourceOutputIndex]
114
- .lockingScript
115
- if (lockingScript == null) {
116
- throw new Error(
117
- 'The lockingScript or input sourceTransaction is required for transaction signing.'
118
- )
119
- }
120
-
121
- const preimage = TransactionSignature.format({
122
- sourceTXID,
123
- sourceOutputIndex: verifyNotNull(input.sourceOutputIndex, 'input.sourceOutputIndex must have value'),
124
- sourceSatoshis,
125
- transactionVersion: tx.version,
126
- otherInputs,
127
- inputIndex,
128
- outputs: tx.outputs,
129
- inputSequence: verifyNotNull(input.sequence, 'input.sequence must have value'),
130
- subscript: lockingScript,
131
- lockTime: tx.lockTime,
132
- scope: signatureScope
80
+ const preimage = formatPreimage({
81
+ tx, inputIndex, signatureScope,
82
+ sourceTXID: resolved.sourceTXID, sourceSatoshis: resolved.sourceSatoshis,
83
+ lockingScript: resolved.lockingScript, otherInputs: resolved.otherInputs
133
84
  })
134
85
 
135
86
  const rawSignature = privateKey.sign(sha256(preimage))
@@ -9,6 +9,7 @@ import {
9
9
  import { WalletInterface, WalletProtocol } from '../../wallet/Wallet.interfaces.js'
10
10
  import { Transaction } from '../../transaction/index.js'
11
11
  import { verifyNotNull } from '../../primitives/utils.js'
12
+ import { computeSignatureScope, resolveSourceDetails, formatPreimage } from './SignatureUtils.js'
12
13
 
13
14
  /**
14
15
  * For a given piece of data to push onto the stack in script, creates the correct minimally-encoded script chunk,
@@ -216,60 +217,16 @@ export default class PushDrop implements ScriptTemplate {
216
217
  tx: Transaction,
217
218
  inputIndex: number
218
219
  ): Promise<UnlockingScript> => {
219
- let signatureScope = TransactionSignature.SIGHASH_FORKID
220
- if (signOutputs === 'all') {
221
- signatureScope |= TransactionSignature.SIGHASH_ALL
222
- }
223
- if (signOutputs === 'none') {
224
- signatureScope |= TransactionSignature.SIGHASH_NONE
225
- }
226
- if (signOutputs === 'single') {
227
- signatureScope |= TransactionSignature.SIGHASH_SINGLE
228
- }
229
- if (anyoneCanPay) {
230
- signatureScope |= TransactionSignature.SIGHASH_ANYONECANPAY
231
- }
232
-
233
- const input = tx.inputs[inputIndex]
234
-
235
- const otherInputs = tx.inputs.filter(
236
- (_, index) => index !== inputIndex
237
- )
238
-
239
- const sourceTXID = input.sourceTXID ?? input.sourceTransaction?.id('hex')
240
- if (sourceTXID == null || sourceTXID === undefined) {
241
- throw new Error(
242
- 'The input sourceTXID or sourceTransaction is required for transaction signing.'
243
- )
244
- }
245
- sourceSatoshis ||=
246
- input.sourceTransaction?.outputs[input.sourceOutputIndex].satoshis
247
- if (sourceSatoshis == null || sourceSatoshis === undefined) {
248
- throw new Error(
249
- 'The sourceSatoshis or input sourceTransaction is required for transaction signing.'
250
- )
251
- }
252
- lockingScript ||=
253
- input.sourceTransaction?.outputs[input.sourceOutputIndex]
254
- .lockingScript
255
- if (lockingScript == null) {
256
- throw new Error(
257
- 'The lockingScript or input sourceTransaction is required for transaction signing.'
258
- )
259
- }
260
-
261
- const preimage = TransactionSignature.format({
262
- sourceTXID,
263
- sourceOutputIndex: verifyNotNull(input.sourceOutputIndex, 'input.sourceOutputIndex must have value'),
264
- sourceSatoshis,
265
- transactionVersion: tx.version,
266
- otherInputs,
267
- inputIndex,
268
- outputs: tx.outputs,
269
- inputSequence: input.sequence ?? 0xffffffff,
270
- subscript: lockingScript,
271
- lockTime: tx.lockTime,
272
- scope: signatureScope
220
+ const signatureScope = computeSignatureScope(signOutputs, anyoneCanPay)
221
+ const resolved = resolveSourceDetails(tx, inputIndex, sourceSatoshis, lockingScript)
222
+ sourceSatoshis = resolved.sourceSatoshis
223
+ lockingScript = resolved.lockingScript as LockingScript
224
+
225
+ const preimage = formatPreimage({
226
+ tx, inputIndex, signatureScope,
227
+ sourceTXID: resolved.sourceTXID, sourceSatoshis: resolved.sourceSatoshis,
228
+ lockingScript: resolved.lockingScript, otherInputs: resolved.otherInputs,
229
+ inputSequence: tx.inputs[inputIndex].sequence ?? 0xffffffff
273
230
  })
274
231
 
275
232
  const preimageHash = Hash.sha256(preimage)
@@ -0,0 +1,111 @@
1
+ import TransactionSignature from '../../primitives/TransactionSignature.js'
2
+ import Transaction from '../../transaction/Transaction.js'
3
+ import Script from '../Script.js'
4
+ import { verifyNotNull } from '../../primitives/utils.js'
5
+
6
+ /**
7
+ * Computes the signature scope flags from the given signing parameters.
8
+ */
9
+ export function computeSignatureScope(
10
+ signOutputs: 'all' | 'none' | 'single',
11
+ anyoneCanPay: boolean
12
+ ): number {
13
+ let signatureScope = TransactionSignature.SIGHASH_FORKID
14
+ if (signOutputs === 'all') {
15
+ signatureScope |= TransactionSignature.SIGHASH_ALL
16
+ }
17
+ if (signOutputs === 'none') {
18
+ signatureScope |= TransactionSignature.SIGHASH_NONE
19
+ }
20
+ if (signOutputs === 'single') {
21
+ signatureScope |= TransactionSignature.SIGHASH_SINGLE
22
+ }
23
+ if (anyoneCanPay) {
24
+ signatureScope |= TransactionSignature.SIGHASH_ANYONECANPAY
25
+ }
26
+ return signatureScope
27
+ }
28
+
29
+ /**
30
+ * Resolves and validates the source transaction details needed for signing.
31
+ * Returns the resolved sourceTXID, sourceSatoshis, lockingScript, and otherInputs.
32
+ */
33
+ export function resolveSourceDetails(
34
+ tx: Transaction,
35
+ inputIndex: number,
36
+ providedSourceSatoshis?: number,
37
+ providedLockingScript?: Script
38
+ ): {
39
+ sourceTXID: string
40
+ sourceSatoshis: number
41
+ lockingScript: Script
42
+ otherInputs: typeof tx.inputs
43
+ } {
44
+ const input = tx.inputs[inputIndex]
45
+
46
+ const otherInputs = tx.inputs.filter(
47
+ (_, index) => index !== inputIndex
48
+ )
49
+
50
+ const sourceTXID = input.sourceTXID ?? input.sourceTransaction?.id('hex')
51
+ if (sourceTXID == null || sourceTXID === undefined) {
52
+ throw new Error(
53
+ 'The input sourceTXID or sourceTransaction is required for transaction signing.'
54
+ )
55
+ }
56
+ if (sourceTXID === '') {
57
+ throw new Error(
58
+ 'The input sourceTXID or sourceTransaction is required for transaction signing.'
59
+ )
60
+ }
61
+ const sourceSatoshis = providedSourceSatoshis ??
62
+ input.sourceTransaction?.outputs[input.sourceOutputIndex].satoshis
63
+ if (sourceSatoshis == null || sourceSatoshis === undefined) {
64
+ throw new Error(
65
+ 'The sourceSatoshis or input sourceTransaction is required for transaction signing.'
66
+ )
67
+ }
68
+ const lockingScript = providedLockingScript ??
69
+ input.sourceTransaction?.outputs[input.sourceOutputIndex]
70
+ .lockingScript
71
+ if (lockingScript == null) {
72
+ throw new Error(
73
+ 'The lockingScript or input sourceTransaction is required for transaction signing.'
74
+ )
75
+ }
76
+
77
+ return { sourceTXID, sourceSatoshis, lockingScript, otherInputs }
78
+ }
79
+
80
+ /** Parameters for formatting the transaction preimage */
81
+ export interface FormatPreimageParams {
82
+ tx: Transaction
83
+ inputIndex: number
84
+ signatureScope: number
85
+ sourceTXID: string
86
+ sourceSatoshis: number
87
+ lockingScript: Script
88
+ otherInputs: Transaction['inputs']
89
+ inputSequence?: number
90
+ }
91
+
92
+ /**
93
+ * Formats the transaction preimage for signing.
94
+ */
95
+ export function formatPreimage(params: FormatPreimageParams): number[] {
96
+ const { tx, inputIndex, signatureScope, sourceTXID, sourceSatoshis, lockingScript, otherInputs, inputSequence } = params
97
+ const input = tx.inputs[inputIndex]
98
+ return TransactionSignature.format({
99
+ sourceTXID,
100
+ sourceOutputIndex: verifyNotNull(input.sourceOutputIndex, 'input.sourceOutputIndex must have value'),
101
+ sourceSatoshis,
102
+ transactionVersion: tx.version,
103
+ otherInputs,
104
+ inputIndex,
105
+ outputs: tx.outputs,
106
+ inputSequence: inputSequence ?? verifyNotNull(input.sequence, 'input.sequence must have value'),
107
+ subscript: lockingScript,
108
+ lockTime: tx.lockTime,
109
+ scope: signatureScope
110
+ })
111
+ }
@@ -15,6 +15,8 @@ import { defaultBroadcaster } from './broadcasters/DefaultBroadcaster.js'
15
15
  import { defaultChainTracker } from './chaintrackers/DefaultChainTracker.js'
16
16
  import { Beef, BEEF_V1 } from './Beef.js'
17
17
  import P2PKH from '../script/templates/P2PKH.js'
18
+ import type { WalletInterface, DescriptionString5to50Bytes, CreateActionOptions } from '../wallet/Wallet.interfaces.js'
19
+ import TransactionSignature from '../primitives/TransactionSignature.js'
18
20
 
19
21
  /**
20
22
  * Represents a complete Bitcoin transaction. This class encapsulates all the details
@@ -1291,4 +1293,49 @@ export default class Transaction {
1291
1293
  ...newTransaction.metadata
1292
1294
  }
1293
1295
  }
1296
+
1297
+ /**
1298
+ * Returns the formatted preimage of a transaction for the requested input index, signature scope (default SIGHASH_FORKID | SIGHASH_ALL), and optional subscript.
1299
+ * @param inputIndex - The index of the input to generate the preimage for
1300
+ * @param signatureScope - The signature scope to use for the preimage
1301
+ * @param subscript - The subscript to use for the preimage (optional)
1302
+ * @returns The formatted preimage
1303
+ */
1304
+ preimage (inputIndex?: number, signatureScope?: number, subscript?: LockingScript): number[] {
1305
+ inputIndex ??= 0
1306
+ signatureScope ??= TransactionSignature.SIGHASH_FORKID | TransactionSignature.SIGHASH_ALL
1307
+ if (inputIndex < 0 || inputIndex >= this.inputs.length) {
1308
+ throw new Error('Invalid input index')
1309
+ }
1310
+ const flags = signatureScope & 0xf0
1311
+ if (flags !== 224 && flags !== 192 && flags !== 64) {
1312
+ throw new Error('FORKID must be set')
1313
+ }
1314
+ const coverage = signatureScope & 0x0f
1315
+ if (coverage < 1 || coverage > 3) {
1316
+ throw new Error('Invalid signature coverage, must be all, none or single')
1317
+ }
1318
+ const input = this.inputs[inputIndex]
1319
+ if (input.sourceTransaction == null) {
1320
+ throw new Error('Source transaction is required')
1321
+ }
1322
+ const output = input.sourceTransaction.outputs[input.sourceOutputIndex]
1323
+ if (output == null) {
1324
+ throw new Error(`Source transaction's output at index ${input.sourceOutputIndex} is required`)
1325
+ }
1326
+ const otherInputs = this.inputs.filter((_, index) => index !== inputIndex)
1327
+ return TransactionSignature.format({
1328
+ sourceTXID: input.sourceTXID ?? input.sourceTransaction.id('hex'),
1329
+ sourceOutputIndex: input.sourceOutputIndex,
1330
+ sourceSatoshis: output.satoshis,
1331
+ transactionVersion: this.version,
1332
+ otherInputs,
1333
+ inputIndex,
1334
+ outputs: this.outputs,
1335
+ inputSequence: input.sequence ?? 0xffffffff,
1336
+ subscript: subscript ?? output.lockingScript,
1337
+ lockTime: this.lockTime,
1338
+ scope: signatureScope
1339
+ })
1340
+ }
1294
1341
  }
@@ -1462,7 +1462,7 @@ describe('Transaction', () => {
1462
1462
 
1463
1463
  describe('completeWithWallet', () => {
1464
1464
  // Mock implementation of WalletInterface for testing
1465
- class MockWallet implements Partial<WalletInterface> {
1465
+ class MockWallet {
1466
1466
  public lastCreateActionArgs: CreateActionArgs | null = null
1467
1467
  public lastSignActionArgs: any = null
1468
1468
  public signActionCalled: boolean = false
@@ -1624,7 +1624,7 @@ describe('Transaction', () => {
1624
1624
  const mockWallet = new MockWallet()
1625
1625
 
1626
1626
  // Complete the transaction with the wallet
1627
- await tx.completeWithWallet(mockWallet, 'Test transaction completion')
1627
+ await tx.completeWithWallet(mockWallet as unknown as WalletInterface,'Test transaction completion')
1628
1628
 
1629
1629
  // Verify that the wallet's createAction was called with correct arguments
1630
1630
  expect(mockWallet.lastCreateActionArgs).not.toBeNull()
@@ -1665,7 +1665,7 @@ describe('Transaction', () => {
1665
1665
  const mockWallet = new MockWallet()
1666
1666
 
1667
1667
  // Expect completeWithWallet to throw an error
1668
- await expect(tx.completeWithWallet(mockWallet))
1668
+ await expect(tx.completeWithWallet(mockWallet as unknown as WalletInterface))
1669
1669
  .rejects
1670
1670
  .toThrow('All inputs must have a sourceTransaction when using completeWithWallet')
1671
1671
  })
@@ -1710,7 +1710,7 @@ describe('Transaction', () => {
1710
1710
  const mockWallet = new MockWallet()
1711
1711
 
1712
1712
  // Expect completeWithWallet to throw an error
1713
- await expect(tx.completeWithWallet(mockWallet))
1713
+ await expect(tx.completeWithWallet(mockWallet as unknown as WalletInterface))
1714
1714
  .rejects
1715
1715
  .toThrow('All inputs must have an unlockingScript when using completeWithWallet')
1716
1716
  })
@@ -1766,7 +1766,7 @@ describe('Transaction', () => {
1766
1766
  const mockWallet = new MockWallet()
1767
1767
 
1768
1768
  // Complete the transaction with the wallet
1769
- await tx.completeWithWallet(mockWallet, 'Test with template')
1769
+ await tx.completeWithWallet(mockWallet as unknown as WalletInterface,'Test with template')
1770
1770
 
1771
1771
  // Verify that signAction was called
1772
1772
  expect(mockWallet.signActionCalled).toBe(true)
@@ -1850,7 +1850,7 @@ describe('Transaction', () => {
1850
1850
  const mockWallet = new MockWallet()
1851
1851
 
1852
1852
  // Complete the transaction with the wallet
1853
- await tx.completeWithWallet(mockWallet, 'Test with mixed inputs')
1853
+ await tx.completeWithWallet(mockWallet as unknown as WalletInterface,'Test with mixed inputs')
1854
1854
 
1855
1855
  // Verify that signAction was called (because at least one template exists)
1856
1856
  expect(mockWallet.signActionCalled).toBe(true)
@@ -1937,7 +1937,7 @@ describe('Transaction', () => {
1937
1937
  const mockWallet = new MockWallet()
1938
1938
 
1939
1939
  // Should throw error about missing script/template on input 1
1940
- await expect(tx.completeWithWallet(mockWallet))
1940
+ await expect(tx.completeWithWallet(mockWallet as unknown as WalletInterface))
1941
1941
  .rejects
1942
1942
  .toThrow('Input 1 must have either an unlockingScript or unlockingScriptTemplate')
1943
1943
  })
@@ -1964,7 +1964,7 @@ describe('Transaction', () => {
1964
1964
  returnTXIDOnly: true
1965
1965
  }
1966
1966
 
1967
- await tx.completeWithWallet(mockWallet, 'Test with options', undefined, options)
1967
+ await tx.completeWithWallet(mockWallet as unknown as WalletInterface,'Test with options', undefined, options)
1968
1968
 
1969
1969
  // Verify options were passed to createAction
1970
1970
  expect(mockWallet.lastCreateActionArgs?.options).toEqual(options)
@@ -1997,7 +1997,7 @@ describe('Transaction', () => {
1997
1997
  randomizeOutputs: false
1998
1998
  }
1999
1999
 
2000
- await tx.completeWithWallet(mockWallet, 'Test template with options', undefined, options)
2000
+ await tx.completeWithWallet(mockWallet as unknown as WalletInterface,'Test template with options', undefined, options)
2001
2001
 
2002
2002
  // Verify signAndProcess: false was set for createAction (required for template flow)
2003
2003
  expect(mockWallet.lastCreateActionArgs?.options?.signAndProcess).toBe(false)
@@ -2054,4 +2054,174 @@ describe('Transaction', () => {
2054
2054
  await expect(tx.verify('scripts only', new SatoshisPerKilobyte(1), 35)).resolves.toBe(true)
2055
2055
  })
2056
2056
  })
2057
+
2058
+ describe('#preimage', () => {
2059
+ let sourceTx: Transaction
2060
+ let spendTx: Transaction
2061
+ let privateKey: PrivateKey
2062
+ let publicKey: any
2063
+ let publicKeyHash: number[]
2064
+ let p2pkh: P2PKH
2065
+
2066
+ beforeEach(() => {
2067
+ privateKey = new PrivateKey(1)
2068
+ publicKey = new Curve().g.mul(privateKey)
2069
+ publicKeyHash = hash160(publicKey.encode(true))
2070
+ p2pkh = new P2PKH()
2071
+ sourceTx = new Transaction(
2072
+ 1,
2073
+ [],
2074
+ [
2075
+ {
2076
+ lockingScript: p2pkh.lock(publicKeyHash),
2077
+ satoshis: 4000
2078
+ }
2079
+ ],
2080
+ 0
2081
+ )
2082
+ spendTx = new Transaction(
2083
+ 1,
2084
+ [
2085
+ {
2086
+ sourceTransaction: sourceTx,
2087
+ sourceOutputIndex: 0,
2088
+ unlockingScript: new UnlockingScript(),
2089
+ sequence: 0xffffffff
2090
+ }
2091
+ ],
2092
+ [
2093
+ {
2094
+ satoshis: 1000,
2095
+ lockingScript: p2pkh.lock(publicKeyHash)
2096
+ },
2097
+ {
2098
+ lockingScript: p2pkh.lock(publicKeyHash),
2099
+ change: true
2100
+ }
2101
+ ],
2102
+ 0
2103
+ )
2104
+ })
2105
+
2106
+ it('should generate preimage with default parameters', () => {
2107
+ const preimage = spendTx.preimage()
2108
+ expect(Array.isArray(preimage)).toBe(true)
2109
+ expect(preimage.length).toBeGreaterThan(0)
2110
+ expect(typeof preimage[0]).toBe('number')
2111
+ })
2112
+
2113
+ it('should generate preimage with custom inputIndex', () => {
2114
+ // Add another input to test inputIndex > 0
2115
+ const sourceTx2 = new Transaction(
2116
+ 1,
2117
+ [],
2118
+ [
2119
+ {
2120
+ lockingScript: p2pkh.lock(publicKeyHash),
2121
+ satoshis: 2000
2122
+ }
2123
+ ],
2124
+ 0
2125
+ )
2126
+ spendTx.addInput({
2127
+ sourceTransaction: sourceTx2,
2128
+ sourceOutputIndex: 0,
2129
+ unlockingScript: new UnlockingScript(),
2130
+ sequence: 0xffffffff
2131
+ })
2132
+
2133
+ const preimage0 = spendTx.preimage(0)
2134
+ const preimage1 = spendTx.preimage(1)
2135
+ expect(Array.isArray(preimage0)).toBe(true)
2136
+ expect(Array.isArray(preimage1)).toBe(true)
2137
+ expect(preimage0).not.toEqual(preimage1)
2138
+ })
2139
+
2140
+ it('should generate preimage with custom signatureScope', () => {
2141
+ const defaultPreimage = spendTx.preimage()
2142
+ const customPreimage = spendTx.preimage(0, TransactionSignature.SIGHASH_FORKID | TransactionSignature.SIGHASH_NONE)
2143
+ expect(Array.isArray(customPreimage)).toBe(true)
2144
+ expect(customPreimage).not.toEqual(defaultPreimage)
2145
+ })
2146
+
2147
+ it('should generate preimage with custom subscript', () => {
2148
+ const customScript = LockingScript.fromASM(
2149
+ 'OP_CHECKSIG'
2150
+ )
2151
+ const preimage = spendTx.preimage(0, undefined, customScript)
2152
+ expect(Array.isArray(preimage)).toBe(true)
2153
+ })
2154
+
2155
+ it('should throw error for invalid input index (negative)', () => {
2156
+ expect(() => {
2157
+ spendTx.preimage(-1)
2158
+ }).toThrow('Invalid input index')
2159
+ })
2160
+
2161
+ it('should throw error for invalid input index (out of bounds)', () => {
2162
+ expect(() => {
2163
+ spendTx.preimage(1)
2164
+ }).toThrow('Invalid input index')
2165
+ })
2166
+
2167
+ it('should throw error when sourceTransaction is missing', () => {
2168
+ const txWithoutSource = new Transaction(
2169
+ 1,
2170
+ [
2171
+ {
2172
+ sourceTXID: '00'.repeat(32),
2173
+ sourceOutputIndex: 0,
2174
+ unlockingScript: new UnlockingScript(),
2175
+ sequence: 0xffffffff
2176
+ }
2177
+ ],
2178
+ [
2179
+ {
2180
+ satoshis: 1000,
2181
+ lockingScript: p2pkh.lock(publicKeyHash)
2182
+ }
2183
+ ],
2184
+ 0
2185
+ )
2186
+ expect(() => {
2187
+ txWithoutSource.preimage()
2188
+ }).toThrow('Source transaction is required')
2189
+ })
2190
+
2191
+ it('should throw error when source transaction output is missing', () => {
2192
+ const tx = new Transaction(
2193
+ 1,
2194
+ [
2195
+ {
2196
+ sourceTransaction: new Transaction(1, [], [], 0), // No outputs
2197
+ sourceOutputIndex: 0,
2198
+ unlockingScript: new UnlockingScript(),
2199
+ sequence: 0xffffffff
2200
+ }
2201
+ ],
2202
+ [
2203
+ {
2204
+ satoshis: 1000,
2205
+ lockingScript: p2pkh.lock(publicKeyHash)
2206
+ }
2207
+ ],
2208
+ 0
2209
+ )
2210
+ expect(() => {
2211
+ tx.preimage()
2212
+ }).toThrow('Source transaction\'s output at index 0 is required')
2213
+ })
2214
+
2215
+ it('should throw error when FORKID is not set in signatureScope', () => {
2216
+ expect(() => {
2217
+ spendTx.preimage(0, TransactionSignature.SIGHASH_ALL)
2218
+ }).toThrow('FORKID must be set')
2219
+ })
2220
+
2221
+ it('should throw error for invalid signature coverage', () => {
2222
+ expect(() => {
2223
+ spendTx.preimage(0, TransactionSignature.SIGHASH_FORKID | 0x04) // Invalid coverage
2224
+ }).toThrow('Invalid signature coverage, must be all, none or single')
2225
+ })
2226
+ })
2057
2227
  })