Estou tentando assinar uma transação Psbt do bitcoinjs-lib seguindo o que encontrei aqui:
Eu verifiquei que o publicKey compactado do ledger e o do bitcoinjsLib retornaram o mesmo valor.
Eu poderia assiná-lo com o ECPair bitcoinjs-lib, mas quando tento assiná-lo usando o razão, ele sempre é inválido.
Alguém pode me ajudar a apontar onde cometi um erro?
Essas variáveis já são mencionadas no código abaixo, mas para fins de clareza:
- mnemonics:
abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
- previousTx:
02000000000101869362410c61a69ab9390b2167d08219662196e869626e8b0350f1a8e4075efb0100000017160014ef3fdddccdb6b53e6dd1f5a97299a6ba2e1c11c3ffffffff0240420f000000000017a914f748afee815f78f97672be5a9840056d8ed77f4887df9de6050000000017a9142ff4aa6ffa987335c7bdba58ef4cbfecbe9e49938702473044022061a01bf0fbac4650a9b3d035b3d9282255a5c6040aa1d04fd9b6b52ed9f4d20a022064e8e2739ef532e6b2cb461321dd20f5a5d63cf34da3056c428475d42c9aff870121025fb5240daab4cee5fa097eef475f3f2e004f7be702c421b6607d8afea1affa9b00000000
- paths:
["0'/0/0"]
- redeemScript: (non-multisig segwit)
00144328adace54072cd069abf108f97cf80420b212b
Este é o meu código reprodutível mínimo que eu tenho.
/* tslint:disable */
// @ts-check
require('regenerator-runtime');
const bip39 = require('bip39');
const { default: Transport } = require('@ledgerhq/hw-transport-node-hid');
const { default: AppBtc } = require('@ledgerhq/hw-app-btc');
const bitcoin = require('bitcoinjs-lib');
const mnemonics = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
const NETWORK = bitcoin.networks.regtest;
/**
* @param {string} pk
* @returns {string}
*/
function compressPublicKey(pk) {
const { publicKey } = bitcoin.ECPair.fromPublicKey(Buffer.from(pk, 'hex'));
return publicKey.toString('hex');
}
/** @returns {Promise<any>} */
async function appBtc() {
const transport = await Transport.create();
const btc = new AppBtc(transport);
return btc;
}
const signTransaction = async() => {
const ledger = await appBtc();
const paths = ["0'/0/0"];
const [ path ] = paths;
const previousTx = "02000000000101869362410c61a69ab9390b2167d08219662196e869626e8b0350f1a8e4075efb0100000017160014ef3fdddccdb6b53e6dd1f5a97299a6ba2e1c11c3ffffffff0240420f000000000017a914f748afee815f78f97672be5a9840056d8ed77f4887df9de6050000000017a9142ff4aa6ffa987335c7bdba58ef4cbfecbe9e49938702473044022061a01bf0fbac4650a9b3d035b3d9282255a5c6040aa1d04fd9b6b52ed9f4d20a022064e8e2739ef532e6b2cb461321dd20f5a5d63cf34da3056c428475d42c9aff870121025fb5240daab4cee5fa097eef475f3f2e004f7be702c421b6607d8afea1affa9b00000000"
const utxo = bitcoin.Transaction.fromHex(previousTx);
const segwit = utxo.hasWitnesses();
const txIndex = 0;
// ecpairs things.
const seed = await bip39.mnemonicToSeed(mnemonics);
const node = bitcoin.bip32.fromSeed(seed, NETWORK);
const ecPrivate = node.derivePath(path);
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 = p2sh.redeem.output;
const fromLedger = await ledger.getWalletPublicKey(path, { format: 'p2sh' });
const ledgerPublicKey = compressPublicKey(fromLedger.publicKey);
const bitcoinJsPublicKey = ecPublic.publicKey.toString('hex');
console.log({ ledgerPublicKey, bitcoinJsPublicKey, address: p2sh.address, segwit, fromLedger, redeemScript: redeemScript.toString('hex') });
var tx1 = ledger.splitTransaction(previousTx, true);
const psbt = new bitcoin.Psbt({ network: NETWORK });
psbt.addInput({
hash: utxo.getId(),
index: txIndex,
nonWitnessUtxo: Buffer.from(previousTx, 'hex'),
redeemScript,
});
psbt.addOutput({
address: 'mgWUuj1J1N882jmqFxtDepEC73Rr22E9GU',
value: 5000,
});
psbt.setMaximumFeeRate(1000 * 1000 * 1000); // ignore maxFeeRate we're testnet anyway.
psbt.setVersion(2);
/** @type {string} */
// @ts-ignore
const newTx = psbt.__CACHE.__TX.toHex();
console.log({ newTx });
const splitNewTx = await ledger.splitTransaction(newTx, true);
const outputScriptHex = await ledger.serializeTransactionOutputs(splitNewTx).toString("hex");
const expectedOutscriptHex = '0188130000000000001976a9140ae1441568d0d293764a347b191025c51556cecd88ac';
// stolen from: https://github.com/LedgerHQ/ledgerjs/blob/master/packages/hw-app-btc/tests/Btc.test.js
console.log({ outputScriptHex, expectedOutscriptHex, eq: expectedOutscriptHex === outputScriptHex });
const inputs = [ [tx1, 0, p2sh.redeem.output.toString('hex') /** ??? */] ];
const ledgerSignatures = await ledger.signP2SHTransaction(
inputs,
paths,
outputScriptHex,
0, // lockTime,
undefined, // sigHashType = SIGHASH_ALL ???
utxo.hasWitnesses(),
2, // version??,
);
const signer = {
network: NETWORK,
publicKey: ecPrivate.publicKey,
/** @param {Buffer} $hash */
sign: ($hash) => {
const expectedSignature = ecPrivate.sign($hash); // just for comparison.
const [ ledgerSignature0 ] = ledgerSignatures;
const decodedLedgerSignature = bitcoin.script.signature.decode(Buffer.from(ledgerSignature0, 'hex'));
console.log({
$hash: $hash.toString('hex'),
expectedSignature: expectedSignature.toString('hex'),
actualSignature: decodedLedgerSignature.signature.toString('hex'),
});
// return signature;
return decodedLedgerSignature.signature;
},
};
psbt.signInput(0, signer);
const validated = psbt.validateSignaturesOfInput(0);
psbt.finalizeAllInputs();
const hex = psbt.extractTransaction().toHex();
console.log({ validated, hex });
};
if (process.argv[1] === __filename) {
signTransaction().catch(console.error)
}
fonte
Buffer.from
, mas o código acima é apenas eu copypasting o código da referência que eu mencionei acima, pois parece que o código original deveria estar sendo executado no navegador.