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

How create a partially sign multisig transaction using Psbt #1714

Closed
isacrodriguesdev opened this issue Aug 13, 2021 · 5 comments
Closed

How create a partially sign multisig transaction using Psbt #1714

isacrodriguesdev opened this issue Aug 13, 2021 · 5 comments

Comments

@isacrodriguesdev
Copy link

isacrodriguesdev commented Aug 13, 2021

I can sign a multisig transaction with two private keys, but I need to sign with only one key and send the partially signed transaction to the next sign, how to create a partially signed multisig transaction?

const bitcoin = require("bitcoinjs-lib")
const network = bitcoin.networks.testnet

const keyPairAlice = bitcoin.ECPair.fromWIF("cQPKbLJwChEXHLbdPq5BEGCWErTxwtuVMaqaxD7DooQPPbxo5yQi", network)
const keyPairBob = bitcoin.ECPair.fromWIF("cSXHt1RSmr4cps1FGXjSx4nu71dn7hkxMqrQ6kbaBDhKWAeMr4LQ", network)
const keyPairKira = bitcoin.ECPair.fromWIF("cPrwok7NntxviTPFmnGdcGqfc8ReHUjQUNoGrMssScHwUUMhGoo1", network)

const psbt = new bitcoin.Psbt({ network })

const pubkeys = [
  keyPairAlice.publicKey.toString("hex"),
  keyPairBob.publicKey.toString("hex"),
  keyPairKira.publicKey.toString("hex"),
].map(hex => Buffer.from(hex, "hex"))

const p2ms = bitcoin.payments.p2ms({
  m: 2, pubkeys, network
})

const hash160sha256 = bitcoin.crypto.hash160(
  Buffer.from('0020' + bitcoin.crypto.sha256(p2ms.output).toString('hex'),
    'hex'))

const p2wsh = bitcoin.payments.p2wsh({ redeem: p2ms, network })
const p2sh = bitcoin.payments.p2sh({ redeem: p2wsh, network })

console.log('P2SH address:')
console.log(p2sh.address)
console.log()

const fee = 300
const amount = 1000

psbt.addInput({
  hash: 'eb117e79b0523946af5165601493c40350a88c7baeddecc2ac275b014c97d53f',
  index: 1,
  redeemScript: Buffer.from('0020' + bitcoin.crypto.sha256(p2ms.output).toString('hex'), 'hex'),
  witnessScript: p2wsh.redeem.output,
  witnessUtxo: {
    script: Buffer.from('a914' + hash160sha256.toString('hex') + '87', 'hex'),
    value: 49000,
  }
})
  .addOutput({
    address: "2N5HDPVvFp3jHm2XjNvtvf9Jik69G992pyC",
    value: 49000 - amount - fee,
  })
  .addOutput({
    address: "mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt",
    value: amount,
  })


try {
  psbt
    .signInput(0, keyPairAlice)
    .signInput(0, keyPairKira)
} catch (error) {
  console.log(error)
}

psbt.finalizeAllInputs()
const tx = psbt.extractTransaction().toHex()
console.log(tx)
console.log()
@junderw
Copy link
Member

junderw commented Aug 13, 2021

This is a good example of how you could export and import a PSBT and transport between people.

it('can create (and broadcast via 3PBP) a typical Transaction', async () => {
// these are { payment: Payment; keys: ECPair[] }
const alice1 = createPayment('p2pkh');
const alice2 = createPayment('p2pkh');
// give Alice 2 unspent outputs
const inputData1 = await getInputData(
5e4,
alice1.payment,
false,
'noredeem',
);
const inputData2 = await getInputData(
7e4,
alice2.payment,
false,
'noredeem',
);
{
const {
hash, // string of txid or Buffer of tx hash. (txid and hash are reverse order)
index, // the output index of the txo you are spending
nonWitnessUtxo, // the full previous transaction as a Buffer
} = inputData1;
assert.deepStrictEqual({ hash, index, nonWitnessUtxo }, inputData1);
}
// network is only needed if you pass an address to addOutput
// using script (Buffer of scriptPubkey) instead will avoid needed network.
const psbt = new bitcoin.Psbt({ network: regtest })
.addInput(inputData1) // alice1 unspent
.addInput(inputData2) // alice2 unspent
.addOutput({
address: 'mwCwTceJvYV27KXBc3NJZys6CjsgsoeHmf',
value: 8e4,
}) // the actual "spend"
.addOutput({
address: alice2.payment.address, // OR script, which is a Buffer.
value: 1e4,
}); // Alice's change
// (in)(5e4 + 7e4) - (out)(8e4 + 1e4) = (fee)3e4 = 30000, this is the miner fee
// Let's show a new feature with PSBT.
// We can have multiple signers sign in parrallel and combine them.
// (this is not necessary, but a nice feature)
// encode to send out to the signers
const psbtBaseText = psbt.toBase64();
// each signer imports
const signer1 = bitcoin.Psbt.fromBase64(psbtBaseText);
const signer2 = bitcoin.Psbt.fromBase64(psbtBaseText);
// Alice signs each input with the respective private keys
// signInput and signInputAsync are better
// (They take the input index explicitly as the first arg)
signer1.signAllInputs(alice1.keys[0]);
signer2.signAllInputs(alice2.keys[0]);
// If your signer object's sign method returns a promise, use the following
// await signer2.signAllInputsAsync(alice2.keys[0])
// encode to send back to combiner (signer 1 and 2 are not near each other)
const s1text = signer1.toBase64();
const s2text = signer2.toBase64();
const final1 = bitcoin.Psbt.fromBase64(s1text);
const final2 = bitcoin.Psbt.fromBase64(s2text);
// final1.combine(final2) would give the exact same result
psbt.combine(final1, final2);
// Finalizer wants to check all signatures are valid before finalizing.
// If the finalizer wants to check for specific pubkeys, the second arg
// can be passed. See the first multisig example below.
assert.strictEqual(psbt.validateSignaturesOfInput(0), true);
assert.strictEqual(psbt.validateSignaturesOfInput(1), true);
// This step it new. Since we separate the signing operation and
// the creation of the scriptSig and witness stack, we are able to
psbt.finalizeAllInputs();
// build and broadcast our RegTest network
await regtestUtils.broadcast(psbt.extractTransaction().toHex());
// to build and broadcast to the actual Bitcoin network, see https://github.com/bitcoinjs/bitcoinjs-lib/issues/839
});

The same concept applies, except instead of two people signing two inputs, your version would be two people signing the same input (because it's multisig)

@junderw junderw closed this as completed Aug 13, 2021
@junderw
Copy link
Member

junderw commented Aug 13, 2021

Also:

  1. Why are you converting pubkeys to hex and back? Just don't convert to hex to begin with.
  2. Why are you using crypto.hash160 etc. witnessUtxo.script is p2sh.output and redeemScript is p2sh.redeem.output

@isacrodriguesdev
Copy link
Author

it's because I'm following this tutorial, it was the only one that worked for me, so I'm lost, the code you sent me above doesn't work for me

https://bitcoinjs-guide.bitcoin-studio.com/bitcoinjs-guide/v5/part-three-pay-to-script-hash/multi_signatures/multisig_np2wsh_2_4.html#_creating_and_funding_the_p2sh

@isacrodriguesdev
Copy link
Author

my code is working, the only problem is I don't know how to do it partially signed

@junderw
Copy link
Member

junderw commented Aug 13, 2021

In the example I gave you, what method did it use to export psbt data to "send out to the signers"?

What static method did each signer use to import psbt data into signer1 and signer2 objects?

After they signed, and re-exported using the same method in question 1, what did the person receiving the exported text from signer1 and signer2 do with the text (s1text, s2text)?

Once you can answer these questions you'll understand what you need to do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants