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.

3
  • What is bitcoin address for this derivation do you have?
    – bitaps.com
    Jul 10, 2018 at 17:31
  • The address for the ledger's script is same as the one derived in my Bitcore script, i.e. 2NB9YNZwwKXannuZryo2KfvMNe4jeSNcSp5 Jul 11, 2018 at 9:29
  • hi, did you succeed doing this? since I noticed that you also using p2sh(p2ms()) address, but I didn't found a way to sign a p2sh(p2ms()) address with ledger. the example in the hw-app-btc's test, only show how to sign p2sh(p2wsh(p2ms())). if you successfully do that, may I look at your snippet?
    – Ramadoka
    Feb 4, 2020 at 15:47

3 Answers 3

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)

1
  • 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). Jul 13, 2018 at 7:30
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.

1
  • 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. Jul 13, 2018 at 7:24
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?

1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.