bitcoinjs / bitcoinjs-lib

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

Failing to broadcast - non-mandatory-script-verify-flag (Witness program hash mismatch) #2136

Closed Arunpandiaraja closed 1 month ago

Arunpandiaraja commented 2 months ago

I'm trying to do a text inscription with this reference https://github.com/fboucquez/ordinal-inscription-example-using-bitcoinjs-lib , but running into this issue RPC error: {"code":-26,"message":"non-mandatory-script-verify-flag (Witness program hash mismatch)"} Note: (im using from and to same address, for testing), have added code below and other info to debug and also im using custom signet. any help would be much appreciated

Public key: 036adf52825306784f4488892acdf65eab7c9793435879e7d74391fc1ce11710a1

From and to Taproot address: tb1p0h2kzz5utk2rlw5ytkyqdv0v8mr2hz35k548nvwa7xz8z8qks6psjy0t8f

TxHex: 02000000000101d34f9a2d2b2cffa68776c181d7d2ae7f6c9047518be59a72703b5524e8dcfbe80000000000ffffffff0140420f0000000000160014b206974d3306ff4fc78acb60b7c0cbfb9c38da5e034086ada43d5d7b497abb7f7b1c2328a9111bfd290577c5c5bbbd82389e2ea22a8d9395cdcae6994694b0e2219dd889b3160262303dbd411c512a4091aff52e82db51206adf52825306784f4488892acdf65eab7c9793435879e7d74391fc1ce11710a1ac0063036f7264010118746578742f706c61696e3b636861727365743d7574662d38000b48656c6c6f204172756e216821c06adf52825306784f4488892acdf65eab7c9793435879e7d74391fc1ce11710a100000000

My Utxos:

[
    {
        "txid": "921602b4568caeb7ec095f38e7a1a3d06fabbc98bd119a4429934b03382dce7e",
        "vout": 0,
        "status": {
            "confirmed": true,
            "block_height": 228135,
            "block_hash": "00000083950af4cdf1a575939d35861d702ab2f0ebf0ef75ba195cd5bcd8db95",
            "block_time": 1721709242
        },
        "value": 1000000
    },
    {
        "txid": "e8fbdce824553b70729ae58b5147906c7faed2d781c17687a6ff2c2b2d9a4fd3",
        "vout": 0,
        "status": {
            "confirmed": true,
            "block_height": 228166,
            "block_hash": "000000ec85762d6ff892392ac2a93938f44eb2334a0e692fd7896bd8ef15c156",
            "block_time": 1721709711
        },
        "value": 100000000
    },
    {
        "txid": "a4715efb3dd58abf3dd6922445fb10840abff84a0e3228c12e4d80b3d25856c5",
        "vout": 1,
        "status": {
            "confirmed": true,
            "block_height": 222159,
            "block_hash": "00000090f28e326ec5d30989f1c3ad1830c912439a1497e6a57b618bf432131a",
            "block_time": 1721624411
        },
        "value": 47943606
    },
    {
        "txid": "35f6efc5bf898ad5bbe36811a6291983352a7e7a10d41c77ff339ffbe549d923",
        "vout": 0,
        "status": {
            "confirmed": true,
            "block_height": 228129,
            "block_hash": "000002cd01080c6080c297a61e4407d0e9f8c5fc19e279bb3f187413a9f86cb7",
            "block_time": 1721709157
        },
        "value": 1000000
    },
    {
        "txid": "5223582741ea9f06aba5dbf397001721f52f4cfdd91ceec533b0863c613db397",
        "vout": 0,
        "status": {
            "confirmed": true,
            "block_height": 228133,
            "block_hash": "00000322c958c8065d6aabc03613fbc147d755f8291db9432877a1af1028336d",
            "block_time": 1721709205
        },
        "value": 1000000
    },
    {
        "txid": "a7eaba5ba8c8f1de117224486fe94e688aaed2abed612076df62636033bbd160",
        "vout": 0,
        "status": {
            "confirmed": true,
            "block_height": 228134,
            "block_hash": "0000031ad185eb5cd37ac6cfb1475b0a0eac7edfae1b6e4fcbb366358ebf94ce",
            "block_time": 1721709217
        },
        "value": 1000000
    }
]
const sendTransaction = async () => {
    setLoader(true)
    try {
      const utxos = await getUtxos({ address: txObject.fromAddress })
      const { selectedUtxos } = selectUtxos(utxos, amountToSend)

      const data = await createRevealTx(
        commitTxData,
        selectedUtxos,
        txObject.toAddress,
        privateKey,
        amountToSend
      )
      console.log(data.rawTx, 'rawTx')
      const broadcastData = await pushTx(data.rawTx)
      console.log(broadcastData, 'broadcastData')
    } catch (error: any) {
      console.error(error.message)
    }

    setLoader(false)
  }
  import ecc from '@bitcoinerlab/secp256k1'
import {
  Psbt,
  initEccLib,
  networks,
  opcodes,
  payments,
  script,
} from 'bitcoinjs-lib'
import { witnessStackToScriptWitness } from 'bitcoinjs-lib/src/psbt/psbtutils'
import { ECPairFactory } from 'ecpair'

const ECPair = ECPairFactory(ecc)
initEccLib(ecc)
const network = networks.testnet

const encoder = new TextEncoder()

export const toXOnly = (pubkey: Buffer): Buffer => {
  return pubkey.subarray(1, 33)
}

export const createTextInscription = (text: string, postage = 10000) => {
  const contentType = Buffer.from(encoder.encode('text/plain;charset=utf-8'))
  const content = Buffer.from(encoder.encode(text))
  return { contentType, content, postage }
}

function createInscriptionScript(xOnlyPublicKey: Buffer, inscription: any) {
  const protocolId = Buffer.from(encoder.encode('ord'))
  return [
    xOnlyPublicKey,
    opcodes.OP_CHECKSIG,
    opcodes.OP_0,
    opcodes.OP_IF,
    protocolId,
    1,
    1,
    inscription.contentType,
    opcodes.OP_0,
    inscription.content,
    opcodes.OP_ENDIF,
  ]
}

export const createCommitTxData = (publicKey: Buffer, inscription: any) => {
  const xOnlyPublicKey = toXOnly(publicKey)
  const scripts = createInscriptionScript(xOnlyPublicKey, inscription)

  const outputScript = script.compile(scripts)

  const scriptTree = {
    output: outputScript,
    redeemVersion: 192,
  }

  const scriptTaproot = payments.p2tr({
    internalPubkey: xOnlyPublicKey,
    scriptTree,
    redeem: scriptTree,
    network,
  })

  const tapleaf = scriptTaproot.hash?.toString('hex')

  const revealAddress = scriptTaproot.address
  const tpubkey = scriptTaproot.pubkey?.toString('hex')
  const cblock =
    scriptTaproot.witness?.[scriptTaproot.witness.length - 1].toString('hex')

  return {
    script,
    tapleaf,
    tpubkey,
    cblock,
    revealAddress,
    scriptTaproot,
    outputScript,
  }
}

export const createRevealTx = async (
  commitTxData: any,
  commitTxResult: any,
  toAddress: string,
  privateKey: Buffer,
  amount: number
) => {
  const { cblock, scriptTaproot, outputScript } = commitTxData
  console.log(outputScript, 'outputScript')
  const tapLeafScript = {
    leafVersion: scriptTaproot.redeemVersion, // 192 0xc0
    script: outputScript,
    controlBlock: Buffer.from(cblock, 'hex'),
  }
  console.log(tapLeafScript, 'tapLeafScript')
  const keypair = ECPair.fromPrivateKey(privateKey, { network })
  const psbt = new Psbt({ network })
  commitTxResult.forEach((utxo: any) => {
    psbt.addInput({
      hash: utxo.txid,
      index: utxo.vout,
      witnessUtxo: {
        value: utxo.value,
        script: scriptTaproot.output,
      },
      tapLeafScript: [tapLeafScript],
    })
    console.log(utxo, 'utxo')
  })

  psbt.addOutput({
    value: amount, // generally 1000 for nfts, 549 for brc20
    address: toAddress,
  })

  psbt.signAllInputs(keypair)

  const customFinalizer = (_inputIndex: any, input: any) => {
    const witness = [input.tapScriptSig[0].signature]
      .concat(outputScript)
      .concat(tapLeafScript.controlBlock)

    return {
      finalScriptWitness: witnessStackToScriptWitness(witness),
    }
  }

  psbt.finalizeInput(0, customFinalizer)

  const tx = psbt.extractTransaction(true)

  const rawTx = tx.toBuffer().toString('hex')
  const txId = tx.getId()

  const virtualSize = tx.virtualSize()

  return {
    txId,
    rawTx,
    inscriptionId: `${txId}i0`,
    virtualSize,
  }
}
Arunpandiaraja commented 2 months ago

Hey @junderw if u take a look at this, it would be really helpful

jasonandjay commented 2 months ago

The code seems to be fine

I suggest optimizing psbt.finalizeInput(0, customFinalizer) with psbt.finalizeInput(0) first

if it does not work

I suggest providing a code that can be run directly so that I can help you pick food

jasonandjay commented 1 month ago

hello @Arunpandiaraja

Is this problem solved?

Arunpandiaraja commented 1 month ago

Hey @jasonandjay, turns out I was trying to pull a magic trick with only the reveal and forgot to do the commit first! 🤦‍♂️ But once I finally passed the commit output to the reveal, it all worked like a charm! 🎩✨ Thanks for the assist! 😅 So closing this issue.

liusongjie575 commented 2 weeks ago

Can you tell me how you solved it? Where did the specific code change? I am now experiencing the same problem as you. I manually transferred the UTXO number to reveal and then broadcasted it to me through it