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.
2NB9YNZwwKXannuZryo2KfvMNe4jeSNcSp5