LedgerHQ / ledgerjs

⛔️ MOVED to monorepo "ledger-live"
https://github.com/LedgerHQ/ledger-live
Apache License 2.0
575 stars 377 forks source link

signP2SHTransaction is Producing Invalid Signature #182

Closed blockchainguyy closed 6 years ago

blockchainguyy commented 6 years ago

Hi @gre

I was receiving Errors while using signatures produced by signP2SHTransaction function of @ledgerhq/hw-app-btc. So I created a script using bitcore-lib to cross check it against the signature produced by Ledger and pinpoint the change causing the Error. I also verified all my input params and found them to be correct.

I will detail both scripts here, though I think it is an issue of ledger's signP2SHTransaction function.

I'm using the following dependencies and node v8.9.3:

 "@ledgerhq/hw-app-btc": "^4.17.0",
    "@ledgerhq/hw-app-eth": "^4.19.0",
    "@ledgerhq/hw-transport-node-hid": "^4.18.0",
    "babel-runtime": "^6.26.0",
    "bip32": "^0.1.0",
    "bitcoinjs-lib": "^3.3.2",
    "bitcore-lib": "^0.15.0"

I have created a 2 of 2 Multisig address using the following 2 paths of my ledger- 48'/0'/0'/69/0/0, 48'/0'/0'/96/0/0. I used the following rawTx:

01000000016f4fbe65fe5fcb98028d67172f72bdeadc1f45cb49c50f2eb7aca4668e94d50a01000000490047522102a9d50f9817a9cf20f3feb7ad4038e88c8bd471e90dfba3a80c2e0bfd79c893122102faf805ea3652cec322dda6f7571d926f359d8abbd73af1512924151edbec90e752aeffffffff0220d613000000000017a9148eaba4fd80f515c78ddbc2509538e37c40ffcf1287904a96070000000017a914c45f1d5dde5c0f7008dd6c228c1702cfdafdf1a98700000000

I used the following Redeem Script:

522102a9d50f9817a9cf20f3feb7ad4038e88c8bd471e90dfba3a80c2e0bfd79c893122102faf805ea3652cec322dda6f7571d926f359d8abbd73af1512924151edbec90e752ae

Here's the code I used to produce it:

const bitcore = require("bitcore-lib");
const PublicKey = bitcore.PublicKey;
const Script = bitcore.Script;

var publicKey1 = new PublicKey(
  "02a9d50f9817a9cf20f3feb7ad4038e88c8bd471e90dfba3a80c2e0bfd79c89312"
); // public key for path- 48'/0'/0'/69/0/0

var publicKey = new PublicKey(
  "02faf805ea3652cec322dda6f7571d926f359d8abbd73af1512924151edbec90e7"
); // public key for path- 48'/0'/0'/96/0/0

var pubkeys = [publicKey, publicKey1];

var redeemScript = Script.buildMultisigOut(pubkeys, 2);

console.log(redeemScript.toHex());

You can decode it here: https://live.blockcypher.com/btc/decodetx/ Here is my ledger's code which is creating a signature for 48'/0'/0'/69/0/0 path:

const TransportHid = require("@ledgerhq/hw-transport-node-hid").default;
const AppBtc = require("@ledgerhq/hw-app-btc").default;

TransportHid.create()
  .then(async transport => {
    if (!transport) return console.log("err: Unable to establish connection");
    var btc = new AppBtc(transport);

    const rawTx = "01000000016f4fbe65fe5fcb98028d67172f72bdeadc1f45cb49c50f2eb7aca4668e94d50a01000000490047522102a9d50f9817a9cf20f3feb7ad4038e88c8bd471e90dfba3a80c2e0bfd79c893122102faf805ea3652cec322dda6f7571d926f359d8abbd73af1512924151edbec90e752aeffffffff0220d613000000000017a9148eaba4fd80f515c78ddbc2509538e37c40ffcf1287904a96070000000017a914c45f1d5dde5c0f7008dd6c228c1702cfdafdf1a98700000000";
    const redeemHex =
      "522102a9d50f9817a9cf20f3feb7ad4038e88c8bd471e90dfba3a80c2e0bfd79c893122102faf805ea3652cec322dda6f7571d926f359d8abbd73af1512924151edbec90e752ae";
    const bufferedData = await btc.splitTransaction(rawTx);

    let input = [];

    input.push(bufferedData);
    input.push(1);
    input.push(redeemHex);
    const outputScript = btc
      .serializeTransactionOutputs(bufferedData)
      .toString("hex");

    console.log("\nOutput script hex:", outputScript);

    const accountIndex = 69;

    console.log("\npath:", `48'/0'/0'/${accountIndex}/0/0`);

    await btc
      .signP2SHTransaction(
        [input],
        [`48'/0'/0'/${accountIndex}/0/0`],
        outputScript
      )
      .then(sig => console.log("\n\nSig hash:", sig))
      .catch(err => console.log("\n\nErr:", err));
  })
  .catch(err => console.log(err));

It returns the following Signature when I pass the RedeemScript with the tx:

Sig hash: [ '304402203e24b5ad68c1fe3bf55a11afb6a61e3525c6f0a2780a0ee4bf37401bfd1445ff02207b18b7277b4f2625d04d0fcfd796300a9f6923c1df11a96876ab094138ea2a94' ]

And I find it strange that when I remove the redeem script param of signP2SHTransaction function (which is labelled optional), I get a different signature:

Sig hash: [ '3045022100f40f8fa2b75196a50b2f2543a06a3ed3ed79814cd29510f1225a359620b1c19102201af093addc93fce4fe8c59ad127c42064c2840ec7c71fbf5d0bae7ee5cb4cd39' ]

But both are unable to sign the transaction in a proper manner and it error while I validate it against the transaction.

I used the following script to find a valid signature using bitcore-lib:

const bitcoin = require("bitcoinjs-lib");
const bitcore = require("bitcore-lib");

// These are the private keys of paths- 48'/0'/0'/69/0/0 and 48'/0'/0'/96/0/0 of my ledger
// I retrieved them using https://iancoleman.io/bip39/ and my ledger's mnemonic

const privateKeys = [
  new bitcore.PrivateKey(
    "cNKAjjSL5buaP6q7fE375jkt72JAvvoe8RVh2v5TXv6gdjzXwPVX",
    "testnet"
  ),
  new bitcore.PrivateKey(
    "cMmNVwdfid1FnT4LjH4SJ1mZvTEGnMfUxdasGKTrHvD5nCY1UCvR",
    "testnet"
  )
];

const publicKeys = privateKeys.map(bitcore.PublicKey, bitcoin.networks.testnet);
const address = new bitcore.Address(publicKeys, 2); // 2 of 2

console.log("\n\nCreated this Address:", address);
console.log("\n\nPublic keys:", publicKeys);

// This utxo will create the same rawTx as I have used in my code with Ledger
const utxo = {
  address: "2NB9YNZwwKXannuZryo2KfvMNe4jeSNcSp5",
  txid: "0ad5948e66a4acb72e0fc549cb451fdceabd722f17678d0298cb5ffe65be4f6f",
  vout: 1,
  scriptPubKey: "a914c45f1d5dde5c0f7008dd6c228c1702cfdafdf1a987",
  // "script" : new bitcore.Script(address).toHex(),
  satoshis: 128600000
};

const fee = 10000;

const tx = new bitcore.Transaction()
  .from(utxo, publicKeys, 2)
  .fee(fee)
  .to("2N6FbZbJsGHWRpnbu8vrowCfGATKsYxuDf9", 1300000)
  .change("2NB9YNZwwKXannuZryo2KfvMNe4jeSNcSp5");

const txObj = tx.toObject();
console.log("\n\nTransaction object:", txObj);
console.log("\n\nTransaction hash:", tx);

const signature1 = tx.getSignatures(privateKeys[0])[0];
console.log("\n\nSignature1 object:", signature1.toObject());
console.log("\n\nSignature1 hash:", signature1.signature.toString()); // Outputs a DER signature1
console.log("\n\nSignature1 type:", signature1.sigtype);

console.log("\n\nIs valid tx 1:", tx.isValidSignature(signature1));
if (!tx.isValidSignature(signature1)) throw "Not a valid Signature";

It produces the following valid signature:

Signature1 hash: 304402203fb366ffd2840a900abc7ed25e945e4fdcf37679870a6ee45ce0030dd725856e02202d3b1c86458121cf4b79382281eca074a56d8b67a86dc754e2f7c65501f75b0b

I think there is a bug in the current release of ledger, can you please help me.

dhoffmann commented 6 years ago

Make sure your output script is using your new tx and not the tx from your utxo.

const outputScript = btc
      .serializeTransactionOutputs(newTx)
      .toString("hex");
blockchainguyy commented 6 years ago

Thanks a lot @dhoffmann, it worked.

blockchainguyy commented 6 years ago

Hi @dhoffmann,

As by your help I was successfully able to sign the BTC P2SH transaction, But could you please suggest what shall be the changed into the approach or in any method (https://github.com/LedgerHQ/ledgerjs/issues/176). So that I can sign the BCH P2SH transaction. Since the mentioned code is producing the invalid signature when I sign BCH P2SH transaction.

I am using sigHash: 0x41 in case of signing the BCH P2SH transaction. Also its not required in signing the BTC P2SH transaction. But If i try to sign the BCH transaction without this signHash it thows me error TransportStatusError: Ledger device: Invalid data received (0x6a80) so as to fix this error I had to use the sigHash.

Please help me out @dhoffmann

patwhite commented 5 years ago

Hi, I'm super unclear what @dhoffmann means here - I'm looking at the documentation, and it explicitly calls the splitTransaction right before serialize txn output, here:

const tx1 = btc.splitTransaction("01000000014ea60aeac5252c14291d428915bd7ccd1bfc4af009f4d4dc57ae597ed0420b71010000008a47304402201f36a12c240dbf9e566bc04321050b1984cd6eaf6caee8f02bb0bfec08e3354b022012ee2aeadcbbfd1e92959f57c15c1c6debb757b798451b104665aa3010569b49014104090b15bde569386734abf2a2b99f9ca6a50656627e77de663ca7325702769986cf26cc9dd7fdea0af432c8e2becc867c932e1b9dd742f2a108997c2252e2bdebffffffff0281b72e00000000001976a91472a5d75c8d2d0565b656a5232703b167d50d5a2b88aca0860100000000001976a9144533f5fb9b4817f713c48f0bfe96b9f50c476c9b88ac00000000"); const outputScript = btc.serializeTransactionOutputs(tx1).toString('hex');

Is that not correct? In your example where you say "newTxn" I don't see that in the code anywhere, where is that coming from? I'm experience a similar problem, and I believe I'm having a similar issue. Any help would be appreicated

SiestaMadokaist commented 4 years ago

Hi, I'm also experiencing the same problem. I couldn't figure out where I made the mistake that make my signature invalid.

my question was posted here https://stackoverflow.com/questions/59082832/how-to-sign-bitcoin-psbt-with-ledger.

does anyone have a working example of a code to sign a new transaction?

it'll be helpful if it include:

  1. The Mnemonics (so we can set our ledger to use this mnemonic)
  2. The Derivation path.
  3. Redeem Script (Optional? but probably useful for validity check)
  4. The Raw Transaction (utxo).

so that everyone could run the code locally with their own ledger.