Creating and Signing a SegWit Transaction from Scratch

otto
Coinmonks
7 min readOct 5, 2021

--

Step by Step

We previously created and signed a P2PKH transaction from scratch. This time we will create a SegWit transaction with 3 inputs from the following addresses:

One legacy, one SegWit and one wrapped-SegWit. Yes, this makes it a lot more complicated, but it will help to understand the differences in transaction serialization, the signing process and how to combine legacy and SegWit inputs.

The following table summarizes our UTXOs:

From the total 16,091,269 satoshis, we will send everything but the miner fee (2000 sats) to address tb1qeds7u3tgpqkttxkzdwukaj8muqgf5nqq6w05ak.
I’ll try to be more succinct this time, skipping some details. I recommend reading the previous tutorial for a more detailed explanation, if needed.

It seems I need a pointless image to get a header — sorry, n00b here

1. Generating the Private Keys

Let’s start by generating our PEM private keys:

We now have the following private keys files: priv-legacy.pem , priv-segwit.pem and priv-wrapped.pem .

2. Transaction Serialization

If a transaction has at least one SegWit input, native or wrapped, then it’s a SegWit transaction. Such transactions are serialized differently from legacy ones. Actually, it’s a little more complex, because we have one format for signing and a whole different one for pushing.
And, since we have a legacy input and those are signed using the old format, we will also have to construct a legacy transaction. So, this time we will have to do 3 different serializations:

  • The legacy serialization, to sign our legacy input;
  • The SegWit one, for signing each of the SegWit inputs;
  • The final serialization, to push the transaction;

We can start with any of the inputs. Let’s do the legacy one first.

3. Signing the Legacy Input

We first have to serialize the transaction as if we were to push a legacy transaction. After we obtain the signature for the legacy input, we discard this transaction and work on the SegWit inputs.

Using version 2 and having 3 inputs, we start with:

Then, for the legacy input, we get the previous transaction ID:

And output index 01000000 :

For the scriptSig, we add the scriptPubKey of the previous transaction and its size: 19 76a914b780d54c6b03b053916333b50a213d566bbedd1388ac . After also adding sequence ffffffff (no RBF or locktime), our first input is done:

The other inputs are SegWit, but that doesn’t change anything here, since, for legacy signing, the other scriptSigs should be empty. So, for the native SegWit input, after gathering the UTXO information, we have:

And finally, the same for the nested SegWit UTXO:

Our transaction with inputs becomes:

For the outputs part, we have a single output, so 0x01 . We’re transferring 16,089,269 satoshis, so let’s use Python’s REPL to get the amount bytes in little-endian:

We are sending to address tb1qeds7u3tgpqkttxkzdwukaj8muqgf5nqq6w05ak. The scriptPubKey for P2WPKH is OP_0 <pubkey hash>. While validating transactions, when nodes find this pattern in an output being spent, they recognize it as a P2WPKH and add the same opcodes a P2PKH has, turning it into a OP_DUP OP_HASH160 <pubkey hash> OP_EQUALVERIFY OP_CHECKSIG.

Using a Web tool like bech32 demo or bech32-buffer, decode the address to obtain the public key hash: cb61ee4568082cb59ac26bb96ec8fbe0109a4c00. Then add the push operation 0x14 and the scriptPubKey size 0x16 and we have the output:

After adding the number of outputs 0x01 and the output itself, our transaction becomes:

Lastly, append the locktime 00000000 and the sighash 01000000 and we’re ready to sign. This is what we are signing:

3.1 Signing

Let’s hash256 the transaction and sign it:

I won’t go through the details of the signature again. Please refer to the previous article for them. This time, I actually grew bored of the repetitive work, so I wrote a little Python script to fix the signatures. Even though I’m not explicitly writing it, I’ve piped all signing commands with fix-signature.py .

After appending the sighash 0x01 , we have our first signature:

Let’s save it and move on to the next input.

4. Signing the SegWit Inputs

Time to sign the other inputs. BIP143 defines a new transaction format for signing SegWit inputs:

There’s some new parlance here too, which we’ll see. Let’s start with the native input.

4.1 Signing Input #1

After adding version 02000000 , we have something new: the hash of the previous outpoints. An outpoint is the concatenation of the previous transaction ID and the output index. We’ve already extracted that data from the UTXOs, so let’s hash it:

Serialization so far:

Then, hashSequence:

We have:

Now the outpoint, which is specific to this input:

And then the scriptCode, which, in P2WPKH’s case, is 1976a914 <pubkey hash> 88ac . This is the same as P2PKH’s scriptPubKey. After getting the pubkey hash with either of the bech32 tools, we add the scriptCode:

Then the input value: 9300 sats, which is 5424000000000000 , followed by the sequence ffffffff :

Then hashOutputs, which is the hash of all outputs. Each output is amount || scriptPubKey size || scriptPubKey . We only have one, so:

We’re almost there:

And finally, locktime and sighash:

We are ready to sign.

4.1.1 Signing
Nothing new here. As before, just double hash and sign:

Now add sighash 0x01 and there’s the second signature:

4.2 Signing Input #2

The first 3 items (version, hashPrevouts and hashSequence) are identical to the ones in the last input:

Then comes the outpoint of this input:

Now, the scriptCode. This input is also P2WPKH, so 1976a914 <pubkey hash> 88ac :

I’ll then add the input value (16,029,969 = 1199f40000000000) and sequence ffffffff at once:

And finally, hashOutputs, locktime and sighash, which we already have from the previous input:

4.2.1 Signing
As always, double hash, sign and append the sighash:

This is our last signature with sighash:

5. Final Transaction Serialization

The transaction to be pushed is actually a new format described in BIP144. It’s similar to the old format.
We start with version:

We then have two new fields, the SegWit marker 0x00 and flag 0x01 , which allow nodes to identify this as a SegWit transaction:

Then, the inputs. We have 0x03 . They are the same as before, except for the scriptSig, which depends on the input type. For the legacy input, we already know it’s <sig> <pubkey> . Together with the push operations, the scriptSig with size becomes:

Adding the outpoint and sequence, we finish the legacy input:

The native SegWit input is simpler, as the scriptSig is empty. The signature and public key will appear in the witness section. Second input:

The wrapped SegWit one needs to push the redeemScript in the scriptSig, since this is a P2SH. The nested script is a P2WPKH, so it is OP_0 <pubkey hash> . Including the push opcode 0x14 for the pubkey hash and 0x16 for the redeemScript itself, not forgetting the script size 0x17 , we end up with:

The same remarks about the signature and public key location apply here. This is the transaction so far, after adding the number of inputs and the inputs themselves:

The output part is the same as in the legacy transaction. We already have it from the first signature, so:

We’re close to the end.
Now, before the locktime, we have a new section. The witness part is where we place the witnesses, required for solving the scriptPubKey of the SegWit UTXOs. This is the part of the transaction that is discounted (making SegWit transactions cheaper) and is allowed to exceed the 1MB block size. Since legacy nodes do not receive the witnesses, it’s still possible to produce a block within the 1MB limit, making the block size increase retrocompatible.

Every input needs its own array of witness items. Each input has to specify:

  • The number of items;
  • The item size;
  • The item itself;

The first input doesn’t need any items, as everything required for validation is already in the scriptSig; hence we just need the number of items: 0x00 . The second and third inputs have two items each, which are the signature and public key.
Here’s the witness field of our transaction:

Adding witness:

And finally, after adding the locktime, we have finished our transaction!

Let’s test it…

Yeah!

6. Conclusion

Phew… we’ve come a long way, but we accomplished it. We have created and signed a SegWit transaction from scratch. We also learned the differences between the old and new transaction formats, how the signing processes differ and how to combine legacy and SegWit UTXOs.

Hope you enjoyed it and see you next time!

Join Coinmonks Telegram Channel and Youtube Channel learn about crypto trading and investing

--

--