bitcoinjs / bitcoinjs-lib

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

Error: Can not sign for this input with the key <keys> #1725

Closed mosesadeyinka closed 3 years ago

mosesadeyinka commented 3 years ago

Hello i created a testnet address.

private key: "cQNr2RTfTjo4c5R9pyGm2CNRpRbJxKLpkm2JedbyC5dH9F33MKa4",
address: "n4iRvNhWXup6gqu2C25K83KiQcoKrSoR1x"

here is my code

const TESTNET = bitcoin.networks.testnet;
const alice = bitcoin.ECPair.fromWIF(
  'cQNr2RTfTjo4c5R9pyGm2CNRpRbJxKLpkm2JedbyC5dH9F33MKa4',TESTNET
);
const psbt = new bitcoin.Psbt({network: TESTNET});

psbt.setVersion(2); // These are defaults. This line is not needed.
psbt.setLocktime(0); // These are defaults. This line is not needed.
psbt.addInput({
  // hash is gotton from here https://api.blockcypher.com/v1/btc/test3/addrs/2N1uzcCxmVxRFVtwCtsJLVGbUZbSG3syZsE/full?limit=50?unspentOnly=true&includeScript=true
  hash: 'c3be915b6a725c890d80d48027423bdb947ca180bbcc2f5a006eee654acf4af5',
  index: 0,
  sequence: 0xffffffff, // These are defaults. This line is not needed.
  witnessUtxo: {
    script: Buffer.from(
      'a9147171cb5d4454a8335604230cb0e0e46f5200cfb387',
      'hex',
    ),
    value: 90000,
  },
  // Not featured here:
  redeemScript: Buffer.from(
    '001425f0b6587bddbe23cf9a87828d655df31ab28b99',
    'hex',
  ),
  //   witnessScript. A Buffer of the witnessScript for P2WSH
});
psbt.addOutput({
  address: 'mv4rnyY3Su5gjcDNzbMLKBQkBicCtHUtFB',
  value: 80000,
  network: TESTNET
});
psbt.signInput(0, alice);
psbt.validateSignaturesOfInput(0);
psbt.finalizeAllInputs();

const rawTx = psbt.extractTransaction().toHex();
console.log(rawTx);

but i keep getting this error. Error: Can not sign for this input with the key

junderw commented 3 years ago

Exactly how the error states, your keys don't match.

  1. The key of the private key for alice
  2. The key that you mention in the comment (all UTXOs are spent)
  3. Some other key (that you are referencing the script in witnessUtxo)
Private Address Script
cQNr2RTfTjo4c5R9pyGm2CNRpRbJxKLpkm2JedbyC5dH9F33MKa4 2N4KUfKqvbBZzw1RjWs54mh8yk9v7vAcCyt a91479774bc599362faa7deabfc342623e16d2d4320487
??? 2N1uzcCxmVxRFVtwCtsJLVGbUZbSG3syZsE a9145f160f26e702212ac2520000c89171caac3e8a2d87
??? 2N3b4fXunecA3pgRGFwpdKUtzFFwFDifXp3 a9147171cb5d4454a8335604230cb0e0e46f5200cfb387

You need to figure out what is what before it will work.

Also, none of those three addresses have any coins left. (2 had some coins but they've already been spent.)

mosesadeyinka commented 3 years ago

Any idea or link on how to generate private key when creating an address, in Testnet or mainnet. I think that’s where the error is coming from.

Let privatekey = keyPair.toWif(); this is how I got the private key

junderw commented 3 years ago

It might be better to make a simple wrapper class that lets you keep everything together in one place.

Wallet class

```js // Be sure to install using: // npm install isomorphic-fetch // Requiring this adds fetch to global scope require('isomorphic-fetch'); const crypto = require('crypto'); const { ECPair, networks, payments, Psbt } = require('bitcoinjs-lib'); const WALLET_TYPES = ['p2wpkh', 'p2sh-p2wpkh']; class Wallet { constructor(privateKey, type, network = networks.bitcoin) { this.privateKey = privateKey; this.type = type; this.network = network; if (!Buffer.isBuffer(this.privateKey) || this.privateKey.length !== 32) { throw new Error(`privateKey must be 32 byte Buffer`); } if (!WALLET_TYPES.includes(type)) { throw new Error( `Wrong type ${type}, type must be in [${WALLET_TYPES.join(', ')}]`, ); } // The base URL for the blockstream API this.apiUrl = `https://blockstream.info/${ this.network.bech32 === 'bc' ? '' : 'testnet/' }api`; // null means we have never checked the utxos with the API this.utxos = null; this.keyPair = ECPair.fromPrivateKey(this.privateKey, { network: this.network, }); switch (this.type) { case 'p2wpkh': { this.payment = payments.p2wpkh({ pubkey: this.keyPair.publicKey, network: this.network, }); break; } case 'p2sh-p2wpkh': { this.payment = payments.p2sh({ redeem: payments.p2wpkh({ pubkey: this.keyPair.publicKey, network: this.network, }), network: this.network, }); break; } default: { throw new Error('no type'); } } } static import(importData) { const { wif, type, network } = importData; const pair = ECPair.fromWIF(wif, network); return new Wallet(pair.privateKey, type, network); } static makeRandom(type = 'p2sh-p2wpkh', network = networks.bitcoin) { return new Wallet(crypto.randomBytes(32), type, network); } export() { return { wif: this.keyPair.toWIF(), type: this.type, network: this.network, }; } get script() { return this.payment.output; } get address() { return this.payment.address; } get redeemScript() { if (this.payment.redeem) { return this.payment.redeem.output; } } async getUtxos() { const response = await fetch(`${this.apiUrl}/address/${this.address}/utxo`); if (!response.ok) { throw new Error(await response.text()); } const data = await response.json(); // Sort by descending value this.utxos = data.sort((a, b) => b.value - a.value); return this.utxos; } async broadcast(txHex) { const response = await fetch(`${this.apiUrl}/tx`, { method: 'POST', body: txHex, }); if (!response.ok) { throw new Error(await response.text()); } // returns the txid return response.text(); } calculateBalanceFromUtxos() { return (this.utxos || []).reduce((total, utxo) => total + utxo.value, 0); } async makePsbt(toAddress, amount) { const fee = 1000; // For mainnet this should be dynamically calculated const smallestOutputSize = 564; // "dust limit" if (amount < smallestOutputSize) { throw new Error('send amount too small'); } if (this.utxos === null) { await this.getUtxos(); } if (this.calculateBalanceFromUtxos() < amount + fee) { throw new Error(`Not enough coins`); } const shuffleArray = array => { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } }; let totalInputAmount = 0; const inputs = []; // Add inputs for (const utxo of this.utxos) { if (totalInputAmount >= amount + fee) { break; } inputs.push({ hash: utxo.txid, index: utxo.vout, witnessUtxo: { script: this.script, value: utxo.value, }, ...(this.redeemScript ? { redeemScript: this.redeemScript } : {}), }); totalInputAmount += utxo.value; } // Add outputs const outputs = []; outputs.push({ address: toAddress, value: amount, }); if (totalInputAmount - amount - fee >= smallestOutputSize) { // Add change outputs.push({ address: this.address, value: totalInputAmount - amount - fee, }); } // randomize the order of inputs and outputs shuffleArray(inputs); shuffleArray(outputs); // add to psbt const psbt = new Psbt({ network: this.network }); psbt.addInputs(inputs); psbt.addOutputs(outputs); return psbt; } async sendCoins(toAddress, amount) { const psbt = await this.makePsbt(toAddress, amount); // sign all inputs psbt.signAllInputs(this); psbt.finalizeAllInputs(); // returns the txid return this.broadcast(psbt.extractTransaction().toHex()); } // Implement Signer interface get publicKey() { return this.keyPair.publicKey; } getPublicKey() { return this.keyPair.publicKey; } sign(hash, lowR) { return this.keyPair.sign(hash, lowR); } } ```

This can be used like this: ("p2wpkh" is pure Segwit, "p2sh-p2wpkh" is Segwit wrapped in a more-compatible address format) ("p2wpkh" is cheaper fees to spend since it uses less space)

Usage

```js const wallet = Wallet.makeRandom('p2sh-p2wpkh', networks.testnet); console.log(wallet.address); // outputs the address console.log(wallet.export()); // This outputs a data object like this // { wif: '...', type: 'p2sh-p2wpkh', network: { ... } } // now you can use the wallet class to create your script and redeemScript for you psbt.addInput({ hash: '...', index: 0, sequence: 0xffffffff, witnessUtxo: { script: wallet.script, value: 90000, }, redeemScript: wallet.redeemScript, // wallet.redeemScript will be undefined if the wallet is "p2wpkh" // But that is ok, since p2wpkh doesn't need a redeemScript }) // You can also pass this wallet directly in as a signer for PSBT, because it implements the Signer interface. psbt.signInput(0, wallet); // You can also use async/await to get info from an API and send the coins // Copying the following into a script js file and running it with 2 args: // == send 1234 satoshis to ADDRESS // node script.js ADDRESS 1234 // == or generate a new wallet to paste into your script == // node script.js gen async function main(args) { if (args[0] === 'gen') { // generate new wallet if first argument is 'gen' console.log(Wallet.makeRandom('p2wpkh', networks.testnet).export()); return; } const [toAddress, amount] = args; const wallet = Wallet.import({ wif: 'cTByh4w8RVc9PtZJL9X8HvS3tXPohvUM9w1DJ914bD2ECp3xdnNZ', type: 'p2wpkh', network: { messagePrefix: '\u0018Bitcoin Signed Message:\n', bech32: 'tb', bip32: { public: 70617039, private: 70615956 }, pubKeyHash: 111, scriptHash: 196, wif: 239, }, }); await wallet.getUtxos(); if (wallet.utxos.length === 0) { throw new Error(`Need coins. Please send to ${wallet.address}`); } console.log( `Current balance: ${wallet.calculateBalanceFromUtxos() / 1e8} tBTC`, ); console.log(`Current utxos:`); console.log(wallet.utxos); if (toAddress !== undefined && !isNaN(parseInt(amount))) { const value = parseInt(amount); const txid = await wallet.sendCoins(toAddress, value); console.log(`Sent ${value / 1e8} tBTC coins at txid ${txid}`); } } main(process.argv.slice(2)).catch(console.error); ```

junderw commented 3 years ago

(FYI: You can click "Wallet class" and "Usage" to open the code examples in the comment above)

mosesadeyinka commented 3 years ago

the wallet class

wallet.txt

// The testnet code to sign a transaction for broadcast
const TESTNET = bitcoin.networks.testnet;
const psbt = new bitcoin.Psbt({ network: TESTNET });
psbt.addInput({
  hash: '266c68e8c85349956cce274159bfbad46ab913f4484ee733a6b9b48632efaad1',
  index: 0,
  sequence: 0xffffffff,
  witnessUtxo: {
    script: wallet.script,
    value: 90000,
  },
  redeemScript: wallet.redeemScript, // wallet.redeemScript will be undefined if the wallet is "p2wpkh"
  // But that is ok, since p2wpkh doesn't need a redeemScript
});
psbt.addOutput({
  address: 'mv4rnyY3Su5gjcDNzbMLKBQkBicCtHUtFB',
  value: 80000,
  network: TESTNET,
});
psbt.signInput(0, wallet);
psbt.validateSignaturesOfInput(0);
psbt.finalizeAllInputs();
const rawTx = psbt.extractTransaction().toHex();
console.log(rawTx);

here is the signed result

rawTx log :

02000000000101d1aaef3286b4b9a633e74e48f413b96ad4babf594127ce6c954953c8e8686c260000 000017160014776589c83b9ca11d8574f9e13f32829a65f11d63ffffffff0180380100000000001976a9149f9a7 abd600c0caa03983a77c8c3df8e062cb2fa88ac0247304402200fecb4476cb135c8d868fc99209f533cdc3f3427 dc4b3519a64f88bb5094eedc02206923581163169a774541921365808795c7446be7ecd0a9a1d88396257b8c9fc 401210245816b5b24fa965da1982c9f65c42b130a3c4b47288e47a2c79141ce199b8d0700000000

The rawTx when i try to broadcast to testnet network gives error "sendrawtransaction RPC error: {"code":-25,"message":"bad-txns-inputs-missingorspent"}"

junderw commented 3 years ago

I looked at the transaction here: https://blockstream.info/testnet/tx/266c68e8c85349956cce274159bfbad46ab913f4484ee733a6b9b48632efaad1?expand

  1. You are trying to spend output 0 (first output) but it is already spent.
  2. Your witnessUtxo has value of 90000 but the neither of the two outputs have that value.

Perhaps you want to use index: 1, and value: 1969075, which is the value to the 2nd (index 1) output.

The keys must be the same though.

junderw commented 3 years ago

I have updated the Wallet class and Usage comment above with some useful features using blockstream API.

Be careful when doing things quickly, since blockstream API is replicated across many load balancers, so if you run the get utxos query right after sending a tx, it will return different results every time for a few seconds.

Please use the example above to help in your understanding.

Note: redeemScript can not be defined as undefined. If you do so, the library throws an error, so I made it only add redeemScript if it exists.

mosesadeyinka commented 3 years ago

I modified some codes in the wallet class that was throwing an error

wallet.txt

The execution:

let res = await wallet.sendCoins("mv4rnyY3Su5gjcDNzbMLKBQkBicCtHUtFB", "1909075","2N4K9pEkDWWrsfYMx5jmVUrPg7zJXj4xaWm"); console.log(res);

the broadcast throw error "mandatory-script-verify-flag-failed (Script evaluated without error but finished with a false/empty top stack element)"

junderw commented 3 years ago

Your modifications won't work.

  1. You aren't incrementing totalInputAmount
  2. You are trying to sign using a function parameter when the class is trying to use this.script and this.redeemScript from the internal key.

If you want to use your own key, use my code as is, and import the WIF private key along with the key type and network.

mosesadeyinka commented 3 years ago

i think there's a misunderstanding with your code, how do you identify which address you are sending from since you aren't passing it from the sendcoin()

 makeRandom(type = 'p2sh-p2wpkh', network = networks.bitcoin) {
    return new Wallet(crypto.randomBytes(32), type, network);
  }

generates new private keys on every call which isn't stored crypto.randomBytes(32)

however, to my understanding this private key is needed to sign the transaction before broadcast.

let's move here this.address is the random address that is just been generated with random private key which is empty const response = await fetch(${this.apiUrl}/address/${this.address}/utxo); This fetch is for new address that was generated? What of the address that contains the money?

so i modified the code to accept the parameter (address)of which contains the money & get the txtid

thanks, pls help out.

mosesadeyinka commented 3 years ago

i solved the code & broadcast the BTC by using a unique private key, i will upload the finish codes for others

junderw commented 3 years ago

you don't have to use makeRandom every time.

you can use import.

If you want to see what to pass import, look at the Usage example I added.

junderw commented 3 years ago

For example:

  const wallet = Wallet.import({
    wif: 'cQNr2RTfTjo4c5R9pyGm2CNRpRbJxKLpkm2JedbyC5dH9F33MKa4',
    type: 'p2sh-p2wpkh',
    // This is testnet network info
    network: {
      messagePrefix: '\u0018Bitcoin Signed Message:\n',
      bech32: 'tb',
      bip32: { public: 70617039, private: 70615956 },
      pubKeyHash: 111,
      scriptHash: 196,
      wif: 239,
    },
  });
  await wallet.getUtxos();
  if (wallet.utxos.length === 0) {
    throw new Error(`Need coins. Please send to ${wallet.address}`);
  }

  console.log(
    `Current balance: ${wallet.calculateBalanceFromUtxos() / 1e8} tBTC`,
  );
  console.log(`Current utxos:`);
  console.log(wallet.utxos);

  const txid = await wallet.sendCoins(toAddress, value);
  console.log(`Sent ${value / 1e8} tBTC coins at txid ${txid}`);
Nisthar commented 3 years ago

@junderw can you tell me the difference b/w p2sh-p2wpkh & p2wpkh? people using non segwit addresses won't be able to send me payments if i use p2wpkh address Right?