bitcoinjs / bitcoinjs-lib

A javascript Bitcoin library for node.js and browsers.
MIT License
5.72k stars 2.11k forks source link

Error: Can not sign for input #0 with the key 03... #2081

Closed ambitious922 closed 5 months ago

ambitious922 commented 7 months ago
const expectedAddress =
  "tb1p2kclfdlyf35z2cz6r05ptx45utsypyjxzmj4ftm0tr2xq55yd47qtnh4j5";

bitcoin.initEccLib(ecc);
const bip32 = BIP32Factory(ecc);

// Your network (testnet in this case)
const network = bitcoin.networks.testnet;

const blockStreamApi = "https://blockstream.info/testnet/api";

// Load your private key (WIF)
const ECPair = ECPairFactory.ECPairFactory(ecc);
const privateKeyWIF = process.env.PRIVATE_KEY_WIF;
const keyPair = ECPair.fromWIF(privateKeyWIF, network);
// Replace with your private key
const internalPubkey = Buffer.from(keyPair.publicKey, "hex").slice(1, 33);
console.log(internalPubkey, "internalPubkey");
const mnemonic =
  "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
const xprv =
  "xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu";
const path = `m/86'/0'/0'/0/0`; // Path to first child of receiving wallet on first account

const seed = await bip39.mnemonicToSeed(mnemonic);
const rootKey = bip32.fromSeed(seed);
assert.strictEqual(rootKey.toBase58(), xprv);
const childNode = rootKey.derivePath(path);

// Since internalKey is an xOnly pubkey, we drop the DER header byte
const childNodeXOnlyPubkey = toXOnly(internalPubkey);
assert.deepEqual(childNodeXOnlyPubkey, internalPubkey);

const { address, output } = bitcoin.payments.p2tr({
  internalPubkey,
  network,
});
assert(output);
assert.strictEqual(address, expectedAddress);
const tweakedChildNode = childNode.tweak(
  bitcoin.crypto.taggedHash("TapTweak", childNodeXOnlyPubkey)
);

console.log("address", address);

// const taprootP2TR = getTaprootP2TR(keyPair);
// const payment = taprootP2TR.payment;
// const taprootP2TR = getTaprootP2TR(keyPair);
// const signer = taprootP2TR.signer;

const utxos = [
  {
    hash: "4587ae424e038415dbf43103f096cc68e4615641063cb49ac2345e16675af3ed",
    index: 0,
    value: 546,
    // nonWitnessUtxo: Buffer.from(previousHex, "hex"),
  },
  {
    hash: "d4f0cf0b6642222da3e68491876d424f5d67b1697e8eae2357dbbb786e93f02d",
    index: 2,
    value: 99260,
    // nonWitnessUtxo: Buffer.from(previousHex, "hex"),
  },
];
const runeId = "2585883:3795";

const receiverAddress =
  "tb1pwzwkht8gzm39933ytfzf3uks89a5h4cvkfjwml3m5xceu4eshw3sz83wtw";

export async function createTransferTransaction() {
  // Fetch UTXOs (Unspent Transaction Outputs) for your address
  const utxosResponse = await fetch(
    `https://api.blockcypher.com/v1/btc/test3/addrs/${address}?unspentOnly=true`
  );
  const utxosData = await utxosResponse.json();
  const runeUtxosResponse = await fetch(
    `https://wallet-api-testnet.unisat.io/v5/runes/utxos?address=${address}&runeid=2585883:3795`
  );
  const runeUtxosData = await runeUtxosResponse.json();
  // console.log(runeUtxosData, "runeData");

  const outputScript = bitcoin.script.compile([
    internalPubkey,
    bitcoin.opcodes.OP_RETURN,
    bitcoin.opcodes.OP_13,
    [(runeId, 0, 80000000), (runeId, 1, 10000000)],
  ]);

  // Psbt class is used to do this.
  const txb = new bitcoin.Psbt({ network });

  for (let utxo of utxos) {
    txb.addInput({
      hash: utxo.hash,
      index: utxo.index,
      witnessUtxo: { script: output, value: utxo.value },
      tapInternalKey: toXOnly(internalPubkey),
    });
  }
  // add outputs as the buffer of receiver's address and the value with amount
  // of satoshis you're sending.
  txb.addOutput({
    script: outputScript,
    value: 0,
  });

  txb.addOutput({
    script: Buffer.from(address, "hex"),
    value: 546,
  });

  txb.addOutput({
    script: Buffer.from(receiverAddress, "hex"),
    value: 546,
  });

  txb.addOutput({
    script: Buffer.from(address, "hex"),
    value: 95000,
  });

  // sign with the generate keyPair and finalize the transansction
  txb.signInput(0, tweakedChildNode);
  txb.finalizeAllInputs();

Please help me with this error.

jasonandjay commented 7 months ago

The code you provided is incomplete and it is difficult for us to help you.

I think you can provide a complete and runnable example or a separate github repo for us to debug.

acsonservice commented 4 months ago

It looks like you are attempting to create and sign a Taproot transaction using bitcoinjs-lib and the Node.js environment. There are several issues in your code related to creating and adding inputs and outputs, as well as the signing and finalizing process. Let's go through your code and correct the mistakes to make it functional.

Here's a step-by-step guide and corrected code:

Step 1: Import Libraries and Initialize Variables

Ensure all required libraries are imported and environment variables are set correctly.

const bitcoin = require('bitcoinjs-lib');
const bip39 = require('bip39');
const ecc = require('tiny-secp256k1');
const BIP32Factory = require('bip32').default;
const ECPairFactory = require('ecpair').default;
const fetch = require('node-fetch');
const assert = require('assert');
require('dotenv').config();

bitcoin.initEccLib(ecc);
const bip32 = BIP32Factory(ecc);
const network = bitcoin.networks.testnet;

const expectedAddress = "tb1p2kclfdlyf35z2cz6r05ptx45utsypyjxzmj4ftm0tr2xq55yd47qtnh4j5";
const privateKeyWIF = process.env.PRIVATE_KEY_WIF;
const mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
const path = `m/86'/0'/0'/0/0`; // Path to first child of receiving wallet on first account
const receiverAddress = "tb1pwzwkht8gzm39933ytfzf3uks89a5h4cvkfjwml3m5xceu4eshw3sz83wtw";
const runeId = "2585883:3795";

Step 2: Generate Internal Public Key

const ECPair = ECPairFactory(ecc);
const keyPair = ECPair.fromWIF(privateKeyWIF, network);
const internalPubkey = keyPair.publicKey.slice(1, 33); // Get x-only pubkey

const seed = await bip39.mnemonicToSeed(mnemonic);
const rootKey = bip32.fromSeed(seed, network);
const childNode = rootKey.derivePath(path);

// Since internalKey is an xOnly pubkey, we drop the DER header byte
const childNodeXOnlyPubkey = internalPubkey;
assert.deepEqual(childNodeXOnlyPubkey, internalPubkey);

const { address, output } = bitcoin.payments.p2tr({
  internalPubkey: childNodeXOnlyPubkey,
  network,
});
assert(output);
assert.strictEqual(address, expectedAddress);

const tweakedChildNode = childNode.tweak(
  bitcoin.crypto.taggedHash("TapTweak", childNodeXOnlyPubkey)
);

console.log("address", address);

Step 3: Fetch UTXOs and Create PSBT

Replace your utxos array with data fetched from Blockcypher and Unisat APIs. Make sure your utxos have the correct format.

Step 4: Create the PSBT and Add Inputs and Outputs

The main issue in your original code was related to the handling of script types and encoding. Let's correct that:

export async function createTransferTransaction() {
  const utxosResponse = await fetch(`https://api.blockcypher.com/v1/btc/test3/addrs/${address}?unspentOnly=true`);
  const utxosData = await utxosResponse.json();
  const runeUtxosResponse = await fetch(`https://wallet-api-testnet.unisat.io/v5/runes/utxos?address=${address}&runeid=2585883:3795`);
  const runeUtxosData = await runeUtxosResponse.json();

  const utxos = utxosData.txrefs.map(utxo => ({
    hash: utxo.tx_hash,
    index: utxo.tx_output_n,
    value: utxo.value,
    script: output.toString('hex'), // Adjust if necessary
  }));

  const psbt = new bitcoin.Psbt({ network });

  for (let utxo of utxos) {
    psbt.addInput({
      hash: utxo.hash,
      index: utxo.index,
      witnessUtxo: {
        script: Buffer.from(utxo.script, 'hex'),
        value: utxo.value,
      },
      tapInternalKey: toXOnly(internalPubkey),
    });
  }

  const outputScript = bitcoin.script.compile([
    bitcoin.opcodes.OP_RETURN,
    Buffer.from(runeId),
  ]);

  psbt.addOutput({
    address: receiverAddress,
    value: 546,
  });

  psbt.addOutput({
    address: address,
    value: 546,
  });

  psbt.addOutput({
    script: outputScript,
    value: 0,
  });

  psbt.addOutput({
    address: address,
    value: utxos.reduce((acc, utxo) => acc + utxo.value, 0) - 1092, // Adjust for actual fee
  });

  for (let i = 0; i < utxos.length; i++) {
    psbt.signInput(i, tweakedChildNode);
  }

  psbt.finalizeAllInputs();
  const rawTxHex = psbt.extractTransaction().toHex();
  console.log('Raw Transaction Hex:', rawTxHex);
  return rawTxHex;
}

createTransferTransaction().catch(console.error);

Explanation

  1. Initialize Variables and Fetch UTXOs: Ensure all necessary libraries are imported and initialize all required variables. Fetch UTXOs from the relevant APIs and format them correctly.

  2. Generate Internal Public Key: Generate the internal public key using the private key and BIP32 derivation.

  3. Create and Add Inputs and Outputs: Create the PSBT, add inputs and outputs correctly. Ensure the scripts are correctly encoded in hexadecimal format.

  4. Sign and Finalize the Transaction: Sign each input with the appropriate key and finalize the transaction.

This code should help you generate the correct transaction hex without using Blockcypher. Make sure to test it with your actual data and environment.