Build a Transaction

In this section we will do a step-by-step walkthrough of how one creates a new Spending Transaction. We will be assuming that we are working with the most common type of Spending Transaction called P2PKH (Pay-to-Public-Key-Hash).

We cover P2PKH in detail in the next section on Standard Script Templates

While creating the spending Transaction itself is rather straighforward when using the TwoStack SDK, there is a bit of ceremony we need to endure in order to create a Transaction where we can spend from.

The Script

To successfully build our Spending Transaction we will have an existing Transaction on the blockchain which has a Locking Script (ScriptPubkey) in one of its Outputs of the form:

OP_DUP OP_HASH160 <hash of public key> OP_EQUAL OP_CHECKSIG

We will need to provide the Unlocking Script (ScriptSig) as a counterpart to this which will have the general form :

<signature> <Public Key>

When combined by the Script Interpreter to validate our Spending Transaction the executing Script will have the form:

<signature> <Public Key> OP_DUP OP_HASH160 <hash of public key> OP_EQUAL OP_CHECKSIG

Creating a Private Key

The TwoStack Dart SDK provides some built-in convenience methods which abstracts a lot of the above complexity away from the developer. It is still important to note that the ScriptSig created by the spending Transaction will need two items:

  • An ECDSA Signature
  • An ECDSA Public Key

The library will automatically :

  • generate the Signature for us in accordance with Sighash flags that we provide.
  • derive the Public Key from the Private Key and include that in the ScriptSig for us.

For the above to happen, we need to provide a Private Key to the library. This Private Key must of course be the key that will generate a valid Signature and Public key capable for unlocking the funds.

We create a random Private Key as follows :

var privateKey = SVPrivateKey(networkType: NetworkType.REGTEST);

Where you see classes prefixed with SV, this is typically to avoid namespace clashes and potential confusion with similarly named classes in other libraries. Here there is an existing PrivateKey class in the pointycastle cryptographic library

Receiving some coins

A Transaction where we can spend from is the same as saying a UTXO. Yet another way of putting it is to say that we need to send some coins to a bitcoin address for which we hold the private key.

Before we can spend any coins, we need to receive some first. This means we need a way to get our hands on a Transaction that contains a UTXO (Unspent Transaction Output) that we are able to spend using our Private Key.

The simplest way to get our hands on some bitcoins to play with is to use fake bitcoins… or at least bitcoins which are not spendable on the public Bitcoin network. Earlier in the Getting Started section, we learnt how to generate some coins into a local wallet. We will make use of this technique now.

If we are using the bitcoin-cli to interact with our local wallet, we will need an Address to send those coins to. Let’s create an Address now.

:FIXME: Somewhere please explain the relationship between Private Key, Public Key and bitcoin Addresses used in P2PKH Transactions. [Insert graphic here]

Code to create a Bitcoin Address

privateKey.toAddress();

Let’s put this information together and create a simple commandline application that writes a Private Key and an associated Address to the commandline.

void main(List<String> arguments) {
  var privateKey = SVPrivateKey(networkType: NetworkType.REGTEST);

  var address = privateKey.toAddress();

  print("Private KEY: ${privateKey.toWIF()}");
  print("Address: ${address}");

}

Running the above application produced the following output. Because the Private Key is randomly generated in this case, you will likely get a different key.

Private KEY: cVVvUsNHhbrgd7aW3gnuGo2qJM45LhHhTCVXrDSJDDcNGE6qmyCs
Address: mu4DpTaD75nheE4z5CQazqm1ivej1vzL4L

If you want to follow along with the code samples by using the same Private Key as above, you can import this Private Key using the following code inside your own program:

  var privateKey = SVPrivateKey.fromWIF("cVVvUsNHhbrgd7aW3gnuGo2qJM45LhHhTCVXrDSJDDcNGE6qmyCs");

You can learn more about the WIF format for Private Keys in the Private Key section of the Appendix.

Now that we have an Address we can use the bitcoin-cli program to send ourselves some coins.

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

dbc9967fdddedc98f2eda81391065baad8e45e0249923f5ff6499c20bb6b84d8

Notice that in sending that 1 Bitcoin to ourselves, bitcoin-cli has provided us with a Transaction ID (Your Transaction ID will be different to this one).

Let’s use that Transaction ID to query our local test node for the raw transaction.

Raw Transaction data refers to the serialized hexadecimal encoding of a Transaction.

bin/bitcoin-cli -regtest -datadir=$BITCOIN_HOME/data getrawtransaction dbc9967fdddedc98f2eda81391065baad8e45e0249923f5ff6499c20bb6b84d8


02000000016b748661a108dc35d8868a9a552b9364c6ee3f06a4604f722882d49cdc4d13020000000048473044022073062451397fb5e7e2e02f1603e2a92677d516a5e747b1ae2ad0996387916d4302200ae2ec97d4525621cef07f75f0b92b5e83341761fa604c83daf0390a76d5024241feffffff0200e1f505000000001976a91494837d2d5d6106aa97db38957dcc294181ee91e988ac00021024010000001976a9144d991c88b4fd954ea62aa7182d3b3e251896a83188acd5000000

That information is not very useful. We can decode that information into JSON by asking the bitcoin-cli program to decode it for us:

 bin/bitcoin-cli -regtest -datadir=$BITCOIN_HOME/data decoderawtransaction 02000000016b748661a108dc35d8868a9a552b9364c6ee3f06a4604f722882d49cdc4d13020000000048473044022073062451397fb5e7e2e02f1603e2a92677d516a5e747b1ae2ad0996387916d4302200ae2ec97d4525621cef07f75f0b92b5e83341761fa604c83daf0390a76d5024241feffffff0200e1f505000000001976a91494837d2d5d6106aa97db38957dcc294181ee91e988ac00021024010000001976a9144d991c88b4fd954ea62aa7182d3b3e251896a83188acd5000000

The decoded transaction produces the following JSON output:

{
  "txid": "dbc9967fdddedc98f2eda81391065baad8e45e0249923f5ff6499c20bb6b84d8",
  "hash": "dbc9967fdddedc98f2eda81391065baad8e45e0249923f5ff6499c20bb6b84d8",
  "version": 2,
  "size": 191,
  "locktime": 213,
  "vin": [
    {
      "txid": "02134ddc9cd48228724f60a4063feec664932b559a8a86d835dc08a16186746b",
      "vout": 0,
      "scriptSig": {
        "asm": "3044022073062451397fb5e7e2e02f1603e2a92677d516a5e747b1ae2ad0996387916d4302200ae2ec97d4525621cef07f75f0b92b5e83341761fa604c83daf0390a76d50242[ALL|FORKID]",
        "hex": "473044022073062451397fb5e7e2e02f1603e2a92677d516a5e747b1ae2ad0996387916d4302200ae2ec97d4525621cef07f75f0b92b5e83341761fa604c83daf0390a76d5024241"
      },
      "sequence": 4294967294
    }
  ],
  "vout": [
    {
      "value": 1.00,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 94837d2d5d6106aa97db38957dcc294181ee91e9 OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a91494837d2d5d6106aa97db38957dcc294181ee91e988ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "mu4DpTaD75nheE4z5CQazqm1ivej1vzL4L"
        ]
      }
    },
    {
      "value": 48.9999616,
      "n": 1,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 4d991c88b4fd954ea62aa7182d3b3e251896a831 OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a9144d991c88b4fd954ea62aa7182d3b3e251896a83188ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "mnbFkCJqqJctAPEDDBeetc54rYaYk6QTpz"
        ]
      }
    }
  ],
  "hex": "02000000016b748661a108dc35d8868a9a552b9364c6ee3f06a4604f722882d49cdc4d13020000000048473044022073062451397fb5e7e2e02f1603e2a92677d516a5e747b1ae2ad0996387916d4302200ae2ec97d4525621cef07f75f0b92b5e83341761fa604c83daf0390a76d5024241feffffff0200e1f505000000001976a91494837d2d5d6106aa97db38957dcc294181ee91e988ac00021024010000001976a9144d991c88b4fd954ea62aa7182d3b3e251896a83188acd5000000"
}

The Spending Transaction

Now that we have received some coins at a Bitcoin Address that we control, we can finally create a spending Transaction.

String createWalletTxn( ){

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

  var changeAddress = Address("mu4DpTaD75nheE4z5CQazqm1ivej1vzL4L"); // my address

  var recipientAddress = Address("n3aZKucfWmXeXhX13MREQQnqNfbrWiYKtg"); //wallet address obtained with *getnewaddress* from bitcoin-cli

  //Create a Transaction instance from the RAW transaction data create by bitcoin-cli.
  //Remember that this transaction contains the UTXO we are interested in
  var txWithUTXO = Transaction.fromHex("02000000016b748661a108dc35d8868a9a552b9364c6ee3f06a4604f722882d49cdc4d13020000000048473044022073062451397fb5e7e2e02f1603e2a92677d516a5e747b1ae2ad0996387916d4302200ae2ec97d4525621cef07f75f0b92b5e83341761fa604c83daf0390a76d5024241feffffff0200e1f505000000001976a91494837d2d5d6106aa97db38957dcc294181ee91e988ac00021024010000001976a9144d991c88b4fd954ea62aa7182d3b3e251896a83188acd5000000");


  //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]

  var transaction = new Transaction()
      .spendFromOutputs([utxo], Transaction.NLOCKTIME_MAX_VALUE) //implicitly creates correct Inputs in spending transaction
      .spendTo(recipientAddress, BigInt.from(50000000)) //spend half a bitcoin == 50 million satoshis (creates Outputs in spending transaction)
      .sendChangeTo(changeAddress) // spend change to myself
      .withUnLockingScriptBuilder(P2PKHUnlockBuilder()) //FIXME: ! This should not be required. Default to P2PKH
      .withFeePerKb(1000); //set fee to 1 satoshi per byte (1000 satoshis per 1000 bytes)

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

  print(transaction.serialize());

}

The application has now produced serialized transaction output. This is a Raw Transaction, i.e. a Transaction serialized to it’s hexadecimal encoding. To broadcast this to the bitcoin network (our local test node in this case), we will use the sendrawtransaction command from bitcoin-cli.

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

a2ea6505272439a3b3b68afab6eca138927b0b8af92a5ef40fa099ed237d5b48

In the exceptional case that we might have a malformed transaction, we can always use bitcoin-cli to decode the raw transaction.

We can now once again follow that up by inspecting the Transaction with ID a2ea6505272439a3b3b68afab6eca138927b0b8af92a5ef40fa099ed237d5b48 and dumping it as before.

This chapter might have been a little heavy. We delved rather deeply into how one would usually interact between a local test node and one’s own code. It is a good idea to maybe take some time here to play with the various parameters exposed by bitcoin-cli.