In this post I’ll show you how I generate multisig bitcoin addresses in python3.6. In a previous post, we have seen how to generate P2PKH addresses.

With multisig addresses, you can delegate the expenditure of an UTXO to a set of public keys. Doing so, you can manage complex scenarios like death, lost keys, kidnapping or extortion.

We have already seen how to generate public keys so we shall to focus on the new parts of code to handle multisig generation.

Bitcoin P2SH

In contrast to what a lot of people think, Bitcoin has smart contracts. However, the language is not Turing complete and only a set of scripts are valid for a standard transaction. Standard transactions are those who are propagated by bitcoin nodes (you can activate the propagation of non standard programs but it’s not the default). Despite it can be seen as a limitation, it bring us much more security than other alternatives. It’s what we would expect from a platform where a lot of value will be stored.

Standard transactions

The following scripts are available for a standard pubkey:

  • Pubkey (P2PK) (first bitcoin 0.1 release)
    • Script PubKey: <pubkey> OP_CHECKSIG
    • ScriptSig: “<sig>“
    • witness: “(empty)“
  • Pay To Public Key Hash (first bitcoin 0.1 release)
    • ScriptPubKey: “OP_DUP OP_HASH160 <PubkeyHash> OP_EQUALVERIFY OP_CHECKSIG“
    • ScriptSig: “<sig> <PubKey>“
    • witness: “(empty)“
  • Pay To Witness Public Key Hash (P2WPKH) [BIP-141]
    • ScriptPubKey: “0 <v0-witness program>“
    • ScriptSig: “(empty)“
    • witness: “<sig> <PubKey>“
  • Pay To Script Hash (P2SH) [BIP-16]
    • ScriptPubKey: “OP_HASH160 <Hash160(redeemScript)> OP_EQUAL“
    • ScriptSig: “<sig> [sig] [sig…] <redeemScript>“
    • witness: “(empty)“
  • Pay To Witness Script Hash (P2WSH) [BIP-141]
    • ScriptPubKey: “0 <v0-witness program>“
    • ScriptSig: “(empty)“
    • witness: “0 <sig> [sig] [sig…] <v0-witnessScript>“
  • Pay To Script Hash Witness version (P2SH-P2WPKH/P2WSH) [BIP-141]
    • ScriptPubKey: “OP_HASH160 <Hash160(P2SH witness program)> OP_EQUAL“
    • ScriptSig: “0 <redeemScript>“
    • witness:
      • Option 1 (P2WPK): “<sig> <PubKey>“
      • Option 2 (P2WSH): “0 <sig> [sig] [sig…] <redeemScript>“
  • Multisig [BIP-11]
    • ScriptPubKey: “<m> <A pubkey> [B pubkey] [C pubkey…] <n> OP_CHECKMULTISIG“
    • ScriptSig: “OP_0 <A sig> [B sig] [C sig…]“
    • witness: “(empty)“
  • Null Data (it creates an UTXO that cannot be spent, we’ll ignore this case)
    • ScriptPubKey: “OP_RETURN <0 to 40 bytes of data>“
    • ScriptSig: “(Null data scripts cannot be spent, so there’s no signature script.)“
    • witness: “(empty)“

Generating the redeem script

Despite there is the opcode OP_CHECKMULTISIG, multisig addresses are now widely generated under the P2SH address schema. In a nutshell, with P2SH you are sending funds to a script. You, as a sender, only know the hash of this script. When the receiver wants to expend funds, he sends the redeem script and the needed data (signatures, etc.) for the script to execute successfully.

The redeem script addresses were defined in BIP-13. The valid redeem script schemes are:

  • P2PK: <pubkey> OP_CHECKSIG
  • Multisig: <m> <A pubkey> [B pubkey] [C pubkey…] <n> OP_CHECKMULTISIG

That’s the reason it’s commonly called P2SH-P2PKH, etc. Because you are paying to a script where the type of the script is a P2PKH. Remind that when you send funds to a script, you don’t know what type the script is. The redeem conditions are delegated to the receiver.

First, let’s define our multisig address:

  • 2 public keys will be able to unlock funds
  • From a total of 3 public keys

The 3 public keys are (you can generate them following the previous post):

  1. 04bb02aad88960b2b93c6b4a61b683966c3720e70ae7da71676129f441a095787e239b3e2ad9305f1011241d106fa0adabfb2eefe264b52a108051f324b01d4109
  2. 0497647726c390d855b7d208c841bf4a250816998eb169b60eaf3b9f7e058f42c8b4f8c9b6e0a4206dcabbf1ba851f56eff6c746e378aa62bf8bf25da0a10b65af
  3. 0460d434c9a71ec0eec0cf5905604a56c904d2cc4895e5d77292682be73cd2550224433462264a36e30d008f21daeb2d3ecf91fa6a87194ddaa00f9f926c3a9428

Then, we can construct our redeem script:

<m> <A pubkey> <B pubkey> <C pubkey> <n> OP_CHECKMULTISIG

Where (all data is represented in hexadecimal):

  • m = 2 = OP_2 = 52
  • A pubkey = 04bb02aad88960b2b93c6b4a61b683966c3720e70ae7da71676129f441a095787e239b3e2ad9305f1011241d106fa0adabfb2eefe264b52a108051f324b01d4109
  • B pukey = 0497647726c390d855b7d208c841bf4a250816998eb169b60eaf3b9f7e058f42c8b4f8c9b6e0a4206dcabbf1ba851f56eff6c746e378aa62bf8bf25da0a10b65af
  • C pubkey = 0460d434c9a71ec0eec0cf5905604a56c904d2cc4895e5d77292682be73cd2550224433462264a36e30d008f21daeb2d3ecf91fa6a87194ddaa00f9f926c3a9428
  • n = 3 = OP_3 = 53

You can check the hexadecimal values of the opcodes in this page. If you want to know how this scripts are being executed, you can see this video from Andreas Antonopoulos.

For the data that is not an opcode, you have to specify the size, in bytes, before the data, ie, we have to prefix the addresses with the 41 hexadecimal number (that’s is 65 bytes or 132 bits).

Finally, we concatenate all the data:


Generating the bitcoin address

Once we have the redeem script, we follow the BIP-13 specification:

    base58-encode: [one-byte version][20-byte hash][4-byte checksum]

For mainnet, the one-byte version is 05. The 20-byte hash is the hash160 of the previous script:

def get_address_for_P2PSH_payments(self, redeemScript, encoding="base58"):
        address_version_byte = binascii.unhexlify("05")
        addr_without_checksum = address_version_byte + hash160(binascii.unhexlify(redeemScript)).digest()
        btc_addr = base58.b58encode_check(addr_without_checksum)
        return (btc_addr.decode(), redeemScript)


The previous script give us the address: 338Qs329uDr8t12YNVdfBJqFWR9NoHdxTU


Python code

Reedem script generator

    def p2multisig_script(self, pubKeyList, n=1, m=1):
        redeemScript = binascii.unhexlify(hex(n + 80)[2:])

        uncompressed_pubkey = binascii.unhexlify("41") + binascii.unhexlify(pubKeyList[0].encode('utf-8'))
        redeemScript = redeemScript + uncompressed_pubkey

        for pubKey in pubKeyList[1:]:
            uncompressed_pubkey = binascii.unhexlify("41") + binascii.unhexlify(pubKey.encode('utf-8'))
            redeemScript = redeemScript + uncompressed_pubkey

        redeemScript = redeemScript + binascii.unhexlify(hex(m + 80)[2:]) + binascii.unhexlify("ae")
        return binascii.hexlify(redeemScript).decode()

Leave a comment

Your email address will not be published. Required fields are marked *