Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

'mandatory-script-verify-flag-failed (Signature must use SIGHASH_FORKID) #4

Open
unqjohnny opened this issue Dec 13, 2017 · 37 comments

Comments

@unqjohnny
Copy link

I have a problem:
org.bitcoinj.core.RejectedTransactionException: Reject: tx for reason 'mandatory-script-verify-flag-failed (Signature must use SIGHASH_FORKID)' (16)
at org.bitcoinj.core.TransactionBroadcast$2.onPreMessageReceived(TransactionBroadcast.java:102) [bitcoincashj-core-0.14.5-SNAPSHOT.jar:?]
at org.bitcoinj.core.Peer.processMessage(Peer.java:461) [bitcoincashj-core-0.14.5-SNAPSHOT.jar:?]
at org.bitcoinj.core.PeerSocketHandler.receiveBytes(PeerSocketHandler.java:182) [bitcoincashj-core-0.14.5-SNAPSHOT.jar:?]
at org.bitcoinj.net.ConnectionHandler.handleKey(ConnectionHandler.java:223) [bitcoincashj-core-0.14.5-SNAPSHOT.jar:?]
at org.bitcoinj.net.NioClientManager.handleKey(NioClientManager.java:86) [bitcoincashj-core-0.14.5-SNAPSHOT.jar:?]
at org.bitcoinj.net.NioClientManager.run(NioClientManager.java:122) [bitcoincashj-core-0.14.5-SNAPSHOT.jar:?]
at com.google.common.util.concurrent.AbstractExecutionThreadService$1$2.run(AbstractExecutionThreadService.java:60) [guava-18.0.jar:?]
at com.google.common.util.concurrent.Callables$3.run(Callables.java:95) [guava-18.0.jar:?]
at org.bitcoinj.utils.ContextPropagatingThreadFactory$1.run(ContextPropagatingThreadFactory.java:49) [bitcoincashj-core-0.14.5-SNAPSHOT.jar:?]
at java.lang.Thread.run(Thread.java:745) [?:1.8.0_73]
2017-12-13 07:40:24 ERROR Peer [514]-[119.23.160.146]:8333 /Bitcoin ABC:0.16.1(EB8.0)/: Received Reject

@HashEngineering
Copy link
Collaborator

SendRequest.setUseForkId() will be needed.

There is probably a better way to handle this. to maintain backward compatibility with bitcoinj unit tests, the default signature hash function is the Bitcoin version. But for Bitcoin Cash, it will be the replay protection signature hash.

@Danconnolly
Copy link
Collaborator

Danconnolly commented Dec 15, 2017 via email

@HashEngineering
Copy link
Collaborator

It would be easier for users of the library if we removed the requirement. Somehow we still need the unit tests to work. There are a few unit tests that will probably fail, if we change the sighash to the BCH version. It may not be that many tests that would have to be modified to use the BTC sighash.

@Danconnolly
Copy link
Collaborator

Danconnolly commented Dec 18, 2017 via email

@Danconnolly
Copy link
Collaborator

Danconnolly commented Dec 18, 2017 via email

@rafa-js
Copy link

rafa-js commented Jan 3, 2018

I think the provided solution is a good balance between backwards compatibility and introduced complexity.
Just a piece of suggestion: I'd use an enum rather than an integer to describe the "fork id" concept. It's less ambiguous and even it's a hint of the values I can chose as a parameter; otherwise it seems ambiguous. Also, you avoid anyone to use meaningless parameters like 100, 200 or any stupid fail.

Regards,

@robinkanters
Copy link

So this has been quiet for a while. I am having issues where the network is rejecting my tx that were signed offline using bitcoinj.cash, exactly because of the reasons above.

I've tried creating the transaction (from unsigned hex tx) and setting setVersion(2), without success. I'm currently using this commit.

Any solutions yet?

@HashEngineering
Copy link
Collaborator

@robinkanters For now you will need to use SendRequest.setUseForkId(true).

@Danconnolly Your suggested changes above are a good start, but we need make changes to SendRequest and Wallet to SendRequest.setUseForkId(true) if the transaction version is 2. Currently, the code sets the transaction version to 2 if the SendRequest.setUseForkId(true). Therefore developers such as @robinkanters and @unqjohnny are getting the error Reject: tx for reason 'mandatory-script-verify-flag-failed (Signature must use SIGHASH_FORKID)'

@robinkanters
Copy link

robinkanters commented Feb 13, 2018

@HashEngineering I don't have a SendRequest, only a Transaction (offline signing)

@HashEngineering
Copy link
Collaborator

@robinkanters In that case, we will need to make a different change.

How do you to sign your transaction?

@robinkanters
Copy link

robinkanters commented Feb 13, 2018

@HashEngineering

  1. I build my transaction hex with a tool like coinb.in
  2. call a method with the hex as param
  3. that method parses the tx, copies the inputs, clears the inputs and adds them back as signed inputs
  4. then I serialize the tx and convert it back to hex

This works for BTC, but BCH rejects the tx for the fork id

@rafa-js
Copy link

rafa-js commented Feb 13, 2018

I think you need to use tx.setVersion(Transaction.CURRENT_VERSION), this is what SendRequest.useForkId(true) does internally.

Btw @HashEngineering since this is the most common case I think this should be done by default.

@robinkanters
Copy link

I think I tried that, will try again tomorrow and see if I can post some code. It's in Kotlin but should be understandable enough ;)

@HashEngineering
Copy link
Collaborator

HashEngineering commented Feb 13, 2018 via email

@HashEngineering
Copy link
Collaborator

@rseibane SendRequest.useForkId(true) does set the transaction version to 2, but it also triggers bitcoinj to use the Bitcoin Cash signature creation. Having a transaction version 2 doesn't trigger anything, currently. However, it should, as you say, be the default behavior (tx version 2 and sign with forkId).

@robinkanters
Copy link

robinkanters commented Feb 20, 2018

@HashEngineering did not mean to be condescending, just not used to dealing with people familiar with Kotlin ;)

Anyway, this is the code I'm currently using to sign raw incoming txs. Don't know if this is correct, but to my understanding this should work

val txHex = Hex.decode(transactionRaw)
val t = Transaction(params, txHex)
t.confidence.source = TransactionConfidence.Source.SELF
t.purpose = Transaction.Purpose.USER_PAYMENT

val inputs = t.inputs.toList()
t.clearInputs()

inputs.forEach {
    val key: ECKey = // getting and parsing ECKey from db
    t.addSignedInput(it.outpoint, it.scriptSig, key)
}

return String(Hex.encode(t.bitcoinSerialize()))

@HashEngineering
Copy link
Collaborator

This:
t.addSignedInput(it.outpoint, it.scriptSig, key)

should be:
t.addSignedInput(it.outpoint, it.scriptSig, key, true)

It looks like public TransactionInput addSignedInput(TransactionOutPoint prevOut, Script scriptPubKey, ECKey sigKey, SigHash sigHash, boolean anyoneCanPay, boolean forkId) throws ScriptException hasn't been tested. You can let us know if it works.

@robinkanters
Copy link

robinkanters commented Feb 20, 2018

@HashEngineering thanks, unfortunately that doesn't work either :( Your code misses the SigHash parameter anyway, I made it into t.addSignedInput(it.outpoint, it.scriptSig, key, Transaction.SigHash.ALL, true)

I'm thinking the library really does need changes for this to work, like you said.

Just in case I'm doing something stupid, this is my (signed) tx:

0100000001c29fbf81af688157629b86c5947441ed92db44e48d40378bacf65f97ad510ac6000000006a4730440220236facc97aa2c7990a8b23d03b364bdbbdd747c57194e09ae272b9e3ea3500d802205c6928a37803c547512434dc7b601aaac0026c52249fa174e51a22be6ea4f8e3812102165c23bd2f34851e20ac66066c6d0f324262e3660000b6530a2d167ca6fbca12ffffffff0140e0f505000000001976a9141692e5fe6d4058e1c20966c52f4f4386cae9fbea88ac00000000

and this is what I got before your comment:

0100000001c29fbf81af688157629b86c5947441ed92db44e48d40378bacf65f97ad510ac6000000006b483045022100df3130085e15bad1e28b0fbfdc7b2d526f88b534d3818d27a700fe43d2b3004a022050388acf008cc1164f27db8ab4c9af2d6eb21c9093bf18a1cc7c8b0ca0c02435012102165c23bd2f34851e20ac66066c6d0f324262e3660000b6530a2d167ca6fbca12ffffffff0140e0f505000000001976a9141692e5fe6d4058e1c20966c52f4f4386cae9fbea88ac00000000

Both are rejected by network rules for missing a fork id.

For completeness, this is my unsigned tx:

0100000001c29fbf81af688157629b86c5947441ed92db44e48d40378bacf65f97ad510ac6000000001976a914015b17ec1e13254e940b57eccad80aa229c4383188acffffffff0140e0f505000000001976a9141692e5fe6d4058e1c20966c52f4f4386cae9fbea88ac00000000

@HashEngineering
Copy link
Collaborator

I will need to look at this later. My comment did result in a different signature than yours, so some part of it "works".

It's possible that having a version 2 transaction might help, but I doubt it.

@robinkanters
Copy link

robinkanters commented Feb 21, 2018

I can try it, at least. Do I just call some setVersion somewhere?

Edit: v2 tx here, also rejected:

0200000001c29fbf81af688157629b86c5947441ed92db44e48d40378bacf65f97ad510ac6000000006a47304402204706d92bbc8116da59455296786714678c06bd5b487df4ad5b6fa51ff1a6bf79022005c65cd464dd9799c33f38b45e1b62d4c55354f26aaf41cb1eb20ceb5c87540c412102165c23bd2f34851e20ac66066c6d0f324262e3660000b6530a2d167ca6fbca12ffffffff0140e0f505000000001976a9141692e5fe6d4058e1c20966c52f4f4386cae9fbea88ac00000000

@HashEngineering
Copy link
Collaborator

I started to make a test based on your code above and then I found the problem and it has to do with making the transaction "offline!" Your signature is not correct due to missing information.

This is the code that does the work -- addSignedInput

public TransactionInput addSignedInput(TransactionOutPoint prevOut, Script scriptPubKey, ECKey sigKey,
                                           SigHash sigHash, boolean anyoneCanPay, boolean forkId) throws ScriptException {
        // Verify the API user didn't try to do operations out of order.
        checkState(!outputs.isEmpty(), "Attempting to sign tx without outputs.");
        TransactionInput input = new TransactionInput(params, this, new byte[]{}, prevOut);
        addInput(input);
        Sha256Hash hash = forkId ?
                hashForSignatureWitness(inputs.size() -1, scriptPubKey, prevOut.getConnectedOutput().getValue(), sigHash, anyoneCanPay) :
                hashForSignature(inputs.size() - 1, scriptPubKey, sigHash, anyoneCanPay);

        ECKey.ECDSASignature ecSig = sigKey.sign(hash);
        TransactionSignature txSig = new TransactionSignature(ecSig, sigHash, anyoneCanPay, forkId);
        if (scriptPubKey.isSentToRawPubKey())
            input.setScriptSig(ScriptBuilder.createInputScript(txSig));
        else if (scriptPubKey.isSentToAddress())
            input.setScriptSig(ScriptBuilder.createInputScript(txSig, sigKey));
        else
            throw new ScriptException("Don't know how to sign for this kind of scriptPubKey: " + scriptPubKey);
        return input;
}

The key line is this:
hashForSignatureWitness(inputs.size() -1, scriptPubKey, prevOut.getConnectedOutput().getValue(), sigHash, anyoneCanPay)

If the forkId parameter is set true, then we use a different Signature Hash. One of the necessary pieces of information is the value of the input(prevOut.getConnectedOutput().getValue()) being signed. This information will not be available in a created transaction, whether in hex format or not. bitcoinj-cash is typically used to manage a wallet and when BCH are being spent, the wallet has the transaction information for the inputs, so it has the values of of those inputs as well. Therefore it can properly sign a new transaction.

My Bitcoin Cash Wallet app is able to sweep a paper wallet where it queries an electron cash server to get the unspent outputs. From there it creates a wallet with those transactions and it is able to sign the sweep transaction properly. An upsent API at a block explorer can also be a source for such information.

In your case, you will need to get the transaction information for the inputs and then for the inputs call connect(Transaction transaction, ConnectMode mode) to connect it and addSignedInput will be able to provide the correct information to the signature hash function. Hope this makes sense.

@robinkanters
Copy link

@HashEngineering thanks for all the info. Yes, it does make sense, but I can't help but wonder if it really needs to be this way. I've developed virtually the same code for BTC but that works just fine. Seems to me that it should be possible for BCH as well. If not, I can always alter my API to accept the connected output value of course, but I don't particularly like that approach.

@robinkanters
Copy link

@HashEngineering would it be possible to not clear the inputs but instead add the signatures afterwards? that seems to be what addSignedInput does anyway; add the input and then amend the signature

@HashEngineering
Copy link
Collaborator

@robinkanters - I don't think there is a way around this issue by following what works for BTC. The whole point of changing the Signature Hash for BCH was for replay projection. BCH transactions are not valid on the BTC blockchain and vise versa.

https://github.com/bitcoincashorg/spec/blob/master/replay-protected-sighash.md

@robinkanters
Copy link

Yes I understand that the steps won't be completely the same, but I can't imagine it would now be impossible to sign txs offline

@HashEngineering
Copy link
Collaborator

Whether offline or online, knowledge of the value of the inputs should be known to properly calculate the outputs and fees.

If you like, we could simplify the process for you and users doing similar work, by adding an amount parameter. This can eliminate connecting inputs to other transactions:

public TransactionInput addSignedInput(TransactionOutPoint prevOut, Coin amount, Script scriptPubKey, ECKey sigKey,
                                           SigHash sigHash, boolean anyoneCanPay, boolean forkId)

@robinkanters
Copy link

robinkanters commented Feb 23, 2018

@HashEngineering that would already be a big help! Still not ideal of course because I have to have a different API from BTC now, but already a lot better!

I was just quickly browsing the code to see if this is something that I could create a PR for, but I don't think so...

@HashEngineering
Copy link
Collaborator

If your software is ever to support BTC segwit transactions, you will need similar modifications. The BCH signature hash method is the same as for BTC segwit transactions.

When working with different coins, eventually one comes across the differences.

@robinkanters
Copy link

@HashEngineering fair enough. I don't ever like asking this question on OSS projects but can you give me a very rough estimate on when this fix could be available? I need it for my job and would like to plan my next issues

@HashEngineering
Copy link
Collaborator

HashEngineering commented Mar 5, 2018

By the end of March, this should be done.

This is one of several parts that need to be addressed with signatures.

@HashEngineering
Copy link
Collaborator

@robinkanters - if you get a chance, please take a look at Pull Request #28 and see if the additional addSignedInput methods are the answer you are looking for.

@robinkanters
Copy link

@HashEngineering will check it out tomorrow, will report back

@robinkanters
Copy link

@HashEngineering unfortunately I couldn't even hook it up to that branch because I'm still dependent on com.github.rseibane:bitcoinj-cash:5aaae259b5 for cashaddr. If you have any other ideas for how to test it, I'd love to hear it 😊

@HashEngineering
Copy link
Collaborator

There are ways to get the code with git, but it gets complicated with so many branches from different repos.

What you can do from your branch where you are working with com.github.rseibane:bitcoinj-cash:5aaae259b5

git checkout -b bitcoinj-cash-addsignedinputs
git pull https://github.com/bitcoincash-wallet/bitcoinj.git addsignedinputs

If this works, then it will change the code on a new branch, so it doesn't mess up what you currently have. It may give an error.

After you determine if one of the new addSignedInputs works for your application, you can let us know. and then you can return to your original branch.

If using git doesn't work, it would require looking at the code changes and doing it yourself for the one or two methods that you needed changed.

@robinkanters
Copy link

robinkanters commented Mar 27, 2018

okay disregard what I posted earlier.

Just successfully signed and sent tx c025a889b5033cb9e02462105b169b326e115052f1a1fecb701da60f1373bcb5.

t.addSignedInput(transactionOutPoint, it.scriptSig, key, Transaction.SigHash.ALL, true, true)

@HashEngineering
Copy link
Collaborator

@robinkanters, do you need any addSignedInput methods with a Coin amount parameter. That is what I had put in pull request #28?

@robinkanters
Copy link

@HashEngineering not needed by me, the one I'm currently using is enough for my use case

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants