Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TransactionBuilder.fromTransaction (Transaction.fromHex ()) and segwit input signing #901

Closed
dakk opened this issue Sep 22, 2017 · 6 comments
Assignees
Labels

Comments

@dakk
Copy link

dakk commented Sep 22, 2017

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

@dakk dakk changed the title TransactionBuilder.fromHex and segwit input signing TransactionBuilder.fromTransaction (Transaction.fromHex ()) and segwit input signing Sep 22, 2017
@dcousens dcousens self-assigned this Sep 26, 2017
@dcousens
Copy link
Contributor

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

@dakk
Copy link
Author

dakk commented Sep 26, 2017

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);

@dcousens
Copy link
Contributor

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.

@dcousens
Copy link
Contributor

Closing in favour of #908

@dcousens
Copy link
Contributor

dcousens commented Sep 26, 2017

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

@dcousens
Copy link
Contributor

See #910

Melvillian added a commit to BitGo/bitcoinjs-lib that referenced this issue Dec 6, 2017
* add testnet bip49 example

* Should be able to deal with incomplete P2SH/P2WSH inputs when allowIncomplete is set

* remove redundant baddress.toOutputScript call from tests

* set p2sh=true only if redeemScriptType is set

* testing/integration/examples: isolate to addresses/transactions
examples, use public broadcast endpoints

* tests/integration: add BIP32 serialization and multi-child examples

* README: re-generate examples list

* README: drop Contributors

* README: add bech32

* README: move BCoin to Alternative

* README: drop insight

* README: add helperbit

* LICENSE: 2017 too

* add from/toBech32

* add Bech32 support to toOutputScript/fromOutputScript

* README/tests: add BIP173/BIP16 SegWit address examples

* tests: add P2WPK, P2WSH spend example

* tests: resist txn-mempool-conflicts

* tests/txb: add P2WSH(multisig), incomplete fixture

* txbuilder: refactor branches for readability

* add witnessPubKeyHash compressed policy

* templates/pubkey: only canonical pubkeys to encode

* TxBuilder: restrict uncompressed keyPairs for P2WPK and P2WSH

* script: use asMinimalOP for ASM/decompile

* Fix the integration url's to latest version

also some of the urls were broken

* add bech32 fixture

* Fixed Segwit links

The links were redirecting to 404 - I've modified them so they adhere to the beginning of the respective Segwit unit tests.

* Fixed some README links

Found some more links that were displaced by the Segwit unit tests and fixed them

* typescript instructions on README

closes:
bitcoinjs#815

* README: cleanup typescript help

* Add witness is true to signing

* Add test for witness = true edge case during multisigning

* TransactionBuilder: collect witnessValue as input.value, and match it

* Fix txb.__overMaximumFees for segwit

* Add test case

* Fix absurd fee in fixture

* buildstack - don't return op_0

* multisig.input.encodestack - replace OP_0 (permitted by partialSignature) with EMPTY_BUFFER

* update CHANGELOG

* 3.2.0

* tests: script tests can validate template fixtures too

* match scriptHash types 1 for 1, ignore classify order

* add fixture to verify input type classification

(cherry picked from commit 8f9d8b7)

* respond to Jonathan Underwood's comments

(cherry picked from commit 8126ca2)

* tests/fixtures: amend truncated outputHex

* README: add notes about ES5, Node LTS feature tracking

* package: rm contributors field, outdated, update wallet estimate

* rm bscript circular dependencies

* txbuilder: fix canSign returning true for missing witness value

* address/txbuilder: require templates to prevent undefined exports

* tests: add passing and failing tests for witness*.input.encode/decode

* witnessScriptHash: fixed implementation

* tests: add failing staged transaction building example bitcoinjs#901

* txbuilder: apply input.value before prepareInput

* s/checkP2shInput/checkP2SHInput

* 3.2.1

* ECSignature: add toRSBuffer/fromRSBuffer

* TxBuilder: add support for RSBuffer type keyPairs and .publicKey

* 3.3.0

* tests: txb for TxBuilder, Tx for Transaction

* increase max feerate sanity check from 1000 to 2500

* 3.3.1

* opt-in bitcoin-cash support in transaction_builder

* TransactionBuilder.fromTransaction & Bitcoin Cash

Adds an extra parameter to fromTransaction, which tells the
library to expect a value property to be added on each txin
which uses bitcoin cash's sighashtype

* bitcoin gold support

* package: rename to bitcoinforksjs-lib
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants
@dcousens @dakk and others