Skip to content

Instantly share code, notes, and snippets.

@fkfk
Last active September 11, 2019 03:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fkfk/5b76ad525b35c1db93681338f7212e6b to your computer and use it in GitHub Desktop.
Save fkfk/5b76ad525b35c1db93681338f7212e6b to your computer and use it in GitHub Desktop.
test multisig
const bitcoin = require('bitcoinjs-lib')
// for testnet
/*
const network = {
messagePrefix: '\x19Monacoin Signed Message:\n',
bip32: {
public: 0x043587cf,
private: 0x04358394
},
pubKeyHash: 111,
scriptHash: 117,
wif: 239,
bech32: "tmona"
}
*/
// for regtest
const network = {
messagePrefix: '\x19Monacoin Signed Message:\n',
bip32: {
public: 0x043587cf,
private: 0x04358394
},
pubKeyHash: 111,
scriptHash: 117,
wif: 239,
bech32: "rmona"
}
const keys = [
{
mnemonic: 'coach meat purchase planet spray east elbow adapt play comfort burden arch',
seed: '38d960288f7a0ef4fec8d58244f3c7e47babf1cdd92ced50e7d8468a6a9ed2f115b8033c506e802b182a98a83ef4594452e2ae5638fdb52235e6f379c1448a47',
privateKey: '136c1e1e7aa9a3d193b435e284c6d3c2e5f7affd4ab78a82672e87989379519c',
publicKey: '03c2b54c7d0cab9283479d16d67ce34492d8589e7ac02413aace08a178312487c4'
},
{
mnemonic: 'quick blind gauge nerve category expose cheap general orient path inject sock',
seed: '675bdfcccd1e7b974fede91b8b3b1882ae2bd8ecffd5103af36e3a768b6d299cf878db21fddfc4e0b7df0ecc5ae40a5b3eaeb542bebb7e85ae282775260a6873',
privateKey: '0503a06bd63b2665e24dbda21cd77b07926f5803faadbaa92d452d80d70374ce',
publicKey: '03de6d9a37de85a67c888d04dc02dcf4c6e4457eeba8c9beab81bbe4879bfbac81'
},
{
mnemonic: 'volume ecology snake dune glide tonight ancient dice act series blood foot',
seed: '47197bb1cd12bcc04ba7ee0deb30c409bdddb634f572a2507774acab2ad34e392b8d9d6ac2e39126b9c50c95209238ddf1f3504862f60bdea1cae1467275857b',
privateKey: '866313171ec1f368cbb9972798d36aae18285aa4f97073c4fec1adef82db37eb',
publicKey: '0347902fd32739a57bd6675296c4e01ec83e927eb35bd267f8e9ea9ea2fe5867c6'
}
]
const utxos = [
{
address: "pNgCocTd1gEyjMRZU5V8UvX1XnK7uXcLxk",
txid: "195cf80babf9fa97c44cbbbdd3e48b4d7f268b67f84010aff11d34bb5dec9892",
vout: 0,
scriptPubKey: "a914bbeb9a8e3c6a93d19340c0c197d4c443325742ac87",
amount: 10,
value: 1000000000,
satoshis: 1000000000,
height: 111
},
{
address: "pNgCocTd1gEyjMRZU5V8UvX1XnK7uXcLxk",
txid: "5096ef230c7d82a1d571824fa5255edb9057f4f220c93eea1665599d4322909d",
vout: 0,
scriptPubKey: "a914bbeb9a8e3c6a93d19340c0c197d4c443325742ac87",
amount: 10,
value: 1000000000,
satoshis: 1000000000,
height: 111
},
{
address: "pNgCocTd1gEyjMRZU5V8UvX1XnK7uXcLxk",
txid: "806d7c48dfb7c27949890aad6208033273c526b58b22dfa6fe33f03dfad11fb5",
vout: 1,
scriptPubKey: "a914bbeb9a8e3c6a93d19340c0c197d4c443325742ac87",
amount: 10,
value: 1000000000,
satoshis: 1000000000,
height: 111
},
]
const pubkeys = keys.map((key) => Buffer.from(key.publicKey, 'hex'))
const { address } = bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2ms({ m: 2, pubkeys, network }),
network
})
const txBuilder = new bitcoin.TransactionBuilder(network)
const inputs = [
{
address: 'pNgCocTd1gEyjMRZU5V8UvX1XnK7uXcLxk',
txid: '195cf80babf9fa97c44cbbbdd3e48b4d7f268b67f84010aff11d34bb5dec9892',
vout: 0,
scriptPubKey: 'a914bbeb9a8e3c6a93d19340c0c197d4c443325742ac87',
amount: 10,
value: 1000000000,
satoshis: 1000000000,
height: 111
}
]
const outputs = [
{ address: 'mujr6nBZ7MpaiVZ3upDfkrzGy4ewaP7ueB', value: 5 },
{ value: 999966245 }
]
const fee = 33750
inputs.forEach(input => txBuilder.addInput(input.txid, input.vout))
outputs.forEach(output => {
if (output.address === undefined) {
output.address = address
}
txBuilder.addOutput(output.address, output.value)
})
const unsigned = txBuilder.buildIncomplete()
const unsignedBuffer = unsigned.toBuffer()
const keyPairs = keys.map((key) => bitcoin.bip32.fromSeed(Buffer.from(key.seed, 'hex'), network))
const p2ms = bitcoin.payments.p2ms({ m: 2, pubkeys: pubkeys, network })
const p2sh = bitcoin.payments.p2sh({ redeem: p2ms, network })
const unsignedTx = bitcoin.Transaction.fromBuffer(unsignedBuffer, true)
// 普通に署名した場合
txBuilder.sign(0, keyPairs[0], p2sh.redeem.output)
txBuilder.sign(0, keyPairs[1], p2sh.redeem.output)
signed = txBuilder.build()
// バラバラに署名を集める場合
// scriptPubKeyのコンパイル
const pubkeyScript = bitcoin.script.compile([
bitcoin.script.OPS.OP_2,
pubkeys[0],
pubkeys[1],
pubkeys[2],
bitcoin.script.OPS.OP_3,
bitcoin.script.OPS.OP_CHECKMULTISIG
])
// シグネチャハッシュを生成してそれぞれの秘密鍵で署名
const signatureHash = unsignedTx.hashForSignature(0, p2sh.redeem.output, bitcoin.Transaction.SIGHASH_ALL)
const sig1 = bitcoin.script.signature.encode(keyPairs[0].sign(signatureHash, false), bitcoin.Transaction.SIGHASH_ALL)
const sig2 = bitcoin.script.signature.encode(keyPairs[1].sign(signatureHash, false), bitcoin.Transaction.SIGHASH_ALL)
// signature Scriptのコンパイル
const signatureScript = bitcoin.script.compile([
bitcoin.script.OPS.OP_0,
sig1,
sig2,
pubkeyScript
])
// 未署名トランザクションにsignature scriptを差し込む
unsignedTx.ins[0].script = signatureScript
// 普通に署名した場合と比較してhexが一致しているか確認
console.log(unsignedTx.toHex() === signed.toHex())
const bitcoin = require('bitcoinjs-lib')
// for regtest
const network = {
messagePrefix: '\x19Monacoin Signed Message:\n',
bip32: {
public: 0x043587cf,
private: 0x04358394
},
pubKeyHash: 111,
scriptHash: 117,
wif: 239,
bech32: "rmona"
}
const privkeys = [
Buffer.from('136c1e1e7aa9a3d193b435e284c6d3c2e5f7affd4ab78a82672e87989379519c', 'hex'),
Buffer.from('0503a06bd63b2665e24dbda21cd77b07926f5803faadbaa92d452d80d70374ce', 'hex'),
Buffer.from('866313171ec1f368cbb9972798d36aae18285aa4f97073c4fec1adef82db37eb', 'hex')
]
const pubkeys = [
Buffer.from('03c2b54c7d0cab9283479d16d67ce34492d8589e7ac02413aace08a178312487c4', 'hex'),
Buffer.from('03de6d9a37de85a67c888d04dc02dcf4c6e4457eeba8c9beab81bbe4879bfbac81', 'hex'),
Buffer.from('0347902fd32739a57bd6675296c4e01ec83e927eb35bd267f8e9ea9ea2fe5867c6', 'hex')
]
const p2ms = bitcoin.payments.p2ms({ m: 2, pubkeys: pubkeys, network })
const p2sh = bitcoin.payments.p2sh({ redeem: p2ms, network })
const privateECs = privkeys.map((key) => bitcoin.ECPair.fromPrivateKey(key))
const publicECs = pubkeys.map((key) => bitcoin.ECPair.fromPublicKey(key))
// シグネチャハッシュと、それを元とした署名の場合は検証が通ることを確認する
const unsignedTx = bitcoin.Transaction.fromHex('02000000019298ec5dbb341df1af1040f8678b267f4d8be4d3bdbb4cc497faf9ab0bf85c1900000000fdfd000047304402205c185831c3f5bb04e5cbdf5281fc2b86d4fa7dff58118d98e971395ddcbb4b00022067151c3150c08bfaa3de0a805c5a11b1801727c38c5dd63695523f83abcaee8b01483045022100f8a6d3e35c47e5db7500da698db5af2b18d4acea623c20a0e0efe1f5b934cb7902203731717fb40b9c01f5af6d4837dc298a9edbcbfa14b5f399030d7e1c3d7f3c88014c69522103c2b54c7d0cab9283479d16d67ce34492d8589e7ac02413aace08a178312487c42103de6d9a37de85a67c888d04dc02dcf4c6e4457eeba8c9beab81bbe4879bfbac81210347902fd32739a57bd6675296c4e01ec83e927eb35bd267f8e9ea9ea2fe5867c653aeffffffff0205000000000000001976a9149c01ff6705bd4a825ca3125f041c5fc20deb456088ac25469a3b0000000017a914bbeb9a8e3c6a93d19340c0c197d4c443325742ac8700000000')
const signatureHash = unsignedTx.hashForSignature(0, p2sh.redeem.output, bitcoin.Transaction.SIGHASH_ALL)
const sig = bitcoin.script.signature.encode(privateECs[0].sign(signatureHash, false), bitcoin.Transaction.SIGHASH_ALL)
console.log(publicECs.some((ec) => ec.verify(signatureHash, bitcoin.script.signature.decode(sig).signature))) // => true
// マルチシグアドレスに関わりのない鍵ペアによる署名は検証を通らないことを確認
const random = bitcoin.ECPair.makeRandom()
const bagSig = bitcoin.script.signature.encode(random.sign(signatureHash, false), bitcoin.Transaction.SIGHASH_ALL)
console.log(publicECs.some((ec) => ec.verify(signatureHash, bitcoin.script.signature.decode(bagSig).signature))) // => false
// 異なるトランザクションは検証に通らないことを確認
const anotherUnsignedTx = bitcoin.Transaction.fromHex('02000000019d9022439d596516ea3ec920f2f45790db5e25a54f8271d5a1827d0c23ef965000000000fc00473044022031c72f3fe04db6a4c12652ad6bb0b303ec8f29b6940554960099cd8cc4bd171c02204a6a7fc01e366cacc8763d17343c82f5400175c82eeae06b1ef3e9f8fae713490147304402207bb3d92d82d247675d3a126ad4de6cceb2826f58f98df9d8a749be20ed7fb0f20220710ee0ad786b32f6f7bd72fb64a941bed857f3d9656de6d96212ff7940ecd13c014c69522103c2b54c7d0cab9283479d16d67ce34492d8589e7ac02413aace08a178312487c42103de6d9a37de85a67c888d04dc02dcf4c6e4457eeba8c9beab81bbe4879bfbac81210347902fd32739a57bd6675296c4e01ec83e927eb35bd267f8e9ea9ea2fe5867c653aeffffffff0205000000000000001976a9149c01ff6705bd4a825ca3125f041c5fc20deb456088ac25469a3b0000000017a914bbeb9a8e3c6a93d19340c0c197d4c443325742ac8700000000')
const anotherSignatureHash = anotherUnsignedTx.hashForSignature(0, p2sh.redeem.output, bitcoin.Transaction.SIGHASH_ALL)
const anotherSig = bitcoin.script.signature.encode(privateECs[0].sign(anotherSignatureHash, false), bitcoin.Transaction.SIGHASH_ALL)
console.log(publicECs.some((ec) => ec.verify(signatureHash, bitcoin.script.signature.decode(anotherSig).signature))) // => false
console.log(publicECs.some((ec) => ec.verify(anotherSignatureHash, bitcoin.script.signature.decode(anotherSig).signature))) // => true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment