bitcoinjs / bitcoinjs-lib

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

How to get value from nonWitnessUtxo input and how to add change address derivation path to output #1730

Closed AndreasGassmann closed 2 years ago

AndreasGassmann commented 2 years ago

I have the following PSBT with a nonWitnessUtxo input:

(This PSBT was generated by Sparrow Wallet).

70736274ff01005202000000017395e9912a36a3caf6d753b8dca0512f498f1c143a6516c6f5458c18e00b72310100000000fdffffff015615000000000000160014e83d36778b655dc2bc7ee6cbbdd62dac8c792c179dc00a000001009d020000000233e0aaebcb18169e1e6b0f0ef0aaeddd059b219b813551934f39bf7c2843051d0000000000ffffffffe5ed81f9c881f973e00e61c8edbf0498a33525498d1608e0bf244b50a72f31b40100000000ffffffff02e8030000000000001976a914b0b559a4f6e52d88b70dfef746373b0615a7e67988acc4150000000000001600143d2b21c137ea1bbe3115723e6c889ad6613b1d3100000000010304010000002206038c381ca1cbd86bd1bc88587e02522fd4369b599341583b78dc17b34a7180b091186f01ffc85400008000000080000000800100000003000000002202036a278537493c3cdceb9f88c13a15e8354a66a156d27441a199264135aaea8706186f01ffc8540000800000008000000080000000000500000000

I am trying to calculate the fee of the transaction, but I don't know how to get the value of input nonWitnessUtxos.


    const decodedPSBT = bitcoinJS.Psbt.fromHex(psbt)

    let feeCalculator = new BigNumber(0)

    for (const txIn of decodedPSBT.data.inputs) {
      if (txIn.witnessUtxo) {
        feeCalculator = feeCalculator.plus(new BigNumber(txIn.witnessUtxo.value ?? 0))
      } else if (txIn.nonWitnessUtxo) {
        // How to access the value here?
      }
    }

    for (const txOut of decodedPSBT.txOutputs) {
      feeCalculator = feeCalculator.minus(new BigNumber(txOut.value))
    }

Then I have another question. I would like to check/verify that certain outputs actually belong to the users wallet, meaning that he has control over it. But I can't find the derivation path of the outputs anywhere if I prepare the PSBT like this (obviously):

psbt.addOutput({ address: out.recipient, value: parseInt(out.value) })

If I use Sparrow for example, the derivation paths are located under psbt.data.outputs, which contains an array of "bip32Derivations" with the path. How can I add the derivation path to the outputs like that?

dakk commented 2 years ago

For the first question, in order to get the value of a non utxo input you have to retrieve the corresponding output you're going to spend.

junderw commented 2 years ago
  1. Use Transaction.fromBuffer to turn it into a Transaction. then grab tx.outs[index].value where index is the psbt.txInputs[i].index. (where i is the input you are parsing)
  2. bip32Derivations contains the pubkey of the derived key, the master fingerprint, and the path. You will likely need to get the fingerprint from the user, or if you plan to use the same HD wallet as Sparrow, one look at the psbt will tell you the master fingerprint.
junderw commented 2 years ago
const decodedPSBT = bitcoinJS.Psbt.fromHex(psbt)

let feeCalculator = new BigNumber(0)

for (const [inputIndex, txIn] of decodedPSBT.data.inputs.entries()) {
  if (txIn.witnessUtxo) {
    feeCalculator = feeCalculator.plus(new BigNumber(txIn.witnessUtxo.value ?? 0))
  } else if (txIn.nonWitnessUtxo) {
    // Which output of the tx are we using?
    const vout = decodedPSBT.txInputs[inputIndex].index;
    // Get the tx
    const tx = bitcoinJS.Transaction.fromBuffer(txIn.nonWitnessUtxo);
    // Find the value of the output we are using
    const value = tx.outs[vout].value;
    feeCalculator = feeCalculator.plus(new BigNumber(value ?? 0))
  }
}

for (const txOut of decodedPSBT.txOutputs) {
  feeCalculator = feeCalculator.minus(new BigNumber(txOut.value))
}

// Add output with bip32Derivation info

psbt.addOutput({
  address: out.recipient,
  value: parseInt(out.value),
  bip32Derivation: [
    {
      // This is the value of bip32.fromSeed(seed).fingerprint
      // If you don't have the private keys, you will need to get the master fingerprint by
      // 1. Seeing a PSBT that uses a bip32Derivation from the same HD key. You can copy the masterFingerprint from there.
      // 2. If you have an xpub from a depth=1 key, xpubs contain "parentFingerprint" attribute which will be master (depth=0) for depth=1 keys.
      // (ex. bip32.fromBase58(xpub).parentFingerprint only if depth===1)
      masterFingerprint: Buffer.from([1, 2, 3, 4]),
      // This is the path from the root key to the pubkey used in the output (for the address)
      // It must start with m and denote hardened paths with a single quote '
      // This example would be "the 25th change address" according to BIP84
      path: `m/84'/0'/0'/1/24`,
      // This is the pubkey used in the output (for the address)
      pubkey: Buffer.from([3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9]),
      // You can check if your HD key matches the bip32Derivation with the outputHasHDKey method.
      // Unfortunately this requires the root private key currently.
      // This library will use a provided root HD key, check the master Fingerprint matches, derive the path, then check the resulting pubkey matches.
    },
  ],
})
AndreasGassmann commented 2 years ago

Thanks so much for the example, I think that is everything I need!

I actually just now saw that I missed the bip32Derivation option in addOutput. I didn't go deep enough in the typings 😅.

Thanks again for your help!