-
-
Save junderw/b43af3253ea5865ed52cb51c200ac19c to your computer and use it in GitHub Desktop.
// Usage: | |
// getByteCount({'MULTISIG-P2SH:2-4':45},{'P2PKH':1}) Means "45 inputs of P2SH Multisig and 1 output of P2PKH" | |
// getByteCount({'P2PKH':1,'MULTISIG-P2SH:2-3':2},{'P2PKH':2}) means "1 P2PKH input and 2 Multisig P2SH (2 of 3) inputs along with 2 P2PKH outputs" | |
function getByteCount(inputs, outputs) { | |
var totalWeight = 0 | |
var hasWitness = false | |
var inputCount = 0 | |
var outputCount = 0 | |
// assumes compressed pubkeys in all cases. | |
var types = { | |
// MULTISIG-* do not include pubkeys or signatures yet (this is calculated at runtime) | |
// sigs = 73 and pubkeys = 34 (these include pushdata byte) | |
'inputs': { | |
// Non-segwit: (txid:32) + (vout:4) + (sequence:4) + (script_len:3(max)) | |
// + (script_bytes(OP_0,PUSHDATA(max:3),m,n,CHECK_MULTISIG):5) | |
'MULTISIG-P2SH': 51 * 4, | |
// Segwit: (push_count:1) + (script_bytes(OP_0,PUSHDATA(max:3),m,n,CHECK_MULTISIG):5) | |
// Non-segwit: (txid:32) + (vout:4) + (sequence:4) + (script_len:1) | |
'MULTISIG-P2WSH': 8 + (41 * 4), | |
// Segwit: (push_count:1) + (script_bytes(OP_0,PUSHDATA(max:3),m,n,CHECK_MULTISIG):5) | |
// Non-segwit: (txid:32) + (vout:4) + (sequence:4) + (script_len:1) + (p2wsh:35) | |
'MULTISIG-P2SH-P2WSH': 8 + (76 * 4), | |
// Non-segwit: (txid:32) + (vout:4) + (sequence:4) + (script_len:1) + (sig:73) + (pubkey:34) | |
'P2PKH': 148 * 4, | |
// Segwit: (push_count:1) + (sig:73) + (pubkey:34) | |
// Non-segwit: (txid:32) + (vout:4) + (sequence:4) + (script_len:1) | |
'P2WPKH': 108 + (41 * 4), | |
// Segwit: (push_count:1) + (sig:73) + (pubkey:34) | |
// Non-segwit: (txid:32) + (vout:4) + (sequence:4) + (script_len:1) + (p2wpkh:23) | |
'P2SH-P2WPKH': 108 + (64 * 4) | |
}, | |
'outputs': { | |
// (p2sh:24) + (amount:8) | |
'P2SH': 32 * 4, | |
// (p2pkh:26) + (amount:8) | |
'P2PKH': 34 * 4, | |
// (p2wpkh:23) + (amount:8) | |
'P2WPKH': 31 * 4, | |
// (p2wsh:35) + (amount:8) | |
'P2WSH': 43 * 4 | |
} | |
} | |
function checkUInt53 (n) { | |
if (n < 0 || n > Number.MAX_SAFE_INTEGER || n % 1 !== 0) throw new RangeError('value out of range') | |
} | |
function varIntLength (number) { | |
checkUInt53(number) | |
return ( | |
number < 0xfd ? 1 | |
: number <= 0xffff ? 3 | |
: number <= 0xffffffff ? 5 | |
: 9 | |
) | |
} | |
Object.keys(inputs).forEach(function(key) { | |
checkUInt53(inputs[key]) | |
if (key.slice(0,8) === 'MULTISIG') { | |
// ex. "MULTISIG-P2SH:2-3" would mean 2 of 3 P2SH MULTISIG | |
var keyParts = key.split(':') | |
if (keyParts.length !== 2) throw new Error('invalid input: ' + key) | |
var newKey = keyParts[0] | |
var mAndN = keyParts[1].split('-').map(function (item) { return parseInt(item) }) | |
totalWeight += types.inputs[newKey] * inputs[key] | |
var multiplyer = (newKey === 'MULTISIG-P2SH') ? 4 : 1 | |
totalWeight += ((73 * mAndN[0]) + (34 * mAndN[1])) * multiplyer * inputs[key] | |
} else { | |
totalWeight += types.inputs[key] * inputs[key] | |
} | |
inputCount += inputs[key] | |
if (key.indexOf('W') >= 0) hasWitness = true | |
}) | |
Object.keys(outputs).forEach(function(key) { | |
checkUInt53(outputs[key]) | |
totalWeight += types.outputs[key] * outputs[key] | |
outputCount += outputs[key] | |
}) | |
if (hasWitness) totalWeight += 2 | |
totalWeight += 8 * 4 | |
totalWeight += varIntLength(inputCount) * 4 | |
totalWeight += varIntLength(outputCount) * 4 | |
return Math.ceil(totalWeight / 4) | |
} |
Hi.
This is my take on computing bytes for a SCRIPT-P2WSH.
I've run the method for 3 scripts and for 2 it gave the exact amount. In the 3rd if fails 11 bytes short.
The script that failed was the andreas antonopoulos one with script bytes: 196 bytes and path bytes (IF-IF path): 149 bytes
it has to be called:
getByteCount( { "SCRIPT:xxx-yyy": 1}, outputs)
where xxx is the locking script bytes taken from:
const script = bitcoin.script.compile(script)
const scriptBytes = script.length
yyy: is the path (unlocking script) bytes.
These are the updates on the getCountBytes method.
var types = {
// MULTISIG-* do not include pubkeys or signatures yet (this is calculated at runtime)
// sigs = 73 and pubkeys = 34 (these include pushdata byte)
'inputs': {
// Non-segwit: (txid:32) + (vout:4) + (sequence:4) + (script_len:1) = 41. utxo
'SCRIPT': 41 * 4 + 4, // + 4 comes from real txs.
...
Object.keys(inputs).forEach(function(key) {
checkUInt53(inputs[key])
if (key.slice(0,6) === 'SCRIPT') {
var keyParts = key.split(':')
if (keyParts.length !== 2) throw new Error('invalid input: ' + key)
var newKey = keyParts[0]
var scriptAndPath = keyParts[1].split('-').map(function (item) { return parseInt(item) })
totalWeight += types.inputs[newKey] * inputs[key]
totalWeight += (scriptAndPath[0] + scriptAndPath[1]) * inputs[key]
} else if (key.slice(0,8) === 'MULTISIG') {
I would appreciate comments.
Thanks.
plz how to getByteCount
with PT2R
I've been reviewing this script for using it to calculate the total transaction weight for combining SegWit + non-Segwit inputs. My understanding from reading the BIPs is that in a transaction with hasWitness
, a single byte is then also required for non-witness inputs to encode the length of the empty witness stack: encodeLength(0) + 0 = 1
.
bitcoinjs-lib
seems to have specific handling for this: bitcoinjs-lib transaction.ts L228.
Should the weight calculation be modified to better account for the size of the witness data for each input? Something along this (at the end of the snippet):
if (hasWitness) totalWeight += inputs.length;
Note this might not be entirely correct since the witness data size for SegWit inputs might vary, requiring more than one byte for encoding the length of the witness stack.
(EDIT: The witness stack size is already taken into account but only for segwit inputs. I believe the +1 byte is still missing for non-Segwit inputs if hasWitness
).
I'm aware there could be nuances I'm missing, and I'd value your perspective on this matter...
Show me the raw transaction, please.