Bitcoinjs-lib: TransactionBuilder.fromTransaction (Transaction.fromHex ()) and segwit input signing

Created on 22 Sep 2017  路  6Comments  路  Source: bitcoinjs/bitcoinjs-lib

Version: Bitcoinjs-0.3.2 and also bitcoinjs/bitcoinjs-lib#fixes

I'm trying to integrate segwit in my walleting software, but I'm stuck in one problem. The flow is the following:

  1. I create a segwit address from 2of3 p2sh keys
  2. I send some testnet coin to this address
  3. I get the unspent tx
  4. I create the tx with this unspent as input
  5. I generate the incomplete hex, passing it to another software
  6. I then try to sign the transaction with the following code:
var signTxSegwit = function (txhex, pubkeys, privkey, utxos, n) {
var txb = new bitcoinjs.TransactionBuilder.fromTransaction (
    bitcoinjs.Transaction.fromHex (txhex), bitcoinjs.networks.testnet);
var upair = bitcoinjs.ECPair.fromWIF(privkey, bitcoinjs.networks.testnet);
var pubkeys_raw = pubkeys.map(function (hex) { return new Buffer(hex, 'hex'); });
var witnessScript = bitcoinjs.script.multisig.output.encode (n, pubkeys_raw);
var redeemScript = bitcoinjs.script.witnessScriptHash.output.encode (bitcoinjs.crypto.sha256(witnessScript));
var scriptPubKey = bitcoinjs.script.scriptHash.output.encode (bitcoinjs.crypto.hash160(redeemScript));
var address = bitcoinjs.address.fromOutputScript(scriptPubKey, bitcoinjs.networks.testnet);

for (var j = 0; j < txb.tx.ins.length; j++) {
    txb.sign (j, upair, redeemScript, null, parseInt (utxos[j].value * 100000000), witnessScript);
}

var tx = txb.build ();
return tx.toHex ();
}

The program fail at txb.sign, with this error:

   Message:
     Error: Expected property "2" of type Satoshi, got undefined
   Stacktrace:
     Error
    at TfTypeError.Error (native)
    at new TfTypeError (/home/dakk/Repositories/MyRepos/backend/node_modules/typeforce/errors.js:43:24)
    at typeforce (/home/dakk/Repositories/MyRepos/backend/node_modules/typeforce/index.js:193:11)
    at /home/dakk/Repositories/MyRepos/backend/node_modules/typeforce/index.js:152:18
    at Array.every (native)
    at _tuple (/home/dakk/Repositories/MyRepos/backend/node_modules/typeforce/index.js:150:20)
    at typeforce (/home/dakk/Repositories/MyRepos/backend/node_modules/typeforce/index.js:191:9)
    at Transaction.hashForWitnessV0 (/home/dakk/Repositories/MyRepos/backend/node_modules/bitcoinjs-lib/src/transaction.js:320:3)
    at TransactionBuilder.sign (/home/dakk/Repositories/MyRepos/backend/node_modules/bitcoinjs-lib/src/transaction_builder.js:692:29)
    at signTxSegwit (/home/dakk/Repositories/MyRepos/backend/tests/api/middlewares/wallet.js:79:7)

Then I jumped to bitcoinjs-lib/src/transaction_builder:692 and I found this:

signatureHash = this.tx.hashForWitnessV0(vin, input.signScript, input.value, hashType)

Which is using input.value; so I guessed prepareInput should inject this field, and I've found that it injects input.value if witnessScript and reedemScript are not undefined; but they are defined in my example, right? I can't figure out what's the problem here

bug

Most helpful comment

Reproduced with

// Bitcoin address : mhqo9zJRm4gBYFmBD6kCVCfZL3JS5UqLmj
let key1 = {
  priv: 'cUxccFVBdJRq6HnyxiFMd8Z15GLThXaNLcnPBgoXLEv9iX6wuV2b',
  pub: '03c411cf39aca4395c81c35921dc832a0d1585d652ab1b52ccc619ff9fbbc57877'
}
//  Bitcoin address : mpSAvmKXAE8B6H6bgLnGBG8iyC6tQgEy9D
let key2 = {
  priv: 'cVSNe9ZdZRsRvEBL8YRR7YiZmH4cLsf5FthgERWkZezJVrGseaXy',
  pub: '020636d944458a4663b75a912c37dc1cd59b11f9a00106783a65ba230d929b96b0'
}
//  Bitcoin address : mq1HD5wiNLCNQv8u9wdwLvRH5JiMbxJHR4
let key3 = {
  priv: 'cQqbmgCQroize8cD1484C5243Q7twmHq5YjN3fYFayApcfoZykcF',
  pub: '02d1448cbf19528a1a27e5958ba73d930b5b3facdbe5c30c7094951a287fcc9149'
}

let bitcoin = require('./')
let keyPairs = [key1.priv, key2.priv, key3.priv].map(function (wif) {
  return bitcoin.ECPair.fromWIF(wif, bitcoin.networks.testnet)
})
let pubKeys = keyPairs.map(function (x) { return x.getPublicKeyBuffer() })

let itxhex
{
  let witnessScript = bitcoin.script.multisig.output.encode(2, pubKeys)
  let witnessScriptHash = bitcoin.crypto.sha256(witnessScript)

  let redeemScript = bitcoin.script.witnessScriptHash.output.encode(witnessScriptHash)
  let redeemScriptHash = bitcoin.crypto.hash160(redeemScript)

  let scriptPubKey = bitcoin.script.scriptHash.output.encode(redeemScriptHash)
  let P2SHaddress = bitcoin.address.fromOutputScript(scriptPubKey, bitcoin.networks.testnet)

  console.log(P2SHaddress)
  console.log(scriptPubKey)

  // 2Mv8kEd3D7PaYciMYMgQU3zGTv5RgsYDUDy
  let txb = new bitcoin.TransactionBuilder(bitcoin.networks.testnet)

  txb.addInput('f3e785dff9694a463f5f17570c2940aa8e65117ae69184e5fa8de69ac4ae3481', 1)
  txb.addOutput(scriptPubKey, (1000000 - 10000) / 2)
  txb.addOutput('n2iptWzMeDb35222vkp3SA9ytsac3skwjU', (1000000 - 10000) / 2)

  txb.sign(0, keyPairs[0], redeemScript, null, 1000000, witnessScript)
  itxhex = txb.buildIncomplete().toHex()
}

{
  let txb = bitcoin.TransactionBuilder.fromTransaction(bitcoin.Transaction.fromHex(itxhex), bitcoin.networks.testnet)

  let witnessScript = bitcoin.script.multisig.output.encode(2, pubKeys)
  let redeemScript = bitcoin.script.witnessScriptHash.output.encode(bitcoin.crypto.sha256(witnessScript))
//    let scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript))
//    let address = bitcoin.address.fromOutputScript(scriptPubKey, bitcoin.networks.testnet)

  txb.sign(0, keyPairs[1], redeemScript, null, 1000000, witnessScript)

  let tx = txb.build()
  let txhex = tx.toHex()
  console.log(txhex)
}

Which isolates the variables with block scoping.

Will investigate.

All 6 comments

Can you provide a complete test fixture? Aka, a privkey WIF and txhex w/ a UTXO?

I create a full example reproducing my problem; the script sign correctly with the first privkey, but fail trying to sign after serialize/parse (toHex -> fromHex) procedure

var bitcoin = require('bitcoinjs-lib')


// Bitcoin address              : mhqo9zJRm4gBYFmBD6kCVCfZL3JS5UqLmj
var key1 = { 
    priv: 'cUxccFVBdJRq6HnyxiFMd8Z15GLThXaNLcnPBgoXLEv9iX6wuV2b', 
    pub: '03c411cf39aca4395c81c35921dc832a0d1585d652ab1b52ccc619ff9fbbc57877' 
};

// Bitcoin address              : mpSAvmKXAE8B6H6bgLnGBG8iyC6tQgEy9D
var key2 = { 
    priv: 'cVSNe9ZdZRsRvEBL8YRR7YiZmH4cLsf5FthgERWkZezJVrGseaXy', 
    pub: '020636d944458a4663b75a912c37dc1cd59b11f9a00106783a65ba230d929b96b0' 
};

// Bitcoin address              : mq1HD5wiNLCNQv8u9wdwLvRH5JiMbxJHR4
var key3 = {
    priv: 'cQqbmgCQroize8cD1484C5243Q7twmHq5YjN3fYFayApcfoZykcF',
    pub: '02d1448cbf19528a1a27e5958ba73d930b5b3facdbe5c30c7094951a287fcc9149'
};


var keyPairs = [
    'cUxccFVBdJRq6HnyxiFMd8Z15GLThXaNLcnPBgoXLEv9iX6wuV2b',
    'cVSNe9ZdZRsRvEBL8YRR7YiZmH4cLsf5FthgERWkZezJVrGseaXy',
    'cQqbmgCQroize8cD1484C5243Q7twmHq5YjN3fYFayApcfoZykcF'
].map(function (wif) { return bitcoin.ECPair.fromWIF(wif, bitcoin.networks.testnet) })

var pubKeys = keyPairs.map(function (x) { return x.getPublicKeyBuffer() })

var witnessScript = bitcoin.script.multisig.output.encode(2, pubKeys)
var witnessScriptHash = bitcoin.crypto.sha256(witnessScript)

var redeemScript = bitcoin.script.witnessScriptHash.output.encode(witnessScriptHash)
var redeemScriptHash = bitcoin.crypto.hash160(redeemScript)

var scriptPubKey = bitcoin.script.scriptHash.output.encode(redeemScriptHash)
var P2SHaddress = bitcoin.address.fromOutputScript(scriptPubKey, bitcoin.networks.testnet)        

console.log(P2SHaddress)
console.log (scriptPubKey)

// 2Mv8kEd3D7PaYciMYMgQU3zGTv5RgsYDUDy

var txb = new bitcoin.TransactionBuilder(bitcoin.networks.testnet)

txb.addInput('f3e785dff9694a463f5f17570c2940aa8e65117ae69184e5fa8de69ac4ae3481', 1);

txb.addOutput (scriptPubKey, (1000000 - 10000) / 2);
txb.addOutput ("n2iptWzMeDb35222vkp3SA9ytsac3skwjU", (1000000 - 10000) / 2);

txb.sign(0, keyPairs[0], redeemScript, null, 1000000, witnessScript)

var txhex = txb.buildIncomplete ().toHex ();


var txb = new bitcoin.TransactionBuilder.fromTransaction (
    bitcoin.Transaction.fromHex (txhex), bitcoin.networks.testnet);
var witnessScript = bitcoin.script.multisig.output.encode (2, pubKeys);
var redeemScript = bitcoin.script.witnessScriptHash.output.encode (bitcoin.crypto.sha256(witnessScript));
var scriptPubKey = bitcoin.script.scriptHash.output.encode (bitcoin.crypto.hash160(redeemScript));
var address = bitcoin.address.fromOutputScript(scriptPubKey, bitcoin.networks.testnet);

txb.sign (0, keyPairs[1], redeemScript, null, 1000000, witnessScript);

var tx = txb.build ();
var txhex = tx.toHex ();

console.log (txhex);

Also, this is the previous example without toHex fromHex, and it works:

var bitcoin = require('bitcoinjs-lib')


// Bitcoin address              : mhqo9zJRm4gBYFmBD6kCVCfZL3JS5UqLmj
var key1 = { 
    priv: 'cUxccFVBdJRq6HnyxiFMd8Z15GLThXaNLcnPBgoXLEv9iX6wuV2b', 
    pub: '03c411cf39aca4395c81c35921dc832a0d1585d652ab1b52ccc619ff9fbbc57877' 
};

// Bitcoin address              : mpSAvmKXAE8B6H6bgLnGBG8iyC6tQgEy9D
var key2 = { 
    priv: 'cVSNe9ZdZRsRvEBL8YRR7YiZmH4cLsf5FthgERWkZezJVrGseaXy', 
    pub: '020636d944458a4663b75a912c37dc1cd59b11f9a00106783a65ba230d929b96b0' 
};

// Bitcoin address              : mq1HD5wiNLCNQv8u9wdwLvRH5JiMbxJHR4
var key3 = {
    priv: 'cQqbmgCQroize8cD1484C5243Q7twmHq5YjN3fYFayApcfoZykcF',
    pub: '02d1448cbf19528a1a27e5958ba73d930b5b3facdbe5c30c7094951a287fcc9149'
};


var keyPairs = [
    'cUxccFVBdJRq6HnyxiFMd8Z15GLThXaNLcnPBgoXLEv9iX6wuV2b',
    'cVSNe9ZdZRsRvEBL8YRR7YiZmH4cLsf5FthgERWkZezJVrGseaXy',
    'cQqbmgCQroize8cD1484C5243Q7twmHq5YjN3fYFayApcfoZykcF'
].map(function (wif) { return bitcoin.ECPair.fromWIF(wif, bitcoin.networks.testnet) })

var pubKeys = keyPairs.map(function (x) { return x.getPublicKeyBuffer() })

var witnessScript = bitcoin.script.multisig.output.encode(2, pubKeys)
var witnessScriptHash = bitcoin.crypto.sha256(witnessScript)

var redeemScript = bitcoin.script.witnessScriptHash.output.encode(witnessScriptHash)
var redeemScriptHash = bitcoin.crypto.hash160(redeemScript)

var scriptPubKey = bitcoin.script.scriptHash.output.encode(redeemScriptHash)
var P2SHaddress = bitcoin.address.fromOutputScript(scriptPubKey, bitcoin.networks.testnet)        

console.log(P2SHaddress)
console.log (scriptPubKey)

// 2Mv8kEd3D7PaYciMYMgQU3zGTv5RgsYDUDy

var txb = new bitcoin.TransactionBuilder(bitcoin.networks.testnet)

txb.addInput('f3e785dff9694a463f5f17570c2940aa8e65117ae69184e5fa8de69ac4ae3481', 1);

txb.addOutput (scriptPubKey, (1000000 - 10000) / 2);
txb.addOutput ("n2iptWzMeDb35222vkp3SA9ytsac3skwjU", (1000000 - 10000) / 2);

txb.sign(0, keyPairs[0], redeemScript, null, 1000000, witnessScript)

var witnessScript = bitcoin.script.multisig.output.encode (2, pubKeys);
var redeemScript = bitcoin.script.witnessScriptHash.output.encode (bitcoin.crypto.sha256(witnessScript));
var scriptPubKey = bitcoin.script.scriptHash.output.encode (bitcoin.crypto.hash160(redeemScript));
var address = bitcoin.address.fromOutputScript(scriptPubKey, bitcoin.networks.testnet);

txb.sign (0, keyPairs[1], redeemScript, null, 1000000, witnessScript);

var tx = txb.build ();
var txhex = tx.toHex ();

console.log (txhex);

Reproduced with

// Bitcoin address : mhqo9zJRm4gBYFmBD6kCVCfZL3JS5UqLmj
let key1 = {
  priv: 'cUxccFVBdJRq6HnyxiFMd8Z15GLThXaNLcnPBgoXLEv9iX6wuV2b',
  pub: '03c411cf39aca4395c81c35921dc832a0d1585d652ab1b52ccc619ff9fbbc57877'
}
//  Bitcoin address : mpSAvmKXAE8B6H6bgLnGBG8iyC6tQgEy9D
let key2 = {
  priv: 'cVSNe9ZdZRsRvEBL8YRR7YiZmH4cLsf5FthgERWkZezJVrGseaXy',
  pub: '020636d944458a4663b75a912c37dc1cd59b11f9a00106783a65ba230d929b96b0'
}
//  Bitcoin address : mq1HD5wiNLCNQv8u9wdwLvRH5JiMbxJHR4
let key3 = {
  priv: 'cQqbmgCQroize8cD1484C5243Q7twmHq5YjN3fYFayApcfoZykcF',
  pub: '02d1448cbf19528a1a27e5958ba73d930b5b3facdbe5c30c7094951a287fcc9149'
}

let bitcoin = require('./')
let keyPairs = [key1.priv, key2.priv, key3.priv].map(function (wif) {
  return bitcoin.ECPair.fromWIF(wif, bitcoin.networks.testnet)
})
let pubKeys = keyPairs.map(function (x) { return x.getPublicKeyBuffer() })

let itxhex
{
  let witnessScript = bitcoin.script.multisig.output.encode(2, pubKeys)
  let witnessScriptHash = bitcoin.crypto.sha256(witnessScript)

  let redeemScript = bitcoin.script.witnessScriptHash.output.encode(witnessScriptHash)
  let redeemScriptHash = bitcoin.crypto.hash160(redeemScript)

  let scriptPubKey = bitcoin.script.scriptHash.output.encode(redeemScriptHash)
  let P2SHaddress = bitcoin.address.fromOutputScript(scriptPubKey, bitcoin.networks.testnet)

  console.log(P2SHaddress)
  console.log(scriptPubKey)

  // 2Mv8kEd3D7PaYciMYMgQU3zGTv5RgsYDUDy
  let txb = new bitcoin.TransactionBuilder(bitcoin.networks.testnet)

  txb.addInput('f3e785dff9694a463f5f17570c2940aa8e65117ae69184e5fa8de69ac4ae3481', 1)
  txb.addOutput(scriptPubKey, (1000000 - 10000) / 2)
  txb.addOutput('n2iptWzMeDb35222vkp3SA9ytsac3skwjU', (1000000 - 10000) / 2)

  txb.sign(0, keyPairs[0], redeemScript, null, 1000000, witnessScript)
  itxhex = txb.buildIncomplete().toHex()
}

{
  let txb = bitcoin.TransactionBuilder.fromTransaction(bitcoin.Transaction.fromHex(itxhex), bitcoin.networks.testnet)

  let witnessScript = bitcoin.script.multisig.output.encode(2, pubKeys)
  let redeemScript = bitcoin.script.witnessScriptHash.output.encode(bitcoin.crypto.sha256(witnessScript))
//    let scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript))
//    let address = bitcoin.address.fromOutputScript(scriptPubKey, bitcoin.networks.testnet)

  txb.sign(0, keyPairs[1], redeemScript, null, 1000000, witnessScript)

  let tx = txb.build()
  let txhex = tx.toHex()
  console.log(txhex)
}

Which isolates the variables with block scoping.

Will investigate.

This isn't over yet... the witness is malformed (missing key1 signature).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

thrastarson picture thrastarson  路  3Comments

Beardcoding picture Beardcoding  路  3Comments

ghost picture ghost  路  3Comments

LeonYanghaha picture LeonYanghaha  路  3Comments

hoshsadiq picture hoshsadiq  路  3Comments