blockstack
Version:
The Blockstack Javascript library for authentication, identity, and storage.
219 lines (183 loc) • 6.79 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.DUST_MINIMUM = undefined;
exports.hash160 = hash160;
exports.hash128 = hash128;
exports.estimateTXBytes = estimateTXBytes;
exports.sumOutputValues = sumOutputValues;
exports.decodeB40 = decodeB40;
exports.addUTXOsToFund = addUTXOsToFund;
exports.signInputs = signInputs;
var _bitcoinjsLib = require('bitcoinjs-lib');
var _bitcoinjsLib2 = _interopRequireDefault(_bitcoinjsLib);
var _ripemd = require('ripemd160');
var _ripemd2 = _interopRequireDefault(_ripemd);
var _bigi = require('bigi');
var _bigi2 = _interopRequireDefault(_bigi);
var _errors = require('../errors');
var _signers = require('./signers');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var DUST_MINIMUM = exports.DUST_MINIMUM = 5500;
function hash160(buff) {
var sha256 = _bitcoinjsLib2.default.crypto.sha256(buff);
return new _ripemd2.default().update(sha256).digest();
}
function hash128(buff) {
return Buffer.from(_bitcoinjsLib2.default.crypto.sha256(buff).slice(0, 16));
}
// COPIED FROM coinselect, because 1 byte matters sometimes.
// baseline estimates, used to improve performance
var TX_EMPTY_SIZE = 4 + 1 + 1 + 4;
var TX_INPUT_BASE = 32 + 4 + 1 + 4;
var TX_INPUT_PUBKEYHASH = 107;
var TX_OUTPUT_BASE = 8 + 1;
var TX_OUTPUT_PUBKEYHASH = 25;
function inputBytes(input) {
if (input && input.script && input.script.length > 0) {
return TX_INPUT_BASE + input.script.length;
} else {
return TX_INPUT_BASE + TX_INPUT_PUBKEYHASH;
}
}
function outputBytes(output) {
if (output && output.script && output.script.length > 0) {
return TX_OUTPUT_BASE + output.script.length;
} else {
return TX_OUTPUT_BASE + TX_OUTPUT_PUBKEYHASH;
}
}
function transactionBytes(inputs, outputs) {
return TX_EMPTY_SIZE + inputs.reduce(function (a, x) {
return a + inputBytes(x);
}, 0) + outputs.reduce(function (a, x) {
return a + outputBytes(x);
}, 0);
}
//
function estimateTXBytes(txIn, additionalInputs, additionalOutputs) {
var innerTx = txIn;
if (txIn instanceof _bitcoinjsLib2.default.TransactionBuilder) {
innerTx = txIn.__tx;
}
var dummyInputs = new Array(additionalInputs);
dummyInputs.fill(null);
var dummyOutputs = new Array(additionalOutputs);
dummyOutputs.fill(null);
var inputs = [].concat(innerTx.ins, dummyInputs);
var outputs = [].concat(innerTx.outs, dummyOutputs);
return transactionBytes(inputs, outputs);
}
function sumOutputValues(txIn) {
var innerTx = txIn;
if (txIn instanceof _bitcoinjsLib2.default.TransactionBuilder) {
innerTx = txIn.__tx;
}
return innerTx.outs.reduce(function (agg, x) {
return agg + x.value;
}, 0);
}
function decodeB40(input) {
// treat input as a base40 integer, and output a hex encoding
// of that integer.
//
// for each digit of the string, find its location in `characters`
// to get the value of the digit, then multiply by 40^(-index in input)
// e.g.,
// the 'right-most' character has value: (digit-value) * 40^0
// the next character has value: (digit-value) * 40^1
//
// hence, we reverse the characters first, and use the index
// to compute the value of each digit, then sum
var characters = '0123456789abcdefghijklmnopqrstuvwxyz-_.+';
var base = _bigi2.default.valueOf(40);
var inputDigits = input.split('').reverse();
var digitValues = inputDigits.map(function (character, exponent) {
return _bigi2.default.valueOf(characters.indexOf(character)).multiply(base.pow(_bigi2.default.valueOf(exponent)));
});
var sum = digitValues.reduce(function (agg, cur) {
return agg.add(cur);
}, _bigi2.default.ZERO);
return sum.toHex();
}
/**
* Adds UTXOs to fund a transaction
* @param {TransactionBuilder} txBuilderIn - a transaction builder object to add the inputs to. this
* object is _always_ mutated. If not enough UTXOs exist to fund, the tx builder object
* will still contain as many inputs as could be found.
* @param {Array<{value: number, tx_hash: string, tx_output_n}>} utxos - the utxo set for the
* payer's address.
* @param {number} amountToFund - the amount of satoshis to fund in the transaction. the payer's
* utxos will be included to fund up to this amount of *output* and the corresponding *fees*
* for those additional inputs
* @param {number} feeRate - the satoshis/byte fee rate to use for fee calculation
* @param {boolean} fundNewFees - if true, this function will fund `amountToFund` and any new fees
* associated with including the new inputs.
* if false, this function will fund _at most_ `amountToFund`
* @returns {number} - the amount of leftover change (in satoshis)
* @private
*/
function addUTXOsToFund(txBuilderIn, utxos, amountToFund, feeRate) {
var fundNewFees = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true;
if (utxos.length === 0) {
throw new _errors.NotEnoughFundsError(amountToFund);
}
// how much are we increasing fees by adding an input ?
var newFees = feeRate * (estimateTXBytes(txBuilderIn, 1, 0) - estimateTXBytes(txBuilderIn, 0, 0));
var utxoThreshhold = amountToFund;
if (fundNewFees) {
utxoThreshhold += newFees;
}
var goodUtxos = utxos.filter(function (utxo) {
return utxo.value >= utxoThreshhold;
});
if (goodUtxos.length > 0) {
goodUtxos.sort(function (a, b) {
return a.value - b.value;
});
var selected = goodUtxos[0];
var change = selected.value - amountToFund;
if (fundNewFees) {
change -= newFees;
}
txBuilderIn.addInput(selected.tx_hash, selected.tx_output_n);
return change;
} else {
utxos.sort(function (a, b) {
return b.value - a.value;
});
var largest = utxos[0];
if (newFees >= largest.value) {
throw new _errors.NotEnoughFundsError(amountToFund);
}
txBuilderIn.addInput(largest.tx_hash, largest.tx_output_n);
var remainToFund = amountToFund - largest.value;
if (fundNewFees) {
remainToFund += newFees;
}
return addUTXOsToFund(txBuilderIn, utxos.slice(1), remainToFund, feeRate, fundNewFees);
}
}
function signInputs(txB, defaultSigner, otherSigners) {
var signerArray = txB.__tx.ins.map(function () {
return defaultSigner;
});
if (otherSigners) {
otherSigners.forEach(function (signerPair) {
signerArray[signerPair.index] = signerPair.signer;
});
}
var signingPromise = Promise.resolve();
var _loop = function _loop(i) {
signingPromise = signingPromise.then(function () {
return signerArray[i].signTransaction(txB, i);
});
};
for (var i = 0; i < txB.__tx.ins.length; i++) {
_loop(i);
}
return signingPromise.then(function () {
return txB;
});
}