bitcoin spinoff fork - how to make a clean fork without any replay attack and no blockchain visible changes

in bitcoin •  3 years ago  (edited)

You probably know about the ETC fork of ETH. I dont want to talk about the why or the politics of it, just the technical aspect of the replay attack.

If a spinoff of a coin is created, it inherits the genesis, the ledger state, basically everything. This is good as it means everyone with the original coin gets a new coin for "free". However there is a potential replay attack if nothing is done to prevent it.

The replay attack is just a matter of taking any transaction from one chain and broadcasting it to the other chain. So even if ports and low level network wire protocols are changed, unless the signatures are invalid, the transaction is valid. Now in a lot of cases the funds go to the same person for both replay and the original. however, these are two different coins so it is possible that the same address ends up crediting a different person's account. All that is required is a shared deposit address that uses some other means to determine who should be credited

As soon as this becomes possible, that means all sorts of mischief can happen. One simple example is a trading exchange that uses the same address for multiple customers and using some email confirmed field to credit accounts. You can see the problem now. If you happen to send funds to such an exchange, then literally anybody can replay that transaction on the other chain, at any time. Like right after they start a deposit to the same deposit address at that exchange. The funds go to the address, but they get the credit, they sell it on the exchange and there is little chance you will recover your replayed coins.

Notice that either direction is possible. So you could end up losing the lower value coin, by having the higher value coin's tx replayed, but the opposite is also possible. Quite a potential disaster, especially if we are talking about bitcoins.

Now one attempt to make the replay attack not possible is to use a different version id in the block. But that fails immediate as the transactions are still all the same. OK, so lets change the version number in the transaction. This is a bit better, but the problem is that it only works in one direction. The lower version number would be accepted by the new chain.

Of course an entirely new crypto signing can be used to create mutually exclusive signatures, but that involves a lot of code, maybe the size changes, and the ripple effect could be that blockexplorers and many other blockchain cognizant software just wont work. So in addition to wanting signatures to be mutually exclusive to prevent replay attack, we want to be as backward compatible as possible. Ideally fully backward compatible.

Now some devs say this is just impossible. I like to do the impossible :)

Here is the solution that doesnt change the blockchain at all, but yet creates mutually exclusive signatures. Signatures from one chain only work on that chain and since it is incompatible at the signature level, any transaction that has a signature will be invalid on the other chain, which prevents the replay attack.

In order to fully understand the solution, the following needs to be understood: http://bitcoinfactswiki.github.io/OP_CHECKSIG/

If you take a look at that page, be careful, it has been known to explode some people's brains. It is a summary and the actual signing code needs to deal with even more details and it is quite tricky and making any code change creates risk of making something that just doesnt work. That is why I like my solution which doesnt need to change the code!

The key is:
SIGHASH_ALL 0x00000001
SIGHASH_NONE 0x00000002
SIGHASH_SINGLE 0x00000003
SIGHASH_ANYONECANPAY 0x00000080

These are the four SIGHASH types that are made part of the data that is signed and the lowest byte ends up in the blockchain. However, the upper three bytes affect the data that is signed, but it doesnt end up in the blockchain. So my solution is to change the above constants to:

SIGHASH_ALL 0x77700001
SIGHASH_NONE 0x77700002
SIGHASH_SINGLE 0x77700003
SIGHASH_ANYONECANPAY 0x77700080

This fix is just as easy to do in JS: https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/src/transaction.js#L17
Transaction.SIGHASH_ALL = 0x01
Transaction.SIGHASH_NONE = 0x02
Transaction.SIGHASH_SINGLE = 0x03
Transaction.SIGHASH_ANYONECANPAY = 0x80

So by changing the upper bits it changes the SHA256 hash of the tx that is signed, but all the signing logic stays the same, which means no code changes, but yet the signatures are mutually exclusive. No replay attack

There is the very rare transaction that can be spent without any signatures for any of the inputs. If there is no signature, then a replay attack is possible just for these offbeat transactions. But I estimate that the percentage of transactions that dont require any signatures is very low.

Another edge case are transactions that are not in the original blockchain. It is probably obvious, but any transaction that is not confirmed in the original blockchain cant make it into the spinoff chain. This means anything in the mempool wont happen in the new chain and would need to be signed again if the transaction is desired to happen on both chains. Since unconfirmed transactions with existing signatures, like nLockTime, are not confirmed, they wont be valid in the new chain.

However, with an estimated 95%+ (might even be 99%+) of existing transactions retained, this is a very practical way to do a hardfork that arguably has the smallest footprint for a solution that creates mutually exclusive signatures

James

P.S. Still need to verify that SIGHASH_ANYONECANPAY is not used as a bitmask, if it is, then a slight modification is needed, but that is a very rare usage and might not even have signatures in the tx

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

What do you think about Waves founder Sashas response?

sasha ivanov @sasha35625

"I kinda sh*t my pants when I hear about #bitcoin spinoff forks. Please don't do it guys. you'll kill #crypto https://twitter.com/Jl777News/status/766166365300457472
TwitterToday at 10:12 AM"

If all it takes to kill crypto is releasing some slightly modified software then it was never meant to survive. No money is even going into this forking project right now which means if what Sasha is saying is true (it's not) then the cost to destroy cryptocurrencies is zero.

On the contrary, Ethereum has showed us that even in highly adverse circumstances a split does not seem to cause major adverse effects.

Jl777News jl777 news tweeted @ 18 Aug 2016 - 06:54 UTC

bitcoin spinoff fork - how to make a clean fork without any replay attack and no blockchain visible changes ift.tt/2bJVaqk

Disclaimer: I am just a bot trying to be helpful.

Thanks for being helpful. :D

I just read the whole article, hoping I was intelligent enough to participate in this conversation. Nope.

There are talks about forking bitcoin in parallel to the bitcoin core team. What are your thoughts in this?

I was asked to provide some technical advice due to my iguana experience, so am helping as I can.

It is a security risk to having full bitcoin blocks and reduces the value of bitcoin. It seems that non-technical factors have delayed fixing this problem, some say it is because core team wants to create a fee market, ie where you have to pay more and more to get your tx confirmed.

Regardless of the reason, it should have been fixed over a year ago!

The fact that there is a serious effort now to free bitcoin development from corporate sponsorship, can only be a good thing.

But dont you think the fork would have catastrophic results on the economical side?

even a haphazard ETC fork with replay attack, DAO hacker, white hat group behaving like hackers, etc., the combined value of ETH+ETC is slightly higher than before the fork.

Is that what you mean by "catastrophic results"?

Long term having a more open bitcoin development process will be the opposite of catastrophic. So short term, volatility but with combined value likely higher. Long term two independently evolving serious crypto projects.

Dont let the fear mongers make you fearful of change. Not changing will be the worst thing as full blocks breaks the network

I am not scared )

Thought, bitcoin is the "mother" of all crypto, and when its forked it will casue a butterfly effect for the masses and the specultors on the maket, IF, it will go down, all will follow. It wont do any long term damage, but who knows

I'm not sure the consequences have played out yet. At the very least, Ethereum's momentum has come to screeching halt. There is no more talk of blowing by Bitcoin.

Also, it was demonstrated that it's trivial to fork Ethereum and poach massive amounts of hash power almost instantly. The miners are whores and will happily secure any major fork launched by a banking/corporate consortium.

Thanks a lot for sharing your knowledge. If you're not careful, you'll get me into programming. :)

Where in the Core code does the SIGHASH int get truncated to 1 byte before signing?

HASH(the modified tx with the 4 byte sighash int) -> 256 bits that is signed
then the SIGHASH byte (not int) is concatenated to the signature and that is what goes in the blockchain. it is explained in the http://bitcoinfactswiki.github.io/OP_CHECKSIG/

Right that's what I meant sorry, 4 bytes are signed but only 1 byte goes into the serialized transaction for broadcast. So what I'm asking is, where in the Tx serialization process is the 4 bytes reduced to 1? Like literally where in the code or protocol spec?

the signature is generated, 70 to 72 bytes (maybe I am off by 1 here), and the sighash byte is just added to the end. It is in the protocol spec, so that is what I did and it works. not familiar with bitcoind code as I wrote iguanacore from scratch

Maybe it's this line right here, casting nHashType as an unsigned char (1 byte):
https://github.com/bitcoin/bitcoin/blob/d612837814020ae832499d18e6ee5eb919a87907/src/script/sign.cpp#L32

yes, you found it. casting an int to char will truncate it

@jt777 could you contact me via my website for some consulting on a planned fork. Thanks