Multi-Signature

Complete examples for creating and spending multi-signature (multisig) transactions in various configurations.

Overview

This code feature demonstrates multi-signature transactions, which require multiple signatures to spend funds. Common use cases include corporate wallets, escrow services, and enhanced security setups. This guide covers 2-of-2, 2-of-3, 3-of-5, and custom multisig configurations.

Related SDK Components:

2-of-2 Multisig

import { Transaction, PrivateKey, PublicKey, Script, OP } from '@bsv/sdk'

/**
 * 2-of-2 Multisig
 *
 * Both parties must sign to spend the funds
 */
class TwoOfTwoMultisig {
  /**
   * Create 2-of-2 multisig locking script
   */
  createLockingScript(pubKey1: PublicKey, pubKey2: PublicKey): Script {
    const script = new Script()

    script.writeOpCode(OP.OP_2)  // Require 2 signatures
    script.writeBin(pubKey1.encode())
    script.writeBin(pubKey2.encode())
    script.writeOpCode(OP.OP_2)  // Out of 2 keys
    script.writeOpCode(OP.OP_CHECKMULTISIG)

    return script
  }

  /**
   * Create transaction with 2-of-2 multisig output
   */
  async createMultisigOutput(
    senderKey: PrivateKey,
    recipientKey1: PublicKey,
    recipientKey2: PublicKey,
    amount: number,
    utxo: { txid: string; vout: number; satoshis: number; script: Script }
  ): Promise<Transaction> {
    const tx = new Transaction()

    // Add input
    tx.addInput({
      sourceTXID: utxo.txid,
      sourceOutputIndex: utxo.vout,
      unlockingScriptTemplate: new P2PKH().unlock(senderKey),
      sequence: 0xffffffff
    })

    // Add 2-of-2 multisig output
    tx.addOutput({
      satoshis: amount,
      lockingScript: this.createLockingScript(recipientKey1, recipientKey2)
    })

    // Add change output
    const fee = 500
    const change = utxo.satoshis - amount - fee

    if (change > 546) {
      tx.addOutput({
        satoshis: change,
        lockingScript: new P2PKH().lock(senderKey.toPublicKey().toHash())
      })
    }

    await tx.sign()
    return tx
  }

  /**
   * Spend from 2-of-2 multisig (both signatures required)
   */
  async spendMultisigOutput(
    key1: PrivateKey,
    key2: PrivateKey,
    multisigUtxo: { txid: string; vout: number; satoshis: number; script: Script },
    toAddress: string,
    amount: number
  ): Promise<Transaction> {
    const tx = new Transaction()

    // Add multisig input
    tx.addInput({
      sourceTXID: multisigUtxo.txid,
      sourceOutputIndex: multisigUtxo.vout,
      unlockingScript: new Script(), // Will be filled after signing
      sequence: 0xffffffff
    })

    // Add output
    tx.addOutput({
      satoshis: amount,
      lockingScript: Script.fromAddress(toAddress)
    })

    // Create signatures
    const preimage = tx.inputs[0].getPreimage(multisigUtxo.script)
    const sig1 = key1.sign(preimage)
    const sig2 = key2.sign(preimage)

    // Build unlocking script
    const unlockingScript = new Script()
    unlockingScript.writeOpCode(OP.OP_0)  // Bug workaround for CHECKMULTISIG
    unlockingScript.writeBin(sig1.toDER())
    unlockingScript.writeBin(sig2.toDER())

    tx.inputs[0].unlockingScript = unlockingScript

    return tx
  }
}

/**
 * Usage Example
 */
async function twoOfTwoExample() {
  const multisig = new TwoOfTwoMultisig()

  // Generate keys
  const sender = PrivateKey.fromRandom()
  const key1 = PrivateKey.fromRandom()
  const key2 = PrivateKey.fromRandom()

  // Create multisig output
  const utxo = {
    txid: 'abc123...',
    vout: 0,
    satoshis: 100000,
    script: new P2PKH().lock(sender.toPublicKey().toHash())
  }

  const createTx = await multisig.createMultisigOutput(
    sender,
    key1.toPublicKey(),
    key2.toPublicKey(),
    50000,
    utxo
  )

  console.log('Created multisig output:', createTx.id('hex'))

  // Spend from multisig (requires both keys)
  const multisigUtxo = {
    txid: createTx.id('hex'),
    vout: 0,
    satoshis: 50000,
    script: multisig.createLockingScript(
      key1.toPublicKey(),
      key2.toPublicKey()
    )
  }

  const spendTx = await multisig.spendMultisigOutput(
    key1,
    key2,
    multisigUtxo,
    '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa',
    49000
  )

  console.log('Spent from multisig:', spendTx.id('hex'))
}

2-of-3 Multisig

3-of-5 Multisig

Custom M-of-N Multisig

See Also

SDK Components:

Learning Paths:

Last updated