While broadcasting raw transaction has to chain, got the below error RPC error: {"code":-26,"message":"non-mandatory-script-verify-flag (Invalid Schnorr signature)"} but signature verification method says its a valid signature for the given message.
I'm trying to do a simple taproot send transaction (key path spending). Signature is given by external signer
My data points are as below
{
message: '60ccb78b5936d80f63c32eeafe9b746909d7e716f0f96988f437972e633ec7e0',
signature: 'ed3061523f39137cdab4c9ca0bf9b62433b9642bb8165d996dcc120662866e8060e6f2314d7b03bab31f0a95b4574ddbed2892a176d2723810bd28fda66d1496',
publicKey: 'ddb666300b8d7ef23e61e2ff3af3c70b59f882d6de1deb601590bb4d15b5d5d3'
}
Transaction hash
020000000001012bd272fa216d2370e265f591907266a8e76a26bc0be4318c9b1b340eaa6134860000000000ffffffff021027000000000000225120ddb666300b8d7ef23e61e2ff3af3c70b59f882d6de1deb601590bb4d15b5d5d3a8c8fa0200000000225120ddb666300b8d7ef23e61e2ff3af3c70b59f882d6de1deb601590bb4d15b5d5d30141ed3061523f39137cdab4c9ca0bf9b62433b9642bb8165d996dcc120662866e8060e6f2314d7b03bab31f0a95b4574ddbed2892a176d2723810bd28fda66d14960100000000
This is my entire code
import sha256 from "sha256";
import { Psbt, networks, payments, Transaction } from "bitcoinjs-lib";
import dotenv from 'dotenv';
import { verifySchnorrSignature } from "./mpc/address.js";
import { signBTCTransaction } from "./systemCallerUtils.js";
dotenv.config();
const UNCONFIRMED_HEIGHT = 4194303;
async function btcSendTransaction() {
try {
const address = process.env.BTC_ADDRESS;
return await createSendTransaction(address);
} catch (err) {
console.error("Error in btcSendTransaction:", err);
throw err;
}
}
async function createSendTransaction(address) {
const txObj = await handleTxObj(address);
const { preSignTxHashes, psbt } = await generatePreSignTxHash(txObj);
console.log("preSignTxHashes", preSignTxHashes);
// Generate signatures for each input
const generatedSigns = [];
for (let i = 0; i < preSignTxHashes.length; i++) {
const generatedSign = await signBTCTransaction(preSignTxHashes[i], "HASH");
if (generatedSign) {
generatedSigns.push(generatedSign);
}
}
// Remove the '0x' prefix for verification
const hashToVerify = preSignTxHashes[0].replace('0x', '');
console.log('Hash for verification:', hashToVerify);
try {
const isValid = await verifySchnorrSignature(
hashToVerify,
generatedSigns[0].sign
);
console.log("Signature verification:", isValid);
if (!isValid) {
console.error('Verification failed:', {
hash: hashToVerify,
signature: generatedSigns[0].sign,
pubkey: process.env.MPC_PUB_KEY
});
throw new Error('Invalid signature generated');
}
} catch (error) {
console.error('Verification error:', error);
throw error;
}
try {
if (generatedSigns.length) {
const response = await broadCastTx(generatedSigns, psbt);
return response;
}
} catch (e) {
console.error('Broadcast error:', e);
throw e;
}
}
const handleTxObj = async (address) => {
try {
let utxos = await getUtxos(address);
utxos = utxos.filter((v) => v.height !== UNCONFIRMED_HEIGHT);
const outputAmount = 10000;
const feeRate = 200;
const outputs = [{
address: address,
amount: outputAmount,
}];
const { selectedUtxos, totalValue } = selectUtxos(utxos, outputAmount + feeRate);
console.log("selectedUtxos", selectedUtxos);
return {
fromAddress: address,
utxos: selectedUtxos,
outputs,
feeRate,
toAddress: address,
};
} catch (error) {
console.error("handleTxObj error:", error);
throw error;
}
}
export function selectUtxos(utxos, targetAmount) {
utxos.sort((a, b) => b.value - a.value);
const selectedUtxos = [];
let totalValue = 0;
for (const utxo of utxos) {
selectedUtxos.push(utxo);
totalValue += utxo.value;
if (totalValue >= targetAmount) break;
}
if (totalValue < targetAmount) {
throw new Error('Insufficient UTXOs to cover the target amount');
}
return { selectedUtxos, totalValue };
}
export const generatePreSignTxHash = async (txObject) => {
const network = networks.testnet;
const psbt = new Psbt({ network });
try {
const { utxos, fromAddress, outputs, feeRate } = txObject;
let inputSum = 0;
const pubKey = process.env.MPC_PUB_KEY;
const xOnlyPubKey = Buffer.from(pubKey, 'hex');
// Add inputs using the actual UTXO script
for (const utxo of utxos) {
inputSum += utxo.value;
// Get the actual UTXO details
const txDetails = await fetch(`https://esplora.signet.surge.dev/tx/${utxo.txid}/hex`).then(r => r.text());
const tx = Transaction.fromHex(txDetails);
const outputScript = tx.outs[utxo.vout].script;
console.log('UTXO details:', {
script: outputScript.toString('hex'),
value: utxo.value
});
psbt.addInput({
hash: utxo.txid,
index: utxo.vout,
witnessUtxo: {
script: outputScript,
value: utxo.value,
},
tapInternalKey: xOnlyPubKey,
});
}
// Add outputs
for (const output of outputs) {
psbt.addOutput({
address: output.address,
value: output.amount,
});
}
// Add change output
const totalOutputAmount = outputs.reduce((sum, output) => sum + output.amount, 0);
const changeAmount = inputSum - totalOutputAmount - feeRate;
if (changeAmount > 546) {
psbt.addOutput({
address: fromAddress,
value: changeAmount,
});
}
// Generate signing hashes
const unsignedTx = Transaction.fromBuffer(psbt.data.globalMap.unsignedTx.toBuffer());
const preSignTxHashes = psbt.data.inputs.map((input, index) => {
const hash = unsignedTx.hashForWitnessV1(
index,
[input.witnessUtxo.script],
[input.witnessUtxo.value],
Transaction.SIGHASH_ALL
);
console.log('Input details:', {
script: input.witnessUtxo.script.toString('hex'),
value: input.witnessUtxo.value,
hash: hash.toString('hex')
});
return `0x${hash.toString('hex')}`;
});
return { preSignTxHashes, psbt };
} catch (error) {
console.error('Error in generatePreSignTxHash:', error);
throw error;
}
};
export const broadCastTx = async (signatures, psbt) => {
if (!signatures.length) {
throw new Error('No signatures provided');
}
try {
signatures.forEach((sig, index) => {
const sigBuffer = Buffer.from(sig.sign.replace('0x', ''), 'hex');
console.log(`Raw signature for input ${index}:`, sigBuffer.toString('hex'));
console.log('Signature length:', sigBuffer.length);
// For Taproot with SIGHASH_ALL
const sigWithHashtype = Buffer.concat([
sigBuffer,
Buffer.from([Transaction.SIGHASH_ALL])
]);
console.log(`Signature with hashtype for input ${index}:`, sigWithHashtype.toString('hex'));
psbt.updateInput(index, {
tapKeySig: sigWithHashtype
});
});
psbt.finalizeAllInputs();
const tx = psbt.extractTransaction(true);
const rawTx = tx.toHex();
console.log('Final transaction:', rawTx);
console.log('Transaction details:', {
version: tx.version,
inputs: tx.ins.map(input => ({
txid: input.hash.reverse().toString('hex'),
vout: input.index,
witness: input.witness.map(w => w.toString('hex'))
})),
outputs: tx.outs.map(output => ({
value: output.value,
script: output.script.toString('hex')
}))
});
return await pushTx(rawTx);
} catch (error) {
console.error('Failed to broadcast transaction:', error);
throw error;
}
};
export const pushTx = async (rawtx) => {
try {
const endpoint = 'https://esplora.signet.surge.dev/tx';
const resp = await fetch(endpoint, {
method: "POST",
headers: { "Content-Type": "text/plain" },
body: rawtx,
});
if (!resp.ok) {
const errorText = await resp.text();
throw new Error(`HTTP error! status: ${resp.status}, message: ${errorText}`);
}
const txid = await resp.text();
return { txid };
} catch (error) {
console.error('Push transaction error:', error);
throw error;
}
};
export const getUtxos = async (address) => {
try {
const endpoint = `https://esplora.signet.surge.dev/address/${address}/utxo`;
const resp = await fetch(endpoint, {
method: "GET",
headers: { "Content-Type": "application/json" },
});
if (!resp.ok) {
throw new Error(`HTTP error! status: ${resp.status}`);
}
return await resp.json();
} catch (error) {
console.error('Get UTXOs error:', error);
throw error;
}
};
export { btcSendTransaction };
Can anyone help me with this?