@bsv/templates 1.2.2 → 1.3.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.
@@ -10,9 +10,23 @@ function concatPubkeys(pubkeys: PublicKey[]): number[] {
10
10
  return pubkeys.map((p) => p.toDER() as number[]).reduce((a, b) => a.concat(b), [])
11
11
  }
12
12
 
13
+ function numberFromScriptChunk(chunk: ScriptChunk): number {
14
+ let returnNum: number
15
+ if (!chunk.data) {
16
+ returnNum = 1 + (chunk.op as number) - OP.OP_1
17
+ } else {
18
+ const reader = new Utils.Reader(chunk.data)
19
+ const threshold = reader.readInt64LEBn()
20
+ returnNum = threshold.toNumber()
21
+ }
22
+ return returnNum
23
+ }
24
+
13
25
  export class MultiSigPubkeyHash implements ScriptTemplate {
14
26
 
15
27
  static address(pubkeys: PublicKey[], threshold: number): string {
28
+ if (threshold < 1 || threshold > pubkeys.length) throw new Error('threshold must be between 1 and the number of pubkeys')
29
+ if (!pubkeys || pubkeys.length < 2 || pubkeys.length < threshold) throw new Error(`at least ${threshold || 2} pubkeys are required`)
16
30
  const concat = concatPubkeys(pubkeys)
17
31
  const hash = Hash.hash160(concat)
18
32
  const writer = new Utils.Writer()
@@ -51,9 +65,9 @@ export class MultiSigPubkeyHash implements ScriptTemplate {
51
65
  address?: string,
52
66
  pubkeys?: PublicKey[],
53
67
  threshold: number = 1,
54
- ): LockingScript {
68
+ ): LockingScript {
55
69
  let hash: number[]
56
- let total: number
70
+ let total: number = pubkeys?.length || 0
57
71
  if (address) {
58
72
  if (typeof address !== 'string') throw new Error('address must be a string')
59
73
  const result = MultiSigPubkeyHash.thresholdAndTotalFromAddress(address)
@@ -61,12 +75,13 @@ export class MultiSigPubkeyHash implements ScriptTemplate {
61
75
  total = result.total
62
76
  threshold = result.threshold
63
77
  } else {
64
- if (!pubkeys || pubkeys.length < 2 || pubkeys.length < threshold) throw new Error(`at least ${threshold} pubkeys are required, or use an address`)
78
+ if (!pubkeys || total < 2) throw new Error(`at least 2 pubkeys are required`)
65
79
  const concat = concatPubkeys(pubkeys)
66
80
  hash = Hash.hash160(concat)
67
- total = pubkeys.length
68
81
  }
69
-
82
+ if (!threshold || threshold < 1 || threshold > total) throw new Error('threshold must be between 1 and the number of pubkeys')
83
+ if (total > 10) throw new Error('total must be less than or equal to 10')
84
+
70
85
  const script = new LockingScript();
71
86
  for (let i = 0; i < total - 1; i++) {
72
87
  script.writeOpCode(OP.OP_CAT)
@@ -104,53 +119,52 @@ export class MultiSigPubkeyHash implements ScriptTemplate {
104
119
  return {
105
120
  sign: async (tx: Transaction, inputIndex: number) => {
106
121
  if (!workingUnlockingScript) {
107
- workingUnlockingScript = new UnlockingScript()
108
- workingUnlockingScript.writeOpCode(OP.OP_0)
109
- customInstructions.pubkeys.forEach((pubkey) => {
110
- workingUnlockingScript!.writeBin(PublicKey.fromString(pubkey).toDER() as number[])
111
- })
112
- }
113
- let signatureScope = TransactionSignature.SIGHASH_FORKID;
114
- if (signOutputs === "all") {
115
- signatureScope |= TransactionSignature.SIGHASH_ALL;
116
- }
117
- if (signOutputs === "none") {
118
- signatureScope |= TransactionSignature.SIGHASH_NONE;
119
- }
120
- if (signOutputs === "single") {
121
- signatureScope |= TransactionSignature.SIGHASH_SINGLE;
122
- }
123
- if (anyoneCanPay) {
124
- signatureScope |= TransactionSignature.SIGHASH_ANYONECANPAY;
125
- }
126
- const input = tx.inputs[inputIndex];
127
-
128
- const otherInputs = tx.inputs.filter(
129
- (_, index) => index !== inputIndex
130
- )
122
+ workingUnlockingScript = new UnlockingScript()
123
+ workingUnlockingScript.writeOpCode(OP.OP_0)
124
+ customInstructions.pubkeys.forEach((pubkey) => {
125
+ workingUnlockingScript!.writeBin(PublicKey.fromString(pubkey).toDER() as number[])
126
+ })
127
+ }
128
+
129
+ let signatureScope = TransactionSignature.SIGHASH_FORKID;
130
+ if (signOutputs === "all") {
131
+ signatureScope |= TransactionSignature.SIGHASH_ALL;
132
+ }
133
+ if (signOutputs === "none") {
134
+ signatureScope |= TransactionSignature.SIGHASH_NONE;
135
+ }
136
+ if (signOutputs === "single") {
137
+ signatureScope |= TransactionSignature.SIGHASH_SINGLE;
138
+ }
139
+ if (anyoneCanPay) {
140
+ signatureScope |= TransactionSignature.SIGHASH_ANYONECANPAY;
141
+ }
142
+ const input = tx.inputs[inputIndex];
143
+
144
+ const otherInputs = tx.inputs.filter(
145
+ (_, index) => index !== inputIndex
146
+ )
131
147
 
132
148
  const sourceTXID = input.sourceTXID
133
- ? input.sourceTXID
134
- : input.sourceTransaction?.id("hex");
149
+ ? input.sourceTXID
150
+ : input.sourceTransaction?.id("hex")
151
+
135
152
  if (!sourceTXID) {
136
- throw new Error(
137
- "The input sourceTXID or sourceTransaction is required for transaction signing."
138
- );
153
+ throw new Error(
154
+ "The input sourceTXID or sourceTransaction is required for transaction signing."
155
+ )
139
156
  }
140
- sourceSatoshis ||=
141
- input.sourceTransaction?.outputs[input.sourceOutputIndex].satoshis;
157
+ sourceSatoshis ||= input.sourceTransaction?.outputs[input.sourceOutputIndex].satoshis;
142
158
  if (!sourceSatoshis) {
143
- throw new Error(
144
- "The sourceSatoshis or input sourceTransaction is required for transaction signing."
145
- );
159
+ throw new Error(
160
+ "The sourceSatoshis or input sourceTransaction is required for transaction signing."
161
+ )
146
162
  }
147
- lockingScript ||=
148
- input.sourceTransaction?.outputs[input.sourceOutputIndex]
149
- .lockingScript;
163
+ lockingScript ||= input.sourceTransaction?.outputs[input.sourceOutputIndex].lockingScript;
150
164
  if (!lockingScript) {
151
- throw new Error(
152
- "The lockingScript or input sourceTransaction is required for transaction signing."
153
- );
165
+ throw new Error(
166
+ "The lockingScript or input sourceTransaction is required for transaction signing."
167
+ )
154
168
  }
155
169
 
156
170
  const preimage = TransactionSignature.format({
@@ -182,32 +196,28 @@ export class MultiSigPubkeyHash implements ScriptTemplate {
182
196
 
183
197
  workingUnlockingScript.writeBin(sigForScript)
184
198
  const chunkforSig = workingUnlockingScript.chunks.pop() as ScriptChunk
185
- // add it to the array at position 1, pushing the other content to the right
186
- workingUnlockingScript.chunks.splice(1, 0, chunkforSig)
199
+ // add it to the array before the pubkeys, pushing the other content to the right
200
+ workingUnlockingScript.chunks.splice(workingUnlockingScript.chunks.length - customInstructions.pubkeys.length, 0, chunkforSig)
187
201
  return workingUnlockingScript
188
202
  },
189
203
 
190
204
  estimateLength: (tx: Transaction, inputIndex: number) => {
191
205
  let numberOfPubkeys = 2
192
206
  let numberOfSignatures = 1
193
- const staticLength = 28
207
+ const staticLength = 1
194
208
  const input = tx.inputs[inputIndex];
195
209
  const lockingScript = input.sourceTransaction?.outputs[input.sourceOutputIndex].lockingScript;
196
210
  if (!lockingScript) {
197
211
  return Promise.resolve(1000) // guess
198
212
  }
199
- let chunks = lockingScript.chunks.length - 8 // remove static chunks
200
- const numPubKeys = Math.floor(chunks / 3)
201
- const thresholdPos = 5 + (numPubKeys - 1)
202
- const n = lockingScript?.chunks[thresholdPos] as { op: number, data: number[] }
203
- if (!n.data) {
204
- numberOfSignatures = 1 + (n.op as number) - OP.OP_1
205
- } else {
206
- const reader = new Utils.Reader(n.data)
207
- const threshold = reader.readInt64LEBn()
208
- numberOfSignatures = threshold.toNumber()
209
- }
210
- return Promise.resolve(staticLength + (numberOfPubkeys * 34) + (numberOfSignatures * 73))
213
+
214
+ const totalChunk = lockingScript?.chunks[lockingScript.chunks.length - 2] as { op: number, data: number[] }
215
+ numberOfPubkeys = numberFromScriptChunk(totalChunk)
216
+
217
+ const thresholdPos = lockingScript.chunks.map(chunk => chunk.op === OP.OP_EQUALVERIFY).indexOf(true) + 1
218
+ const thresholdChunk = lockingScript?.chunks[thresholdPos] as { op: number, data: number[] }
219
+ numberOfSignatures = numberFromScriptChunk(thresholdChunk)
220
+ return Promise.resolve(staticLength + (numberOfSignatures * 74) + (numberOfPubkeys * 34))
211
221
  }
212
222
  }
213
223
  }