bitcoinjs / bitcoinjs-lib

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

HowTo: Construct a txID of an unsigned PSBT #1540

Closed MyNameIsOka closed 4 years ago

MyNameIsOka commented 4 years ago

Hi,

as the title says, I would like to build the txID of an unsigned PSBT transaction. I built the PSBT with the following code snippet:

const psbt = new bitcoin.Psbt({ network });
psbt.setVersion(1);
for (const input of finalInputs) {
  psbt.addInput({ hash: input.txId, index: input.vout, witnessUtxo: input.WitnessUtxo });
}
finalOutputs.forEach(output => {
   psbt.addOutput({ address: output.address, value: output.value });
});

As I read through some threads here, I saw that it's possible to get the txID with BuildIncomplete via the TransactionBuilder but I wasn't able to get it in any way from a PSBT transaction.

I would really appreciate if you could point me into the right direction!

SiestaMadokaist commented 4 years ago

you could get the TransactionBuilder via:

const psbt = new bitcoin.Psbt({ network })
// doing things with psbt.
const tx = psbt.__CACHE.__TX;
// that will give you a bitcoin.Transaction object, if you need the bitcoin.TransactionBuilder then you need to add the following line.
const txb = bitcoin.TransactionBuilder.fromTransaction(tx)

although, the __CACHE is for now still private interface, meaning, it might break in the future update.

I'm not sure if there's a more appropriate way to do that as of now.

MyNameIsOka commented 4 years ago

Wow, thanks a lot @SiestaMadokaist! That worked perfectly.

junderw commented 4 years ago

Please note that if any of the inputs are not segwit, the TXID will change after signature.

Also, could you explain the use case for a txid before the tx is signed?

After signing and finalizing you can just use extractTransaction to get a Transaction object.

MyNameIsOka commented 4 years ago

Thanks for pointing that out, I will use segwit only so that shouldn't be a problem.

I could have used a random number, as well but I figured, why not. It's just necessary to keep track of the tx.

MyNameIsOka commented 4 years ago

Just to clarify, I have to use bitcoin.payments.p2wsh for this in order to work, right? I tested it with p2sh-p2wsh and the txid between unsigned and signed state was different.

junderw commented 4 years ago

I was speaking on a theoretical basis.

In terms of your method (unsupported) the reason why this is happening is because PSBT leaves the inputs empty until the tx is extracted.

With P2SH-P2WSH you will need to have a scriptSig (input script) with the redeemscript.

tx.ins[i].script = bitcoinjs.script.compile([redeemScriptBuffer]) should allow you to use the work around for P2SH-P2WSH as well.

However, be careful, as JavaScript passes by reference, so if you add those scripts the scripts will be added to the internal TX of the PSBT as well... so don't do anything with the PSBT object after you've done this.

KayBeSee commented 3 years ago

Hey Jon, sorry for resurrecting this old issue but I was also trying to get the txId of a Segwit PSBT before signing.

My use case is a trustless way of doing software licensing.

Flow: The user sends a request to my server saying they want a license. I respond with a BTC address and amount. The user then constructs a transaction (using PSBT) and sends it back to me. I take the txId, sign a message with the txId and a blockheight length (x) and some other info with my private key, and send it back to the user. The user then signs the transaction and broadcasts it.

The software being licensed checks the blockchain for a transaction with that txId, sees if it has been confirmed, takes the blockheight that it was confirmed, adds x to it, and makes sure that the current blockheight is less than that.

The solution that @SiestaMadokaist gave works, but it would be nice if I could grab the ID not from the cache.

junderw commented 3 years ago

I'm still on the fence about such a feature.

  1. It's easily worked around. If your app relies on undefined behavior (internal caches etc.) you can just lock your dependency at a specific version so you don't automatically pull in a patch that will break your app.
  2. By adding a method, we are giving the consumers a lot of assumptions which might not hold true for their use case. Explaining the nuance of the method would require a couple paragraphs worth of comments, what is safe to assume, what is not safe to assume, etc. And those assumptions could change in a future soft fork.

That said. It would be easy to implement.

  1. Check to make sure all inputs have enough info to verify all of them are segwit inputs. fail if any non-segwit or not enough info
  2. Construct a Transaction by cloning the cache
  3. Add in the redeemScript into the input for any p2sh-p2wpkh or p2sh-p2wsh-* inputs
  4. getId() on the cloned Transaction

I would definitely consider merging such a pull request, but I personally wouldn't spend time implementing something I'm still 50:50 on.