Skip to content

Instantly share code, notes, and snippets.

@christroutner
Last active March 30, 2022 13:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save christroutner/8a201095c4a141aa7bbd6e6e68064361 to your computer and use it in GitHub Desktop.
Save christroutner/8a201095c4a141aa7bbd6e6e68064361 to your computer and use it in GitHub Desktop.
BCH P2SH Multisig

This is my attempt to use the bitcore-lib-cash examples to construct a P2SH multisig transaction.

There are two files:

  1. multisig01.js generates a 2-of-3 P2SH address.
  2. multisig02.js spends the 2,000 sats in the address.
/*
Scratchpad trying to generate a 2-of-3 multisig address.
Following this example:
https://github.com/bitpay/bitcore/blob/master/packages/bitcore-lib-cash/docs/examples.md#create-a-2-of-3-multisig-p2sh-address
*/
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
const bitcore = require('bitcore-lib-cash')
// bitcoincash:qqstkr2f03te8kpcr2dlxcfhhz25uum28slvdvyn4j
const aliceMnemonic = 'weekend exchange salute rocket despair cube improve work train fox degree evolve'
// bitcoincash:qq2z8psqlvfw55ttc3uqqejjsykn3qvf2ykd48vh0u
const bobMnemonic = 'cool elite air budget trial turn west midnight verify minor olive execute'
// bitcoincash:qr2jhqtnvtj8yd5gws5uexhdh6270vraaur20ate86
const samMnemonic = 'two sell word immense dignity face glove merry hen wool erupt drop'
async function runTest() {
try {
// Create public key for Alice
const aliceSeed = await bchjs.Mnemonic.toSeed(aliceMnemonic)
const aliceHDNode = bchjs.HDNode.fromSeed(aliceSeed)
const aliceNode = bchjs.HDNode.derivePath(aliceHDNode, "m/44'/145'/0'/0/0")
const alicePubKey = bchjs.HDNode.toPublicKey(aliceNode)
const alicePrivKey = bchjs.HDNode.toWIF(aliceNode)
// Create public key for Bob
const bobSeed = await bchjs.Mnemonic.toSeed(bobMnemonic)
const bobHDNode = bchjs.HDNode.fromSeed(bobSeed)
const bobNode = bchjs.HDNode.derivePath(bobHDNode, "m/44'/145'/0'/0/0")
const bobPubKey = bchjs.HDNode.toPublicKey(bobNode)
const bobPrivKey = bchjs.HDNode.toWIF(bobNode)
// Create public key for Sam
const samSeed = await bchjs.Mnemonic.toSeed(samMnemonic)
const samHDNode = bchjs.HDNode.fromSeed(samSeed)
const samNode = bchjs.HDNode.derivePath(samHDNode, "m/44'/145'/0'/0/0")
const samPubKey = bchjs.HDNode.toPublicKey(samNode)
const samPrivKey = bchjs.HDNode.toWIF(samNode)
const publicKeys = [
new bitcore.PrivateKey(alicePrivKey).toPublicKey(),
new bitcore.PrivateKey(bobPrivKey).toPublicKey(),
new bitcore.PrivateKey(samPrivKey).toPublicKey(),
]
const requiredSignatures = 2
// Generate a P2SH multisig address.
const address = new bitcore.Address(publicKeys, requiredSignatures)
console.log(`P2SH multisig address: ${address}`)
// Note: address is actually a Class Object. There is much more to it
// than just the string output.
// In a normal spend, no one signer would have all the information to
// recreate the address object. This object would need to be serialized
// and then deserialized by the spending app.
// console.log('address object: ', address)
} catch(err) {
console.error(err)
}
}
runTest()
/*
Scratchpad trying to spend a 2-of-3 multisig wallet.
Following this example:
https://github.com/bitpay/bitcore/blob/master/packages/bitcore-lib-cash/docs/examples.md#spend-from-a-2-of-2-multisig-p2sh-address
Lessons learned:
Required information to spend:
- P2SH multisig address holding the coins.
- The public key of *all* participants.
*/
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
const bitcore = require('bitcore-lib-cash')
const RECEIVER_ADDR = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
// bitcoincash:qqstkr2f03te8kpcr2dlxcfhhz25uum28slvdvyn4j
const aliceMnemonic = 'weekend exchange salute rocket despair cube improve work train fox degree evolve'
// bitcoincash:qq2z8psqlvfw55ttc3uqqejjsykn3qvf2ykd48vh0u
const bobMnemonic = 'cool elite air budget trial turn west midnight verify minor olive execute'
// bitcoincash:qr2jhqtnvtj8yd5gws5uexhdh6270vraaur20ate86
const samMnemonic = 'two sell word immense dignity face glove merry hen wool erupt drop'
async function runTest() {
try {
// Create a private key for Alice
const aliceSeed = await bchjs.Mnemonic.toSeed(aliceMnemonic)
const aliceHDNode = bchjs.HDNode.fromSeed(aliceSeed)
const aliceNode = bchjs.HDNode.derivePath(aliceHDNode, "m/44'/145'/0'/0/0")
const alicePrivKey = bchjs.HDNode.toWIF(aliceNode)
const alicePubKey = bchjs.HDNode.toPublicKey(aliceNode)
// Create HD node for Bob
const bobSeed = await bchjs.Mnemonic.toSeed(bobMnemonic)
const bobHDNode = bchjs.HDNode.fromSeed(bobSeed)
const bobNode = bchjs.HDNode.derivePath(bobHDNode, "m/44'/145'/0'/0/0")
const bobPrivKey = bchjs.HDNode.toWIF(bobNode)
const bobPubKey = bchjs.HDNode.toPublicKey(bobNode)
// Create HD node for Sam
const samSeed = await bchjs.Mnemonic.toSeed(samMnemonic)
const samHDNode = bchjs.HDNode.fromSeed(samSeed)
const samNode = bchjs.HDNode.derivePath(samHDNode, "m/44'/145'/0'/0/0")
const samPrivKey = bchjs.HDNode.toWIF(samNode)
const samPubKey = bchjs.HDNode.toPublicKey(samNode)
// Regnerate the input Script
const allPublicKeys = [
new bitcore.PrivateKey(alicePrivKey).toPublicKey(),
new bitcore.PrivateKey(bobPrivKey).toPublicKey(),
new bitcore.PrivateKey(samPrivKey).toPublicKey(),
]
const requiredSignatures = 2
const address = new bitcore.Address(allPublicKeys, requiredSignatures)
// Use 2 of the 3 private keys.
const privateKeys = [
new bitcore.PrivateKey(alicePrivKey),
new bitcore.PrivateKey(bobPrivKey)
// new bitcore.PrivateKey(samPrivKey)
]
// 2 of 3 public keys.
const publicKeys = privateKeys.map(bitcore.PublicKey)
// Get the UTXO
const utxos = await bchjs.Utxo.get(address.toString())
const utxo = utxos.bchUtxos[0]
// console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`)
// Add properties to the UTXO expected by bitcore
utxo.outputIndex = utxo.vout
utxo.script = new bitcore.Script(address).toHex()
utxo.satoshis = utxo.value
console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`)
// Generate the transaction object.
const txObj = new bitcore.Transaction()
.from(utxo, allPublicKeys, requiredSignatures)
.to(RECEIVER_ADDR, 1500)
.feePerByte(1)
.change(address)
.sign(privateKeys)
// Serialize the transaction to a hex string, ready to broadcast to the network.
// const txHex = txObj.toString()
let txHex = txObj.toBuffer()
txHex = txHex.toString('hex')
console.log('hex: ', txHex)
// Note: Attempting to broadcast the transaction results in:
// hex: 02000000017ef119fc32cfbda48ec0e859e5e8687e12779c8e0258360f0cd2107bd03127ce0000000000ffffffff01a4060000000000001976a9143e31055173cf58d56edb075499daf29d7b488f0988ac00000000
// { error: 'bad-txns-undersize (code 64)' }
// Broadcast the transaction to the network.
const txid = await bchjs.RawTransactions.sendRawTransaction(txHex)
console.log(`txid: ${txid}`)
} catch(err) {
console.error(err)
}
}
runTest()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment