Error: Need validator function to validate signatures
Closed this issue · 11 comments
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);
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
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
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.