bitcoinjs / bitcoinjs-lib

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

multi-stage co-operation multisignature schema support or documentation #1211

Closed pluswave closed 6 years ago

pluswave commented 6 years ago

I want to use bitcoinjs-lib in this case:

Alice and Bob already made a P2SH(P2WSH(multisig))) address;

co-operation process:

  1. Alice build an transaction to spend a UTXO from the multisign address;
  2. Alice sign it with her key
  3. Alice serialize it, send to Bob
  4. Bob deserialize it
  5. Bob check the transaction, if OK, sign it with his key
  6. Bob broadcast the final transaction

AFAIK, current TransactionBuilder and Transaction are not suitable for this case, especially for (de)serialization.

pluswave commented 6 years ago

It seems I was wrong. with this patch test also pass now.

--- a/test/integration/transactions.js
+++ b/test/integration/transactions.js
@@ -240,10 +240,19 @@ describe('bitcoinjs-lib (transactions)', function () {
       txb.addInput(unspent.txId, unspent.vout, null, p2sh.output)
       txb.addOutput(regtestUtils.RANDOM_ADDRESS, 3e4)
       txb.sign(0, keyPairs[0], p2sh.redeem.output, null, unspent.value, p2wsh.redeem.output)
+
+      const txhex = txb.buildIncomplete().toHex()
+      const txb1 = bitcoin.TransactionBuilder.fromTransaction(bitcoin.Transaction.fromHex(txhex), regtest)
       txb.sign(0, keyPairs[2], p2sh.redeem.output, null, unspent.value, p2wsh.redeem.output)
       txb.sign(0, keyPairs[3], p2sh.redeem.output, null, unspent.value, p2wsh.redeem.output)

+      txb1.sign(0, keyPairs[2], p2sh.redeem.output, null, unspent.value, p2wsh.redeem.output)
+      txb1.sign(0, keyPairs[3], p2sh.redeem.output, null, unspent.value, p2wsh.redeem.output)
       const tx = txb.build()
+      const tx1 = txb1.build()
+      if (tx.toHex() !== tx1.toHex()) {
+        return done('serialization and deserialization inconsistent for transaction.')
+      }

       // build and broadcast to the Bitcoin RegTest network
       regtestUtils.broadcast(tx.toHex(), function (err) {
junderw commented 6 years ago

Is this issue resolved? I am confused.

coinflippa commented 6 years ago

These are the lines you are looking for:

This serializes your partially signed transaction:

const txhex = txb.buildIncomplete().toHex()

This deserializes your partially signed transaction:

const txb1 = bitcoin.TransactionBuilder.fromTransaction(bitcoin.Transaction.fromHex(txhex), regtest)

The p2sh.redeem.output and p2wsh.redeem.output ~can be regenerated using the public keys stored in txb1.__inputs[0].pubkeys.map(key => key.toString('hex'))~ fetched via txb1.__inputs[0].pubkeys.

The problem is the input amounts. They are now not available. We need to use an API to get the listunspent. Segwit introduces this burden which has broken most tools like coinb.in.

So our script looks something like:

const txhex = txb.buildIncomplete().toHex()

const txb1 = bitcoin.TransactionBuilder.fromTransaction(bitcoin.Transaction.fromHex(txhex), regtest)

//const pubkeys = txb1.__inputs[0].pubkeys.map(key => key.toString('hex')) not required
const p2ms = bitcoin.payments.p2ms({ m: 2, pubkeys: txb1.__inputs[0].pubkeys, network })
const p2wsh = bitcoin.payments.p2wsh({redeem: p2ms, network})
const p2sh = bitcoin.payments.p2sh({ redeem: p2wsh, network })

// get unspent from API `const unspent`
const privateKey = bitcoin.ECPair.fromWIF(privateWIF)

unspent.forEach((utxo, index) => {
  txb1.sign(index, privateKey, p2sh.redeem.output, null, utxo.amount, p2wsh.redeem.output)
})

txb1.build().toHex()

I think that's everything you need to sign with the second party's key.

dcousens commented 6 years ago

The p2sh.redeem.output and p2wsh.redeem.output can be regenerated using the public keys stored in txb1.__inputs[0].pubkeys.map(key => key.toString('hex')).

:scream: What can we do to help you here

Segwit introduces this burden which has broken most tools like coinb.in.

Alternative wording would be: "Segwit introduces this security feature to protect users from losing funds without explicit knowledge"

coinflippa commented 6 years ago

Whoops they are buffers already and I was converting them to strings. D'oh!

const pubkeys = txb1.__inputs[0].pubkeys.map(key => key.toString('hex'))
const p2ms = bitcoin.payments.p2ms({ m: 2, pubkeys, network })

Becomes:

const p2ms = bitcoin.payments.p2ms({ m: 2, pubkeys: txb1.__inputs[0].pubkeys, network })

I don't mind Segwit as it reduces transaction sizes but I wish that they come up with a standard that allows all the information required to be in the TX and then drop the extra info when placed into the block.

coinflippa commented 6 years ago

Tested this and it works...


const bitcoin = require('bitcoinjs-lib')
const bitcore = require('bitcore-lib')

const txhex = "..."
const privateWIF = "..."
const url = "..."

fetch(`https://chain.so/api/v2/get_tx_unspent/btctest/${address}`)
    .then(res => res.json())
    .then(json => {
      let unspent = json.data.txs.map((val, index) => {
        return {index, amount: bitcore.Unit.fromBTC(val.value).toSatoshis()}
      })

      const txb = bitcoin.TransactionBuilder.fromTransaction(bitcoin.Transaction.fromHex(txhex), bitcoin.networks.testnet)

      const p2ms = bitcoin.payments.p2ms({ m: 2, pubkeys: txb.__inputs[0].pubkeys, network: bitcoin.networks.testnet })
      const p2wsh = bitcoin.payments.p2wsh({redeem: p2ms, network: bitcoin.networks.testnet})
      const p2sh = bitcoin.payments.p2sh({ redeem: p2wsh, network: bitcoin.networks.testnet })
      const privateKey = bitcoin.ECPair.fromWIF(privateWIF, bitcoin.networks.testnet)

      unspent.forEach((utxo, index) => {
        txb.sign(index, privateKey, p2sh.redeem.output, null, utxo.amount, p2wsh.redeem.output)
      })

      txb.build().toHex() // <- broadcast this
    })
    .catch(reason => console.log(reason))
junderw commented 6 years ago

I don't mind Segwit as it reduces transaction sizes but I wish that they come up with a standard that allows all the information required to be in the TX and then drop the extra info when placed into the block.

Well...

they

There is no "they." Segwit solved a problem and increased security. Consensus devs do not manage wallet software, so if any wallet software devs had a question/concern they wanted addressed, they should have said something during the 1.5 years that Segwit was well defined and in development.

a standard that allows all the information required to be in the TX and then drop the extra info when placed into the block.

There is a standard. It's called BIP174 (https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki) We currently do not have this supported in bitcoinjs-lib because it's still in active development (changing rapidly) so we don't want to put it out there until things settle down. Look forward to it, though. Also tell coinbin and other wallets to support it. Interoperability will increase greatly.

junderw commented 6 years ago

Looks like this issue is solved?

Reopen if any new problems arise.

susmit commented 5 years ago

@junderw is bip174 implemented in bitcoinjs?

junderw commented 5 years ago

there is an initial implementation pull request over at our bip174 repo.

It's not ready, but it passes all the tests on the BIP.