10

I originally thought it would be relatively simple to add replay attack protection (change something about how the tx hash is generated) in the event of a fork. But the BU folks mentioned that this would break SPV clients (including many mobile wallets). So it is not free to do that way, although still an option.

One solution we brainstormed, was using some coins that were only valid on one chain (for example newly mined coins) and adding 1 satoshi of "taint" to all generated transactions. This would work but is not perfect. For instance, newly mined coins can't be spent for 100 blocks, so at a minimum it would cause economic nodes to have a delay in the event of a hard fork. It is also non-trivial for all wallet software to implement this (fair amount of work, especially at scale). There is also an operational challenge that you would have to rush to get these newly mined coins while the hard fork was happening, so you could not prepare in advance. It would be a messy for sure.

Another option would be to try and double spend some coins across the forked chains (get a transaction with some coins going to two different addresses mined across the two chains). These coins could then also be used for "taint", but you wouldn't have to wait the full 100 blocks. This is a little bit better, but still not perfect.

I suspect there is a better solution though. What are people's thoughts?

6

One solution we brainstormed, was using some coins that were only valid on one chain (for example newly mined coins) and adding 1 satoshi of "taint" to all generated transactions. This would work but is not perfect.

It so sounds like you have two problems:

  1. How to get some inputs that are only valid on one side of the chain.
  2. How to mix these inputs in with all of your transactions.

I don't have any good answers for the second question; you'll have to mix in the inputs by either putting them in every transaction you send or by mixing them with a large amount of coins which you then split into dozens or hundreds of inputs that you'll use normally.

For the first question:

Coin splitting

The best way would be to push for replay protection in the hard forking client(s) as they're already changing the rules. But if that's not possible, there are some potential differences between chains that can be used to get one version of a transaction confirmed on one chain and another version of the same transaction confirmed on the other chain.

I'll warn that (1) Bitcoin clients are not designed to operate under ongoing hard forks, so lots of things that would normally work just fine can end up resulting in monetary loss, (2) without replay attack protection, there's no guarantee that any of the mechanisms below will split your coins---so you should probably send the coins from yourself to yourself so that, if the split fails, you still have all your coins (minus any transaction fees), and (3) in a hard fork, any rule can be changed, so the following generic information may not apply on the hard-forked chain; check with the hard fork developers to be sure.

Methods:

  1. Coinbase transactions: not the kind your company makes, but the kind miners use to claim subsidy and transaction fees. As your question indicates, these are only valid on the side of the hard fork they're generated on but they also have to wait 100 blocks to mature.

    Pros: simple, guaranteed to work

    Cons: requires miner help, have to wait 100 blocks

  2. nLockTime: if the two chains diverge in height, you can create a transaction with the nLockTime set to the height of longest chain. That transaction can be immediately confirmed on that chain, but it can't be added to the shorter chain until that chain catches up to the same height---giving you a window of time into which to broadcast an alternative transaction on the shorter chain and split your coins.

    Handily, Bitcoin Core has a feature called anti-fee sniping that automatically (in current versions) sets the nLockTime to the current block height for default wallet transactions, so this trick is pretty easy if you just want to split your Bitcoin Core wallet.

    Pros: should be pretty effective because it depends on consensus rules

    Cons: you still need to wait a few blocks after the fork to give the chains time to diverge in chain height.

    (Credit: I first heard of a method like this from Peter Todd; I don't know if he originated it, nor if this is exactly the method he was thinking about---I had to guess at how it would work from him just saying "use nLockTime".)

    You can also do the same thing with consensus-enforced nSequence, but it doesn't get you anything extra.

  3. Fee differences: It's possible miners on one side of the fork may be mining transactions with lower fees than on the other side, so you can broadcast a low-fee version of your transaction on the low-fee chain, wait for it to confirm, and then broadcast a higher-fee version on the high-fee chain.

    Pros: simple

    Cons: you may still have to wait a few blocks for fees to diverge by much and since each miner has their own policy, you may need to try multiple times. Also, many wallets don't let you chose arbitrary fees (they just give you a reasonable fee or let you choose from reasonable range of fees).

    (Credit: I first heard this described today by @ElectrumWallet on Twitter.)

  4. Find some other feature that diverges between miners on the two chains. For example, as I write this, I think Bitcoin Core by default will not accept into its memory pool any transactions that require mining more than 20 ancestors first (this prevents abuse and allows the mining code to run more efficiently, since Bitcoin Core provides child-pays-for-parent (CPFP) mining); Bitcoin Unlimited, being based on an older version of Bitcoin Core, doesn't provide the CPFP optimization and so will allow an unlimited depth of transaction ancestors to enter the mempool. This means you could get, say, 101 related transactions mined on one side of the fork and then you'd have up to five block to get a different 101st transaction mined on the other side of the fork.

    There are potentially other of these relay-level differences between clients that could be used.

    Pros: relay differences could possibly be used immediately after a hard fork to get a transaction mined in the first fork block (if you knew when that was going to happen) or the second fork block (if you started as soon as the first fork block was produced). Also, they may be fairly simple.

    Cons: these tricks depend on all miners on one side of the chain running one version of the code and all miners on the other side running a different version of the code. The more overlap there is, the lower the probability these tricks will work.

2

Here is the github ticket where we've been thinking about this problem for Zcash. Compared to Bitcoin, Zcash has the luxury of a smaller installed base and a longer runway before any probable blockchain split, so we don't have as many constraints on the design space as you have, but nonetheless some of these ideas might be helpful:

https://github.com/zcash/zcash/issues/174

Here is an idea:

https://github.com/luke-jr/bips/blob/bip-noreplay/bip-noreplay.mediawiki

It seems to me that this would work and would not be incompatible with SPV clients as far as I can think.

  • 1
    Hi Zooko, welcome to Bitcoin.SE. Could you please edit your answer to add a short summary of the linked resources? It would be nice to have a bit of an idea in your answer without needing to read another page. – Murch Mar 19 '17 at 12:54
2

A) Currently, Unlimited retains the 100K MAX_STANDARD_TX_SIZE policy limit https://github.com/BitcoinUnlimited/BitcoinUnlimited/blob/release/src/policy/policy.h#L23 so it would be difficult to propagate a large transaction via P2P. However, a miner could mine a large 1MB TX if you got one directly to them, and then the UTXOs from the big TX could be used for taint since they would only be in the Unlimited chain. Also, node policy differences between Unlimited and Core could probably be used to get transactions to propagate, and therefore get mined, on only one side of the split.

B) TX malleability might be useful to help with splitting. For example, miners could provide a service to help split UTXOs. There are at least a few options:

Option 1) Sign your inputs with SIGHASH_ANYONECANPAY|SIGHASH_ALL and allow the miners to add a single satosi tainted input that they get back in the form of fees (miners can't add outputs because of the SIGHASH_ALL of the signatures for this option).

Option 2) Sign your inputs with SIGHASH_ANYONECANPAY|SIGHASH_SINGLE and use a single zero index output. This allows a miner to add inputs and outputs that input taint and pay back one of their addresses. These would stand out because they are signed significantly differently than normal. See regtest example below.

Option 3) Miners on one side could mechanically perform scriptSig malleability transformations in order to get spends to confirm with different TXID's than the other chain.

regtest network example:
WIF private key --> regtest network address
cMahea7zqjxrtgAbB7LSGbcQUr1uX1ojuat9jZodMN87JcbXMTcA --> mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r
cMahea7zqjxrtgAbB7LSGbcQUr1uX1ojuat9jZodMN87K7XCyj5v --> mg8Jz5776UdyiYcBb9Z873NTozEiADRW5H
cMahea7zqjxrtgAbB7LSGbcQUr1uX1ojuat9jZodMN87KcLPVfXz --> mrzKXEpXfEDHk7vFS3LBXVXoa4YXFcCkje

> ./bitcoin/src/qt/bitcoin-qt -regtest -txindex -datadir=/test/1/

in debug window rpc console:
generatetoaddress 1 mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r
generatetoaddress 1 mrzKXEpXfEDHk7vFS3LBXVXoa4YXFcCkje
generate 100
getblock(getblockhash(1))[tx][0]
getblock(getblockhash(2))[tx][0]

User does this:
createrawtransaction '[{"txid":"54bdb97bdc75580f8564f348ab312f2147c945fcb96a7c2a01231aff50030212","vout":0}]' '{"mg8Jz5776UdyiYcBb9Z873NTozEiADRW5H":49.099}'
signrawtransaction 020000000112020350ff1a23012a7c6ab9fc45c947212f31ab48f364850f5875dc7bb9bd540000000000ffffffff01e020a724010000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac00000000 'null' '["cMahea7zqjxrtgAbB7LSGbcQUr1uX1ojuat9jZodMN87JcbXMTcA"]' 'SINGLE|ANYONECANPAY'
020000000112020350ff1a23012a7c6ab9fc45c947212f31ab48f364850f5875dc7bb9bd54000000006a47304402202e229bf02236b209271da602cf9a16b2e651b8419d3610d311a5d443804b0bba022072c5cbc37310f27e047a5110ac2fae802d7fb51ed0fda86eeaa0c6ab5518b1ec83210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff01e020a724010000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac00000000

The above raw transaction is what you would send to the network in actual usage.

Miner does this:
createrawtransaction '[{"txid":"cebee3f6f6f2d74f4513fa04d67040954ef216c962f3b000ed68cd7a37234293","vout":0}]' '{"mrzKXEpXfEDHk7vFS3LBXVXoa4YXFcCkje":50}'
0200000001934223377acd68ed00b0f362c916f24e954070d604fa13454fd7f2f6f6e3bece0000000000ffffffff0100f2052a010000001976a9147dd65592d0ab2fe0d0257d571abf032cd9db93dc88ac00000000

Miners manually merge the previous two raw transactions into:
020000000212020350ff1a23012a7c6ab9fc45c947212f31ab48f364850f5875dc7bb9bd54000000006a47304402202e229bf02236b209271da602cf9a16b2e651b8419d3610d311a5d443804b0bba022072c5cbc37310f27e047a5110ac2fae802d7fb51ed0fda86eeaa0c6ab5518b1ec83210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff934223377acd68ed00b0f362c916f24e954070d604fa13454fd7f2f6f6e3bece0000000000ffffffff02e020a724010000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac00f2052a010000001976a9147dd65592d0ab2fe0d0257d571abf032cd9db93dc88ac00000000

signrawtransaction 020000000212020350ff1a23012a7c6ab9fc45c947212f31ab48f364850f5875dc7bb9bd54000000006a47304402202e229bf02236b209271da602cf9a16b2e651b8419d3610d311a5d443804b0bba022072c5cbc37310f27e047a5110ac2fae802d7fb51ed0fda86eeaa0c6ab5518b1ec83210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff934223377acd68ed00b0f362c916f24e954070d604fa13454fd7f2f6f6e3bece0000000000ffffffff02e020a724010000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac00f2052a010000001976a9147dd65592d0ab2fe0d0257d571abf032cd9db93dc88ac00000000 'null' '["cMahea7zqjxrtgAbB7LSGbcQUr1uX1ojuat9jZodMN87KcLPVfXz"]' 'ALL'
020000000212020350ff1a23012a7c6ab9fc45c947212f31ab48f364850f5875dc7bb9bd54000000006a47304402202e229bf02236b209271da602cf9a16b2e651b8419d3610d311a5d443804b0bba022072c5cbc37310f27e047a5110ac2fae802d7fb51ed0fda86eeaa0c6ab5518b1ec83210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff934223377acd68ed00b0f362c916f24e954070d604fa13454fd7f2f6f6e3bece000000006a473044022013d1c41326ff95dafc1f15a25bcec916d06da6e61a5808ca9ad6de0b8a527cb902206f120f53512809721cb71b3d1a797e698621229cba2b2e87b1c654e2f9336b02012102f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9ffffffff02e020a724010000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac00f2052a010000001976a9147dd65592d0ab2fe0d0257d571abf032cd9db93dc88ac00000000

This is effectively what the miner would put in a block:
sendrawtransaction 020000000212020350ff1a23012a7c6ab9fc45c947212f31ab48f364850f5875dc7bb9bd54000000006a47304402202e229bf02236b209271da602cf9a16b2e651b8419d3610d311a5d443804b0bba022072c5cbc37310f27e047a5110ac2fae802d7fb51ed0fda86eeaa0c6ab5518b1ec83210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff934223377acd68ed00b0f362c916f24e954070d604fa13454fd7f2f6f6e3bece000000006a473044022013d1c41326ff95dafc1f15a25bcec916d06da6e61a5808ca9ad6de0b8a527cb902206f120f53512809721cb71b3d1a797e698621229cba2b2e87b1c654e2f9336b02012102f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9ffffffff02e020a724010000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac00f2052a010000001976a9147dd65592d0ab2fe0d0257d571abf032cd9db93dc88ac00000000 true

double check that the tx is accepted and mined:
generate 6
gettxout(882f4d4faf6880572cde354dcc6d41de9d14fb2a184bc12635822e36dfa9b2e9, 0)[confirmations]
gettxout(882f4d4faf6880572cde354dcc6d41de9d14fb2a184bc12635822e36dfa9b2e9, 0)
gettxout(882f4d4faf6880572cde354dcc6d41de9d14fb2a184bc12635822e36dfa9b2e9, 1)

notes: to understand how this works and limitations, see:
https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L1052-#L1141
https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L1223-#L1243

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.