Last active
July 31, 2019 03:41
-
-
Save fkfk/d2d574130cb967bb0b815b95fc1ce039 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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