Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

MultiSig API

Multi-Signature (MultiSig) is implemented in src/multisig.rs and supports Bitcoin’s M-of-N signature scheme: out of N participants, at least M valid ECDSA signatures are required to authorize fund movement.


Core Concepts

M-of-N signatures: N participants each hold a key pair; any M of them can authorize a transaction. Common combinations:

TypeMeaningTypical Use Case
2-of-2Both must agreeJoint accounts, partnerships
2-of-3Any two of three agreeCorporate fund management, escrow services
3-of-5Any three of five agreeLarge institutions, board decisions

Address format: Multisig addresses start with "3", corresponding to Bitcoin’s P2SH (Pay-to-Script-Hash) format.


MultiSigAddress Struct

Represents an M-of-N multisig address and its configuration.

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MultiSigAddress {
    pub address: String,          // Multisig address ("3"-prefixed, P2SH format)
    pub required_sigs: usize,     // Required number of signatures M
    pub total_keys: usize,        // Total number of keys N
    pub public_keys: Vec<String>, // Public key list for all participants
    pub script: String,           // Locking script (simplified Script code)
}
}

Field Descriptions

FieldTypeDescription
addressStringMultisig address starting with "3", generated by taking 42 characters of the SHA256 hash of the locking script.
required_sigsusizeM value: minimum number of valid signatures required to spend funds.
total_keysusizeN value: total number of participants (equals public_keys.len()).
public_keysVec<String>Public key list of all participants (hex-encoded).
scriptStringLocking script in the format OP_{M}{pubkeys...}OP_CHECKMULTISIG.

MultiSigAddress Methods

MultiSigAddress::new

Creates an M-of-N multisig address. Validates parameter legality, then generates the locking script and the corresponding P2SH address.

#![allow(unused)]
fn main() {
pub fn new(
    required_sigs: usize,
    public_keys: Vec<String>,
) -> Result<Self, String>
}

Parameters:

  • required_sigs — Required number of signatures M; must satisfy 1 <= M <= N.
  • public_keys — List of all participants’ public keys; length equals N, maximum 15.

Return value:

  • Ok(MultiSigAddress) — Created successfully.
  • Err(String) — Invalid parameters; error reasons include:
    • "Invalid signature requirement"required_sigs == 0 or required_sigs > total_keys.
    • "Maximum 15 keys supported"public_keys.len() > 15 (Bitcoin protocol limit).
#![allow(unused)]
fn main() {
use simplebtc::multisig::MultiSigAddress;
use simplebtc::wallet::Wallet;

// Create three participant wallets
let wallet1 = Wallet::new();
let wallet2 = Wallet::new();
let wallet3 = Wallet::new();

let public_keys = vec![
    wallet1.public_key.clone(),
    wallet2.public_key.clone(),
    wallet3.public_key.clone(),
];

// Create 2-of-3 multisig address
let multisig = MultiSigAddress::new(2, public_keys).unwrap();
assert!(multisig.address.starts_with('3'));
assert_eq!(multisig.required_sigs, 2);
assert_eq!(multisig.total_keys, 3);
println!("Multisig address: {}", multisig.address);
println!("Locking script: {}", multisig.script);

// Parameter validation error examples
let result = MultiSigAddress::new(0, vec!["key1".to_string()]);
assert!(result.is_err()); // required_sigs cannot be 0

let result = MultiSigAddress::new(3, vec!["key1".to_string(), "key2".to_string()]);
assert!(result.is_err()); // required_sigs(3) > total_keys(2)
}

MultiSigAddress::verify_signatures

Quickly checks whether the number of signatures meets the requirement (does not verify signature content, only validates count).

#![allow(unused)]
fn main() {
pub fn verify_signatures(&self, signatures: &[String]) -> bool
}

Parameters:

  • signatures — List of signatures.

Return value: signatures.len() >= self.required_sigs.

#![allow(unused)]
fn main() {
let sigs = vec!["sig1".to_string(), "sig2".to_string()];
let count_ok = multisig.verify_signatures(&sigs);
println!("Signature count meets requirement: {}", count_ok); // true (2 >= 2)
}

MultiSigAddress::verify_signatures_with_data

Full signature verification: not only checks count, but also uses ECDSA to verify whether each signature was produced by one of the keys in public_keys.

#![allow(unused)]
fn main() {
pub fn verify_signatures_with_data(
    &self,
    signatures: &[String],
    data: &str,
) -> bool
}

Parameters:

  • signatures — List of ECDSA signatures in hex DER encoding.
  • data — The original data string that was signed (typically a transaction ID or transaction digest).

Return value:

  • true — Number of valid signatures >= required_sigs. Each signature matches at most one public key (preventing the same signature from being counted twice).
  • false — Insufficient valid signatures, or signatures do not correspond to any key in public_keys.
#![allow(unused)]
fn main() {
// Sign transaction data with wallet1 and wallet2
let data = "Transaction digest: Alice transfers 0.01 BTC to Bob";
let sig1 = wallet1.sign(data);
let sig2 = wallet2.sign(data);

let valid = multisig.verify_signatures_with_data(
    &[sig1, sig2],
    data,
);
println!("ECDSA verification passed: {}", valid);
}

MultiSigTxBuilder Struct

The multisig transaction builder is responsible for collecting signatures and confirming whether the M-of-N requirement is met. Built-in duplicate signature detection.

#![allow(unused)]
fn main() {
pub struct MultiSigTxBuilder {
    pub multisig_address: MultiSigAddress,
    pub signatures: Vec<String>,
    // signed_keys: HashMap<String, bool>  // Private field, prevents same public key from signing twice
}
}
FieldTypeDescription
multisig_addressMultiSigAddressAssociated multisig address configuration (includes M, N, public key list).
signaturesVec<String>List of collected valid ECDSA signatures.

MultiSigTxBuilder Methods

MultiSigTxBuilder::new

Creates a multisig transaction builder associated with the specified MultiSigAddress.

#![allow(unused)]
fn main() {
pub fn new(multisig_address: MultiSigAddress) -> Self
}

Parameters:

  • multisig_address — An already-created MultiSigAddress instance.
#![allow(unused)]
fn main() {
use simplebtc::multisig::MultiSigTxBuilder;

let builder = MultiSigTxBuilder::new(multisig);
assert_eq!(builder.signatures.len(), 0);
assert!(!builder.is_complete());
}

MultiSigTxBuilder::add_signature

Adds an ECDSA signature from a participant. Internally validates automatically:

  1. The wallet’s public key must be in multisig_address.public_keys.
  2. The wallet cannot sign twice (prevents the same person from signing twice to fake M signatures).
#![allow(unused)]
fn main() {
pub fn add_signature(
    &mut self,
    wallet: &Wallet,
    data: &str,
) -> Result<(), String>
}

Parameters:

  • wallet — The participant’s Wallet instance (used to call wallet.sign(data) to generate a signature).
  • data — Data to sign (typically a transaction digest or transaction ID).

Return value:

  • Ok(()) — Signature added successfully.
  • Err(String) — Error reasons:
    • "This wallet is not in the multisig address" — The wallet’s public key is not in the public_keys list.
    • "This wallet has already signed" — The wallet has previously signed.
#![allow(unused)]
fn main() {
let mut builder = MultiSigTxBuilder::new(multisig.clone());
let data = "transfer_tx_hash_abc123";

// wallet1 signs successfully
builder.add_signature(&wallet1, data).unwrap();
assert_eq!(builder.signatures.len(), 1);

// wallet1 cannot sign again
let err = builder.add_signature(&wallet1, data);
assert!(err.is_err());
println!("Duplicate signature error: {}", err.unwrap_err()); // "This wallet has already signed"

// A wallet not in the multisig address cannot sign
let outsider = Wallet::new();
let err = builder.add_signature(&outsider, data);
assert!(err.is_err());
println!("External wallet error: {}", err.unwrap_err()); // "This wallet is not in the multisig address"
}

MultiSigTxBuilder::is_complete

Checks whether enough signatures have been collected (signatures.len() >= required_sigs).

#![allow(unused)]
fn main() {
pub fn is_complete(&self) -> bool
}

Return value: true means the M-of-N requirement is satisfied and the transaction can be broadcast.

#![allow(unused)]
fn main() {
let mut builder = MultiSigTxBuilder::new(multisig);
assert!(!builder.is_complete()); // 0 signatures

builder.add_signature(&wallet1, data).unwrap();
assert!(!builder.is_complete()); // 1 signature, not enough yet (needs 2)

builder.add_signature(&wallet2, data).unwrap();
assert!(builder.is_complete());  // 2 signatures, satisfies 2-of-3
println!("Multisig complete, transaction can be broadcast");
}

MultiSigTxBuilder::get_signatures

Gets a copy of all collected signatures.

#![allow(unused)]
fn main() {
pub fn get_signatures(&self) -> Vec<String>
}

Return value: Vec<String> — Clone of the signature list (does not affect the builder’s internal state).

#![allow(unused)]
fn main() {
let sigs = builder.get_signatures();
println!("Collected {} signatures", sigs.len());
for (i, sig) in sigs.iter().enumerate() {
    println!("  Signature {}: {}...", i + 1, &sig[..16]);
}
}

MultiSigType Enum (Convenience API)

Provides quick creation for common multisig types.

#![allow(unused)]
fn main() {
pub enum MultiSigType {
    TwoOfTwo,    // 2-of-2
    TwoOfThree,  // 2-of-3 (most common)
    ThreeOfFive, // 3-of-5
}

impl MultiSigType {
    pub fn create_address(&self, wallets: &[Wallet]) -> Result<MultiSigAddress, String>
}
}
#![allow(unused)]
fn main() {
use simplebtc::multisig::MultiSigType;
use simplebtc::wallet::Wallet;

let wallets: Vec<Wallet> = (0..3).map(|_| Wallet::new()).collect();
let ms_addr = MultiSigType::TwoOfThree.create_address(&wallets).unwrap();
println!("2-of-3 address: {}", ms_addr.address);

// Returns an error if the number of wallets does not match
let err = MultiSigType::ThreeOfFive.create_address(&wallets); // Requires 5 wallets
assert!(err.is_err());
}

Complete Usage Examples

Scenario 1: Corporate Fund Management (2-of-3)

#![allow(unused)]
fn main() {
use simplebtc::multisig::{MultiSigAddress, MultiSigTxBuilder};
use simplebtc::wallet::Wallet;

fn corporate_treasury() {
    // Three company executives each hold one key
    let ceo = Wallet::new();
    let cfo = Wallet::new();
    let cto = Wallet::new();

    // Create 2-of-3 corporate multisig address
    let pub_keys = vec![
        ceo.public_key.clone(),
        cfo.public_key.clone(),
        cto.public_key.clone(),
    ];
    let treasury = MultiSigAddress::new(2, pub_keys).unwrap();
    println!("Corporate treasury address: {}", treasury.address);

    // Initiate a payment (requires CEO + CFO signatures)
    let tx_data = "Pay 10 BTC to supplier ABC";
    let mut builder = MultiSigTxBuilder::new(treasury);

    builder.add_signature(&ceo, tx_data).unwrap();
    println!("CEO has signed, waiting for second authorization...");

    builder.add_signature(&cfo, tx_data).unwrap();
    println!("CFO has signed");

    if builder.is_complete() {
        let signatures = builder.get_signatures();
        println!("Transaction authorized, signature count: {}", signatures.len());
        // Here, attach signatures to the transaction and broadcast to the network
    }
}
}

Scenario 2: Third-Party Escrow (Buyer-Seller-Arbitrator)

#![allow(unused)]
fn main() {
fn escrow_service() {
    let buyer  = Wallet::new();
    let seller = Wallet::new();
    let arbiter = Wallet::new();

    let pub_keys = vec![
        buyer.public_key.clone(),
        seller.public_key.clone(),
        arbiter.public_key.clone(),
    ];

    // 2-of-3: normally buyer+seller; in a dispute, either party + arbitrator
    let escrow = MultiSigAddress::new(2, pub_keys).unwrap();
    println!("Escrow address: {}", escrow.address);

    let release_tx = "Release escrow funds to seller address";

    // Normal flow: buyer confirms receipt; buyer+seller sign to release funds
    let mut builder = MultiSigTxBuilder::new(escrow.clone());
    builder.add_signature(&buyer, release_tx).unwrap();
    builder.add_signature(&seller, release_tx).unwrap();
    assert!(builder.is_complete());
    println!("Funds released normally to seller");

    // Dispute flow: arbitrator intervenes; seller+arbitrator sign to release funds
    let dispute_tx = "Dispute ruling: refund to buyer";
    let mut dispute_builder = MultiSigTxBuilder::new(escrow);
    dispute_builder.add_signature(&buyer, dispute_tx).unwrap();
    dispute_builder.add_signature(&arbiter, dispute_tx).unwrap();
    assert!(dispute_builder.is_complete());
    println!("Arbitration complete, funds returned to buyer");
}
}

Scenario 3: Full Signature Verification Flow

#![allow(unused)]
fn main() {
fn full_verification() {
    let w1 = Wallet::new();
    let w2 = Wallet::new();

    let pub_keys = vec![w1.public_key.clone(), w2.public_key.clone()];
    let ms = MultiSigAddress::new(2, pub_keys).unwrap(); // 2-of-2

    let tx_data = "Transfer 1 BTC";

    // Collect signatures
    let sig1 = w1.sign(tx_data);
    let sig2 = w2.sign(tx_data);
    let sigs = vec![sig1, sig2];

    // Full ECDSA verification (verifies signatures were produced by keys in public_keys)
    let valid = ms.verify_signatures_with_data(&sigs, tx_data);
    println!("ECDSA multisig verification: {}", valid);

    // Quick count check (does not verify content)
    let count_ok = ms.verify_signatures(&sigs);
    println!("Signature count satisfied: {}", count_ok);
}
}

Limitations and Notes

  • Maximum keys: Due to Bitcoin script limits, N is at most 15 (returns an error if exceeded).
  • Duplicate signature prevention: MultiSigTxBuilder internally maintains a set of already-signed public keys; calling add_signature twice with the same wallet returns an error.
  • Signature order: Verification does not require signatures to be in the same order as the public keys; any M valid signatures will do.
  • Zero-confirmation risk: A multisig transaction is still unconfirmed before being mined. For important transactions, wait for at least 1 block confirmation.

  • Wallet — Provides sign() and verify_signature() methods, the foundation of multisig ECDSA operations.
  • AdvancedTxBuilder — Can be combined with timelocks to implement advanced scenarios such as “reduced multisig requirement after time expiry.”
  • Advanced Modules Overview — Understand multisig’s place in SimpleBTC’s overall architecture.