Estoy tratando de firmar una transacción Psbt de bitcoinjs-lib siguiendo lo que encontré aquí:
He comprobado que la clave pública comprimida tanto del libro mayor como la de bitcoinjsLib devolvieron el mismo valor.
Podría firmarlo con el bitcoinjs-lib ECPair, pero cuando intento firmarlo usando el libro mayor, siempre es inválido.
¿Alguien puede ayudarme a señalar dónde cometí un error?
Estas variables ya se mencionan en el siguiente código, pero para mayor claridad:
- 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 es mi código reproducible mínimo que tengo.
/* 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)
}
Buffer.from
, pero el código anterior es solo yo copiando el código de la referencia que mencioné anteriormente, ya que parece que se supone que el código original se está ejecutando en el navegador.