2

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

enter image description here

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?

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.