Ledger Nono S (ledgerjs) signP2SHTransaction is Producing Invalid Signature

0

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 the Ledger, can you please help me.

Solution: I was creating a wrong input for my Ledger, I had to use utxos to create the input tx but I used the final rawtx (which I was creating using the texts). Those stuck with this issue, keep the following in mind:

In Ledger's documentation of signP2SHTransaction function- http://ledgerhq.github.io/ledgerjs/docs/#btc - inputs: refer to the utxos you are consuming to form your tx - outputScriptHex: can be used to specify all the desired output params like fees, change address, etc.

Ayush Tiwari

Posted 2018-07-10T16:54:05.000

Reputation: 5

What is bitcoin address for this derivation do you have?bitaps.com 2018-07-10T17:31:23.503

The address for the ledger's script is same as the one derived in my Bitcore script, i.e. 2NB9YNZwwKXannuZryo2KfvMNe4jeSNcSp5Ayush Tiwari 2018-07-11T09:29:48.993

Answers

0

(this is too long for a comment, so here in the answer section):

My observation: I tried to verify the three sigs at the command line (with OpenSSL). I can see the two pubkeys from your first code fragment, and the tx. I created the raw, unsigned tx, and only the third signature is ok (with pubkey 02a9d50f9817a9...) - I assume the raw tx in your first code example is somehow incorrect. I used this unsigned, raw tx (with redeemscript and 0x47 as length):

01000000016f4fbe65fe5fcb98028d67172f72bdeadc1f45cb49c50f2eb7aca4668e94d50a0100000047522102a9d50f9817a9cf20f3feb7ad4038e88c8bd471e90dfba3a80c2e0bfd79c893122102faf805ea3652cec322dda6f7571d926f359d8abbd73af1512924151edbec90e752aeffffffff0220d613000000000017a9148eaba4fd80f515c78ddbc2509538e37c40ffcf1287904a96070000000017a914c45f1d5dde5c0f7008dd6c228c1702cfdafdf1a9870000000001000000

which is double sha256'd this:

bad8b7f175a67e1ecfd857119609646993eb026b2bb507a49ce47b9ba5d321f7

What unsigned raw tx do you use to sign in your code fragment(s)? Do they have the same double sha256'd hash? I see your raw tx having "[version][tx_in count][prev tx id][prev outpoint]490047[redeem script]...", why is this "4900"?

(no need to reply again, you may append to your question by editing it)

pebwindkraft

Posted 2018-07-10T16:54:05.000

Reputation: 4 568

Yes, you were right, I was creating a wrong input for my Ledger, I had to use utxos to create the input tx but I used the final rawtx (which I was creating using the utxos).Ayush Tiwari 2018-07-13T07:30:27.520

0

Just signed your tx Transaction succesfully broadcast. Transaction id: 2ef82827b3e80f34a95fd7b9268a1cd6dc81447822927225f310fa9fd573b176

Please read this article with an explanation how to sign p2sh-multisig

https://medium.com/@support_62391/exploring-bitcoin-signing-p2sh-input-2dde869c5f5c

>>> from pybtc import *
>>> tx = Transaction("01000000016f4fbe65fe5fcb98028d67172f72bdeadc1f45cb49c50f2eb7aca4668e94d50a01000000490047522102a9d50f9817a9cf20f3feb7ad4038e88c8bd471e90dfba3a80c2e0bfd79c893122102faf805ea3652cec322dda6f7571d926f359d8abbd73af1512924151edbec90e752aeffffffff0220d613000000000017a9148eaba4fd80f515c78ddbc2509538e37c40ffcf1287904a96070000000017a914c45f1d5dde5c0f7008dd6c228c1702cfdafdf1a98700000000", testnet=1)
>>> import pprint
>>> pprint.pprint(tx)
{'amount': 128590000,
 'bSize': 188,
 'blockHash': None,
 'blockIndex': None,
 'blockTime': None,
 'coinbase': False,
 'confirmations': None,
 'data': None,
 'fee': None,
 'format': 'decoded',
 'hash':     'bb788dbf706a4e6a2a4fb3d1f19852773be71e98b5819b57b45e6d8c1e341034',
 'lockTime': 0,
 'rawTx':     '01000000016f4fbe65fe5fcb98028d67172f72bdeadc1f45cb49c50f2eb7aca4668e94d50a01000000490047522102a9d50f9817a9cf20f3feb7ad4038e88c8bd471e90dfba3a80c2e0bfd79c893122102faf805ea3652cec322dda6f7571d926f359d8abbd73af1512924151edbec90e752aeffffffff0220d613000000000017a9148eaba4fd80f515c78ddbc2509538e37c40ffcf1287904a96070000000017a914c45f1d5dde5c0f7008dd6c228c1702cfdafdf1a98700000000',
 'segwit': False,
 'size': 188,
 'testnet': 1,
 'time': None,
 'txId': 'bb788dbf706a4e6a2a4fb3d1f19852773be71e98b5819b57b45e6d8c1e341034',
 'vIn': {0: {'scriptSig':     '0047522102a9d50f9817a9cf20f3feb7ad4038e88c8bd471e90dfba3a80c2e0bfd79c893122102faf805ea3652cec322dda6f7571d926f359d8abbd73af1512924151edbec90e752ae',
         'scriptSigAsm': 'OP_0 '
                         '522102a9d50f9817a9cf20f3feb7ad4038e88c8bd471e90dfba3a80c2e0bfd79c893122102faf805ea3652cec322dda6f7571d926f359d8abbd73af1512924151edbec90e752ae',
         'scriptSigOpcodes': **'OP_0 [71]'**, <<<< script sig should be empty before signing
         'sequence': 4294967295,
         'txId': '0ad5948e66a4acb72e0fc549cb451fdceabd722f17678d0298cb5ffe65be4f6f',
         'vOut': 1}},
 'vOut': {0: {'address': '2N6FbZbJsGHWRpnbu8vrowCfGATKsYxuDf9',
          'addressHash': '8eaba4fd80f515c78ddbc2509538e37c40ffcf12',
          'nType': 1,
          'reqSigs': None,
          'scriptPubKey': 'a9148eaba4fd80f515c78ddbc2509538e37c40ffcf1287',
          'scriptPubKeyAsm': 'OP_HASH160 '
                             '8eaba4fd80f515c78ddbc2509538e37c40ffcf12 '
                             'OP_EQUAL',
          'scriptPubKeyOpcodes': 'OP_HASH160 [20] OP_EQUAL',
          'type': 'P2SH',
          'value': 1300000},
      1: {'address': '2NB9YNZwwKXannuZryo2KfvMNe4jeSNcSp5',
          'addressHash': 'c45f1d5dde5c0f7008dd6c228c1702cfdafdf1a9',
          'nType': 1,
          'reqSigs': None,
          'scriptPubKey': 'a914c45f1d5dde5c0f7008dd6c228c1702cfdafdf1a987',
          'scriptPubKeyAsm': 'OP_HASH160 '
                             'c45f1d5dde5c0f7008dd6c228c1702cfdafdf1a9 '
                             'OP_EQUAL',
          'scriptPubKeyOpcodes': 'OP_HASH160 [20] OP_EQUAL',
          'type': 'P2SH',
          'value': 127290000}},
 'vSize': 188,
 'version': 1,
 'weight': 752}



>>> tx["vIn"][0]["scriptSig"]=""
>>> tx.sign_input(0, private_key=["cNKAjjSL5buaP6q7fE375jkt72JAvvoe8RVh2v5TXv6gdjzXwPVX", "cMmNVwdfid1FnT4LjH4SJ1mZvTEGnMfUxdasGKTrHvD5nCY1UCvR"], redeem_script="522102a9d50f9817a9cf20f3feb7ad4038e88c8bd471e90dfba3a80c2e0bfd79c893122102faf805ea3652cec322dda6f7571d926f359d8abbd73af1512924151edbec90e752ae", witness_version=None)
{'format': 'decoded', 'testnet': 1, 'segwit': False, 'txId': '2ef82827b3e80f34a95fd7b9268a1cd6dc81447822927225f310fa9fd573b176', 'hash': '2ef82827b3e80f34a95fd7b9268a1cd6dc81447822927225f310fa9fd573b176', 'version': 1, 'size': 333, 'vSize': 333, 'bSize': 333, 'lockTime': 0, 'vIn': {0: {'txId': '0ad5948e66a4acb72e0fc549cb451fdceabd722f17678d0298cb5ffe65be4f6f', 'vOut': 1, 'scriptSig': '0047304402203fb366ffd2840a900abc7ed25e945e4fdcf37679870a6ee45ce0030dd725856e02202d3b1c86458121cf4b79382281eca074a56d8b67a86dc754e2f7c65501f75b0b01483045022100df9dfe852cd592062e8e22b5e1cad18a2a66e35a19611960fc314b7b36814ff20220404dcb809cb122a2adb8c16c507ed1be8780c08bbb89c2510775fccfa11162af0147522102a9d50f9817a9cf20f3feb7ad4038e88c8bd471e90dfba3a80c2e0bfd79c893122102faf805ea3652cec322dda6f7571d926f359d8abbd73af1512924151edbec90e752ae', 'sequence': 4294967295, 'scriptSigOpcodes': 'OP_0 [71] [72] [71]', 'scriptSigAsm': 'OP_0 304402203fb366ffd2840a900abc7ed25e945e4fdcf37679870a6ee45ce0030dd725856e02202d3b1c86458121cf4b79382281eca074a56d8b67a86dc754e2f7c65501f75b0b01 3045022100df9dfe852cd592062e8e22b5e1cad18a2a66e35a19611960fc314b7b36814ff20220404dcb809cb122a2adb8c16c507ed1be8780c08bbb89c2510775fccfa11162af01 522102a9d50f9817a9cf20f3feb7ad4038e88c8bd471e90dfba3a80c2e0bfd79c893122102faf805ea3652cec322dda6f7571d926f359d8abbd73af1512924151edbec90e752ae'}}, 'vOut': {0: {'value': 1300000, 'scriptPubKey': 'a9148eaba4fd80f515c78ddbc2509538e37c40ffcf1287', 'nType': 1, 'type': 'P2SH', 'addressHash': '8eaba4fd80f515c78ddbc2509538e37c40ffcf12', 'reqSigs': None, 'address': '2N6FbZbJsGHWRpnbu8vrowCfGATKsYxuDf9', 'scriptPubKeyOpcodes': 'OP_HASH160 [20] OP_EQUAL', 'scriptPubKeyAsm': 'OP_HASH160 8eaba4fd80f515c78ddbc2509538e37c40ffcf12 OP_EQUAL'}, 1: {'value': 127290000, 'scriptPubKey': 'a914c45f1d5dde5c0f7008dd6c228c1702cfdafdf1a987', 'nType': 1, 'type': 'P2SH', 'addressHash': 'c45f1d5dde5c0f7008dd6c228c1702cfdafdf1a9', 'reqSigs': None, 'address': '2NB9YNZwwKXannuZryo2KfvMNe4jeSNcSp5', 'scriptPubKeyOpcodes': 'OP_HASH160 [20] OP_EQUAL', 'scriptPubKeyAsm': 'OP_HASH160 c45f1d5dde5c0f7008dd6c228c1702cfdafdf1a9 OP_EQUAL'}}, 'rawTx': '01000000016f4fbe65fe5fcb98028d67172f72bdeadc1f45cb49c50f2eb7aca4668e94d50a01000000da0047304402203fb366ffd2840a900abc7ed25e945e4fdcf37679870a6ee45ce0030dd725856e02202d3b1c86458121cf4b79382281eca074a56d8b67a86dc754e2f7c65501f75b0b01483045022100df9dfe852cd592062e8e22b5e1cad18a2a66e35a19611960fc314b7b36814ff20220404dcb809cb122a2adb8c16c507ed1be8780c08bbb89c2510775fccfa11162af0147522102a9d50f9817a9cf20f3feb7ad4038e88c8bd471e90dfba3a80c2e0bfd79c893122102faf805ea3652cec322dda6f7571d926f359d8abbd73af1512924151edbec90e752aeffffffff0220d613000000000017a9148eaba4fd80f515c78ddbc2509538e37c40ffcf1287904a96070000000017a914c45f1d5dde5c0f7008dd6c228c1702cfdafdf1a98700000000', 'blockHash': None, 'confirmations': None, 'time': None, 'blockTime': None, 'blockIndex': None, 'coinbase': False, 'fee': None, 'data': None, 'amount': 128590000, 'weight': 1332}
>>>

First, your error is scriptSig should be empty before signing your scriptSig is only 1 signature and no redeem script inside scriptSig.

bitaps.com

Posted 2018-07-10T16:54:05.000

Reputation: 563

I know how to create a p2sh transaction, (I had problems in doing it using my Ledger nano S. I solved it though, adding info about my solution by editing my question.Ayush Tiwari 2018-07-13T07:24:40.567

0

Signature1 hash: 304402203fb366ffd2840a900abc7ed25e945e4fdcf37679870a6ee45ce0030dd725856e02202d3b1c86458121cf4b79382281eca074a56d8b67a86dc754e2f7c65501f75b0b

This may seem like a really trivial question... but what kind of signature is this? This is not the raw output information you send on the wire.

In my scenario i have 2 endpoints that need to sign a transaction (normal multisignature). One endpoint is the nano ledger.. the other is a bitcoin-core node. I can get this "style" signature hash from the ledger.. but what do i need to give to the bitcoin-core node to finish signing the transaction. I have my scriptsig, vout,redeemscript, txids etc.. but i am trying to piece together what this hash is.. and how can i send it back to the bitcoin-core node to finishing signing?

user3554230

Posted 2018-07-10T16:54:05.000

Reputation: 11

try searching applySignature in this doc- https://bitcore.io/api/lib/transaction and read about it, you will understand.

Ayush Tiwari 2018-07-23T07:55:00.410