Skip to content

Multisig

A multisig (P2MS) Locking Script names n Public Keys, and requires Signatures from m of them: OP_m <pubkey>… OP_n OP_CHECKMULTISIG. DartSV builds the script with P2MSLockBuilder, and the Unlocking Script with P2MSUnlockBuilder. This is bare multisig, with the keys in the Output itself, which is how multisig has been done on Bitcoin SV since P2SH was retired.

The examples use the keys and the fundingTransaction helper described in About the examples.

// 2-of-3: any two of these three keys can spend.
final locker = P2MSLockBuilder([aliceKey.publicKey, bobKey.publicKey, carolKey.publicKey], 2);
final lockingScript = locker.getScriptPubkey();
// OP_2 <pubkey> <pubkey> <pubkey> OP_3 OP_CHECKMULTISIG, keys sorted by hex

The builder sorts the keys by their hex encoding, unless we pass sorting: false:

// Keep the keys in the order given, e.g. to match a script made elsewhere.
final locker = P2MSLockBuilder([carolKey.publicKey, aliceKey.publicKey], 1, sorting: false);

Each signer adds one Signature, and an Input can only have one signer in the builder. We therefore build the Transaction unsigned, and sign it afterwards:

final unlocker = P2MSUnlockBuilder();
final tx = TransactionBuilder()
.spendFromTxn(fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER, unlocker) // no signer yet
.spendToPKH(Address('n3aZKucfWmXeXhX13MREQQnqNfbrWiYKtg'), BigInt.from(90000))
.sendChangeToPKH(aliceKey.toAddress(networkType: NetworkType.TEST))
.build(false);

Then each cosigner signs. In a real setup the unsigned Transaction travels to each cosigner (as hex) and the Signatures come back. Here both keys are local.

// CHECKMULTISIG wants the signatures in the same order as the keys in the script.
final keyOrder = locker.publicKeys.map((k) => k.toHex()).toList();
final cosigners = [carolKey, aliceKey] // any two of the three
..sort((a, b) => keyOrder.indexOf(a.publicKey.toHex()).compareTo(keyOrder.indexOf(b.publicKey.toHex())));
final utxo = fundingTx.outputs[0];
for (final key in cosigners) {
DefaultTransactionSigner(sighashAll, key).sign(tx, utxo, 0); // each call appends one signature
}
// Read an m-of-n script with the P2MS template.
final info = P2MSTemplate().extractScriptInfo(script);
info['threshold']; // 2
info['totalKeys']; // 3
info['publicKeys']; // List<SVPublicKey>, in script order