Multisig
A bare multisig (P2MS) Output lists n Public Keys in its Locking Script, and needs
Signatures from m of them to spend: OP_m <pubkey 1> ... <pubkey n> OP_n OP_CHECKMULTISIG. On BSV this
is the usual way to do multisig, since the hash-wrapped P2SH form is legacy. The
standard script templates page in the Developer
Guide covers how OP_CHECKMULTISIG evaluates.
The examples use three keys, belonging to Alice, Bob and Carol, plus the setup from the Transaction builder.
Creating the Locking Script
Section titled “Creating the Locking Script”List<PublicKey> keys = List.of(alice.getPublicKey(), bob.getPublicKey(), carol.getPublicKey());
// 2-of-3. The keys are sorted by their hex encoding unless you pass sortKeys = false.P2MSLockBuilder locker = new P2MSLockBuilder(keys, 2);Sorting makes the script independent of the order in which the parties exchanged keys, so that everyone builds
the same script. The builder sorts a copy, so the list we pass, mutable or not, is left as it was. We pass
false as the third argument to keep our own order.
To find out what an existing multisig Output requires, we parse it.
P2MSLockBuilder parsed = new P2MSLockBuilder(locker.getLockingScript());int required = parsed.getRequiredSigs();List<PublicKey> orderInScript = parsed.getPublicKeys();Spending a multisig Output
Section titled “Spending a multisig Output”The Unlocking Script is OP_0 followed by m Signatures, in the same order as their Public Keys appear
in the Locking Script. OP_CHECKMULTISIG pops one item more than it uses, which is what the OP_0 is for.
P2MSUnlockBuilder writes that Unlocking Script for us. The builder attaches at most one signer per Input,
so we build without signers, and sign once per key.
P2MSLockBuilder locker = new P2MSLockBuilder( List.of(alice.getPublicKey(), bob.getPublicKey(), carol.getPublicKey()), 2);Transaction fundingTx = Fixtures.fundingTransaction(locker, 100_000);TransactionOutput utxo = fundingTx.getOutputs().get(0);
P2MSUnlockBuilder unlocker = new P2MSUnlockBuilder();Transaction tx = new TransactionBuilder() .spendFromTransaction(fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER, unlocker) .spendTo(new P2PKHLockBuilder(recipient), BigInteger.valueOf(50_000)) .sendChangeTo(aliceAddress) .build(true);
// We sign in the order the keys appear in the script. Sorted, that is Bob, then Alice, then Carol.new TransactionSigner(SIGHASH, bob).sign(tx, utxo, 0);new TransactionSigner(SIGHASH, alice).sign(tx, utxo, 0);Each sign() call appends a Signature to the unlocker. The signers can be on different machines. We pass the
unsigned Transaction around (serialised, see
parsing and serialising) and collect the Signatures, as
long as they end up in the unlocker in key order. Out of order, the spend fails.
// The same two Signatures in the other order fail.P2MSUnlockBuilder swapped = new P2MSUnlockBuilder();swapped.addSignature(unlocker.getSignatures().get(1));swapped.addSignature(unlocker.getSignatures().get(0));assertThrows(ScriptException.class, () -> new org.twostack.bitcoin4j.script.Interpreter().correctlySpends( swapped.getUnlockingScript(), utxo.getScript(), tx, 0, Fixtures.FLAGS, org.twostack.bitcoin4j.Coin.valueOf(100_000)));Do note that the builder sizes a multisig Input from its Locking Script, allowing for m Signatures, when it knows that script.
spendFromOutput()without a signer doesn’t pass it in, so for such an Input the builder assumes a P2PKH-sized Unlocking Script, and a larger m needs a fixed fee.

