Tuesday, 3 December 2019

how to sign bitcoin psbt with ledger?

I'm trying to sign a Psbt transaction from bitcoinjs-lib following what I found here:

https://github.com/helperbit/helperbit-wallet/blob/master/app/components/dashboard/bitcoin.service/ledger.ts

I've checked that the compressesd publicKey both from ledger, and the one from bitcoinjsLib returned the same value.

I could sign it with the bitcoinjs-lib ECPair, but when I tries to sign it using ledger, it is always invalid.

Can someone helps me point out where did I made a mistake?

This is my code now.

/* tslint:disable */
require('regenerator-runtime');
const { default: Transport } = require('@ledgerhq/hw-transport-node-hid')
const { default: AppBtc } = require('@ledgerhq/hw-app-btc');
import * as bitcoin from 'bitcoinjs-lib'
const mnemonics = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
// not used, but if someone want to test using their own ledger.
const NETWORK = bitcoin.networks.regtest;
const segwit = true;
const LOCK_TIME = 0;
const SIGHASH_ALL = 1;
const path = `m/12'/0'/55'`


export function toByteArray(hexString: string) {
    const Buffer = require('safe-buffer').Buffer;
    const result = Buffer.alloc(hexString.length / 2);
    let i = 0;
    while (hexString.length >= 2) {
        result[i] = parseInt(hexString.substring(0, 2), 16);
        i += 1;
        hexString = hexString.substring(2, hexString.length);
    }
    return result;
}

function toHexString(buffer: Buffer) {
    return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join('');
}


function compressPublicKey(pk: string): string {
    return toHexString(bitcoin.ECPair.fromPublicKey(toByteArray(pk)).publicKey);
}


async function main(): Promise<void> {
  const nonWitnessUtxo = '02000000000105e11d8c7970aff25a8a431c49397316ce80af25dca6ac395f172e2d7326d42e570100000017160014cccb96f2ec1b5a2b7081f96b78077488769e3d8efeffffff77bcf72d69888cc739341107028e1e8a302d507fe9ffb94c5359fb1e82f08f370100000017160014527aef9caa6e0a12ecea8ce255cb36427a2f451afeffffff3cad9140403d6ec68bc0cf0b261daa4a41d92f04bddad98fc05a305127c7145c00000000171600143ea2fa276d3de26e4e220a1e6d343a235edbaff6feffffff250152b93632b8e275aba954c894dcbc2c0124a5c1db10de3edc5a0fd2880cc30100000017160014954f8b16a236e5d76b1f6fb69ea821b370fba749feffffffafd0c8b8eab835303aa213a0390e40e25a152dc62889b32df6887dc147b4e29b000000001716001436c91ffd3fd014ca8955eaa97e14dc9fdbed08f4feffffff0200e1f5050000000017a914f1f02b570afafacde515fcd6862a50822b56973e876c515f000000000017a9141afb048a646910eb09136f1cb311774d6c63e8ed870247304402200bfc0495e92aece0807c4c709fc05df3725794e766f36d372f3b35714d376775022077f01d90848205088374c2d8a9e28aedbc9f46212ce83fad7ab5ba00e33130680121021e3bbec438d71dc4966c817ee8db61970bd2288799d9bad9a5f8dd3795e2b0410247304402202c871ca7260d427f07f0482f939aa6193785de9b6df8742cf48881bd0525c4e0022056d7f2621d17fcacb3abd2dad030618a74abb259e852c98d10c96b541876a8340121033c41c3f65f7dab256d1cb28b195ba27c7fc4b1bf2c75e17935dfe9a3846b8c580247304402206cfb872c56f3cab4fcad77f83fc953d037c091d7714c0e7eca1257b56dff57c2022024540ce39d13d55de9f626aee397bad27ef5434f8e57890a3012886c4a6bc1c80121025dec89fee179c0248b24fa2c88db9890a386b836485784d53d12df639fb1e1d6024730440220239dc8ac1c0692b2a4642318cbef810114818132721ab2ba403f037ac0732b3302207112d7d1592b22958184f311f25b8e7a90ac353316bea8af12eeb731e9b0dd540121021a13c8f4c8767317c3cd9d0f7e4d8d8509822ca6891f7cbcc33ec10139b461040247304402205a407877f002d6dc86a83b3c31880366fc3f82204eb7d61a4c3a34c03439592c02201200f52a858463e6547287b2f2964594c6c3233886e12d7d3b499ef59cca5713012102b05ca0a2dc317d82f64483d4087419122eb27e5646e8190a7bac6c5cbacadbe500000000';
  // this is the utxo with txid: 25ba0c45cadf92ded94432101d286975dcdb865df44b68da597910ea783cff74

  // ecpairs things.
  const ecPrivate = bitcoin.ECPair.fromPrivateKey(Buffer.from('64e897b5fac936a30a7a73e1c2892697c91b0705a8d061ea28e59f41d2876e0d', 'hex'), { network: NETWORK });
  const ecPublic = bitcoin.ECPair.fromPublicKey(ecPrivate.publicKey, { network: NETWORK });
  const p2wpkh = bitcoin.payments.p2wpkh({ pubkey: ecPublic.publicKey, network: NETWORK });
  const p2sh = bitcoin.payments.p2sh({ redeem: p2wpkh, network: NETWORK });
  const redeemScript = p2wpkh.output as Buffer;

  // psbt things.
  const psbt = new bitcoin.Psbt({ network: NETWORK });
  psbt.addInput({
    hash: '25ba0c45cadf92ded94432101d286975dcdb865df44b68da597910ea783cff74',
    index: 0,
    nonWitnessUtxo: Buffer.from(nonWitnessUtxo, 'hex'),
    redeemScript,
  });
  psbt.addOutput({
    address: '2MuhtGHkdxiL4BjFAAaYjq8Q2mwnCoYSebt',
    value: 4000,
  });

  const transport = await Transport.create();
  const btc = new AppBtc(transport);
    // fromBitcoinJsLib: p2sh.address,
  const fromLedger = await btc.getWalletPublicKey(path, { format: 'p2sh' });
  const ledgerPublicKey = compressPublicKey(fromLedger.publicKey);
  const bitcoinJsPublicKey = (ecPublic.publicKey as Buffer).toString('hex');
  console.log({ ledgerPublicKey, bitcoinJsPublicKey });
  // {
  //   ledgerPublicKey: '02c4ac4c11e08ffe8dce5b9d74d149ebab49949b195f67932be9dac6c810a6e6dc',
  //   bitcoinJsPublicKey: '02c4ac4c11e08ffe8dce5b9d74d149ebab49949b195f67932be9dac6c810a6e6dc'
  // }

  // signing with ledger.
  const txInput = await btc.splitTransaction(nonWitnessUtxo, segwit);
  const inputs = [[txInput, 0, redeemScript.toString('hex'), 0]]
  const paths = [path];
  const outshex = btc.serializeTransactionOutputs(btc.splitTransaction((psbt as any).__CACHE.__TX.toHex(), true)).toString('hex');
  const signatures = await btc.signP2SHTransaction(inputs, paths, outshex, LOCK_TIME, SIGHASH_ALL, segwit, 2);

  const signer = {
    network: NETWORK,
    publicKey: ecPublic.publicKey,
    sign: ($hash: Buffer) => {
      const signature = ecPrivate.sign($hash); // just for comparison.
      const s = signatures[0];
      const h = bitcoin.script.signature.decode(toByteArray(s));
      console.log({
        $hash: $hash.toString('hex'),
        signature: signature.toString('hex'),
        hsig: h.signature.toString('hex'),
      });
      // return signature;
      return h.signature;
    },
  } as unknown as bitcoin.ECPairInterface;
  psbt.signInput(0, signer);
  const validated = psbt.validateSignaturesOfInput(0);
  console.log({ validated });
  // { validated: false }
  // will be true if I used the signature from ecPrivate.sign($hash);

}

main();


from how to sign bitcoin psbt with ledger?

No comments:

Post a Comment