@bsv/sdk 1.6.11 → 1.6.14

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 (88) hide show
  1. package/README.md +4 -4
  2. package/dist/cjs/package.json +1 -1
  3. package/dist/cjs/src/transaction/broadcasters/DefaultBroadcaster.js +1 -1
  4. package/dist/cjs/src/transaction/broadcasters/DefaultBroadcaster.js.map +1 -1
  5. package/dist/cjs/src/wallet/WalletClient.js +1 -0
  6. package/dist/cjs/src/wallet/WalletClient.js.map +1 -1
  7. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  8. package/dist/esm/src/transaction/broadcasters/DefaultBroadcaster.js +1 -1
  9. package/dist/esm/src/transaction/broadcasters/DefaultBroadcaster.js.map +1 -1
  10. package/dist/esm/src/wallet/WalletClient.js +1 -0
  11. package/dist/esm/src/wallet/WalletClient.js.map +1 -1
  12. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  13. package/dist/types/src/wallet/WalletClient.d.ts.map +1 -1
  14. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  15. package/dist/umd/bundle.js +1 -1
  16. package/docs/MARKDOWN_VALIDATION_GUIDE.md +175 -0
  17. package/docs/concepts/beef.md +8 -0
  18. package/docs/concepts/chain-tracking.md +12 -0
  19. package/docs/concepts/decentralized-identity.md +37 -0
  20. package/docs/concepts/fees.md +32 -0
  21. package/docs/concepts/identity-certificates.md +53 -1
  22. package/docs/concepts/index.md +15 -0
  23. package/docs/concepts/key-management.md +9 -0
  24. package/docs/concepts/script-templates.md +13 -0
  25. package/docs/concepts/sdk-philosophy.md +8 -0
  26. package/docs/concepts/signatures.md +15 -0
  27. package/docs/concepts/spv-verification.md +12 -0
  28. package/docs/concepts/transaction-encoding.md +19 -0
  29. package/docs/concepts/transaction-structure.md +4 -0
  30. package/docs/concepts/trust-model.md +16 -0
  31. package/docs/concepts/verification.md +31 -0
  32. package/docs/concepts/wallet-integration.md +6 -0
  33. package/docs/guides/development-wallet-setup.md +374 -0
  34. package/docs/guides/direct-transaction-creation.md +12 -2
  35. package/docs/guides/http-client-configuration.md +122 -48
  36. package/docs/guides/index.md +117 -9
  37. package/docs/guides/large-transactions.md +448 -0
  38. package/docs/guides/multisig-transactions.md +792 -0
  39. package/docs/guides/security-best-practices.md +494 -0
  40. package/docs/guides/transaction-batching.md +132 -0
  41. package/docs/guides/transaction-signing-methods.md +230 -79
  42. package/docs/index.md +0 -2
  43. package/docs/reference/auth.md +212 -159
  44. package/docs/reference/compat.md +120 -96
  45. package/docs/reference/configuration.md +6 -0
  46. package/docs/reference/debugging.md +5 -0
  47. package/docs/reference/errors.md +50 -0
  48. package/docs/reference/identity.md +21 -12
  49. package/docs/reference/index.md +14 -1
  50. package/docs/reference/kvstore.md +21 -19
  51. package/docs/reference/messages.md +3 -0
  52. package/docs/reference/op-codes.md +20 -1
  53. package/docs/reference/overlay-tools.md +46 -18
  54. package/docs/reference/primitives.md +571 -390
  55. package/docs/reference/registry.md +43 -20
  56. package/docs/reference/script.md +140 -105
  57. package/docs/reference/storage.md +32 -12
  58. package/docs/reference/totp.md +16 -11
  59. package/docs/reference/transaction-signatures.md +2 -1
  60. package/docs/reference/transaction.md +201 -120
  61. package/docs/reference/wallet.md +241 -64
  62. package/docs/tutorials/advanced-transaction.md +1 -4
  63. package/docs/tutorials/aes-encryption.md +3 -1
  64. package/docs/tutorials/authfetch-tutorial.md +29 -0
  65. package/docs/tutorials/ecdh-key-exchange.md +2 -0
  66. package/docs/tutorials/elliptic-curve-fundamentals.md +3 -0
  67. package/docs/tutorials/error-handling.md +1 -0
  68. package/docs/tutorials/first-transaction-low-level.md +1 -0
  69. package/docs/tutorials/first-transaction.md +5 -8
  70. package/docs/tutorials/hashes-and-hmacs.md +5 -31
  71. package/docs/tutorials/identity-management.md +27 -0
  72. package/docs/tutorials/index.md +114 -77
  73. package/docs/tutorials/key-management.md +5 -3
  74. package/docs/tutorials/protowallet-development.md +27 -0
  75. package/docs/tutorials/spv-merkle-proofs.md +9 -6
  76. package/docs/tutorials/testnet-transactions-low-level.md +25 -18
  77. package/docs/tutorials/transaction-broadcasting.md +10 -7
  78. package/docs/tutorials/transaction-types.md +5 -4
  79. package/docs/tutorials/type-42.md +0 -14
  80. package/docs/tutorials/uhrp-storage.md +23 -3
  81. package/package.json +1 -1
  82. package/src/identity/README.md +0 -1
  83. package/src/primitives/__tests/SymmetricKey.test.ts +45 -0
  84. package/src/primitives/__tests/SymmetricKeyCompatibility.test.ts +150 -0
  85. package/src/transaction/__tests/Transaction.test.ts +1 -1
  86. package/src/transaction/broadcasters/DefaultBroadcaster.ts +1 -1
  87. package/src/transaction/broadcasters/__tests/ARC.test.ts +1 -1
  88. package/src/wallet/WalletClient.ts +1 -0
@@ -0,0 +1,792 @@
1
+ # Multi-Signature Transactions
2
+
3
+ Multi-signature (multisig) transactions require multiple signatures to spend funds, providing enhanced security and shared control over Bitcoin outputs. This guide covers implementing multisig transactions using the BSV TypeScript SDK, from basic 2-of-3 setups to advanced patterns.
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [Multi-Signature Fundamentals](#multi-signature-fundamentals)
8
+ 2. [Basic 2-of-3 Implementation](#basic-2-of-3-implementation)
9
+ 3. [Funding and Spending](#funding-and-spending)
10
+ 4. [Advanced Patterns](#advanced-patterns)
11
+ 5. [Error Handling and Validation](#error-handling-and-validation)
12
+ 6. [Best Practices](#best-practices)
13
+ 7. [Testing](#testing)
14
+ 8. [Troubleshooting](#troubleshooting)
15
+
16
+ ## Multi-Signature Fundamentals
17
+
18
+ Multi-signature transactions use the `OP_CHECKMULTISIG` opcode to require multiple valid signatures from a set of public keys. Common patterns include:
19
+
20
+ - **2-of-2**: Both parties must sign (joint accounts)
21
+ - **2-of-3**: Any 2 of 3 parties must sign (escrow with arbiter)
22
+ - **3-of-5**: Any 3 of 5 parties must sign (corporate governance)
23
+
24
+ ### Key Concepts
25
+
26
+ - **Threshold**: Minimum number of required signatures
27
+ - **Public Key Set**: All possible signers
28
+ - **Signature Ordering**: Signatures must match public key order
29
+ - **OP_0 Bug**: Required extra OP_0 due to Bitcoin's OP_CHECKMULTISIG implementation
30
+
31
+ ## Basic 2-of-3 Implementation
32
+
33
+ ### Step 1: Generate Key Pairs
34
+
35
+ ```typescript
36
+ import { PrivateKey, PublicKey } from '@bsv/sdk'
37
+
38
+ // Generate three key pairs for the multisig
39
+ const key1 = PrivateKey.fromRandom()
40
+ const key2 = PrivateKey.fromRandom()
41
+ const key3 = PrivateKey.fromRandom()
42
+
43
+ const pubKey1 = key1.toPublicKey()
44
+ const pubKey2 = key2.toPublicKey()
45
+ const pubKey3 = key3.toPublicKey()
46
+
47
+ console.log('Generated 3 key pairs for 2-of-3 multisig')
48
+ ```
49
+
50
+ ### Step 2: Create Multisig Script Template
51
+
52
+ ```typescript
53
+ import { Script, OP, ScriptTemplate, Transaction, UnlockingScript, LockingScript, ScriptTemplateUnlock } from '@bsv/sdk'
54
+
55
+ class MultiSigTemplate implements ScriptTemplate {
56
+ private threshold: number
57
+ private publicKeys: PublicKey[]
58
+
59
+ constructor(threshold: number, publicKeys: PublicKey[]) {
60
+ if (threshold > publicKeys.length) {
61
+ throw new Error('Threshold cannot exceed number of public keys')
62
+ }
63
+ if (threshold < 1) {
64
+ throw new Error('Threshold must be at least 1')
65
+ }
66
+ if (publicKeys.length > 16) {
67
+ throw new Error('Maximum 16 public keys allowed')
68
+ }
69
+
70
+ this.threshold = threshold
71
+ this.publicKeys = publicKeys.sort((a, b) => {
72
+ const aStr = a.toString()
73
+ const bStr = b.toString()
74
+ return aStr.localeCompare(bStr)
75
+ })
76
+ }
77
+
78
+ lock(): LockingScript {
79
+ const script = new Script()
80
+
81
+ // Push threshold (OP_1 through OP_16)
82
+ if (this.threshold <= 16) {
83
+ script.writeOpCode(OP.OP_1 + this.threshold - 1)
84
+ } else {
85
+ script.writeNumber(this.threshold)
86
+ }
87
+
88
+ // Push all public keys
89
+ for (const pubKey of this.publicKeys) {
90
+ const pubKeyHex = pubKey.toString()
91
+ const pubKeyBytes = Array.from(Buffer.from(pubKeyHex, 'hex'))
92
+ script.writeBin(pubKeyBytes)
93
+ }
94
+
95
+ // Push number of public keys
96
+ if (this.publicKeys.length <= 16) {
97
+ script.writeOpCode(OP.OP_1 + this.publicKeys.length - 1)
98
+ } else {
99
+ script.writeNumber(this.publicKeys.length)
100
+ }
101
+
102
+ // Add OP_CHECKMULTISIG
103
+ script.writeOpCode(OP.OP_CHECKMULTISIG)
104
+
105
+ return new LockingScript(script.chunks)
106
+ }
107
+
108
+ unlock(privateKeys: PrivateKey[]): ScriptTemplateUnlock {
109
+ if (privateKeys.length < this.threshold) {
110
+ throw new Error(`Need at least ${this.threshold} private keys`)
111
+ }
112
+
113
+ return {
114
+ sign: async (tx: Transaction, inputIndex: number): Promise<UnlockingScript> => {
115
+ const script = new Script()
116
+
117
+ // Add OP_0 (required due to OP_CHECKMULTISIG bug)
118
+ script.writeOpCode(OP.OP_0)
119
+
120
+ // Create signatures with the first 'threshold' keys
121
+ const signingKeys = privateKeys.slice(0, this.threshold)
122
+
123
+ // Note: In a real implementation, you would create proper signatures here
124
+ // This is a simplified example for demonstration
125
+ for (let i = 0; i < this.threshold; i++) {
126
+ // Placeholder for signature - in real implementation would sign transaction
127
+ const dummySig = new Array(72).fill(0x30) // Dummy DER signature
128
+ script.writeBin(dummySig)
129
+ }
130
+
131
+ return new UnlockingScript(script.chunks)
132
+ },
133
+ estimateLength: async (): Promise<number> => {
134
+ // OP_0 + (threshold * signature_length)
135
+ return 1 + (this.threshold * 73)
136
+ }
137
+ }
138
+ }
139
+ ```
140
+
141
+ ### Step 3: Create Multisig Address
142
+
143
+ ```typescript
144
+ import { Hash } from '@bsv/sdk'
145
+
146
+ // Create 2-of-3 multisig template
147
+ const multisigTemplate = new MultiSigTemplate(2, [pubKey1, pubKey2, pubKey3])
148
+ const lockingScript = multisigTemplate.lock()
149
+
150
+ // Create script hash using available Hash methods
151
+ const scriptBytes = lockingScript.toBinary()
152
+ const scriptHash = Hash.sha256(Array.from(scriptBytes))
153
+
154
+ console.log('Multisig locking script:', lockingScript.toASM())
155
+ console.log('Script hash:', Buffer.from(scriptHash).toString('hex'))
156
+ ```
157
+
158
+ ## Funding the Multisig Address
159
+
160
+ ### Using WalletClient
161
+
162
+ ```typescript
163
+ import { WalletClient } from '@bsv/sdk'
164
+
165
+ async function fundMultisig(wallet: WalletClient, scriptHash: number[], amount: number) {
166
+ try {
167
+ const actionResult = await wallet.createAction({
168
+ description: 'Fund 2-of-3 multisig address',
169
+ outputs: [
170
+ {
171
+ satoshis: amount,
172
+ lockingScript: Buffer.from(scriptHash).toString('hex'),
173
+ outputDescription: 'Multisig funding'
174
+ }
175
+ ],
176
+ })
177
+
178
+ if (actionResult.txid) {
179
+ console.log('Multisig funded with transaction:', actionResult.txid)
180
+ return actionResult.txid
181
+ } else {
182
+ throw new Error('Failed to fund multisig address')
183
+ }
184
+ } catch (error) {
185
+ console.error('Error funding multisig:', error)
186
+ throw error
187
+ }
188
+ }
189
+
190
+ // Example usage
191
+ const wallet = new WalletClient('https://staging-dojo.babbage.systems')
192
+ await wallet.authenticate()
193
+ const fundingTxid = await fundMultisig(wallet, scriptHash, 100) // 100 satoshis
194
+ ```
195
+
196
+ ## Spending from Multisig
197
+
198
+ ### Step 1: Create Spending Transaction
199
+
200
+ ```typescript
201
+ import { Transaction } from '@bsv/sdk'
202
+
203
+ async function createMultisigSpendingTx(
204
+ fundingTxid: string,
205
+ outputIndex: number,
206
+ amount: number,
207
+ recipientScript: Script,
208
+ multisigTemplate: MultiSigTemplate
209
+ ): Promise<Transaction> {
210
+
211
+ const tx = new Transaction()
212
+
213
+ // Add input from multisig funding transaction
214
+ tx.inputs = [{
215
+ sourceTXID: fundingTxid,
216
+ sourceOutputIndex: outputIndex,
217
+ unlockingScript: new Script(), // Will be filled later
218
+ sequence: 0xffffffff
219
+ }] as any
220
+
221
+ // Add output (subtract small fee)
222
+ tx.outputs = [{
223
+ satoshis: amount - 100, // 100 satoshi fee
224
+ lockingScript: recipientScript
225
+ }] as any
226
+
227
+ return tx
228
+ }
229
+ ```
230
+
231
+ ### Step 2: Generate Signatures
232
+
233
+ ```typescript
234
+ function signMultisigTransaction(
235
+ transaction: Transaction,
236
+ inputIndex: number,
237
+ privateKeys: PrivateKey[],
238
+ multisigScript: Script,
239
+ inputAmount: number
240
+ ): Buffer[] {
241
+
242
+ const signatures: Buffer[] = []
243
+
244
+ for (const privateKey of privateKeys) {
245
+ try {
246
+ // Create a signature for the transaction
247
+ // Note: This is a simplified example - in production you would use proper signature hash
248
+ const testMessage = Array.from(Buffer.from('transaction_signature_data'))
249
+ const signature = privateKey.sign(testMessage)
250
+
251
+ // Create signature buffer with SIGHASH flag
252
+ const sigBytes = Array.from(Buffer.from(signature.toDER(), 'hex'))
253
+ sigBytes.push(0x41) // SIGHASH_ALL | SIGHASH_FORKID
254
+
255
+ signatures.push(Buffer.from(sigBytes))
256
+ } catch (error) {
257
+ console.error('Error signing with key:', error)
258
+ throw error
259
+ }
260
+ }
261
+
262
+ return signatures
263
+ }
264
+ ```
265
+
266
+ ### Step 3: Complete Transaction
267
+
268
+ ```typescript
269
+ async function spendFromMultisig(
270
+ fundingTxid: string,
271
+ outputIndex: number,
272
+ amount: number,
273
+ recipientAddress: string,
274
+ signingKeys: PrivateKey[], // 2 keys for 2-of-3
275
+ multisigTemplate: MultiSigTemplate
276
+ ): Promise<string> {
277
+
278
+ try {
279
+ // Create recipient script (P2PKH for simplicity)
280
+ const recipientScript = new Script()
281
+ recipientScript.writeOpCode(OP.OP_DUP)
282
+ recipientScript.writeOpCode(OP.OP_HASH160)
283
+ recipientScript.writeBin(Buffer.from(recipientAddress, 'hex'))
284
+ recipientScript.writeOpCode(OP.OP_EQUALVERIFY)
285
+ recipientScript.writeOpCode(OP.OP_CHECKSIG)
286
+
287
+ // Create spending transaction
288
+ const tx = await createMultisigSpendingTx(
289
+ fundingTxid,
290
+ outputIndex,
291
+ amount,
292
+ recipientScript,
293
+ multisigTemplate
294
+ )
295
+
296
+ // Get multisig locking script
297
+ const multisigScript = multisigTemplate.lock()
298
+
299
+ // Generate signatures
300
+ const signatures = signMultisigTransaction(
301
+ tx,
302
+ 0, // First input
303
+ signingKeys,
304
+ multisigScript,
305
+ amount
306
+ )
307
+
308
+ // Create unlocking script
309
+ const unlockingScript = multisigTemplate.unlock(signatures, tx, 0)
310
+ tx.inputs[0].unlockingScript = unlockingScript.sign()
311
+
312
+ // Verify transaction
313
+ const isValid = tx.verify()
314
+ if (!isValid) {
315
+ throw new Error('Transaction verification failed')
316
+ }
317
+
318
+ console.log('Multisig transaction created successfully')
319
+ console.log('Transaction hex:', tx.toHex())
320
+
321
+ return tx.toHex()
322
+
323
+ } catch (error) {
324
+ console.error('Error spending from multisig:', error)
325
+ throw error
326
+ }
327
+ }
328
+
329
+ // Example usage
330
+ const spendingTx = await spendFromMultisig(
331
+ fundingTxid,
332
+ 0, // Output index
333
+ 1000, // Amount
334
+ 'recipient_address_hash160',
335
+ [key1, key2], // 2 signatures for 2-of-3
336
+ multisigTemplate
337
+ )
338
+ ```
339
+
340
+ ## Advanced Multisig Patterns
341
+
342
+ ### Threshold Signature Coordination
343
+
344
+ ```typescript
345
+ class MultisigCoordinator {
346
+ private template: MultiSigTemplate
347
+ private participants: Map<string, PublicKey>
348
+ private signatures: Map<string, Buffer>
349
+
350
+ constructor(template: MultiSigTemplate, participants: PublicKey[]) {
351
+ this.template = template
352
+ this.participants = new Map()
353
+ this.signatures = new Map()
354
+
355
+ participants.forEach((pubKey, index) => {
356
+ this.participants.set(`participant_${index}`, pubKey)
357
+ })
358
+ }
359
+
360
+ addSignature(participantId: string, signature: Buffer): void {
361
+ if (!this.participants.has(participantId)) {
362
+ throw new Error('Unknown participant')
363
+ }
364
+
365
+ this.signatures.set(participantId, signature)
366
+ console.log(`Signature added for ${participantId}`)
367
+ }
368
+
369
+ hasEnoughSignatures(): boolean {
370
+ return this.signatures.size >= this.template['threshold']
371
+ }
372
+
373
+ getSignatures(): Buffer[] {
374
+ const sigs = Array.from(this.signatures.values())
375
+ return sigs.slice(0, this.template['threshold'])
376
+ }
377
+
378
+ createUnlockingScript(transaction: Transaction, inputIndex: number): UnlockingScript {
379
+ if (!this.hasEnoughSignatures()) {
380
+ throw new Error('Insufficient signatures')
381
+ }
382
+
383
+ return this.template.unlock(this.getSignatures(), transaction, inputIndex)
384
+ }
385
+ }
386
+ ```
387
+
388
+ ### Time-Locked Multisig
389
+
390
+ ```typescript
391
+ class TimeLockMultiSig extends MultiSigTemplate {
392
+ private lockTime: number
393
+
394
+ constructor(threshold: number, publicKeys: PublicKey[], lockTime: number) {
395
+ super(threshold, publicKeys)
396
+ this.lockTime = lockTime
397
+ }
398
+
399
+ lock(): Script {
400
+ const script = new Script()
401
+
402
+ // Add time lock (simplified - just add the number and drop it)
403
+ script.writeNumber(this.lockTime)
404
+ script.writeOpCode(OP.OP_DROP)
405
+
406
+ // Add standard multisig
407
+ const multisigScript = super.lock()
408
+ const scriptBytes = multisigScript.toBinary()
409
+ script.writeBin(Array.from(scriptBytes))
410
+
411
+ return script
412
+ }
413
+ }
414
+
415
+ // Usage
416
+ const timeLockMultisig = new TimeLockMultiSig(
417
+ 2, // 2-of-3
418
+ [pubKey1, pubKey2, pubKey3],
419
+ 1640995200 // Unix timestamp
420
+ )
421
+ ```
422
+
423
+ ## Error Handling and Validation
424
+
425
+ ### Comprehensive Error Handling
426
+
427
+ ```typescript
428
+ class MultisigError extends Error {
429
+ constructor(message: string, public code: string) {
430
+ super(message)
431
+ this.name = 'MultisigError'
432
+ }
433
+ }
434
+
435
+ function validateMultisigSetup(
436
+ threshold: number,
437
+ publicKeys: PublicKey[]
438
+ ): void {
439
+ if (threshold < 1) {
440
+ throw new MultisigError('Threshold must be at least 1', 'INVALID_THRESHOLD')
441
+ }
442
+
443
+ if (threshold > publicKeys.length) {
444
+ throw new MultisigError(
445
+ 'Threshold cannot exceed number of public keys',
446
+ 'THRESHOLD_TOO_HIGH'
447
+ )
448
+ }
449
+
450
+ if (publicKeys.length > 16) {
451
+ throw new MultisigError(
452
+ 'Maximum 16 public keys allowed',
453
+ 'TOO_MANY_KEYS'
454
+ )
455
+ }
456
+
457
+ // Check for duplicate keys
458
+ const keySet = new Set(publicKeys.map(k => k.toString()))
459
+ if (keySet.size !== publicKeys.length) {
460
+ throw new MultisigError('Duplicate public keys detected', 'DUPLICATE_KEYS')
461
+ }
462
+ }
463
+
464
+ function validateSignatures(
465
+ signatures: Buffer[],
466
+ expectedCount: number
467
+ ): void {
468
+ if (signatures.length !== expectedCount) {
469
+ throw new MultisigError(
470
+ `Expected ${expectedCount} signatures, got ${signatures.length}`,
471
+ 'INVALID_SIGNATURE_COUNT'
472
+ )
473
+ }
474
+
475
+ for (let i = 0; i < signatures.length; i++) {
476
+ if (signatures[i].length < 70 || signatures[i].length > 73) {
477
+ throw new MultisigError(
478
+ `Invalid signature length at index ${i}`,
479
+ 'INVALID_SIGNATURE_LENGTH'
480
+ )
481
+ }
482
+ }
483
+ }
484
+ ```
485
+
486
+ ### Transaction Verification
487
+
488
+ ```typescript
489
+ function verifyMultisigTransaction(
490
+ transaction: Transaction,
491
+ inputIndex: number,
492
+ multisigScript: Script,
493
+ inputAmount: number
494
+ ): boolean {
495
+ try {
496
+ // Verify transaction structure
497
+ if (!transaction.inputs || transaction.inputs.length === 0) {
498
+ throw new Error('Transaction has no inputs')
499
+ }
500
+
501
+ if (!transaction.outputs || transaction.outputs.length === 0) {
502
+ throw new Error('Transaction has no outputs')
503
+ }
504
+
505
+ // Verify specific input
506
+ const input = transaction.inputs[inputIndex]
507
+ if (!input) {
508
+ throw new Error(`Input at index ${inputIndex} does not exist`)
509
+ }
510
+
511
+ // Verify unlocking script
512
+ if (!input.unlockingScript) {
513
+ throw new Error('Input has no unlocking script')
514
+ }
515
+
516
+ // Verify transaction signature
517
+ const isValid = transaction.verify()
518
+ if (!isValid) {
519
+ throw new Error('Transaction signature verification failed')
520
+ }
521
+
522
+ console.log('Multisig transaction verification passed')
523
+ return true
524
+
525
+ } catch (error) {
526
+ console.error('Multisig transaction verification failed:', error)
527
+ return false
528
+ }
529
+ }
530
+ ```
531
+
532
+ ## Best Practices
533
+
534
+ ### Key Management
535
+
536
+ 1. **Secure Key Generation**: Always use cryptographically secure random number generation
537
+ 2. **Key Distribution**: Distribute keys securely and never transmit private keys over insecure channels
538
+ 3. **Key Storage**: Store private keys in secure hardware or encrypted storage
539
+ 4. **Key Backup**: Implement proper backup and recovery procedures
540
+
541
+ ```typescript
542
+ // Secure key generation example
543
+ function generateSecureKeyPair(): { privateKey: PrivateKey, publicKey: PublicKey } {
544
+ const privateKey = PrivateKey.fromRandom()
545
+ const publicKey = privateKey.toPublicKey()
546
+
547
+ // Validate key pair
548
+ const testMessage = Buffer.from('test message')
549
+ const signature = privateKey.sign(testMessage)
550
+ const isValid = publicKey.verify(testMessage, signature)
551
+
552
+ if (!isValid) {
553
+ throw new Error('Generated key pair failed validation')
554
+ }
555
+
556
+ return { privateKey, publicKey }
557
+ }
558
+ ```
559
+
560
+ ### Transaction Construction
561
+
562
+ 1. **Fee Calculation**: Always account for transaction fees
563
+ 2. **Input Validation**: Validate all inputs before signing
564
+ 3. **Output Verification**: Verify output amounts and scripts
565
+ 4. **Signature Ordering**: Maintain consistent signature ordering
566
+
567
+ ```typescript
568
+ function calculateMultisigFee(
569
+ inputCount: number,
570
+ outputCount: number,
571
+ threshold: number
572
+ ): number {
573
+ // Base transaction size
574
+ const baseSize = 10 // version + locktime + input/output counts
575
+
576
+ // Input size (outpoint + sequence + unlocking script)
577
+ const inputSize = 36 + 4 + (1 + threshold * 73) // Estimated unlocking script size
578
+
579
+ // Output size (value + locking script)
580
+ const outputSize = 8 + 25 // Estimated P2PKH output size
581
+
582
+ const totalSize = baseSize + (inputCount * inputSize) + (outputCount * outputSize)
583
+
584
+ // 1 satoshi per byte
585
+ return totalSize
586
+ }
587
+ ```
588
+
589
+ ### Security Considerations
590
+
591
+ 1. **Signature Verification**: Always verify signatures before broadcasting
592
+ 2. **Script Validation**: Validate all scripts before use
593
+ 3. **Amount Verification**: Double-check all amounts
594
+ 4. **Replay Protection**: Use proper SIGHASH flags
595
+
596
+ ```typescript
597
+ function secureMultisigSpend(
598
+ transaction: Transaction,
599
+ inputIndex: number,
600
+ multisigScript: Script,
601
+ inputAmount: number,
602
+ expectedOutputAmount: number
603
+ ): boolean {
604
+
605
+ // Verify transaction structure
606
+ if (!verifyMultisigTransaction(transaction, inputIndex, multisigScript, inputAmount)) {
607
+ return false
608
+ }
609
+
610
+ // Verify output amounts
611
+ const totalOutput = transaction.outputs.reduce((sum, output) => sum + output.satoshis, 0)
612
+ const fee = inputAmount - totalOutput
613
+
614
+ if (fee < 0) {
615
+ console.error('Invalid transaction: outputs exceed inputs')
616
+ return false
617
+ }
618
+
619
+ if (fee > inputAmount * 0.1) { // Fee should not exceed 10% of input
620
+ console.error('Warning: High transaction fee detected')
621
+ }
622
+
623
+ // Verify expected output amount
624
+ if (totalOutput !== expectedOutputAmount) {
625
+ console.error('Output amount mismatch')
626
+ return false
627
+ }
628
+
629
+ return true
630
+ }
631
+ ```
632
+
633
+ ## Testing Multisig Implementation
634
+
635
+ ### Unit Tests
636
+
637
+ ```typescript
638
+ import { describe, it, expect } from '@jest/globals'
639
+
640
+ describe('MultiSigTemplate', () => {
641
+ let keys: PrivateKey[]
642
+ let pubKeys: PublicKey[]
643
+ let template: MultiSigTemplate
644
+
645
+ beforeEach(() => {
646
+ keys = [
647
+ PrivateKey.fromRandom(),
648
+ PrivateKey.fromRandom(),
649
+ PrivateKey.fromRandom()
650
+ ]
651
+ pubKeys = keys.map(k => k.toPublicKey())
652
+ template = new MultiSigTemplate(2, pubKeys)
653
+ })
654
+
655
+ it('should create valid locking script', () => {
656
+ const lockingScript = template.lock()
657
+ expect(lockingScript).toBeDefined()
658
+ expect(lockingScript.toASM()).toContain('OP_CHECKMULTISIG')
659
+ })
660
+
661
+ it('should create valid unlocking script', () => {
662
+ const tx = new Transaction()
663
+ const signatures = [Buffer.alloc(72), Buffer.alloc(72)]
664
+
665
+ const unlockingScript = template.unlock(signatures, tx, 0)
666
+ expect(unlockingScript.script).toBeDefined()
667
+ expect(unlockingScript.estimatedLength).toBeGreaterThan(0)
668
+ })
669
+
670
+ it('should reject invalid threshold', () => {
671
+ expect(() => new MultiSigTemplate(4, pubKeys)).toThrow('Threshold cannot exceed')
672
+ expect(() => new MultiSigTemplate(0, pubKeys)).toThrow('Threshold must be at least 1')
673
+ })
674
+ })
675
+ ```
676
+
677
+ ### Integration Tests
678
+
679
+ ```typescript
680
+ describe('Multisig Integration', () => {
681
+ it('should create and spend from multisig', async () => {
682
+ // This would be a full integration test
683
+ // involving actual transaction creation and verification
684
+
685
+ const keys = [PrivateKey.fromRandom(), PrivateKey.fromRandom(), PrivateKey.fromRandom()]
686
+ const pubKeys = keys.map(k => k.toPublicKey())
687
+ const template = new MultiSigTemplate(2, pubKeys)
688
+
689
+ // Create mock funding transaction
690
+ const fundingTx = new Transaction()
691
+ // ... setup funding transaction
692
+
693
+ // Create spending transaction
694
+ const spendingTx = new Transaction()
695
+ // ... setup spending transaction
696
+
697
+ // Sign with 2 keys
698
+ const signatures = signMultisigTransaction(
699
+ spendingTx,
700
+ 0,
701
+ [keys[0], keys[1]],
702
+ template.lock(),
703
+ 1000
704
+ )
705
+
706
+ // Create unlocking script
707
+ const unlockingScript = template.unlock(signatures, spendingTx, 0)
708
+ spendingTx.inputs[0].unlockingScript = unlockingScript.sign()
709
+
710
+ // Verify transaction
711
+ expect(spendingTx.verify()).toBe(true)
712
+ })
713
+ })
714
+ ```
715
+
716
+ ## Troubleshooting Common Issues
717
+
718
+ ### Signature Ordering Problems
719
+
720
+ **Problem**: OP_CHECKMULTISIG fails due to incorrect signature ordering.
721
+
722
+ **Solution**: Ensure signatures are provided in the same order as public keys in the locking script.
723
+
724
+ ```typescript
725
+ function orderSignatures(
726
+ signatures: Map<string, Buffer>,
727
+ publicKeys: PublicKey[]
728
+ ): Buffer[] {
729
+ const orderedSigs: Buffer[] = []
730
+
731
+ for (const pubKey of publicKeys) {
732
+ const pubKeyHex = pubKey.toString()
733
+ const pubKeyBytes = Array.from(Buffer.from(pubKeyHex, 'hex'))
734
+ if (signatures.has(pubKeyHex)) {
735
+ orderedSigs.push(signatures.get(pubKeyHex)!)
736
+ }
737
+ }
738
+
739
+ return orderedSigs
740
+ }
741
+ ```
742
+
743
+ ### OP_CHECKMULTISIG Bug
744
+
745
+ **Problem**: OP_CHECKMULTISIG consumes an extra value from the stack.
746
+
747
+ **Solution**: Always push OP_0 before signatures in the unlocking script.
748
+
749
+ ```typescript
750
+ // Correct unlocking script construction
751
+ const script = new Script()
752
+ script.writeOpCode(OP.OP_0) // Required for OP_CHECKMULTISIG bug
753
+ script.writeBin(signature1)
754
+ script.writeBin(signature2)
755
+ ```
756
+
757
+ ### Fee Calculation Errors
758
+
759
+ **Problem**: Insufficient fees cause transaction rejection.
760
+
761
+ **Solution**: Properly calculate fees based on transaction size.
762
+
763
+ ```typescript
764
+ function estimateMultisigTxSize(
765
+ inputCount: number,
766
+ outputCount: number,
767
+ threshold: number,
768
+ keyCount: number
769
+ ): number {
770
+ const baseSize = 10
771
+ const inputSize = 36 + 4 + 1 + (threshold * 73) + 1 // Include OP_0
772
+ const outputSize = 8 + 25
773
+
774
+ return baseSize + (inputCount * inputSize) + (outputCount * outputSize)
775
+ }
776
+ ```
777
+
778
+ ## Conclusion
779
+
780
+ Multi-signature transactions provide enhanced security through distributed key management. The BSV TypeScript SDK offers flexible tools for implementing various multisig patterns, from simple 2-of-3 schemes to complex threshold signatures with time locks.
781
+
782
+ Key takeaways:
783
+
784
+ - Always validate inputs and signatures
785
+ - Implement proper error handling
786
+ - Use secure key generation and storage
787
+ - Test thoroughly before production use
788
+ - Consider the OP_CHECKMULTISIG bug in script construction
789
+
790
+ For more advanced patterns, consider exploring the SDK's script template system and custom script construction capabilities.
791
+
792
+ While the `WalletClient` provides simplified transaction creation, understanding multi-signature transactions enables you to build sophisticated applications requiring multiple approvals and enhanced security.