Lock time transaction to split coins after the Segwit2x split

Kallewoof
9 min readOct 3, 2017

--

Gist: make two transactions spending the same UTXO(s); first does "x true" where x = faster chain block height and true is for RBF (to createrawtransaction) and has a low fee; it goes on the fast chain; second is normal with normal fee and goes on the slow chain after the former confirms.See: bitcoin-cli help createrawtransaction

People are (rightly so) confused about how to split their coins after Segwit2x activates, and one proposed idea is to use locktime, taking advantage of the fact that the two blockchains will move at different speeds. (You can read more about replay protection in a previous post I made.) Assuming Segwit2x gets more miners than Bitcoin, the two chains will start out at the same height, 494,784; this will advance, so you will see e.g.

  • Segwit2x mines 494,785
  • Segwit2x mines 494,786
  • Bitcoin mines 494,785
  • Segwit2x mines 494,787

Let’s assume that this goes on for awhile, and we have Segwit2x at block 494,800, and Bitcoin at block 494,780, twenty blocks behind.

At this point, it is possible to create a transaction that is only valid on Segwit2x, but not on Bitcoin; we do this using locktime.

Let’s try this out on regtest

The steps given here can be done on the main bitcoin network as well, assuming you have a synced up Bitcoin Core node running. This takes awhile (a week or two to fully sync up from scratch) and requires around 150 GB of disk space, so you need to plan ahead if you intend to do this yourself.

Firstly, I will go through some cruft to set things up so we have 3 separate nodes running on regtest (a “local” bitcoin network used for testing). Note that bitcoin on regtest are worthless and can be used and abused to try things out to your heart’s content.

Setting things up

If you just want to see the actual process for creating the locktime script (i.e. if you don’t wanna try this yourself) you can skip this step.

This assumes you have Bitcoin Core installed, and that you have a terminal running. I will assume that bitcoind and bitcoin-cli are both available. You may need to substitute them for e.g. src/bitcoind from the bitcoin folder, if you’ve built it yourself.

cd to some appropriate folder and start by making 3 new folders; we will emulate one bitcoin miner, one segwit2x miner, and ourselves (this is not complicated, as you will see soon).

mkdir btcminerd
mkdir s2xminerd
mkdir med

Now we make bitcoin.conf files for each, so they use different ports. Otherwise we can’t start all of them at once.

echo -n -e “port=10001\nrpcport=10002\n” > btcminerd/bitcoin.conf
echo -n -e “port=10011\nrpcport=10012\n” > s2xminerd/bitcoin.conf
echo -n -e “port=10021\nrpcport=10022\n” > med/bitcoin.conf

Now we start up all three regtest nodes:

bitcoind -regtest -datadir=btcminerd &
bitcoind -regtest -datadir=s2xminerd &
bitcoind -regtest -datadir=med &

To not have to type a bunch of extra stuff, we create aliases for each of them so we can access them using btcminer, s2xminer, and me.

alias btcminer=”bitcoin-cli -regtest -datadir=btcminerd”
alias s2xminer=”bitcoin-cli -regtest -datadir=s2xminerd”
alias me=”bitcoin-cli -regtest -datadir=med”

Initially, all nodes are connected to each other, so we do the same here using addnode .

btcminer addnode 127.0.0.1:10011 onetry
s2xminer addnode 127.0.0.1:10021 onetry

Check that they’re actually connected:

btcminer getconnectioncount # 1
s2xminer getconnectioncount # 2
me getconnectioncount # 1

We start by mining 101 blocks on the bitcoin miner side. This gives us one spendable output of 50 bitcoin that we can send to me — this becomes our actual bitcoins that we split later:

btcminer generate 101
s2xminer generate 1

(We also do a generate on the s2xminer, as it would otherwise not sync up for some reason. It’s irrelevant but was necessary on my side.)

We check that everyone’s up to date: they should all see 101 blocks.

btcminer getblockcount
s2xminer getblockcount
me getblockcount

We now send 5 bitcoin from btcminer to me. That’s the funds we pretend that we have on the actual bitcoin network, and that we want to split.

myaddr=$(me getnewaddress)
btcminer sendtoaddress $myaddr 5

We mine it into a block too, for good measure:

btcminer generate 1

Split time! We now disconnect everyone from each other, as they would otherwise keep in sync. This is our “fork” moment. In bitcoin, this would be block 494,784, but here it’s block 101.

btcminer disconnectnode 127.0.0.1:10011
s2xminer disconnectnode 127.0.0.1:10021

Check that we have no connections left from anyone:

btcminer getconnectioncount # 0
s2xminer getconnectioncount # 0
me getconnectioncount # 0

Block count should be the same still:

btcminer getblockcount
s2xminer getblockcount
me getblockcount

Now let s2x advance a few blocks ahead: I picked 5 here, but in reality you may want to wait longer, so the short chain doesn’t catch up before you finish preparing.

s2xminer generate 5

At this point, block count for btcminer and me should be 102, whereas s2xminer should have 107 blocks, 5 more than the others:

btcminer getblockcount
s2xminer getblockcount
me getblockcount

We have 5 bitcoin (you can check using me getbalance). These 5 bitcoin are tied to one UTXO — in reality, you may have multiple UTXO:s and it’s up to you to either do this manually for each one or aggregate them. Aggregating means you reveal that you own them all, and you need to increase the fee appropriately (and manually), so it may be a bit advanced.

me listunspent

For me, the output looks like this — emphasis mine:

[
{
"txid": "b284dacdfd2a8e782530a0bfd6f7a4fde64ed6859bae7c8c9673b59dcdead392",
"vout": 0,
"address": "mkixywzxjXhVTvzt1BCJEk3RnQAuxy8Wns",
"account": "",
"scriptPubKey": "76a914391e31d4440485b2f9aa2ba54f154f0c1d2fadcf88ac",
"amount": 5.00000000,
"confirmations": 1,
"spendable": true,
"solvable": true,
"safe": true
}
]

We need to keep track of 3 values above (the ones in bold) — the txid, the vout, and the amount. We copy these into their own variables so we don’t have to write them out directly (note: you will see different output if you are following along; don’t copy my values!).

txid=b284dacdfd2a8e782530a0bfd6f7a4fde64ed6859bae7c8c9673b59dcdead392
vout=0
amount=5.00000000

We will be sending these bitcoins to two separate addresses, both of which we ourselves own. Let’s make two addresses now:

s2xaddr=$(me getnewaddress)
btcaddr=$(me getnewaddress)

Segwit2x transaction

Now it’s time to make our first transaction, which is meant for the Segwit2x chain. This transaction has the following properties:

  • it is low fee,
  • it is replace-by-fee enabled,
  • it has a locktime of the current height of Segwit2x; for the real case, this might be 494,800. In our regtest case, it is 107.

A low fee would be something like 2–3 satoshi per byte. A standard transaction is something like 300 bytes, so we simply decide to pay 600 satoshi (2 sat/b). (Note: you need the bc command here. Most systems have it already. If not Google how to install it.)

s2xamount=$(bc <<< $amount-0.00000600)

We check that this is sane:

echo "My fee will be $(bc <<< $amount-$s2xamount) bitcoins, i.e. about $(bc <<< ($amount-$s2xamount)*4500) USD at 4500 USD/BTC"

For me, the above gives:

My fee will be .00000600 bitcoins, i.e. about .02700000 USD at 4500/BTC

3 cents sounds fine by me (and it assumes a $4500 USD/BTC price), so I won’t tweak the fee.

Now we get to the actual transaction. We create it using the createrawtransaction command. We set the locktime to 107 and we set replaceable to true. These are the last arguments in the command below.

rawtx1=$(me createrawtransaction "[{\"txid\":\"$txid\", \"vout\":$vout}]" "{\"$s2xaddr\":$s2xamount}" 107 true)

We can look at what this transaction looks like now to make sure it looks okay:

me decoderawtransaction $rawtx1

For me, this gave (emphasis mine):

{
"txid": "d3bb5470c5cf942ed65664124626bf69e185a4052dfb68207e86357ac4129151",
"hash": "d3bb5470c5cf942ed65664124626bf69e185a4052dfb68207e86357ac4129151",
"version": 2,
"size": 85,
"vsize": 85,
"locktime": 107,
"vin": [
{
"txid": "b284dacdfd2a8e782530a0bfd6f7a4fde64ed6859bae7c8c9673b59dcdead392",
"vout": 0,
"scriptSig": {
"asm": "",
"hex": ""
},
"sequence": 4294967293
}
],
"vout": [
{
"value": 4.99999400,
"n": 0,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 fa0fdd31999c801689b8895e7e482f4c942305e1 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a914fa0fdd31999c801689b8895e7e482f4c942305e188ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"n4KAEGHQt36XcYNdVJ8b2VZydo5cUCqRLi"
]
}
}
]
}

The two bolded parts are what we need to double check: locktime should not be 0, and sequence should not be 4294967295 (what is that? It’s 0xffffffff in hex; if these two conditions hold, i.e. locktime not 0 and sequence not max, then the transaction is replaceable).

You may also wanna check that the address listed corresponds to $s2xaddr if you wish.

Let’s sign this transaction so it’s ready to go:

me signrawtransaction $rawtx1

This will give a JSON object with a hex and complete values. Make sure complete is true and, if it is, copy paste the hex value into a new variable.

signedtx1=PUTHEXSTRINGHERE

Bitcoin transaction

We now create another transaction, this one meant for the Bitcoin network. It spends the same inputs, but it sends to a different output. This transaction has the following properties:

  • it has medium to high fees,
  • it does not have replace by fee enabled (it’s fine if it does, but it doesn’t need to),
  • it does not have a locktime (if it does, we can’t send it even after the Segwit2x transaction is confirmed).

Start by deciding a fee. Here I’m setting 2000 satoshi (around 6–7 satoshi/byte), but you may want to raise that if you are scared your transaction may not be confirmed on time; note that since we are effectively replacing the first transaction, as it concerns the Bitcoin network (because we are raising the fee), a miner could still mine it if the wanted to, or if they never saw the second one. If the first is mined, you ended up losing 600 satoshi on both sides. No biggie, but let’s try to get it the first try.

btcamount=$(bc <<< $amount-0.00002000)

Sanity check:

echo "My fee will be $(bc <<< $amount-$btcamount) bitcoins, i.e. about $(bc <<< ($amount-$btcamount)*4500) USD at 4500 USD/BTC"

For me:

My fee will be .00002000 bitcoins, i.e. about .09000000 USD at 4500 USD/BTC

9 cents, sounds good. Let’s make the transaction itself:

rawtx2=$(me createrawtransaction "[{\"txid\":\"$txid\", \"vout\":$vout}]" "{\"$btcaddr\":$btcamount}")

And take a look at it:

me decoderawtransaction $rawtx2

For comparison, this is what I got:

{
"txid": "aea016421f2fd65eee925bcaa67912380dbf6148fcf5e66e3773643868a11b74",
"hash": "aea016421f2fd65eee925bcaa67912380dbf6148fcf5e66e3773643868a11b74",
"version": 2,
"size": 85,
"vsize": 85,
"locktime": 0,
"vin": [
{
"txid": "b284dacdfd2a8e782530a0bfd6f7a4fde64ed6859bae7c8c9673b59dcdead392",
"vout": 0,
"scriptSig": {
"asm": "",
"hex": ""
},
"sequence": 4294967295
}
],
"vout": [
{
"value": 4.99998000,
"n": 0,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 ffec5e9745dd18154c9f4791ffd5246eebe76c38 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a914ffec5e9745dd18154c9f4791ffd5246eebe76c3888ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"n4r9mDCwCJ9NBT3JsyXWjfeCCAC9TtCi2x"
]
}
}
]
}

As you can see, locktime is now 0 and sequence is now the max, indicating this is not replaceable, and it has no locktime, meaning it is valid at any time, as long as the inputs are unspent.

Let’s sign it, check the complete flag, and store the signed transaction in another variable:

me signrawtransaction $rawtx2
# check that complete is true!
signedtx2=HEXVALUE

Small recap: we now have two transactions; one is a low fee, replace-by-fee enabled transaction meant for the Segwit2x network. The other is a regular fee transaction meant for Bitcoin. The second one must have a fee higher than the first, or it cannot replace it if Bitcoin chain catches up before it is confirmed.

Send your first transaction

It’s time to broadcast your first transaction! For simplicity, we will just send it to the s2xminer node directly; in the real case, you need to either have a node connected to the Segwit2x network, or you need to find a service that lets you push a transaction. Presumably there will be Segwit2x transaction pushers available. You would simply copy signedtx1 and paste it there.

You probably do not wanna attempt this in the real case, but just to demonstrate, we will first try to send our transaction to the bitcoin network. It will be rejected, because the bitcoin network has not mined up to block 107 yet:

btcminer sendrawtransaction $signedtx1

The output on my end (should be the same on yours) is:

error code: -26
error message:
64: non-final

Now we try the segwit2x miner:

s2xminer sendrawtransaction $signedtx1

It should give you the transaction id of your transaction back. We now wait for the transaction to be mined. In the real case, you would simply open your transaction ID (given by the above command) in a blockchain explorer for the Segwit2x net and wait for it to receive 1 or 2 confirmations. Here, we just generate a block on the segwit2x node to simulate that:

s2xminer generate 1

As soon as it’s mined, we want to immediately broadcast our second transaction to the Bitcoin network. Note: this transaction is valid on Bitcoin, because it has no locktime; it is invalid in Segwit2x because it is double spending coins.

btcminer sendrawtransaction $signedtx2

To demonstrate that it is, indeed, invalid on Segwit2x let’s try to broadcast it to the s2x miner:

s2xminer sendrawtransaction $signedtx1

We end up with this error:

error code: -27
error message:
transaction already in block chain

That’s a bit misleading, but regardless, we cannot put our alternative transaction into the Segwit2x net.

Now we just wait for the bitcoin network to mine the transaction and we’re good. Once it’s mined, we have successfully split coins; on bitcoin, we have our coins in $btcaddr and on Segwit2x, we have them in $s2xaddr. Sending from one should no longer risk the other being spent, and vice versa.

--

--