bitcoinjs / bitcoinjs-lib

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

Getting error: No inputs were signed, while transferring BTC from my wallet to other wallet #2147

Closed arunMtDev closed 1 week ago

arunMtDev commented 1 month ago
import * as bitcoin from "bitcoinjs-lib";
import * as ecc from "tiny-secp256k1";
import { ECPairFactory, ECPairInterface } from "ecpair";
import mempoolJS from "@mempool/mempool.js";

const ECPair = ECPairFactory(ecc);

const network = bitcoin.networks.testnet;

const estimateTransactionSize = (
  numInputs: number,
  numOutputs: number
): number => {
  return numInputs * 148 + numOutputs * 34 + 10;
};

const calculateFee = (
  feeRate: number,
  numInputs: number,
  numOutputs: number
): number => {
  const txSize = estimateTransactionSize(numInputs, numOutputs);
  return feeRate * txSize;
};

export const sendBTC = async (
  privateKey: string,     // my private keys
  toAddress: string,
  amount: number      // amount in BTC
): Promise<{ success: boolean; txHash: string }> => {
  try {
    const {
      bitcoin: { addresses, transactions, fees },
    } = mempoolJS({
      hostname: "mempool.space",
      network: "testnet",
    });

    const keyPair: ECPairInterface = ECPair.fromPrivateKey(
      Buffer.from(privateKey, "hex")
    );

    const p2wpkh = bitcoin.payments.p2wpkh({
      pubkey: keyPair.publicKey,
      network,
    });

    const address: string | undefined = p2wpkh?.address;

    if (!address) {
      return {
        success: false,
        txHash: "",
      };
    }

    const utxos = await addresses.getAddressTxsUtxo({
      address,
    });

    const feesRecommended = await fees.getFeesRecommended();

    let totalSatoshisAvailable = 0;

    utxos.forEach((utxo) => {
      totalSatoshisAvailable += utxo.value;
    });

    const numInputs = utxos.length;
    const numOutputs = 2; // One for the destination, one for change
    const fee = calculateFee(feesRecommended.minimumFee, numInputs, numOutputs);

    const change = totalSatoshisAvailable - amount * 100000000 - fee;

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

    for (const utxo of utxos) {
      const txHex = await transactions.getTxHex({ txid: utxo.txid });

      psbt.addInput({
        hash: utxo.txid,
        index: utxo.vout,
        nonWitnessUtxo: Buffer.from(txHex, "hex"),
      });
    }

    // Add outputs
    psbt.addOutput({
      address: toAddress,
      value: amount * 100000000,
    });

    if (change > 0) {
      psbt.addOutput({
        address: address!,
        value: change,
      });
    }

    // // Sign inputs
    // utxos.forEach((utxo, index) => {
    //   psbt.signInput(index, keyPair);
    // });

    psbt.signAllInputs(keyPair);

    // Finalize inputs
    psbt.finalizeAllInputs();

    const rawTransaction = psbt.extractTransaction().toHex();
    console.log("Raw transaction:", rawTransaction);

    const txId = await transactions.postTx({ txhex: rawTransaction });
    console.log(txId);

    if (!txId) {
      return {
        success: false,
        txHash: "",
      };
    }

    return {
      success: true,
      txHash: txId.toString(),
    };
  } catch (error) {
    console.log("error while transferring bitcoin", error);
    return {
      success: false,
      txHash: "",
    };
  }
};
arunMtDev commented 1 month ago

@junderw

jasonandjay commented 1 month ago

Obviously when using P2WPKH type UTXO, you need to use witnessUtxo instead of nonWitnessUtxo

arunMtDev commented 1 month ago

Obviously when using P2WPKH type UTXO, you need to use witnessUtxo instead of nonWitnessUtxo

@jasonandjay

I've updated my code to the following:

    const promises = utxos.map(async (utxo) => {
      const txHex = await transactions.getTxHex({ txid: utxo.txid });

      psbt.addInput({
        hash: utxo.txid,
        index: utxo.vout,
        // nonWitnessUtxo: Buffer.from(txHex, "hex"),
        witnessUtxo: {
          script: Buffer.from(p2wpkh.output!.toString("hex"), "hex"),
          value: utxo.value,
        },
      });
    });
    await Promise.all(promises);

However, when I try to sign the transactions with psbt.signAllInputs(keyPair); I still encounter the same error: "No inputs were signed."

jasonandjay commented 1 month ago

This requires debugging the script line by line, but I don't know if it is convenient to provide me with the code containing the relevant test account.

arunMtDev commented 1 month ago

@jasonandjay

I've tried this code with three different private keys but I'm still encountering the same error. I suspect there might be an issue with the code itself. If it's possible, could you please test it with one of your wallets to help debug the issue?

jasonandjay commented 1 month ago

We have unit tests and integration tests to ensure code logic. This is the first time you have encountered this problem, so I believe it may be related to the parameters. You need to provide specific execution code.

arunMtDev commented 1 month ago

Hey @jasonandjay,

Could you please help me find the appropriate test case in the test/integration/transactions.spec.ts file for my issue? I tried for p2wkph but, i couldn’t understand it properly

jasonandjay commented 1 month ago

You can refer to the integration test of PSBT(test/psbt.spec.ts). The link where you encountered the error is the signature stage. P2WPKH is not much different from other payment methods.

arunMtDev commented 1 month ago

Hey @jasonandjay, I have a question.

In the sendBTC function, I might have different types of toAddress, such as Native Segwit (P2WPKH), Taproot, or Nested Segwit (P2SH-P2WPKH). Do I need to handle each of these separately, or will the same code work for all types of transactions?

jasonandjay commented 1 month ago

Hey @jasonandjay, I have a question.

In the sendBTC function, I might have different types of toAddress, such as Native Segwit (P2WPKH), Taproot, or Nested Segwit (P2SH-P2WPKH). Do I need to handle each of these separately, or will the same code work for all types of transactions?

Handle each of these separately, such as Segwit address need witnessUtxo, others need nonwitnessUtxo

damon1205 commented 3 weeks ago

If you use unisat wallet, please try to use this code.

Bitcoin.initEccLib(ecc);
const Wallet = new WIFWallet({
  networkType: TEST_MODE ? "testnet" : "mainnet",
  privateKey: process.env.CARDINAL_PRIVAATE_KEY as string,
});
const keyPair = RunexWallet.ecPair;
const tweakedSigner = tweakSigner(keyPair, { network });
....
for (const utxo of btcUtxos) {
          psbt.addInput({
          hash: utxo.txid,
          index: utxo.vout,
          witnessUtxo: {
            value: utxo.value,
            script: RunexWallet.output,
          },
          tapInternalKey: Buffer.from(RunexWallet.publicKey, "hex").subarray(1, 33),
        });
      }
}
.....
    psbt.addOutput({
      address: receiverAddress,
      value: amount,
    });

    psbt.addOutput({
      address: senderAddress as string,
      value: tempAmount - amount - fee,
    });
.....
psbt.signAllInputs(tweakedSigner);
psbt.extractTransaction();

... push psbt to mempool

arunMtDev commented 3 weeks ago

@damon1205

Since I am using this code in the backend, I don't believe I can utilise the Unisat wallet there.

damon1205 commented 3 weeks ago

Which wallet do you use now?

arunMtDev commented 3 weeks ago

@damon1205

I am writing the code in backend so no wallet required.

jasonandjay commented 2 weeks ago

hello @arunMtDev,

Is this problem solved?

If not, can you provide the signed PSBT and your public key, and I will help debug it

arunMtDev commented 2 weeks ago

Hello @jasonandjay,

The main issue with my code is that I'm unable to sign the PSBT with the line psbt.signAllInputs(keyPair). I’m encountering the error: 'No inputs were signed.' As a result, I’m not able to provide you with the signed PSBT.

For reference, my public key in hex is 029edd22c25e36288e1a095e53b6fc5a173c53263145a0b55bc1d35945ec1092c3.

Thanks for your help!

jasonandjay commented 2 weeks ago

Hello @jasonandjay,

The main issue with my code is that I'm unable to sign the PSBT with the line psbt.signAllInputs(keyPair). I’m encountering the error: 'No inputs were signed.' As a result, I’m not able to provide you with the signed PSBT.

For reference, my public key in hex is 029edd22c25e36288e1a095e53b6fc5a173c53263145a0b55bc1d35945ec1092c3.

Thanks for your help!

Please provide the PSBT content before signing

you can use const psbtHex = psbt.toHex() before psbt.signAllInputs(keyPair)

and paste psbtHex

workrepo1002 commented 2 weeks ago

Hello @jasonandjay

Following is my psbt hex

70736274ff0100fd5d02020000000d545b4c7a51bd0a9ee2b14054655dcea330fbbd1534b65f9161cd531ef6818cd70000000000ffffffff97879eec38cc5b87fa0b0d538992fba4100b1a438f3ab8a9ea7a0c6434634f9c0000000000ffffffffabd96f7f253469531397f18f3f73cea6a4714a71e98b979b05b57d7f1ae7a17d0000000000ffffffff3021856dd3587708a5739b881748dda09070db466b3095ed046077b2ae0589ff0000000000ffffffffe5c5229a3fa657306006d1cc1200828db652502144af3f1e325cc33313b900470000000000ffffffff31d6902931ccf6610600040dc8521fe343ac742ebfdf44f14dedd72fbde9fdcd0000000000ffffffff281dbe7a87ebdeadcd23e86f75e6373b2b576e6641db6b80ff86ea11d1aad9e30000000000ffffffff1d4f79c59aaea037e4f322aafc469c6d7886e340dde3ee7a8e01e22dfe66104d0000000000ffffffff1b633f139618661c68bf2c7c6568fef3ab8817a766877ebabbed668ce0257b6d0000000000ffffffff35689717e2c8fc11b4f7f38129b13817cb5b0a7a9b4f50bdeca5ef425e874ed50000000000ffffffff360a3a96dfb29502979a0cbb8c1af23024522aed15024c6b2376ccb6e54dfb2a0000000000ffffffff5d0d0aff1d222daed78e09952d03902cf3bc251faf82ea40cd9efe173551766a0000000000ffffffff7c905c2bbda882dd0947f37a3c1f01639855833ceb959b15596c120ff46d2d110000000000ffffffff026a09000000000000160014323a5b7678f283a23c5db743c525b7c2ac8356c5c72e000000000000160014b0c71935116f1472b3879d942f469c8a1f735a50000000000001011fff04000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011ff304000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011f0107000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011f3404000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011fb807000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011f0404000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011f3304000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011fed03000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011fb204000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011f7404000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011fe903000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011f2e05000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011fc304000000000000160014b0c71935116f1472b3879d942f469c8a1f735a50000000

jasonandjay commented 2 weeks ago

Based on the PSBT and public key you provided, I debugged and found that signAllInputs can work properly.

The following is my test code

The next step may require you to carefully check your code yourself

/**
 * update pubkeyInscript
 * node_modules/bitcoinjs-lib/src/psbt/psbtutils.js
 * /
function pubkeyInScript(pubkey, script) {
  pubkey = Buffer.from('029edd22c25e36288e1a095e53b6fc5a173c53263145a0b55bc1d35945ec1092c3', 'hex');
  return pubkeyPositionInScript(pubkey, script) !== -1;
}

----------------------------------------

import bitcoin, { Psbt } from "bitcoinjs-lib";
import * as ecc from "tiny-secp256k1";
import { ECPairFactory } from "ecpair";

bitcoin.initEccLib(ecc);
const ECPair = ECPairFactory(ecc);

const keypair = ECPair.makeRandom();

bitcoin.initEccLib(ecc);

const psbtHex = '70736274ff0100fd5d02020000000d545b4c7a51bd0a9ee2b14054655dcea330fbbd1534b65f9161cd531ef6818cd70000000000ffffffff97879eec38cc5b87fa0b0d538992fba4100b1a438f3ab8a9ea7a0c6434634f9c0000000000ffffffffabd96f7f253469531397f18f3f73cea6a4714a71e98b979b05b57d7f1ae7a17d0000000000ffffffff3021856dd3587708a5739b881748dda09070db466b3095ed046077b2ae0589ff0000000000ffffffffe5c5229a3fa657306006d1cc1200828db652502144af3f1e325cc33313b900470000000000ffffffff31d6902931ccf6610600040dc8521fe343ac742ebfdf44f14dedd72fbde9fdcd0000000000ffffffff281dbe7a87ebdeadcd23e86f75e6373b2b576e6641db6b80ff86ea11d1aad9e30000000000ffffffff1d4f79c59aaea037e4f322aafc469c6d7886e340dde3ee7a8e01e22dfe66104d0000000000ffffffff1b633f139618661c68bf2c7c6568fef3ab8817a766877ebabbed668ce0257b6d0000000000ffffffff35689717e2c8fc11b4f7f38129b13817cb5b0a7a9b4f50bdeca5ef425e874ed50000000000ffffffff360a3a96dfb29502979a0cbb8c1af23024522aed15024c6b2376ccb6e54dfb2a0000000000ffffffff5d0d0aff1d222daed78e09952d03902cf3bc251faf82ea40cd9efe173551766a0000000000ffffffff7c905c2bbda882dd0947f37a3c1f01639855833ceb959b15596c120ff46d2d110000000000ffffffff026a09000000000000160014323a5b7678f283a23c5db743c525b7c2ac8356c5c72e000000000000160014b0c71935116f1472b3879d942f469c8a1f735a50000000000001011fff04000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011ff304000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011f0107000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011f3404000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011fb807000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011f0404000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011f3304000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011fed03000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011fb204000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011f7404000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011fe903000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011f2e05000000000000160014b0c71935116f1472b3879d942f469c8a1f735a500001011fc304000000000000160014b0c71935116f1472b3879d942f469c8a1f735a50000000';
const pubKey = '029edd22c25e36288e1a095e53b6fc5a173c53263145a0b55bc1d35945ec1092c3';

const psbt = Psbt.fromHex(psbtHex, bitcoin.networks.testnet);

psbt.signAllInputs(keypair);

psbt.finalizeAllInputs();
jasonandjay commented 1 week ago

Close this issue

If you can provide a more detailed executable debugger, welcome to open a new issue