3

the code below works:

const btc = require('bitcoinjs-lib');

var tx = new btc.TransactionBuilder(net);
inputs.forEach(o => tx.addInput(o.txId, o.vout));
outputs.forEach(o => tx.addOutput(o.address, o.value));
tx.sign(0, owner);
var body = tx.build().toHex();

but it's complaining that TransactionBuilder is deprecated... sigh...

Deprecation Warning: TransactionBuilder will be removed in the future. (v6.x.x or later) Please use the Psbt class instead. Examples of usage are available in the transactions-psbt.js integration test file on our Github. A high level explanation is available in the psbt.ts and psbt.js files as well

and apparently the signing methodology has also changed (grr):

DEPRECATED: TransactionBuilder sign method arguments will change in v6, please use the TxbSignArg interface

I've read the documentation on Psbt and understand the general concept but have not found a guide that helps with the transition. I tried to simply change tx.sign() for tx.signInput() but it pukes

I've also looked at the test suite for the library but it's too complicated for me to understand. How do I convert my code to the new mechanism?

3 Answers 3

3

You're only signing the first input? tx.sign(0, owner); Usually every input has to be signed somehow.

I also struggled with the switch to Psbt. Biggest difference was basically requirement to have a full transaction hex of every input for non-segwit inputs. Segwit inputs can be simpler but they can also be created using the full hex of the tx using non-segwit command so don't have to add extra logic differentiating between inputs. You can no longer just provide txid and vout, just add on the hex of tx that created each input for your object.

I really just followed examples https://github.com/bitcoinjs/bitcoinjs-lib#examples

So this is my take on a very general tx with psbt that I use for now:

import * as bitcoin from 'bitcoinjs-lib'

// not all parts of inputs have to be defined
// inputs - array of all data needed for inputs
// outputs - array of all data needed for outputs

const network = bitcoin.networks.testnet // or bitcoin.networks.bitcoin

const psbt = new bitcoin.Psbt({ network })
psbt.setVersion(2)    // default
psbt.setLocktime(0)   // default

// ************** create inputs **************

inputs.forEach(input=> {
  psbt.addInput({
    hash: input.txid,  // txid number
    index: input.vout,  // output number
    sequence: input.sequence, // often 0xfffffffe    
    nonWitnessUtxo: Buffer.from(input.hex, 'hex'), // works for witness inputs too!
    redeemScript: input.redeemScript, // only if there's redeem script
    witnessScript: input.witnessScript // only if there's witness script
  }) 
})

// ************** create outputs **************

outputs.forEach(output => {

  psbt.addOutput({
    address: output.address, // only if not op_return type output (no address for those)
    value: output.value,
    script: output.data  // only if there's string (utf8) data to embed in op_return
      ? bitcoin.payments.embed({ data: [Buffer.from(output.data, 'utf8')] }).output 
      : undefined
  }) 
})

// ************** signing **************

inputs.forEach((input, index) => {

  // sign regular inputs that can be simply signed
  if (!input.redeemScript && !input.witnessScript) {
    psbt.signInput(index, input.keyPair)  

    // give error if signature failed
    if (!psbt.validateSignaturesOfInput(index)) {
      throw new Error('Signature validation failed for input index ' + index.toString())
    }    
  }

})

// ************** finalizing inputs **************

inputs.forEach((input, index) => {

  // sign regular inputs that can be simply signed
  if (!input.redeemScript && !input.witnessScript) {
    psbt.finalizeInput(index) 
  }

  // for p2sh or p2wsh script inputs
  if (input.redeemScript || input.witnessScript) {
    psbt.finalizeInput(
      index, 
      getFinalScripts({ 
        inputScript: input.inputScript, 
        network 
      })
    )
  }
})

// ************** make tx **************

const tx = psbt.extractTransaction()

const virtualSize = tx.virtualSize()
const txid = tx.getId()
const hex = tx.toHex()

console.log('tx virtualSize:', virtualSize)
console.log('tx txid:', txid)
console.log('tx hex:', hex)

And for some reason finalizeInput for custom scripts is not exactly built in yet but I found it in examples and just added as a function I import or put somewhere in same file.

function getFinalScripts ({ inputScript, network }) {
  return function (
    inputIndex,
    input,
    script,
    isSegwit,
    isP2SH,
    isP2WSH,
  ) {
    // Step 1: Check to make sure the meaningful script matches what you expect.

    // Step 2: Create final scripts
    let payment = {
      network,
      output: script,
      input: inputScript,
    };
    if (isP2WSH && isSegwit)
      payment = bitcoin.payments.p2wsh({
        network,
        redeem: payment,
      });
    if (isP2SH)
      payment = bitcoin.payments.p2sh({
        network,
        redeem: payment,
      });

    function witnessStackToScriptWitness(witness) {
      let buffer = Buffer.allocUnsafe(0);

      function writeSlice(slice) {
        buffer = Buffer.concat([buffer, Buffer.from(slice)]);
      }

      function writeVarInt(i) {
        const currentLen = buffer.length;
        const varintLen = varuint.encodingLength(i);

        buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]);
        varuint.encode(i, buffer, currentLen);
      }

      function writeVarSlice(slice) {
        writeVarInt(slice.length);
        writeSlice(slice);
      }

      function writeVector(vector) {
        writeVarInt(vector.length);
        vector.forEach(writeVarSlice);
      }

      writeVector(witness);

      return buffer;
    }

    return {
      finalScriptSig: payment.input,
      finalScriptWitness:
        payment.witness && payment.witness.length > 0
          ? witnessStackToScriptWitness(payment.witness)
          : undefined,
    };
  }
}

I haven't tested every kind of input yet but this is basically what I use for p2wpkh and p2wsh inputs. I try to find examples spending exactly the type of input I need usually.

0

Let me first of all say I am a complete newbie so I might be wrong. Regarding the sign method deprecation, what has been already deprecated is not the method tx.sign() itself, but the way we passed the parameters. In order not to get the DEPRECATED message for the sign method you should pass an object of the form

{ prevOutScriptType: string; vin: number; keyPair: Signer; redeemScript?: Buffer; hashType?: number; witnessValue?: number; witnessScript?: Buffer; }

(see https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/ts_src/transaction_builder.ts line 74)

so concretely in your case

tx.sign({prevOutScriptType:'p2pkh', vin: 0, keyPair: owner})

0

Here is an example (native Segwit, you need to modify "witnessUtxo" appropriately for a p2pkh, see this):

const bitcoin = require('bitcoinjs-lib');
let testnet = bitcoin.networks.testnet;


let keyPair1 = bitcoin.ECPair.fromWIF('privateKey1', testnet);    
let keyPair1wpkh = bitcoin.payments.p2wpkh({pubkey: keyPair1.publicKey, network: testnet});    
let keyPair2 = bitcoin.ECPair.fromWIF('privateKey2', testnet);
let keyPair2wpkh = bitcoin.payments.p2wpkh({pubkey: keyPair2.publicKey, network: testnet});

const psbt = new bitcoin.Psbt({ network: testnet }) 
let inputdata1 = {
  hash: "hash of the previous transaction" ,
  index: index,
  witnessUtxo: {
    script: Buffer.from('scriptPubKey_hex', 'hex'),
    value: 98e4 
  }
};

psbt.addInput(inputdata1);    
let output1 = {address: keyPair2wpkh.address, value: 97e4};    
psbt.addOutput(output1);    
psbt.signInput(0, keyPair1);    
psbt.finalizeAllInputs();    
const raw = psbt.extractTransaction().toHex();    
console.log(raw);

Not the answer you're looking for? Browse other questions tagged or ask your own question.