Standard Script Templates

At some point in Bitcoin’s history, the developers made the decision to restrict the types of Locking / Unlocking script combinations that the Bitcoin network would accept to only a few commonly used ones. This, in addition to limiting the overall size of a script meant that the potential application-development use-cases for Bitcoin were effectively restricted to those wherein one could leverage these established limitations. Although the BitcoinSV network is no longer restricted in this manner, the language and monikers for these Transaction Templates remain.

In this section we will examine these most common Transaction Templates (also called Script Templates), and start building actual real-world Transactions with them.

P2PK

Payto-Public-Key should sound familiar. In our earlier example we demonstrated how this script type uses a public key in the locking script. Let’s delve a little deeper into the workings of this method of locking and unlocking, and how it can be used within the Wallet SDK.

The P2PK Transaction format was the original canonical way of sending bitcoin between parties. As such, this influenced the naming of the input and output script. Today we still refer to locking scripts as scriptPubkey and unlocking scripts as scriptSig.

Locking Script (scriptPubkey)

The locking script contains a Public Key followed by an OP_CHECKSIG operation.

<pubkey> OP_CHECKSIG

Unlocking Script (scriptSig)

The unlocking script requires just an ECDSA Signature.

<sig>

Script execution

Script execution proceeds in the normal way by concatenating the scriptSig++scriptPubkey and then executing the combined script as one.

<sig> <pubkey> OP_CHECKSIG

Code example

Let’s take a quick look at how we can use the Script Interpreter directly to experiment with combining scriptPubkey and scriptSig. The following example is a little contrived because one would still require access to a Signature and Public Key, and the Signature can only be obtained by building an actual Transaction. For now assume that we have existing values for Signature and Public Key with which to play.


  var interpreter = Interpreter();

  //signature truncated for readability
  //FIXME: OP_PUSHDATA with size to push
  var scriptSig = SVScript.fromString("3045022100c728eac064154edba15d4f3....");

  //public key truncated for readability
  //FIXME: OP_PUSHDATA with size to push
  var scriptPubkey = SVScript.fromString("0223078d2942df62c45621d209fab84ea9a... OP_CHECKSIG");

  final result = interpreter.verifyScript(scriptSig, scriptPubkey);

The above example is useful for validating that the locking/unlocking combination works. However in practice, when working with transactions :

  • the developer uses a locking script from an existing Transaction
  • the developer needs to create a new Transaction with the appropriate “unlocking” script and one or more locking scripts.

Right now we don’t have an existing P2PK UTXO to spend from. We will need to create one first. Unfortunately the bitcoin-cli does not allow us to create this type of transaction directly, so what we will have to do is to create a new Transaction that:

  • Consumes an existing P2PKH output (UTXO)
  • Sends the funds to a P2PK output (UTXO)

Let’s do that first.

P2PKH UTXO => P2PK UTXO

Use the bitcoin-cli to send 100 bitcoins to our address at mu4DpTaD75nheE4z5CQazqm1ivej1vzL4L

bin/bitcoin-cli -datadir=$BITCOIN_HOME/data sendtoaddress mu4DpTaD75nheE4z5CQazqm1ivej1vzL4L 100

c8facd8e607e7e8ca46d2de853748ce1a75fcdcec29e62e2db6600f0e3a7e70d

Grab the raw transaction data from the raw Transaction data from the Transaction ID created above.

bin/bitcoin-cli -datadir=$BITCOIN_HOME/data getrawtransaction c8facd8e607e7e8ca46d2de853748ce1a75fcdcec29e62e2db6600f0e3a7e70d

0200000003456fb9099cbdb9635c8fa11d3f70a298c37d8c120d7561083c73e6c4d59abbf6000000004847304402203bf1ccf68e27f391bdcb5570af4448e71c6942836fa34b8f36cde678365cf52e02200fd6f2317ca201821be01e6d34b69bb7449d95ef4af29b739c27ecf4671d8be541feffffff485b7d23ed99a00ff45e2af98a0b7b9238a1ecb6fa8ab6b3a33924270565eaa2000000006a47304402204bcafc7997d64087ed86b8734e83aa514aaac3a04fc3884a02087fea62402bb00220512cd5b8d22c1eb2df96e9aa7c07aade1c913a8f2347ddb8f0548f560269f8f0412102cc979cafbdcd782a7d87cf74017728adb0b49e7beacce8cea8882edde1bd59cffeffffff71d368967c6c6c6e7ec666f2e064cbd269af0bdde3b5f5c2e1e0fffae2d1b16f0000000049483045022100d250f8c4b2f8881c9605eb5f48e0c3631b4a412fb3f127f28ffae66adc82243302200a0291c54a13493e07e6be7813a9583043124a6e38570222d2fe7170b89e78cd41feffffff0200e40b54020000001976a91494837d2d5d6106aa97db38957dcc294181ee91e988ac08cdfa02000000001976a91446a26feff80687ba84431fe81f34cd44cf28776088ac94000000

Let’s now decode this raw transaction with bitcoin-cli to see which output we will be spending from. The decoded transaction in JSON format:

{
  "txid": "c8facd8e607e7e8ca46d2de853748ce1a75fcdcec29e62e2db6600f0e3a7e70d",
  "hash": "c8facd8e607e7e8ca46d2de853748ce1a75fcdcec29e62e2db6600f0e3a7e70d",
  "version": 2,
  "size": 452,
  "locktime": 148,
  "vin": [
    {
      "txid": "f6bb9ad5c4e6733c0861750d128c7dc398a2703f1da18f5c63b9bd9c09b96f45",
      "vout": 0,
      "scriptSig": {
        "asm": "304402203bf1ccf68e27f391bdcb5570af4448e71c6942836fa34b8f36cde678365cf52e02200fd6f2317ca201821be01e6d34b69bb7449d95ef4af29b739c27ecf4671d8be5[ALL|FORKID]",
        "hex": "47304402203bf1ccf68e27f391bdcb5570af4448e71c6942836fa34b8f36cde678365cf52e02200fd6f2317ca201821be01e6d34b69bb7449d95ef4af29b739c27ecf4671d8be541"
      },
      "sequence": 4294967294
    },
    {
      "txid": "a2ea6505272439a3b3b68afab6eca138927b0b8af92a5ef40fa099ed237d5b48",
      "vout": 0,
      "scriptSig": {
        "asm": "304402204bcafc7997d64087ed86b8734e83aa514aaac3a04fc3884a02087fea62402bb00220512cd5b8d22c1eb2df96e9aa7c07aade1c913a8f2347ddb8f0548f560269f8f0[ALL|FORKID] 02cc979cafbdcd782a7d87cf74017728adb0b49e7beacce8cea8882edde1bd59cf",
        "hex": "47304402204bcafc7997d64087ed86b8734e83aa514aaac3a04fc3884a02087fea62402bb00220512cd5b8d22c1eb2df96e9aa7c07aade1c913a8f2347ddb8f0548f560269f8f0412102cc979cafbdcd782a7d87cf74017728adb0b49e7beacce8cea8882edde1bd59cf"
      },
      "sequence": 4294967294
    },
    {
      "txid": "6fb1d1e2faffe0e1c2f5b5e3dd0baf69d2cb64e0f266c67e6e6c6c7c9668d371",
      "vout": 0,
      "scriptSig": {
        "asm": "3045022100d250f8c4b2f8881c9605eb5f48e0c3631b4a412fb3f127f28ffae66adc82243302200a0291c54a13493e07e6be7813a9583043124a6e38570222d2fe7170b89e78cd[ALL|FORKID]",
        "hex": "483045022100d250f8c4b2f8881c9605eb5f48e0c3631b4a412fb3f127f28ffae66adc82243302200a0291c54a13493e07e6be7813a9583043124a6e38570222d2fe7170b89e78cd41"
                },
      "sequence": 4294967294
    }
  ],
  "vout": [
    {
      "value": 100.00,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 94837d2d5d6106aa97db38957dcc294181ee91e9 OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a91494837d2d5d6106aa97db38957dcc294181ee91e988ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "mu4DpTaD75nheE4z5CQazqm1ivej1vzL4L"
        ]
      }
    },
    {
      "value": 0.4999092,
      "n": 1,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 46a26feff80687ba84431fe81f34cd44cf287760 OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a91446a26feff80687ba84431fe81f34cd44cf28776088ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "mmxSBpYURuQymNYUD2o4wTNrfv8h7DNmZu"
        ]
      }
    }
  ],
 "hex": "0200000003456fb9099cbdb9635c8fa11d3f70a298c37d8c120d7561083c73e6c4d59abbf6000000004847304402203bf1ccf68e27f391bdcb5570af4448e71c6942836fa34b8f36cde678365cf52e02200fd6f2317ca201821be01e6d34b69bb7449d95ef4af29b739c27ecf4671d8be541feffffff485b7d23ed99a00ff45e2af98a0b7b9238a1ecb6fa8ab6b3a33924270565eaa2000000006a47304402204bcafc7997d64087ed86b8734e83aa514aaac3a04fc3884a02087fea62402bb00220512cd5b8d22c1eb2df96e9aa7c07aade1c913a8f2347ddb8f0548f560269f8f0412102cc979cafbdcd782a7d87cf74017728adb0b49e7beacce8cea8882edde1bd59cffeffffff71d368967c6c6c6e7ec666f2e064cbd269af0bdde3b5f5c2e1e0fffae2d1b16f0000000049483045022100d250f8c4b2f8881c9605eb5f48e0c3631b4a412fb3f127f28ffae66adc82243302200a0291c54a13493e07e6be7813a9583043124a6e38570222d2fe7170b89e78cd41feffffff0200e40b54020000001976a91494837d2d5d6106aa97db38957dcc294181ee91e988ac08cdfa02000000001976a91446a26feff80687ba84431fe81f34cd44cf28776088ac94000000"
}

We can see that there is an address (mu4DpTaD75nheE4z5CQazqm1ivej1vzL4L) which is the one associated with a Private Key we control, that has received 100 bitcoins. The UTXO associated with that address has index n:0, which is index number zero (0).

We can now construct a spending Transaction which takes in a P2PKH Transaction, and spends it to a P2PK output:

  var privateKey = SVPrivateKey.fromWIF("cVVvUsNHhbrgd7aW3gnuGo2qJM45LhHhTCVXrDSJDDcNGE6qmyCs");
  var changeAddress = Address("n3aZKucfWmXeXhX13MREQQnqNfbrWiYKtg"); //bitcoin-cli wallet address

  //rawtx for TxID : c8facd8e607e7e8ca46d2de853748ce1a75fcdcec29e62e2db6600f0e3a7e70d
  var rawTx = '0200000003456fb9099cbdb9635c8fa11d3f70a298c37d8c120d7561083c73e6c4d59abbf6000000004847304402203bf1ccf68e27f391bdcb5570af4448e71c6942836fa34b8f36cde678365cf52e02200fd6f2317ca201821be01e6d34b69bb7449d95ef4af29b739c27ecf4671d8be541feffffff485b7d23ed99a00ff45e2af98a0b7b9238a1ecb6fa8ab6b3a33924270565eaa2000000006a47304402204bcafc7997d64087ed86b8734e83aa514aaac3a04fc3884a02087fea62402bb00220512cd5b8d22c1eb2df96e9aa7c07aade1c913a8f2347ddb8f0548f560269f8f0412102cc979cafbdcd782a7d87cf74017728adb0b49e7beacce8cea8882edde1bd59cffeffffff71d368967c6c6c6e7ec666f2e064cbd269af0bdde3b5f5c2e1e0fffae2d1b16f0000000049483045022100d250f8c4b2f8881c9605eb5f48e0c3631b4a412fb3f127f28ffae66adc82243302200a0291c54a13493e07e6be7813a9583043124a6e38570222d2fe7170b89e78cd41feffffff0200e40b54020000001976a91494837d2d5d6106aa97db38957dcc294181ee91e988ac08cdfa02000000001976a91446a26feff80687ba84431fe81f34cd44cf28776088ac94000000';

  var txWithUtxo = Transaction.fromHex(rawTx);
  var utxo = txWithUtxo.outputs[0];

  var recipient = privateKey.toAddress(networkType: NetworkType.REGTEST);

  //create a P2PKHUnlockBuilder that lets us spend the P2PKH output
  var unlockBuilder = P2PKHUnlockBuilder(privateKey.publicKey);

  //create a P2PKLockBuilder which will render our P2PK output script
  var recipientLockBuilder = P2PKLockBuilder(privateKey.publicKey);

  //we create a P2PKH output for our change (just for kicks)
  var changeLockBuilder = P2PKHLockBuilder(changeAddress);

  var spendingTx = Transaction()
                   .spendFromOutput(utxo, Transaction.NLOCKTIME_MAX_VALUE, scriptBuilder: unlockBuilder)
                   .spendTo(recipient, BigInt.from(100000000), scriptBuilder: recipientLockBuilder) //send myself one bitcoin
                   .sendChangeTo(changeAddress, scriptBuilder: changeLockBuilder);

  spendingTx.signInput(0, privateKey, sighashType: SighashType.SIGHASH_ALL | SighashType.SIGHASH_FORKID );

  print(spendingTx.serialize());

The above code produces a new transaction which we can broadcast to the blockchain using bitcoin-cli

 bin/bitcoin-cli -datadir=$BITCOIN_HOME/data sendrawtransaction 0100000001dbc9967fdddedc98f2eda81391065baad8e45e0249923f5ff6499c20bb6b84d8000000006b483045022100813f54ad333f94e1e84bc00cece30891643759f7f36e8e3355b35ba2a20aa620022037ccd26e33141a54b1a3c5c6fbf98045190b06390d05bcb00f2ad0466989e3fd4121029ddb9308c0dab96d047bbc99191ef60d3882e03dd8f49b8811abe1c9f0b3389dffffffff0280f0fa02000000001976a914f20142228fee3771756cbf6a533b1c01b238fb6488ac71effa02000000001976a91494837d2d5d6106aa97db38957dcc294181ee91e988ac00000000

ffa0dc16493aa316be1aa5ab71c22eab69386e578085b14948c9b76ce712018a

P2PK UTXO => P2PK UTXO

We take this new transaction ID (ffa0dc16493aa316be1aa5ab71c22eab69386e578085b14948c9b76ce712018a), and get the raw Transaction. This is our Transaction which should contain one P2PK UTXOs.

 bin/bitcoin-cli -datadir=$BITCOIN_HOME/data getrawtransaction ffa0dc16493aa316be1aa5ab71c22eab69386e578085b14948c9b76ce712018a
bin/bitcoin-cli -datadir=$BITCOIN_HOME/data decoderawtransaction 01000000010de7a7e3f00066dbe2629ec2cecd5fa7e18c7453e82d6da48c7e7e608ecdfac8000000006b483045022100e17a1142531ca5ae2a5a2b9fe486f5e0d89270f39c51e6e8c88448b2cba6380a02206a33a5dab599e485779373d3a5e0616ec7eb6cea05c4f1e0cbf8370ae01b51b94121029ddb9308c0dab96d047bbc99191ef60d3882e03dd8f49b8811abe1c9f0b3389dffffffff0200e1f505000000002321029ddb9308c0dab96d047bbc99191ef60d3882e03dd8f49b8811abe1c9f0b3389dace701164e020000001976a914f20142228fee3771756cbf6a533b1c01b238fb6488ac00000000
{
  "txid": "ffa0dc16493aa316be1aa5ab71c22eab69386e578085b14948c9b76ce712018a",
  "hash": "ffa0dc16493aa316be1aa5ab71c22eab69386e578085b14948c9b76ce712018a",
  "version": 1,
  "size": 236,
  "locktime": 0,
  "vin": [
    {
      "txid": "c8facd8e607e7e8ca46d2de853748ce1a75fcdcec29e62e2db6600f0e3a7e70d",
      "vout": 0,
      "scriptSig": {
        "asm": "3045022100e17a1142531ca5ae2a5a2b9fe486f5e0d89270f39c51e6e8c88448b2cba6380a02206a33a5dab599e485779373d3a5e0616ec7eb6cea05c4f1e0cbf8370ae01b51b9[ALL|FORKID] 029ddb9308c0dab96d047bbc99191ef60d3882e03dd8f49b8811abe1c9f0b3389d",
        "hex": "483045022100e17a1142531ca5ae2a5a2b9fe486f5e0d89270f39c51e6e8c88448b2cba6380a02206a33a5dab599e485779373d3a5e0616ec7eb6cea05c4f1e0cbf8370ae01b51b94121029ddb9308c0dab96d047bbc99191ef60d3882e03dd8f49b8811abe1c9f0b3389d"
      },
      "sequence": 4294967295
    }
  ],
  "vout": [
    {
      "value": 1.00,
      "n": 0,
      "scriptPubKey": {
        "asm": "029ddb9308c0dab96d047bbc99191ef60d3882e03dd8f49b8811abe1c9f0b3389d OP_CHECKSIG",
        "hex": "21029ddb9308c0dab96d047bbc99191ef60d3882e03dd8f49b8811abe1c9f0b3389dac",
        "reqSigs": 1,
        "type": "pubkey",
        "addresses": [
          "mu4DpTaD75nheE4z5CQazqm1ivej1vzL4L"
        ]
      }
    },
    {
      "value": 98.99999719,
      "n": 1,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 f20142228fee3771756cbf6a533b1c01b238fb64 OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a914f20142228fee3771756cbf6a533b1c01b238fb6488ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "n3aZKucfWmXeXhX13MREQQnqNfbrWiYKtg"
        ]
      }
    }
  ],
  "hex": "01000000010de7a7e3f00066dbe2629ec2cecd5fa7e18c7453e82d6da48c7e7e608ecdfac8000000006b483045022100e17a1142531ca5ae2a5a2b9fe486f5e0d89270f39c51e6e8c88448b2cba6380a02206a33a5dab599e485779373d3a5e0616ec7eb6cea05c4f1e0cbf8370ae01b51b94121029ddb9308c0dab96d047bbc99191ef60d3882e03dd8f49b8811abe1c9f0b3389dffffffff0200e1f505000000002321029ddb9308c0dab96d047bbc99191ef60d3882e03dd8f49b8811abe1c9f0b3389dace701164e020000001976a914f20142228fee3771756cbf6a533b1c01b238fb6488ac00000000"
}

From the above JSON we can see that we now have a P2PK output transaction associated with a UTXO for which we hold the Private Key ( address mu4DpTaD75nheE4z5CQazqm1ivej1vzL4L).

We now substitute the P2PKHUnlockBuilder we used previously with a P2PKUnlockBuilder to build in a Transaction that consumes a P2PK transaction, and also creates a P2PK UTXO.

  var privateKey = SVPrivateKey.fromWIF("cVVvUsNHhbrgd7aW3gnuGo2qJM45LhHhTCVXrDSJDDcNGE6qmyCs");
  var changeAddress = Address("n3aZKucfWmXeXhX13MREQQnqNfbrWiYKtg"); //bitcoin-cli wallet address

  //rawtx for TxID : ffa0dc16493aa316be1aa5ab71c22eab69386e578085b14948c9b76ce712018a
  var rawTx = '01000000010de7a7e3f00066dbe2629ec2cecd5fa7e18c7453e82d6da48c7e7e608ecdfac8000000006b483045022100e17a1142531ca5ae2a5a2b9fe486f5e0d89270f39c51e6e8c88448b2cba6380a02206a33a5dab599e485779373d3a5e0616ec7eb6cea05c4f1e0cbf8370ae01b51b94121029ddb9308c0dab96d047bbc99191ef60d3882e03dd8f49b8811abe1c9f0b3389dffffffff0200e1f505000000002321029ddb9308c0dab96d047bbc99191ef60d3882e03dd8f49b8811abe1c9f0b3389dace701164e020000001976a914f20142228fee3771756cbf6a533b1c01b238fb6488ac00000000';

  var txWithUtxo = Transaction.fromHex(rawTx);
  var utxo = txWithUtxo.outputs[0];

  var recipient = privateKey.toAddress(networkType: NetworkType.REGTEST);

  //P2PKUnlockBuilder takes no parameters in constructor. It only needs the
  //Signature in the Script, and that will be injected by the framework once
  //we run spendingTx.signInput()
  var unlockBuilder = P2PKUnlockBuilder();

  //We will output to ourselves, and send change to the same place
  //We are essentially spending coins we already own, and sending them
  //back to ourselves but with a different Locking / Unlocking condition (P2PK)
  var recipientLockBuilder = P2PKLockBuilder(privateKey.publicKey);
  var changeLockBuilder = P2PKHLockBuilder(changeAddress);

  var spendingTx = Transaction()
      .spendFromOutput(utxo, Transaction.NLOCKTIME_MAX_VALUE, scriptBuilder: unlockBuilder)
      .spendTo(recipient, BigInt.from(50000000), scriptBuilder: recipientLockBuilder) //send myself 0.5 bitcoin
      .sendChangeTo(changeAddress, scriptBuilder: changeLockBuilder);

  //our signing injects the signature into the *unlockBuilder* instance
  spendingTx.signInput(0, privateKey, sighashType: SighashType.SIGHASH_ALL | SighashType.SIGHASH_FORKID );

  print(spendingTx.serialize());

Send it !

P2PKH

P2PKH (Pay to Public Key Hash) is the canonical bitcoin Transaction type. It is by far the most widely used current method of sending coins from one party to another. This locking and unlocking scripts have the following patterns respectively:

Locking script (scriptPubkey)

OP_DUP OP_HASH160 <Public KeyHash> OP_EQUAL OP_CHECKSIG

Unlocking Script (scriptSig)

<Signature> <Public Key>

Script execution

Combined for execution

<Signature> <Public Key> OP_DUP OP_HASH160 <Public KeyHash> OP_EQUAL OP_CHECKSIG

Code example

The SDK will of course abstract away the details of creating this type of locking and unlocking behaviour. We will focus on a practical example. The two primary classes we will be working with are P2PKHUnlockBuilder and P2PKHLockBuilder.

For now, let’s start by creating an address at which to receive some coins.

  //Let's reconstruct our Private Key
  var privateKey = SVPrivateKey.fromWIF("cVVvUsNHhbrgd7aW3gnuGo2qJM45LhHhTCVXrDSJDDcNGE6qmyCs");

  //Creates Address : "mu4DpTaD75nheE4z5CQazqm1ivej1vzL4L"
  var recipientAddress = privateKey.toAddress();  // my address

We can now use the above address to receive some test coins from our bitcoin-cli wallet

bin/bitcoin-cli -datadir=$BITCOIN_HOME/data sendtoaddress mu4DpTaD75nheE4z5CQazqm1ivej1vzL4L 100

712ac512b483cda8adcd1636089facdc8ff70e4405d06d13d52d36f41f2ea406

The above command created a new Transaction within our local RegTest node, with transaction id 712ac512b483cda8adcd1636089facdc8ff70e4405d06d13d52d36f41f2ea406. We can query this transaction ID by executing consecutive commands to:

  • obtain a raw transaction
  • decode the raw transaction
bin/bitcoin-cli -datadir=$BITCOIN_HOME/data getrawtransaction 712ac512b483cda8adcd1636089facdc8ff70e4405d06d13d52d36f41f2ea406

We will go ahead and directly plug the output of the above transaction into our code:

  //Create a Transaction instance from the RAW transaction data created by bitcoin-cli.
 var rawTx = '020000000380e0e6a8a9da9fd29a7d870ee2b923e8590503d12d646b456606176dd268b0550000000049483045022100a3ba204ce3462ad32c2f8def14226ac616a7fb1ab91c50228e9494957f6170ba022048b7318a8d2fd3acf37e0150f222d345ffd4f5ebfe2a808f82c0f7698efcbd1541feffffffa58b78d2779f8174b4402f56826b07e09f5e20a71d889b6d9d11f82aaa5448020000000049483045022100caa4460fb6cf9dacffa0f0202823cf2abc92042638067ea7113cdf719b4da58a02200c4a85f19e875d207312c694f3e6986c2cf1212bafcb18d872646264ad4312b941feffffffd0ed2ee51546fb5c6ab96182679dc1e16003a8cee2858477cfe7b18adb33a5b70000000049483045022100f82a281c57b9ad7f9fa2d8238e8cedb2f7be6049fa0293a8e82dea460ef7ab3e022017bf39316d7e64ea6a5dc279acdfc3dafeef127111f7cf6e78d3a4ba8ea1cf4b41feffffff0200e40b54020000001976a91494837d2d5d6106aa97db38957dcc294181ee91e988ac30d80295000000001976a91421e0df2870cb0a8948d7fa6165049c99416988b588ac4e010000';

 var txWithUTXO = Transaction.fromHex(rawTx);

When we decode the above transaction using the decoderawtransaction command from bitcoin-cli, we notice that our address has been sent 100 bitcoins in UTXO (unspent transaction output) with index zero (0). Let’s grab that output and create a utxo variable.

  var utxo = txWithUTXO.outputs[0]; //looking at the decoded JSON we can see that our UTXO is at vout[0]

We can now go ahead and create our P2PKH locking and unlocking builder instances.

  var unlockBuilder = P2PKHUnlockBuilder(privateKey.publicKey);
  var changeLockBuilder = P2PKHLockBuilder(changeAddress);
  var recipientLockBuilder = P2PKHLockBuilder(recipientAddress);

Finally, we create a spending transaction and plug our prepared variables into it.

  var transaction = new Transaction()
      .spendFromOutput(utxo, Transaction.NLOCKTIME_MAX_VALUE, scriptBuilder: unlockBuilder) //implicitly creates correct Inputs in spending transaction
      .spendTo(recipientAddress, BigInt.from(50000000), scriptBuilder: recipientLockBuilder) //spend half a bitcoin == 50 million satoshis (creates Outputs in spending transaction)
      .sendChangeTo(changeAddress, scriptBuilder: changeLockBuilder) // spend change to myself
      .withFeePerKb(1000); //how much we are willing to pay the miners

  //Sign the Transaction Input
  transaction.signInput(0, privateKey, sighashType: SighashType.SIGHASH_ALL | SighashType.SIGHASH_FORKID);

  print(transaction.serialize());

Putting it all together, we have

  //Let's reconstruct our Private Key
  var privateKey = SVPrivateKey.fromWIF("cVVvUsNHhbrgd7aW3gnuGo2qJM45LhHhTCVXrDSJDDcNGE6qmyCs");

  //Creates Address : "mu4DpTaD75nheE4z5CQazqm1ivej1vzL4L"
  var recipientAddress = privateKey.toAddress();  // my address

  var changeAddress = Address("n3aZKucfWmXeXhX13MREQQnqNfbrWiYKtg"); //bitcoin-cli wallet address

  //Create a Transaction instance from the RAW transaction data created by bitcoin-cli.
  //Remember that this transaction contains the UTXO we are interested in
 var rawTx = '020000000380e0e6a8a9da9fd29a7d870ee2b923e8590503d12d646b456606176dd268b0550000000049483045022100a3ba204ce3462ad32c2f8def14226ac616a7fb1ab91c50228e9494957f6170ba022048b7318a8d2fd3acf37e0150f222d345ffd4f5ebfe2a808f82c0f7698efcbd1541feffffffa58b78d2779f8174b4402f56826b07e09f5e20a71d889b6d9d11f82aaa5448020000000049483045022100caa4460fb6cf9dacffa0f0202823cf2abc92042638067ea7113cdf719b4da58a02200c4a85f19e875d207312c694f3e6986c2cf1212bafcb18d872646264ad4312b941feffffffd0ed2ee51546fb5c6ab96182679dc1e16003a8cee2858477cfe7b18adb33a5b70000000049483045022100f82a281c57b9ad7f9fa2d8238e8cedb2f7be6049fa0293a8e82dea460ef7ab3e022017bf39316d7e64ea6a5dc279acdfc3dafeef127111f7cf6e78d3a4ba8ea1cf4b41feffffff0200e40b54020000001976a91494837d2d5d6106aa97db38957dcc294181ee91e988ac30d80295000000001976a91421e0df2870cb0a8948d7fa6165049c99416988b588ac4e010000';

 var txWithUTXO = Transaction.fromHex(rawTx);

  //Let's create the set of Spending Transaction Inputs. These Transaction Inputs need to refer to the Outputs in
  //the Transaction we are spending from.
  var utxo = txWithUTXO.outputs[0]; //looking at the decoded JSON we can see that our UTXO is at vout[0]

  //1. where is the money coming from - (spendFromOutput())
  //2. where is the money going to - (spendTo())
  //3. where should we send what's left over as difference between (1) and (2) - (sendChangeTo())
  //4. how much are we willing to pay the miners  - (withFeePerKb())

  var unlockBuilder = P2PKHUnlockBuilder(privateKey.publicKey);
  var transaction = new Transaction()
      .spendFromOutput(utxo, Transaction.NLOCKTIME_MAX_VALUE, scriptBuilder: unlockBuilder) //implicitly creates correct Inputs in spending transaction
      .spendTo(recipientAddress, BigInt.from(50000000), scriptBuilder: P2PKHLockBuilder(recipientAddress)) //spend half a bitcoin == 50 million satoshis (creates Outputs in spending transaction)
      .sendChangeTo(changeAddress, scriptBuilder: P2PKHLockBuilder(changeAddress)) // spend change to myself
      .withFeePerKb(1000); //how much we are willing to pay the miners

  //Sign the Transaction Input
  transaction.signInput(0, privateKey, sighashType: SighashType.SIGHASH_ALL | SighashType.SIGHASH_FORKID);

  print(transaction.serialize());

As before, we can now broadcast our transaction to the local client node using the sendrawtransaction command from bitcoin-cli, and passing in the output from the above dart script.

Send it !

P2SH

The P2SH (Pay to Script Hash) transaction type is a bit controversial. During it’s introduction, three years after the launch of the bitcoin network, key changes to how the bitcoin node executes scripts were introduced.

P2SH spending (unlocking) transactions are still allowed to be executed on the BitcoinSV network. However the Bitcoin SV network will reject attempts to create new transactions with the P2SH locking pattern in scriptPubkey. This should not present a problem for the developer. Bitcoin SV has a very large script-size limit, and with the scaled network there really is no need to resort to the old P2SH method of script execution.

Even though new P2SH transactions are now longer allowed to be created within the Bitcoin SV network. This SDK still allows for both the locking and unlocking scripts to be created. P2SHLockBuilder and P2SHUnlockBuilder are the two classes we will be using to create scripts of this type.

It should be noted that the P2SH script type also has it’s own Address format. Address formats are a wallet concern however, and it is important to remember that Addresses are never actually sent to the network. What ends up on the network is a cryptographic hash, from which one may reconstruct the Address.

Locking script (scriptPubkey)

OP_HASH160 <hash of script> OP_EQUAL

Unlocking script (scriptSig)

OP_0 <serialized script data>

Script Execution

P2SH script execution does not follow the usual procedure for executing scripts. Recall that ordinarily, the Script Interpreter will :

  • concatenate the scriptSig and the scriptPubkey
  • execute the concatenated script and evaluate the result of the execution as either success or failure

:FIXME: Should be talk about cleanstack somewhere ?

P2SH script execution proceeds as follows

  • all of the scriptSig elements are pushed onto the stack (in P2SH scriptSig contains only data items)
  • a copy is made of the stack at this point
  • execution now proceeds as normal with the elements from scriptPubkey
  • upon successful completion of this execution the data element which was previously copied(scriptSig), is deserialized and executed as a new script (as if it were a concatenated scriptSig++scriptPubkey) against what remains on the stack

As the developer therefore we need to provide a serialized copy of our entire script as data push in scriptSig (unlocking script), and when we create a new P2SH Transaction Output we must provide a means to evaluate a signed hash of the same script. The complete locking/unlock pattern looks as follows:

The OP_0 at the start of the script is important because of a bug that was introduced along with the P2SH implementation (:FIXME: factcheck). Any operator can be in that position, and in some online examples you might see folks use OP_1 etc. instead. (:FIXME: factcheck)

The typical manner in which this pattern is used, is that a “virtual” scriptSig and scriptPubkey are created offline. Then, in order to successfully spend this output, the spending party (recipient) creates an unlocking script by :

  • obtaining a copy of the “offline scriptPubkey” from the sender
  • constructing their own “offline scriptSig”
  • concatenating the “offline scriptSig” and the “offline scriptPubkey”
  • serializing the concatenated script using the above pattern for (scriptSig) - (HEX(scriptSig++scriptPubkey))
  • creating a spending Transaction with this new scriptSig as part of the P2SH unlocking pattern in the unlocking script ( OP_HASH160 <hash160(scriptSig)> OP_EQUAL)
While the the P2SH method could be employed for arbitrary script execution, it has historically been used primarily for creating multisig transactions. We will have a treatment on multisig transactions in the following section.

Code example

To the extent that creating an unlocking script for a P2SH transaction is primarily a matter of providing a script that can execute successfully, we will show how one would use the SDK to create a locking script for a P2SH transaction.

var inner = SVScript.fromString('OP_DUP OP_HASH160 20 0x06c06f6d931d7bfba2b5bd5ad0d19a8f257af3e3 OP_EQUALVERIFY OP_CHECKSIG');
var scriptHash = hash160(HEX.decode(inner.toHex()));
var lockBuilder = P2SHLockBuilder(HEX.encode(scriptHash));
var script = lockBuilder.getScriptPubkey();

… and to create a P2SH-specific address for wallet use, or to recognise an address as such

var address = Address.fromScript(script, NetworkType.MAIN);

if (address.addressType == AddressType.SCRIPT_HASH){
   print("Yay!")
}

P2MS

P2MS (Pay to Multisig) has had a bit of a rough road over the last eight years. Built around the OP_CHECKMULTISIG Script OpCode, it allows one to construct a spending condition that requires m-of-n Signatures. Motivated primarily by a need to preserve blockspace, and concerns over the size of the UTXO set that a bitcoin node has to maintain, P2MS was moved into a P2SH structure where pushing of Public Keys (public keys are relatively large) onto the blockchain could be deferred until spending time.

This means that since 2012 the canonical way of performing a MultiSig Transaction was to employ the P2SH locking/unlocking pattern. With the advent of Big Blocks, and a persistent low-fee-per-byte model on BitcoinSV, combined with the deprecation of P2SH, P2MS must now once again assume it’s rightful place in the tool of the developer. P2MS is also referred to as naked multisig, because it is no longer wrapped and hidden inside a hex-encoded serialized data blob as per the P2SH method.

Locking Script (scriptPubkey)

The locking script for P2MS follows the following general pattern:

OP_3 <pubKey1> <pubKey2> <pubKey3> <pubKey4> <pubKey5> OP_5 OP_CHECKMULTISIG
  • The first OP_CODE (OP_3 in the above case) designates the number of required Signatures.
  • The number of participant private key holders are represented by the number of public keys following the initial OP_CODE
  • OP_5 in the above example represents the total number of public keys available for checking
  • OP_CHECKMULTISIG closes out the *scriptPubkey

Unlocking Script (scriptSig)

The unlocking script for P2MS needs to provide the minimum number of required Signatures. The pattern is :

OP_0 <Sig> <Sig> <Sig>

The OP_0 in the above example is a requirement due to a bug in OP_CHECKMULTISIG. The OP_CHECKMULTISIG code suffers from an off-by-one bug, wherein it will attempt to pop one more element off the stack than what is required. The inclusion of an OP_CODE like OP_0 allows OP_CHECKMULTISIG to continue functioning in a non-breaking way.

Script Execution

In the case of P2MS script execution proceeds as it usually does, concatenation of scriptSig++scriptPubkey, followed by execution of the concatenated script.

OP_0 <Sig> <Sig> <Sig> OP_3 <pubKey1> <pubKey2> <pubKey3> <pubKey4> <pubKey5> OP_5 OP_CHECKMULTISIG
  • OP_0 results in an empty value being pushed onto the stack
  • All the signatures, which are just data are pushed onto the stack
  • OP_3 results in the value (3) being pushed onto the stack
  • All the public keys, which are just data are pushed onto the stack
  • OP_5 results in the value (5) being pushed onto the stack
  • OP_CHECKMULTISIG executes and
    • pops the top of the stack to know how many public keys to pop next
    • pops (5) elements off the stack and saves them as public keys
    • pops an element (3) off the stack to learn how many Signatures are required
    • pops (3) elements off the stack and saves those values as Signatures
    • pops one more element off the stack for shits-and-giggles (OP_0)
    • performs internal validation to see if the (3) signatures provided match at least (3) of the (5) public keys

In the above example, we have a 3-of-5 MultiSig scheme. At least 3 signatures are required out of a possible five.

A code example

As with the P2PK Transaction earlier, we don’t have an existing P2MS UTXO to spend from. We will need to create one first. We will have to create a new Transaction that:

  • Consumes an existing P2PKH output (UTXO)
  • Sends the funds to a P2MS output (UTXO)

Reminder: If you don’t have any coins to spend in your local Bitcoin RegTest Node’s wallet, you can force a simulated mining operation by issuing the generate command for bitcoin-cli.

P2PKH UTXO => P2MS UTXO

Use the bitcoin-cli to send 100 bitcoins to our address at mu4DpTaD75nheE4z5CQazqm1ivej1vzL4L

I am re-using the code example from P2PK below, with slight adjustments to allow for P2MS nuances.

bin/bitcoin-cli -datadir=$BITCOIN_HOME/data sendtoaddress mu4DpTaD75nheE4z5CQazqm1ivej1vzL4L 100

218cd9412b55b105408a8b1de4e213b481a4e92adf9a8581bd62e55ecf9dc11e

Grab the raw transaction data from the raw Transaction data from the Transaction ID created above.

bin/bitcoin-cli -datadir=$BITCOIN_HOME/data getrawtransaction 218cd9412b55b105408a8b1de4e213b481a4e92adf9a8581bd62e55ecf9dc11e

020000000366441f043569ed8c586a4caaece7ff848917794d9599740bad6ef76e5992ea730000000049483045022100fbfb7ef0df9067214472ca5cd2b3639627d7659365d5be3759bd78471e752623022051885d97a8ad1933002aca937d0abbf7b941e7826a194cc07bd1736710625bd041feffffffa10ff22476e981ed9d7a4a41a29e7be8b0da805dc3bfb02b7a8ae1984010ef450000000048473044022066c40386009c8bc90a5cb01a117bc51fc941cd80cdea5d5117a4f2b152b58c9c0220782eb45e6b44f165837d1d9ef41c8c74b5ce0f154750f4db56e04813ba61e00741feffffffb41af95892eb6f239e888b7e1e90fe70de123b64c3f9c43edd3994911683ea6b000000006b483045022100bac5f23ee5bfb2b47b41b9ac0a3712b2318ddbd2562074e3eed93490d584888b022010ef1da7b598e23d0412333b6a41d69c79073c307aa2b3b4e283992670a265d541210288e059d2a85b948d3dd1dee2075af435c695237d1524a6f5c477e40245e77e95feffffff021886fa02000000001976a9144ba3162330d2ee79de3d5d6ba662ff2a511d082288ac00e40b54020000001976a91494837d2d5d6106aa97db38957dcc294181ee91e988acb3010000

Let’s now decode this raw transaction with bitcoin-cli to see which output we will be spending from. The decoded transaction in JSON format:

{
  "txid": "218cd9412b55b105408a8b1de4e213b481a4e92adf9a8581bd62e55ecf9dc11e",
  "hash": "218cd9412b55b105408a8b1de4e213b481a4e92adf9a8581bd62e55ecf9dc11e",
  "version": 2,
  "size": 453,
  "locktime": 435,
  "vin": [
    {
      "txid": "73ea92596ef76ead0b7499954d79178984ffe7ecaa4c6a588ced6935041f4466",
      "vout": 0,
      "scriptSig": {
        "asm": "3045022100fbfb7ef0df9067214472ca5cd2b3639627d7659365d5be3759bd78471e752623022051885d97a8ad1933002aca937d0abbf7b941e7826a194cc07bd1736710625bd0[ALL|FORKID]",
        "hex": "483045022100fbfb7ef0df9067214472ca5cd2b3639627d7659365d5be3759bd78471e752623022051885d97a8ad1933002aca937d0abbf7b941e7826a194cc07bd1736710625bd041"
      },
      "sequence": 4294967294
    },
    {
      "txid": "45ef104098e18a7a2bb0bfc35d80dab0e87b9ea2414a7a9ded81e97624f20fa1",
      "vout": 0,
      "scriptSig": {
        "asm": "3044022066c40386009c8bc90a5cb01a117bc51fc941cd80cdea5d5117a4f2b152b58c9c0220782eb45e6b44f165837d1d9ef41c8c74b5ce0f154750f4db56e04813ba61e007[ALL|FORKID]",
        "hex": "473044022066c40386009c8bc90a5cb01a117bc51fc941cd80cdea5d5117a4f2b152b58c9c0220782eb45e6b44f165837d1d9ef41c8c74b5ce0f154750f4db56e04813ba61e00741"
      },
      "sequence": 4294967294
    },
    {
      "txid": "6bea8316919439dd3ec4f9c3643b12de70fe901e7e8b889e236feb9258f91ab4",
      "vout": 0,
      "scriptSig": {
        "asm": "3045022100bac5f23ee5bfb2b47b41b9ac0a3712b2318ddbd2562074e3eed93490d584888b022010ef1da7b598e23d0412333b6a41d69c79073c307aa2b3b4e283992670a265d5[ALL|FORKID] 0288e059d2a85b948d3dd1dee2075af435c695237d1524a6f5c477e40245e77e95",
        "hex": "483045022100bac5f23ee5bfb2b47b41b9ac0a3712b2318ddbd2562074e3eed93490d584888b022010ef1da7b598e23d0412333b6a41d69c79073c307aa2b3b4e283992670a265d541210288e059d2a85b948d3dd1dee2075af435c695237d1524a6f5c477e40245e77e95"
      },
      "sequence": 4294967294
    }
  ],
  "vout": [
    {
      "value": 0.4997276,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 4ba3162330d2ee79de3d5d6ba662ff2a511d0822 OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a9144ba3162330d2ee79de3d5d6ba662ff2a511d082288ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "mnQtLtUAZ7WGtAyBaQp6vxxLBF1ZPSjpED"
        ]
      }
    },
    {
      "value": 100.00,
      "n": 1,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 94837d2d5d6106aa97db38957dcc294181ee91e9 OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a91494837d2d5d6106aa97db38957dcc294181ee91e988ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "mu4DpTaD75nheE4z5CQazqm1ivej1vzL4L"
        ]
      }
    }
  ],
  "hex": "020000000366441f043569ed8c586a4caaece7ff848917794d9599740bad6ef76e5992ea730000000049483045022100fbfb7ef0df9067214472ca5cd2b3639627d7659365d5be3759bd78471e752623022051885d97a8ad1933002aca937d0abbf7b941e7826a194cc07bd1736710625bd041feffffffa10ff22476e981ed9d7a4a41a29e7be8b0da805dc3bfb02b7a8ae1984010ef450000000048473044022066c40386009c8bc90a5cb01a117bc51fc941cd80cdea5d5117a4f2b152b58c9c0220782eb45e6b44f165837d1d9ef41c8c74b5ce0f154750f4db56e04813ba61e00741feffffffb41af95892eb6f239e888b7e1e90fe70de123b64c3f9c43edd3994911683ea6b000000006b483045022100bac5f23ee5bfb2b47b41b9ac0a3712b2318ddbd2562074e3eed93490d584888b022010ef1da7b598e23d0412333b6a41d69c79073c307aa2b3b4e283992670a265d541210288e059d2a85b948d3dd1dee2075af435c695237d1524a6f5c477e40245e77e95feffffff021886fa02000000001976a9144ba3162330d2ee79de3d5d6ba662ff2a511d082288ac00e40b54020000001976a91494837d2d5d6106aa97db38957dcc294181ee91e988acb3010000"
}

We can see that there is an address (mu4DpTaD75nheE4z5CQazqm1ivej1vzL4L) which is the one associated with a Private Key we control, that has received 100 bitcoins. The UTXO associated with that address has index n:1, which is index number zero (1).

In order for us to create a multisig output, we will need a number of public keys. We will construct the same 3-of-5 scheme as we used in the introduction to P2MS. We can now construct a spending Transaction which takes in a P2PKH Transaction, and spends it to a P2MS output:


  //create five private keys so we can obtain their corresponding public keys
  var privKey1 = SVPrivateKey.fromWIF('L5MYU25rgrRy1MomVyzuSRuDB3s24CrP5ZXFSWEHDLvwKWVXJDnB');
  var privKey2 = SVPrivateKey.fromWIF('L25sS3jP3XyAgC9dxdSt9azucWcXruaGEDzwRQd4DASCnaEU9Muj');
  var privKey3 = SVPrivateKey.fromWIF('L1QKHjoft6JoUQb1LmZytZLPjjqam2ftmdtKaa8bw3tR8LDT3tV5');
  var privKey4 = SVPrivateKey.fromWIF('Ky7yMfxzsoHwsyB4GKp2TCfUzbBtziC2hcCSqvwGUet11tPXww54');

  var privateKey = SVPrivateKey.fromWIF("cVVvUsNHhbrgd7aW3gnuGo2qJM45LhHhTCVXrDSJDDcNGE6qmyCs");
  var changeAddress = Address("n3aZKucfWmXeXhX13MREQQnqNfbrWiYKtg"); //bitcoin-cli wallet address

  var rawTx = '020000000366441f043569ed8c586a4caaece7ff848917794d9599740bad6ef76e5992ea730000000049483045022100fbfb7ef0df9067214472ca5cd2b3639627d7659365d5be3759bd78471e752623022051885d97a8ad1933002aca937d0abbf7b941e7826a194cc07bd1736710625bd041feffffffa10ff22476e981ed9d7a4a41a29e7be8b0da805dc3bfb02b7a8ae1984010ef450000000048473044022066c40386009c8bc90a5cb01a117bc51fc941cd80cdea5d5117a4f2b152b58c9c0220782eb45e6b44f165837d1d9ef41c8c74b5ce0f154750f4db56e04813ba61e00741feffffffb41af95892eb6f239e888b7e1e90fe70de123b64c3f9c43edd3994911683ea6b000000006b483045022100bac5f23ee5bfb2b47b41b9ac0a3712b2318ddbd2562074e3eed93490d584888b022010ef1da7b598e23d0412333b6a41d69c79073c307aa2b3b4e283992670a265d541210288e059d2a85b948d3dd1dee2075af435c695237d1524a6f5c477e40245e77e95feffffff021886fa02000000001976a9144ba3162330d2ee79de3d5d6ba662ff2a511d082288ac00e40b54020000001976a91494837d2d5d6106aa97db38957dcc294181ee91e988acb3010000';
  var txWithUtxo = Transaction.fromHex(rawTx);

  print('transactionID : ${txWithUtxo.id}');
  var utxo = txWithUtxo.outputs[1];

  var recipient = privateKey.toAddress(networkType: NetworkType.REGTEST);

  var unlockBuilder = P2PKHUnlockBuilder(privateKey.publicKey);

  //we need 5 public keys for our 3-of-5 scheme
  var pubKeys = [
    privKey1.publicKey,
    privKey2.publicKey,
    privKey3.publicKey,
    privKey4.publicKey,
    privateKey.publicKey
  ];

  //create our P2MS Lock Builder, passing 5 keys,
  //specifying 3 required sigs
  var recipientLockBuilder = P2MSLockBuilder(pubKeys, 3);
  var changeLockBuilder = P2PKHLockBuilder(changeAddress);

  var spendingTx = Transaction()
      .spendFromOutput(utxo, Transaction.NLOCKTIME_MAX_VALUE, scriptBuilder: unlockBuilder)
      .spendTo(recipient, BigInt.from(100000000), scriptBuilder: recipientLockBuilder) //send myself one bitcoin
      .sendChangeTo(changeAddress, scriptBuilder: changeLockBuilder);

  spendingTx.signInput(0, privateKey, sighashType: SighashType.SIGHASH_ALL | SighashType.SIGHASH_FORKID );

  print(spendingTx.serialize());

The above code produces a new transaction which we can broadcast to the blockchain using bitcoin-cli

 bin/bitcoin-cli -datadir=$BITCOIN_HOME/data sendrawtransaction 01000000011ec19dcf5ee562bd81859adf2ae9a481b413e2e41d8b8a4005b1552b41d98c21010000006a4730440220506915e435aba77607b9ee4ef7a3de7ef2fec217329fe26347d497b5cca093f8022062e8e715a87512e497708e0e1a4ec3493916f8e3e39765332a0242c80f5fe3744121029ddb9308c0dab96d047bbc99191ef60d3882e03dd8f49b8811abe1c9f0b3389dffffffff0200e1f50500000000ad5321029ddb9308c0dab96d047bbc99191ef60d3882e03dd8f49b8811abe1c9f0b3389d2102cdb96489584d708a2052a857b4bac63eadb367733ecebb62edc2ccae9945fe7c210334124415d5c8f18d41015cffb2eb383fc10025d0e4d44368872bdffb7e9fff2c2103a80f1a58cd7451d3d635f80c1e8dbd334f6a130dfade6e85b28184e105727c4b2103e5c140031682d3b06d4b90ae787ea34519d80db0856a154b67beb9b82906ae8b55ae5d01164e020000001976a914f20142228fee3771756cbf6a533b1c01b238fb6488ac00000000

2840cabf438179da49dcbea3f4ffb374ab85e01d799ae67aacbd30cb121de976

The resultant transaction id is identified as 2840cabf438179da49dcbea3f4ffb374ab85e01d799ae67aacbd30cb121de976, and we can retrieve the raw transaction and decode that to see what we have created:

{
  "txid": "2840cabf438179da49dcbea3f4ffb374ab85e01d799ae67aacbd30cb121de976",
  "hash": "2840cabf438179da49dcbea3f4ffb374ab85e01d799ae67aacbd30cb121de976",
  "version": 1,
  "size": 373,
  "locktime": 0,
  "vin": [
    {
      "txid": "218cd9412b55b105408a8b1de4e213b481a4e92adf9a8581bd62e55ecf9dc11e",
      "vout": 1,
      "scriptSig": {
        "asm": "30440220506915e435aba77607b9ee4ef7a3de7ef2fec217329fe26347d497b5cca093f8022062e8e715a87512e497708e0e1a4ec3493916f8e3e39765332a0242c80f5fe374[ALL|FORKID] 029ddb9308c0dab96d047bbc99191ef60d3882e03dd8f49b8811abe1c9f0b3389d",
        "hex": "4730440220506915e435aba77607b9ee4ef7a3de7ef2fec217329fe26347d497b5cca093f8022062e8e715a87512e497708e0e1a4ec3493916f8e3e39765332a0242c80f5fe3744121029ddb9308c0dab96d047bbc99191ef60d3882e03dd8f49b8811abe1c9f0b3389d"
      },
      "sequence": 4294967295
    }
  ],
  "vout": [
    {
      "value": 1.00,
      "n": 0,
      "scriptPubKey": {
        "asm": "3 029ddb9308c0dab96d047bbc99191ef60d3882e03dd8f49b8811abe1c9f0b3389d 02cdb96489584d708a2052a857b4bac63eadb367733ecebb62edc2ccae9945fe7c 0334124415d5c8f18d41015cffb2eb383fc10025d0e4d44368872bdffb7e9fff2c 03a80f1a58cd7451d3d635f80c1e8dbd334f6a130dfade6e85b28184e105727c4b 03e5c140031682d3b06d4b90ae787ea34519d80db0856a154b67beb9b82906ae8b 5 OP_CHECKMULTISIG",
        "hex": "5321029ddb9308c0dab96d047bbc99191ef60d3882e03dd8f49b8811abe1c9f0b3389d2102cdb96489584d708a2052a857b4bac63eadb367733ecebb62edc2ccae9945fe7c210334124415d5c8f18d41015cffb2eb383fc10025d0e4d44368872bdffb7e9fff2c2103a80f1a58cd7451d3d635f80c1e8dbd334f6a130dfade6e85b28184e105727c4b2103e5c140031682d3b06d4b90ae787ea34519d80db0856a154b67beb9b82906ae8b55ae",
        "reqSigs": 3,
        "type": "multisig",
        "addresses": [
          "mu4DpTaD75nheE4z5CQazqm1ivej1vzL4L",
          "mnumj49RxXRWCSXsTfJnroVWGg8iuRMqhb",
          "mv44ybk8ru7ZbvcuTXgSope3sq7YrXGgMM",
          "mrWymrTFFpedhdvBtHTfZKyMMyih3oBMSy",
          "n2Vgnm8uruBJ5knQL8KQkPxcQutpU3eAL9"
        ]
      }
    },
    {
      "value": 98.99999581,
      "n": 1,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 f20142228fee3771756cbf6a533b1c01b238fb64 OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a914f20142228fee3771756cbf6a533b1c01b238fb6488ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "n3aZKucfWmXeXhX13MREQQnqNfbrWiYKtg"
        ]
      }
    }
  ],
  "hex": "01000000011ec19dcf5ee562bd81859adf2ae9a481b413e2e41d8b8a4005b1552b41d98c21010000006a4730440220506915e435aba77607b9ee4ef7a3de7ef2fec217329fe26347d497b5cca093f8022062e8e715a87512e497708e0e1a4ec3493916f8e3e39765332a0242c80f5fe3744121029ddb9308c0dab96d047bbc99191ef60d3882e03dd8f49b8811abe1c9f0b3389dffffffff0200e1f50500000000ad5321029ddb9308c0dab96d047bbc99191ef60d3882e03dd8f49b8811abe1c9f0b3389d2102cdb96489584d708a2052a857b4bac63eadb367733ecebb62edc2ccae9945fe7c210334124415d5c8f18d41015cffb2eb383fc10025d0e4d44368872bdffb7e9fff2c2103a80f1a58cd7451d3d635f80c1e8dbd334f6a130dfade6e85b28184e105727c4b2103e5c140031682d3b06d4b90ae787ea34519d80db0856a154b67beb9b82906ae8b55ae5d01164e020000001976a914f20142228fee3771756cbf6a533b1c01b238fb6488ac00000000"
}

P2MS => P2MS

We can now take the raw transaction output from transaction id 2840cabf438179da49dcbea3f4ffb374ab85e01d799ae67aacbd30cb121de976, and use that in our code. Note from the previous JSON output that our multisig UTXO has Output Index n:0.

We can now create a transaction that spends from an existing MultiSig UTXO. Please note that for spending a MultiSig UTXO, the signatures are injected by the SDK when you call Transaction.signInput(). The only requirement is that we create a P2MSUnlockBuilder instance and pass that as our scriptBuilder to the Transaction.spendFromOutput() function.


  //create five private keys so we can obtain their corresponding public keys
  var privKey1 = SVPrivateKey.fromWIF('L5MYU25rgrRy1MomVyzuSRuDB3s24CrP5ZXFSWEHDLvwKWVXJDnB');
  var privKey2 = SVPrivateKey.fromWIF('L25sS3jP3XyAgC9dxdSt9azucWcXruaGEDzwRQd4DASCnaEU9Muj');
  var privKey3 = SVPrivateKey.fromWIF('L1QKHjoft6JoUQb1LmZytZLPjjqam2ftmdtKaa8bw3tR8LDT3tV5');
  var privKey4 = SVPrivateKey.fromWIF('Ky7yMfxzsoHwsyB4GKp2TCfUzbBtziC2hcCSqvwGUet11tPXww54');

  var privateKey = SVPrivateKey.fromWIF("cVVvUsNHhbrgd7aW3gnuGo2qJM45LhHhTCVXrDSJDDcNGE6qmyCs");
  var changeAddress = Address("n3aZKucfWmXeXhX13MREQQnqNfbrWiYKtg"); //bitcoin-cli wallet address

  var rawTx = '01000000011ec19dcf5ee562bd81859adf2ae9a481b413e2e41d8b8a4005b1552b41d98c21010000006a4730440220506915e435aba77607b9ee4ef7a3de7ef2fec217329fe26347d497b5cca093f8022062e8e715a87512e497708e0e1a4ec3493916f8e3e39765332a0242c80f5fe3744121029ddb9308c0dab96d047bbc99191ef60d3882e03dd8f49b8811abe1c9f0b3389dffffffff0200e1f50500000000ad5321029ddb9308c0dab96d047bbc99191ef60d3882e03dd8f49b8811abe1c9f0b3389d2102cdb96489584d708a2052a857b4bac63eadb367733ecebb62edc2ccae9945fe7c210334124415d5c8f18d41015cffb2eb383fc10025d0e4d44368872bdffb7e9fff2c2103a80f1a58cd7451d3d635f80c1e8dbd334f6a130dfade6e85b28184e105727c4b2103e5c140031682d3b06d4b90ae787ea34519d80db0856a154b67beb9b82906ae8b55ae5d01164e020000001976a914f20142228fee3771756cbf6a533b1c01b238fb6488ac00000000';
  var txWithUtxo = Transaction.fromHex(rawTx);

  print('transactionID : ${txWithUtxo.id}');
  var utxo = txWithUtxo.outputs[0];

  var recipient = privateKey.toAddress(networkType: NetworkType.REGTEST);

  //our multisig unlock builder takes no parameters. We will supply it as part of
  //spendFromOutput(), and the SDK will inject the signatures upon transaction signing.
  var unlockBuilder = P2MSUnlockBuilder();

  var pubKeys = [
    privKey1.publicKey,
    privKey2.publicKey,
    privKey3.publicKey,
    privKey4.publicKey,
    privateKey.publicKey
  ];

  //We'll create another output spending a multisig back to ourselves
  var recipientLockBuilder = P2MSLockBuilder(pubKeys, 3);
  var changeLockBuilder = P2PKHLockBuilder(changeAddress);

  var spendingTx = Transaction()
      .spendFromOutput(utxo, Transaction.NLOCKTIME_MAX_VALUE, scriptBuilder: unlockBuilder)
      .spendTo(recipient, BigInt.from(5000000), scriptBuilder: recipientLockBuilder) //send ourselves 0.05 bitcoin
      .withFeePerKb(10000)
      .sendChangeTo(changeAddress, scriptBuilder: changeLockBuilder);

  //we sign three times, with three different signatures, causing the P2MSUnlockBuilder()
  //we created above to be injected with the required three signatures needed to
  //generate the correct unlocking script
  spendingTx.signInput(0, privKey3, sighashType: SighashType.SIGHASH_ALL | SighashType.SIGHASH_FORKID );
  spendingTx.signInput(0, privKey2, sighashType: SighashType.SIGHASH_ALL | SighashType.SIGHASH_FORKID );
  spendingTx.signInput(0, privKey1, sighashType: SighashType.SIGHASH_ALL | SighashType.SIGHASH_FORKID );

  print(spendingTx.serialize());

Send it !