Source: p2wsh.js

import {encoding} from 'bufio';

/**
 * This module provides functions and constants for the P2WSH address type.
 * 
 * @module p2wsh
 */

/**
 * Address type constant for "pay-to-witness-script-hash" or (P2WSH)
 * addresses.
 * 
 * @constant
 * @type {string}
 * @default P2WSH
 */
export const P2WSH = "P2WSH";

/**
 * @description provides the size of single tx input for a segwit tx (i.e. empty script)
 * Each input field will look like:
 * prevhash (32 bytes) + prevIndex (4) + scriptsig (1) + sequence bytes (4)
 * @returns {Number} 41 (always 41 for segwit inputs since script sig is in witness)
 */
function txinSize() {
  const PREVHASH_BYTES = 32;
  const PREV_INDEX_BYTES = 4;
  const SCRIPT_LENGTH_BYTES = 1
  const SEQUENCE_BYTES = 4

  return (PREVHASH_BYTES + 
    PREV_INDEX_BYTES + 
    SEQUENCE_BYTES + 
    SCRIPT_LENGTH_BYTES
  )
}

/**
 * @description Returns the approximate size of outputs in tx. 
 * Calculated by adding value field (8 bytes), field providing length
 * scriptPubkey and the script pubkey itself
 * @param {Number} [scriptPubkeySize = 34] size of script pubkey. 
 * Defaults to 34 which is the size of a P2WSH script pubkey and the
 * largest possible standard
 * @returns {Number} size of tx output (default: 43)
 */
function txoutSize(scriptPubkeySize = 34) {
  // per_output: value (8) + script length (1) + 
  const VAL_BYTES = 8;
  const scriptLengthBytes = encoding.sizeVarint(scriptPubkeySize);
  // for P2WSH Locking script which is largest possible(34)
  return VAL_BYTES + scriptLengthBytes + scriptPubkeySize;
}

/**
 * @description calculates size of redeem script given n pubkeys.
 * Calculation looks like: 
 * OP_M (1 byte) + size of each pubkey in redeem script (OP_DATA= 1 byte * N) +
 * pubkey size (33 bytes * N) + OP_N (1 byte) + OP_CHECKMULTISIG (1 byte)
 *  => 1 + (1 * N) + (33 * N) + 1 + 1
 * @param {Number} n - value of n in m-of-n for multisig script
 * @returns {Number} 3 + 34 * N
 */
export function getRedeemScriptSize(n) {
  const OP_M_BYTES = 1;
  const OP_N_BYTES = 1;
  const opDataBytes = n; // 1 byte per pubkey in redeem script
  const pubkeyBytes = 33 * n;
  const OP_CHECKMULTISIG_BYTES = 1;
  return OP_M_BYTES + opDataBytes + pubkeyBytes + OP_N_BYTES + OP_CHECKMULTISIG_BYTES;
}

/**
 * @description Calculates the value of a multisig witness given m-of-n values
 * Calculation is of the following form:
 * witness_items count (varint 1+) + null_data (1 byte) + size of each signature (1 byte * OP_M) + signatures (73 * M) +
 * redeem script length (1 byte) + redeem script size (4 + 34 * N bytes)
 * @param {Number} m - value of m in m-of-n for multisig script
 * @param {Number} n - value of n in m-of-n for multisig script
 * @returns {Number} 6 + (74 * M) + (34 * N)
 */
export function getWitnessSize(m, n) {
  const OP_NULL_BYTES = 1; // needs to be added b/c of bug in multisig implementation
  const opDataBytes = m;
  // assumes largest possible signature size which could be 71, 72, or 73
  const signaturesSize = 73 * m;
  const REDEEM_SCRIPT_LENGTH = 1;
  const redeemScriptSize = getRedeemScriptSize(n);
  // total witness stack will be null bytes + each signature (m) + redeem script
  const WITNESS_ITEMS_COUNT = encoding.sizeVarint(1 + m + 1);
  
  return WITNESS_ITEMS_COUNT + 
    OP_NULL_BYTES + 
    opDataBytes + 
    signaturesSize + 
    REDEEM_SCRIPT_LENGTH + 
    redeemScriptSize;
}

/**
 * @description Calculates the size of the fields in a transaction which DO NOT
 * get counted towards witness discount.
 * Calculated as: version bytes (4) + locktime bytes (4) + input_len (1+) + txins (41+) + output_len (1+) + outputs (9+)
 * @param {*} inputsCount - number of inputs in the tx
 * @param {*} outputsCount - number of outputs in the tx
 * @returns {Number} number of bytes in the tx without witness fields
 */
export function calculateBase(inputsCount, outputsCount) {
  let total = 0;
  total += 4; // version
  total += 4 // locktime

  total += encoding.sizeVarint(inputsCount); // inputs length
  total += inputsCount * txinSize();
  total += encoding.sizeVarint(outputsCount); 
  total += outputsCount * txoutSize();
  return total
}

export function calculateTotalWitnessSize({ numInputs, m, n }) {
  let total = 0;

  total += 1; // segwit marker
  total += 1; // segwit flag

  total += encoding.sizeVarint(numInputs) // bytes for number of witnesses
  total += numInputs * getWitnessSize(m, n) // add witness for each input

  return total;
}

/**
 * @description Calculate virtual bytes or "vsize". 
 * vsize is equal three times "base size" of a tx w/o witness data, plus the
 * total size of all data, with the final result divided by scaling factor
 * of 4 and round up to the next integer. For example, if a transaction is
 * 200 bytes with new serialization, and becomes 99 bytes with marker, flag,
 * and witness removed, the vsize is (99 * 3 + 200) / 4 = 125 with round up.
 * @param {Number} baseSize - base size of transaction
 * @param {Number} witnessSize - size of witness fields
 * @returns {Number} virtual size of tx
 */
function calculateVSize(baseSize, witnessSize) {
  const WITNESS_SCALE_FACTOR = 4;
  const totalSize = baseSize + witnessSize;
  const txWeight = baseSize * 3 + totalSize;
  return Math.ceil(txWeight / WITNESS_SCALE_FACTOR);
}

/**
 * Estimate the transaction virtual size (vsize) when spending inputs
 * from the same multisig P2WSH address. 
 * @param {Object} config - configuration for the calculation
 * @param {number} config.numInputs - number of m-of-n multisig P2SH inputs
 * @param {number} config.numOutputs - number of outputs
 * @param {number} config.m - required signers
 * @param {number} config.n - total signers
 * @returns {number} estimated transaction virtual size in bytes
 */
export function estimateMultisigP2WSHTransactionVSize(config) {
  // non-segwit discount fields
  const baseSize = calculateBase(config.numInputs, config.numOutputs);
  // these are the values that benefit from the segwit discount
  const witnessSize = calculateTotalWitnessSize(config);
  return calculateVSize(baseSize, witnessSize);
}