bitcoinjs/bitcoinjs-lib

Error: Need validator function to validate signatures

Closed this issue · 11 comments

0gf commented

I'm trying to create a script that transfers an amount of Bitcoin to a receiver as well as the remainder of the balance back to the sender. I have checked that the keypair I'm using owns the UTXOs, and this does not seem to be the issue. I have pasted the unspent transactions here: https://pastebin.com/dD2c1fLE. All of them are segwit. The error occurs at psbt.validateSignaturesOfAllInputs():

...\node_modules\bitcoinjs-lib\src\psbt.js:349
      throw new Error('Need validator function to validate signatures');
            ^
Error: Need validator function to validate signatures
    at Psbt.validateSignaturesOfInput (...\node_modules\bitcoinjs-lib\src\psbt.js:349:13)
    at ...\node_modules\bitcoinjs-lib\src\psbt.js:339:12
    at Array.map (<anonymous>)
    at Psbt.validateSignaturesOfAllInputs (...\node_modules\bitcoinjs-lib\src\psbt.js:338:52)
    at createTransaction (..\functions\btcTest.js:76:20)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async main (...\functions\btcTest.js:126:16)

Node.js v20.12.2

This is part of the script which causes the error

const privateKeyStr = "..."

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

async function createTransaction(keyPair, allTransactions, receiverAddress, amount) {
   // is equal to privateKeyStr and the address belonging to this private key
    console.log(keyPair.toWIF(), p2wpkh.address)

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

    for (let i = 0; i < allTransactions.length; i++) {
        const t = allTransactions[i]
        // TODO: check if a previous transaction is segwit
        const rawTransaction = await request("getrawtransaction", [t.transactionId, true])

        const vout = t.blockchainSpecific.vout

        for (let j = 0; j < vout.length; j++) {
            // find the uxto
            if (vout[j].scriptPubKey.addresses.includes(p2wpkh.address)) {
                if (vout[j].isSpent) continue // cant use spent output

                console.log(JSON.stringify(rawTransaction))

                unspentBalance += vout[j].value * 1e8

                psbt.addInput({
                    hash: t.transactionId,
                    index: j,
                    witnessUtxo: {
                        script: Buffer.from(vout[j].scriptPubKey.hex, 'hex'),
                        value: parseInt(vout[j].value * 1e8) // convert to sats
                    }
                })

            }
        }
    }

    //const fee = await request('estimatesmartfee', [10]) 
    const fee = 10000

    console.log("input count", psbt.inputCount)    
    console.log(unspentBalance)
    console.log("to sender", unspentBalance - amount - fee)
    console.log("to receiver", parseInt(amount))

    // transfer (balance - amount - fee) back to sender
    psbt.addOutput({
        address: p2wpkh.address,
        value: parseInt(unspentBalance - amount - fee)
    })

    // transfer amount to receiver
    psbt.addOutput({
        address: receiverAddress,
        value: parseInt(amount)
    })

    psbt.signAllInputs(keyPair)
    
    const t = psbt.validateSignaturesOfAllInputs()
    console.log(t) // Error: Need validator function to validate signatures

    psbt.finalizeAllInputs();
    
    // create tx object
    const tx = psbt.extractTransaction();

    return tx.toHex();
}

validateSignaturesOfAllInputs is missing an argument.

Here's an example from the README.

import * as ecc from 'tiny-secp256k1';
import ECPairFactory from 'ecpair';

const ECPair = ECPairFactory(ecc);

const validator = (pubkey, msghash, signature) => ECPair.fromPublicKey(pubkey).verify(msghash, signature);
0gf commented

Ah thanks for clearing that up. The transaction still doesn't go through for this code, the hex gets generated but when I broadcast the transaction it returns

{
  result: null,
  error: {
    code: -26,
    message: 'mandatory-script-verify-flag-failed (Signature must be zero for failed CHECK(MULTI)SIG operation)'
  },
  id: 1
}

There's something weird going on with the signature. I've pretty much confirmed that all transactions are unspent and belong to the keypair I'm signing with. I have tried removing inputs/outputs, signing iterative by using signInput instead of signAllInputs but nothing has worked so far. If it might help this is what gets returned by psbt.signAllInputs(keyPair):

Psbt {
  inputs: [
    { unknownKeyVals: [], witnessUtxo: [Object], partialSig: [Array] },
    { unknownKeyVals: [], witnessUtxo: [Object], partialSig: [Array] },
    { unknownKeyVals: [], witnessUtxo: [Object], partialSig: [Array] },
    { unknownKeyVals: [], witnessUtxo: [Object], partialSig: [Array] } 
  ],
  outputs: [ { unknownKeyVals: [] }, { unknownKeyVals: [] } ],
  globalMap: { unsignedTx: PsbtTransaction {} }
}

And here's the hex that gets generated:
02000000000104f067285af48c200bd4fd49a25159dba608ffeba2e37c994788b08c2871f979100100000000ffffffffea61511d89633bd8371668cf027740252a2e4b2c5a94a3449e22ec85828637760000000000ffffffffd37b8ec141e232dc0e326571d535599c22638833119803007260f0b89ad5eced0100000000ffffffff6e34dfef9f5fca40c34b2496ef31f99ca97a9a5639d5de79a7f5afda933c31d00200000000ffffffff024f390000000000001600142871bc0ae4a00e416a8b8522123990e2f367ccade8030000000000001600140223e80892c0a2d8388f777da4564a0248cdc4d0024830450221009fa9da7c94888e9265e3e5cac38238bf5bf5aca1215365102fcc8a5f1e535cfe0220065d5c38cc8a3d29335124145e65a08e2bfe9a5471bccd89c81e42f1aa31df3e01210320e4707ff7052e2c5357a3709805797a9a29d66e750b7661eeaaf10dbc12a0e502483045022100aa749e9c2e80018894ea959b11fc6822e785b583cb04c5d95d9d09a6442872650220784a64eaf17179095cb263cb2ff05aa2b8ee0e492de733b2222296e9bab390e401210320e4707ff7052e2c5357a3709805797a9a29d66e750b7661eeaaf10dbc12a0e5024830450221008be8fcc1119e05f02813c4a726a730ec7ec65ea824e0906abbb7a3e7b5512c8b02206f6ed66b9c627625fffbdcf030861988110b67c683868b942f906eda61c59e3a01210320e4707ff7052e2c5357a3709805797a9a29d66e750b7661eeaaf10dbc12a0e50247304402200cdbec64ce146a564ba248e30448c5636bb4bb22080f95aaf52e9afc23cb420e0220635cb1e23f1d6f4691d624a0b52cdd32600d6a556a1d967e0c3aa82ed0fcb09f01210320e4707ff7052e2c5357a3709805797a9a29d66e750b7661eeaaf10dbc12a0e500000000

0gf commented

@junderw Pinging you incase this won't pop up after being closed

https://mempool.space/testnet/tx/1079f971288cb08847997ce3a2ebff08a6db5951a249fdd40b208cf45a2867f0 output 1
https://mempool.space/testnet/tx/7637868285ec229e44a3945a2c4b2e2a25407702cf681637d83b63891d5161ea output 0
https://mempool.space/testnet/tx/edecd59ab8f0607200039811338863229c5935d57165320edc32e241c18e7bd3 output 1
https://mempool.space/testnet/tx/d0313c93daaff5a779ded539569a7aa99cf931ef96244bc340ca5f9fefdf346e output 2

These are the 4 inputs you used. It looks to me like the pubkeys match for each of the inputs and the format of the signatures looks correct.

The only thing I can think of is the amounts.

witnessUtxo: [Object] is not helpful. What is the amount of each of the witnessUtxo objects?

Should be

15261
610
4900
4900

0gf commented

psbt.signAllInputs(keyPair).data.inputs[k].witnessUxto returns this.

{
  script: <Buffer 00 14 28 71 bc 0a e4 a0 0e 41 6a 8b 85 22 12 39 90 e2 f3 67 cc ad>,
  value: 15260
}
{
  script: <Buffer 00 14 28 71 bc 0a e4 a0 0e 41 6a 8b 85 22 12 39 90 e2 f3 67 cc ad>,
  value: 610
}
{
  script: <Buffer 00 14 28 71 bc 0a e4 a0 0e 41 6a 8b 85 22 12 39 90 e2 f3 67 cc ad>,
  value: 4900
}
{
  script: <Buffer 00 14 28 71 bc 0a e4 a0 0e 41 6a 8b 85 22 12 39 90 e2 f3 67 cc ad>,
  value: 4900
}

I'm probably not parsing the float amount of the transaction correctly here value: parseInt(vout[j].value * 1e8) // convert to sats... I'll test it in a bit

The only number that is signed in P2WPKH is the amount. All other data used in signing is Buffers, so there is probably low chance of them being modified unexpectedly.

Anywhere that you are touching values using floats I would suspect that it is causing the incorrect value to be used.

> "0.00015261" * 1e8
15260.999999999998
> parseInt("0.00015261" * 1e8)
15260

Yeah I think we found your problem.

Solution: Learn Rust! 😛 Abandon JavaScript! lol

(Although this would still be an issue if you decided to use f64 in Rust)

fn main() {
    println!("{}", 0.00015261_f64 * 1e8_f64);
    // 15260.999999999998
    println!("{}", (0.00015261_f64 * 1e8_f64) as i64);
    // 15260
}

Solution: Abandon floats!

Most (sane) block explorers return integers for UTXO values. Perhaps check to see if your API that you use has the sats value as an integer so you don't have to parse a string.