Skip to content

Instantly share code, notes, and snippets.

@fkfk
Last active July 31, 2019 03:41
Show Gist options
  • Save fkfk/d2d574130cb967bb0b815b95fc1ce039 to your computer and use it in GitHub Desktop.
Save fkfk/d2d574130cb967bb0b815b95fc1ce039 to your computer and use it in GitHub Desktop.
const pushdata = require('pushdata-bitcoin')
const MAX_SIGNATURE_SIZE = 73
const COMPRESSED_PUBKEY_SIZE = 33
const VERSION_SIZE = 4
const LOCKTIME_SIZE = 4
// txin size
const PREVIOUS_OUTPUT_SIZE = 36
const SEQUENCE_SIZE = 4
// txout size
const VALUE_SIZE = 8
const PK_SCRIPT_SIZE = 25
/**
* マルチシグアドレスのトランザクションサイズを計算する
*
* @param {number} inputCount - 入力総数
* @param {number} outputCount - 出力総数
* @param {number} m - 必要署名数
* @param {number} n - 鍵の総数
* @return {number} トランザクションサイズ
*/
function calcMultisigTxSize (inputCount, outputCount, m, n) {
const redeemSize = (
1 + // OP_M
((pushdata.encodingLength(COMPRESSED_PUBKEY_SIZE) + COMPRESSED_PUBKEY_SIZE) * n) + // pubkeys
1 + // OP_N
1 // OP_CHECKMULTISIG
)
const scriptSigSize = (
1 + // OP_0 bytesize
((pushdata.encodingLength(MAX_SIGNATURE_SIZE) + MAX_SIGNATURE_SIZE) * m) + // signatureScript length
pushdata.encodingLength(redeemSize) + // redeem script length bytesize
redeemSize //redeem script length
)
return VERSION_SIZE +
pushdata.encodingLength(inputCount) +
(inputCount * (PREVIOUS_OUTPUT_SIZE + pushdata.encodingLength(scriptSigSize) + scriptSigSize + SEQUENCE_SIZE)) +
pushdata.encodingLength(outputCount) +
(outputCount * (VALUE_SIZE + pushdata.encodingLength(PK_SCRIPT_SIZE) + PK_SCRIPT_SIZE))
LOCKTIME_SIZE
}
/**
* マルチシグアドレスの想定手数料を計算し、総金額 + 手数料を満たすinputを生成する
*
* @param {array} utxos - UTXOリスト
* @param {array} outputs - お釣りoutputを含まないoutputリスト
* @param {number} feePerByte - 1バイトあたりの手数料
* @param {number} m - 必要署名数
* @param {number} n - 鍵の総数
* @return {object} 総金額 + 手数料を満たすinputリスト, お釣りoutputを満たすoutputリスト、想定トランザクションサイズ、手数料額
*/
function coinSelectMultisig (utxos, outputs, feePerByte, m, n) {
const outputTotal = outputs.reduce((a, x) => {
return a + x.value
}, 0)
let inputs = []
let txsize = 0
let txfee = -1
// Array.prototype.forEach()を使うとbreak出来ないのでfor文を使う
for (let index = 0; index < utxos.length; index++) {
inputs.push(utxos[index])
const inputTotal = inputs.reduce((a, x) => {
return a + x.value
}, 0)
// 送金額 + 手数料がinputと等価であればお釣りがないため、お釣り有無の手数料をそれぞれ計算する
const size = calcMultisigTxSize(inputs.length, outputs.length, m, n)
const fee = size * feePerByte
const sizeWithChange = calcMultisigTxSize(inputs.length, outputs.length + 1, m, n)
const feeWithChange = sizeWithChange * feePerByte
if ((outputTotal + fee) === inputTotal) {
txsize = size
txfee = fee
break
} else if ((outputTotal + feeWithChange) < inputTotal) { // (送金額 + お釣りoutputがある場合の手数料) === input総額の場合、お釣りoutputのvalueが0になってしまうため
txsize = sizeWithChange
txfee = feeWithChange
outputs.push({
value: inputTotal - outputTotal - feeWithChange // お釣り
})
break
}
}
// 指定utxoでは総金額 + お釣り <= input総額を満たせない場合
if (txfee < 0) {
inputs = []
outputs = []
}
return {
inputs,
outputs,
txsize,
txfee
}
}
// 動作確認用
const utxos = [
{
address: "pNgCocTd1gEyjMRZU5V8UvX1XnK7uXcLxk",
txid: "195cf80babf9fa97c44cbbbdd3e48b4d7f268b67f84010aff11d34bb5dec9892",
vout: 0,
scriptPubKey: "a914bbeb9a8e3c6a93d19340c0c197d4c443325742ac87",
amount: 10,
value: 10 * 1e8,
satoshis: 10 * 1e8,
height: 111
},
{
address: "pNgCocTd1gEyjMRZU5V8UvX1XnK7uXcLxk",
txid: "5096ef230c7d82a1d571824fa5255edb9057f4f220c93eea1665599d4322909d",
vout: 0,
scriptPubKey: "a914bbeb9a8e3c6a93d19340c0c197d4c443325742ac87",
amount: 10,
value: 10 * 1e8,
satoshis: 10 * 1e8,
height: 111
},
{
address: "pNgCocTd1gEyjMRZU5V8UvX1XnK7uXcLxk",
txid: "806d7c48dfb7c27949890aad6208033273c526b58b22dfa6fe33f03dfad11fb5",
vout: 1,
scriptPubKey: "a914bbeb9a8e3c6a93d19340c0c197d4c443325742ac87",
amount: 10,
value: 10 * 1e8,
satoshis: 10 * 1e8,
height: 111
},
]
const outputs = [
{
address: 'mujr6nBZ7MpaiVZ3upDfkrzGy4ewaP7ueB',
value: 10 * 1e8
}
]
const m = 2
const n = 3
const feePerByte = 150
console.log(coinSelectMultisig(utxos, outputs, feePerByte, m, n))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment