bitcoinjs / bitcoinjs-lib

A javascript Bitcoin library for node.js and browsers.
MIT License
5.62k stars 2.09k forks source link

64: non-canonical (code -26) after signing a multisig transaction #318

Closed dizda closed 9 years ago

dizda commented 9 years ago

Hi,

I encountered a problem, I'm trying to spend a transaction from a multisig 2-3 address.

It's pretty weird, because it doesn't work when signing the input with bitcoinjs, but there is no problem when signing manually trough bitcoind.

var bitcoin = require('bitcoinjs-lib');

var rawTransaction = '01000000014c80a5129d03d0ebe28c2626fb2b35962c438f548ff399cc85b462109338550b0100000000ffffffff0210270000000000001976a914d356d4d8079f8556be4c8102ecf00cc63344e75488ac307500000000000017a9145010eb90f22f4865766e969fc5e07c1e2445f81a8700000000';
var redeemScriptHex = '522102a6074afbed684715760e4eebe9878fea1ea3862828e687674a841d7e03db07c7210253468fb6d90c4d9bf6d02dde920dc7cfa214b3103fc0f59b7c52a4163df6bbaf210210ec8ae465d9d6a686a8718e9051b581277ee05889fc7776a2925a111c7fc77f53ae';
var privKeys = [
    'KwLPiuHXJGkuyMJkjybUH8M2nQ9A64FYXMtT9RFJLxgtT5wZBAey',
    'KxjVtJZscdLZ1GZR8cHhhDo8bXMmQNjST3f4FW2AVhCFS7h4k8q2'
].map(function(wif) { return bitcoin.ECKey.fromWIF(wif) });

// Recover transaction from raw_transaction
var tx  = bitcoin.Transaction.fromHex(rawTransaction);

// Build it into Transaction Builder
var txb = bitcoin.TransactionBuilder.fromTransaction(tx);

txb.sign(0, privKeys[0], bitcoin.Script.fromHex(redeemScriptHex));
txb.sign(0, privKeys[1], bitcoin.Script.fromHex(redeemScriptHex));

console.log(txb.build().toHex());
// => 01000000014c80a5129d03d0ebe28c2626fb2b35962c438f548ff399cc85b462109338550b01000000fdfd00004830450221008a56762640196d092c897116232d230a14fa72f017a6ee98bd65001237a2e5f1022024ac1279838bf297c80c3a47de75d2277835e43ad6696068c734497bb59a61a901473044022070d4173c23d3cdacc18d6c30cf984b3651de12a9c99a1d5ae3af645aa2a0fd5a02201fff50654f80b9b88062d92d4132cbd6ce70dea65ca30198959744feeda984f4014c69522102a6074afbed684715760e4eebe9878fea1ea3862828e687674a841d7e03db07c7210253468fb6d90c4d9bf6d02dde920dc7cfa214b3103fc0f59b7c52a4163df6bbaf210210ec8ae465d9d6a686a8718e9051b581277ee05889fc7776a2925a111c7fc77f53aeffffffff0210270000000000001976a914d356d4d8079f8556be4c8102ecf00cc63344e75488ac307500000000000017a9145010eb90f22f4865766e969fc5e07c1e2445f81a8700000000

Then, I try to send the raw transaction through bitcoind, I've got "64: non-canonical (code -26)".

But when I try to sign manually with bitcoind :

signrawtransaction 01000000014c80a5129d03d0ebe28c2626fb2b35962c438f548ff399cc85b462109338550b0100000000ffffffff0210270000000000001976a914d356d4d8079f8556be4c8102ecf00cc63344e75488ac307500000000000017a9145010eb90f22f4865766e969fc5e07c1e2445f81a8700000000 '[{"txid":"0b5538931062b485cc99f38f548f432c96352bfb26268ce2ebd0039d12a5804c","vout":1,"redeemScript":"522102a6074afbed684715760e4eebe9878fea1ea3862828e687674a841d7e03db07c7210253468fb6d90c4d9bf6d02dde920dc7cfa214b3103fc0f59b7c52a4163df6bbaf210210ec8ae465d9d6a686a8718e9051b581277ee05889fc7776a2925a111c7fc77f53ae","scriptPubKey":"a914d84ffa293e48acb6f8d3be488eb0c6898a3d465887"}]' '["KwLPiuHXJGkuyMJkjybUH8M2nQ9A64FYXMtT9RFJLxgtT5wZBAey"]'

then

signrawtransaction 01000000014c80a5129d03d0ebe28c2626fb2b35962c438f548ff399cc85b462109338550b01000000b500483045022100bb11d0ec4ed0507a3888110fba30ccfe6348d22d8ea95f4c9df50f156531f34c022027c722cecd7390b6f9b65f9d8142e35c738c977ffef4197cea862f4a2e52752d014c69522102a6074afbed684715760e4eebe9878fea1ea3862828e687674a841d7e03db07c7210253468fb6d90c4d9bf6d02dde920dc7cfa214b3103fc0f59b7c52a4163df6bbaf210210ec8ae465d9d6a686a8718e9051b581277ee05889fc7776a2925a111c7fc77f53aeffffffff0210270000000000001976a914d356d4d8079f8556be4c8102ecf00cc63344e75488ac307500000000000017a9145010eb90f22f4865766e969fc5e07c1e2445f81a8700000000 '[{"txid":"0b5538931062b485cc99f38f548f432c96352bfb26268ce2ebd0039d12a5804c","vout":1,"redeemScript":"522102a6074afbed684715760e4eebe9878fea1ea3862828e687674a841d7e03db07c7210253468fb6d90c4d9bf6d02dde920dc7cfa214b3103fc0f59b7c52a4163df6bbaf210210ec8ae465d9d6a686a8718e9051b581277ee05889fc7776a2925a111c7fc77f53ae","scriptPubKey":"a914d84ffa293e48acb6f8d3be488eb0c6898a3d465887"}]' '["KxjVtJZscdLZ1GZR8cHhhDo8bXMmQNjST3f4FW2AVhCFS7h4k8q2"]'

# => 01000000014c80a5129d03d0ebe28c2626fb2b35962c438f548ff399cc85b462109338550b01000000fdfd0000473044022049f3ab459f56f04c110e9c513862446c50b79edaf6b9eda8dc593686a3591e4e022006235cc8ef671a9e25660e65102965ca4c23919f089f7eec5ceb5b2a22e77ec101483045022100bb11d0ec4ed0507a3888110fba30ccfe6348d22d8ea95f4c9df50f156531f34c022027c722cecd7390b6f9b65f9d8142e35c738c977ffef4197cea862f4a2e52752d014c69522102a6074afbed684715760e4eebe9878fea1ea3862828e687674a841d7e03db07c7210253468fb6d90c4d9bf6d02dde920dc7cfa214b3103fc0f59b7c52a4163df6bbaf210210ec8ae465d9d6a686a8718e9051b581277ee05889fc7776a2925a111c7fc77f53aeffffffff0210270000000000001976a914d356d4d8079f8556be4c8102ecf00cc63344e75488ac307500000000000017a9145010eb90f22f4865766e969fc5e07c1e2445f81a8700000000

And send it via bitcoind, it just works. Did I miss something?

Thanks.

dcousens commented 9 years ago

What version of bitcoind?

dcousens commented 9 years ago

At first glance, it seems something is not canonical with the [bitcoinjs-lib] signatures.

The transactions are identical with the only difference being the script signatures (expected).

< 48 30450221009a56762640196d092c897116232d230a14fa72f017a6ee98bd65001237a2e5f1022024ac1279838bf297c80c3a47de75d2277835e43ad6696068c734497bb59a61a901
< 47 3044022070d4173c23d3cdacc18d6c30cf984b3651de12a9c99a1d5ae3af645aa2a0fd5a02201fff50654f80b9b88062d92d4132cbd6ce70dea65ca30198959744feeda984f401
---
> 47 3044022049f3ab459f56f04c110e9c513862446c50b79edaf6b9eda8dc593686a3591e4e022006235cc8ef671a9e25660e65102965ca4c23919f089f7eec5ceb5b2a22e77ec101
> 48 3045022100bb11d0ec4ed0507a3888110fba30ccfe6348d22d8ea95f4c9df50f156531f34c022027c722cecd7390b6f9b65f9d8142e35c738c977ffef4197cea862f4a2e52752d01

Naturally then, I'll have to look into why

48 30450221009a56762640196d092c897116232d230a14fa72f017a6ee98bd65001237a2e5f1022024ac1279838bf297c80c3a47de75d2277835e43ad6696068c734497bb59a61a901
47 3044022070d4173c23d3cdacc18d6c30cf984b3651de12a9c99a1d5ae3af645aa2a0fd5a02201fff50654f80b9b88062d92d4132cbd6ce70dea65ca30198959744feeda984f401

is non-canonical.

dizda commented 9 years ago

Bitcoind version : v0.9.3.0-gcea5e49-beta (64-bit)

dizda commented 9 years ago

Thanks for the help. Indeed, signature process seems different between the lib and bitcoind. But my knowledge is insufficient to figure out why..

ryanxcharles commented 9 years ago

This is just a stab in the dark as I haven't looked at it in detail, but if you are running 0.9.3, have they enabled any of the BIP62 stuff yet? Maybe bitcoinjs-lib is violating one of the BIP62 rules?

dcousens commented 9 years ago

@ryanxcharles that is my thought as well

edit: The OP_PUSHDATA OPs are both fine (47/48 vs 48/47 respectively for the two signatures)

edit2: Both the signatures by bitcoinjs-lib contain "Low S values in signatures"

dizda commented 9 years ago

You mean the rawsignedtransaction is correct, and I'm able to send it through older version of bitcoind ?

sipa commented 9 years ago

0.9.3 does not have any BIP62 rules, and I can't see at first glance what would be wrong with the signatures posted above. Bitcoind 0.9.3 should put some more detailed message in debug.log when verifying the transaction, though.

dizda commented 9 years ago

2014-11-26 15:54:44 ERROR: AcceptToMemoryPool : inputs already spent

dcousens commented 9 years ago

@dizda that would be the case now, but was that the error that matched to 64: non-canonical (code -26)?

dizda commented 9 years ago

Pretty weird indeed.

Wait I will clear logs, and retry with new inputs.

dizda commented 9 years ago

Ok sorry.

So I repeated same steps with a new input, and debug.log return me this :

2014-11-26 23:30:23 ERROR: CScriptCheck() : ac7dc984d56ebce575cfb15938ce9e089badd72e1230a1587a56e4b5fdeb757c VerifySignature failed
2014-11-26 23:30:23 ERROR: CScriptCheck() : ac7dc984d56ebce575cfb15938ce9e089badd72e1230a1587a56e4b5fdeb757c VerifySignature failed
2014-11-26 23:30:23 ERROR: AcceptToMemoryPool: : ConnectInputs failed ac7dc984d56ebce575cfb15938ce9e089badd72e1230a1587a56e4b5fdeb757c
sipa commented 9 years ago

In 0.9.3?

dizda commented 9 years ago

Yep

dcousens commented 9 years ago

Can you post the transactions so we can check that this isn't bitcoinjs-libs TransactionBuilder assimilating the signatures such that they match the wrong inputs public keys?

sipa commented 9 years ago

@dizda Those are likely about transactions received over the network; not about the one you submitted.

dizda commented 9 years ago

Ok @dcousens so this is the full script :

var bitcoin = require('bitcoinjs-lib');

var rawTransaction = '010000000155a3dd66c03bd64f6512fc47c9156db1a431946e07536d7ed5321df051d6bfc50100000000ffffffff0210270000000000001976a914d356d4d8079f8556be4c8102ecf00cc63344e75488ac102700000000000017a914dec137068316fe8ddffdf05befb24d786e38adf98700000000';
var redeemScriptHex = '522102a71ef05b31072d778b35f47d6204b80733db964498267f61dec2bdaaca22752121025bcd11b34f89704aba4d8f88d5e4d5db2a65ed1d6aabbc1f335f2eec771ee4e421024da9e2fb260317954c54df92d78051ef230de9a5aafef2592bb3b4f666209bcf53ae';
var privKeys = [
    'L1Dz8SqCiSS9j2GWLkmAytv3U9abVYzYQJLncTEgNoKCUTAJxiUi',
    'KwRo1CDf3LEKJ9RS9QNSZ74SXJqTJ3P76j4QVVqF81NjN2tM8QkM'
].map(function(wif) { return bitcoin.ECKey.fromWIF(wif) });

// Recover transaction from raw_transaction created by bitcoind || or raw_signed_transaction
var tx  = bitcoin.Transaction.fromHex(rawTransaction);

// Build it into Transaction Builder
var txb = bitcoin.TransactionBuilder.fromTransaction(tx);

txb.sign(0, privKeys[0], bitcoin.Script.fromHex(redeemScriptHex));
txb.sign(0, privKeys[1], bitcoin.Script.fromHex(redeemScriptHex));

console.log(txb.build().toHex());
dizda commented 9 years ago

Ok thanks @sipa

dizda commented 9 years ago

@dcousens There is only one input, so TransactionBuilder can't mismatch inputs, right ?

dcousens commented 9 years ago

@dizda correct! I meant mismatch the order of the signatures against their respective public keys in the redeemScript.

dizda commented 9 years ago

@dcousens Oh, ok.

dcousens commented 9 years ago

The signatures do not match the associated public keys, and therefore the resultant script is invalid.

dizda commented 9 years ago

Wow ok, if I understand, the first 0 is the # input, the second 0 is the signature, and the boolean is that the input is correctly signed ?

dcousens commented 9 years ago

@dizda the redeem script is:

OP_2 02a71ef05b31072d778b35f47d6204b80733db964498267f61dec2bdaaca227521 025bcd11b34f89704aba4d8f88d5e4d5db2a65ed1d6aabbc1f335f2eec771ee4e4 024da9e2fb260317954c54df92d7805
1ef230de9a5aafef2592bb3b4f666209bcf OP_3 OP_CHECKMULTISIG

Therefore, to spend from that we can sign the corresponding output (aka, our input:0) with signatures such that there are no more than 3-2 (that is, 1) signature failures when verifying against the corresponding public keys in the redeem script. In this case, the signatures are [incorrectly] out of order and the end result during verification is 2 signature failures.

2 > 3-2 therefore the script is considered invalid.

dcousens commented 9 years ago

The issue is because you are signing them out of order and we don't re-order signatures yet.

txb.sign(0, privKeys[0], bitcoin.Script.fromHex(redeemScriptHex));
txb.sign(0, privKeys[1], bitcoin.Script.fromHex(redeemScriptHex));

Should be

txb.sign(0, privKeys[1], bitcoin.Script.fromHex(redeemScriptHex));
txb.sign(0, privKeys[0], bitcoin.Script.fromHex(redeemScriptHex));

Technically it shouldn't be necessary, and I still consider this a bug in TransactionBuilder as a result. But, hopefully this clears up your understanding as to why it failed.

dizda commented 9 years ago

Right, the second private key is in reality, the first one.

So, the second signer cannot sign before the first one ? But Bitcoind can do a re-order of signatures ?

dcousens commented 9 years ago

With the current state of the TransactionBuilder API, yes you are correct in assuming:

the second signer cannot sign before the first one

dizda commented 9 years ago

Ok I get it, so if the third signer sign the transaction, the transaction cannot be signed anymore.

dcousens commented 9 years ago

See https://github.com/bitcoinjs/bitcoinjs-lib/pull/244#issuecomment-50305212. The issue is simply that it is difficult to resolve this problem in the raw (non P2SH) multi signature case, so I'll have to specialize the implementation, which I'm trying to avoid. I'll see what I can do to make this better before/with 2.0.0

dizda commented 9 years ago

Ok great!

Well, thank you for your help guys, I appreciate! Great lib BTW :hand: (high five!)

dcousens commented 9 years ago

Thanks for the feedback @dizda.

Closed in favour of https://github.com/bitcoinjs/bitcoinjs-lib/issues/320.