bitcore-wallet-client
Version:
Client for bitcore-wallet-service
427 lines • 19.5 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Utils = void 0;
const crypto_wallet_core_1 = require("crypto-wallet-core");
const json_stable_stringify_1 = __importDefault(require("json-stable-stringify"));
const preconditions_1 = require("preconditions");
const sjcl_1 = __importDefault(require("sjcl"));
const constants_1 = require("./constants");
const defaults_1 = require("./defaults");
const $ = (0, preconditions_1.singleton)();
const Bitcore_ = {
btc: crypto_wallet_core_1.BitcoreLib,
bch: crypto_wallet_core_1.BitcoreLibCash,
eth: crypto_wallet_core_1.BitcoreLib,
matic: crypto_wallet_core_1.BitcoreLib,
arb: crypto_wallet_core_1.BitcoreLib,
base: crypto_wallet_core_1.BitcoreLib,
op: crypto_wallet_core_1.BitcoreLib,
xrp: crypto_wallet_core_1.BitcoreLib,
doge: crypto_wallet_core_1.BitcoreLibDoge,
ltc: crypto_wallet_core_1.BitcoreLibLtc,
sol: crypto_wallet_core_1.BitcoreLib
};
const PrivateKey = crypto_wallet_core_1.BitcoreLib.PrivateKey;
const PublicKey = crypto_wallet_core_1.BitcoreLib.PublicKey;
const crypto = crypto_wallet_core_1.BitcoreLib.crypto;
const MAX_DECIMAL_ANY_CHAIN = 18;
class Utils {
static getChain(coin) {
try {
let normalizedChain = coin.toLowerCase();
if (constants_1.Constants.BITPAY_SUPPORTED_ETH_ERC20.includes(normalizedChain) ||
!constants_1.Constants.CHAINS.includes(normalizedChain)) {
normalizedChain = 'eth';
}
return normalizedChain;
}
catch (_) {
return 'btc';
}
}
static encryptMessage(message, encryptingKey) {
var key = sjcl_1.default.codec.base64.toBits(encryptingKey);
return sjcl_1.default.encrypt(key, message, {
ks: 128,
iter: 1
});
}
static decryptMessage(cyphertextJson, encryptingKey) {
if (!cyphertextJson)
return;
if (!encryptingKey)
throw new Error('No key');
var key = sjcl_1.default.codec.base64.toBits(encryptingKey);
return sjcl_1.default.decrypt(key, cyphertextJson);
}
static decryptMessageNoThrow(cyphertextJson, encryptingKey) {
if (!encryptingKey)
return '<ECANNOTDECRYPT>';
if (!cyphertextJson)
return '';
var r = this.isJsonString(cyphertextJson);
if (!r || !r.iv || !r.ct) {
return cyphertextJson;
}
try {
return this.decryptMessage(cyphertextJson, encryptingKey);
}
catch (e) {
return '<ECANNOTDECRYPT>';
}
}
static isJsonString(str) {
var r;
try {
r = JSON.parse(str);
}
catch (e) {
return false;
}
return r;
}
static hashMessage(text) {
$.checkArgument(text);
var buf = Buffer.from(text);
var ret = crypto.Hash.sha256sha256(buf);
ret = new crypto_wallet_core_1.BitcoreLib.encoding.BufferReader(ret).readReverse();
return ret;
}
static signMessage(message, privKey) {
$.checkArgument(message);
var priv = new PrivateKey(privKey);
const flattenedMessage = Array.isArray(message) ? message.join(',') : message;
var hash = this.hashMessage(flattenedMessage);
return crypto.ECDSA.sign(hash, priv, { endian: 'little' }).toString();
}
static verifyMessage(message, signature, pubKey) {
$.checkArgument(message);
$.checkArgument(pubKey);
if (!signature)
return false;
var pub = new PublicKey(pubKey);
const flattenedMessage = Array.isArray(message) ? message.join(',') : message;
const hash = this.hashMessage(flattenedMessage);
try {
var sig = new crypto.Signature.fromString(signature);
return crypto.ECDSA.verify(hash, sig, pub, { endian: 'little' });
}
catch (e) {
return false;
}
}
static privateKeyToAESKey(privKey) {
$.checkArgument(privKey && typeof privKey === 'string');
$.checkArgument(crypto_wallet_core_1.BitcoreLib.PrivateKey.isValid(privKey), 'The private key received is invalid');
var pk = crypto_wallet_core_1.BitcoreLib.PrivateKey.fromString(privKey);
return crypto_wallet_core_1.BitcoreLib.crypto.Hash.sha256(pk.toBuffer())
.slice(0, 16)
.toString('base64');
}
static getCopayerHash(name, xPubKey, requestPubKey) {
return [name, xPubKey, requestPubKey].join('|');
}
static getProposalHash(proposalHeader, ...args) {
if (args.length > 0) {
return this.getOldHash.apply(this, [proposalHeader, ...args]);
}
return (0, json_stable_stringify_1.default)(proposalHeader);
}
static getOldHash(toAddress, amount, message, payProUrl) {
return [toAddress, amount, message || '', payProUrl || ''].join('|');
}
static parseDerivationPath(path) {
const pathIndex = /m\/([0-9]*)\/([0-9]*)/;
const [_input, changeIndex, addressIndex] = path.match(pathIndex);
const isChange = Number.parseInt(changeIndex) > 0;
return { _input, addressIndex, isChange };
}
static deriveAddress(scriptType, publicKeyRing, path, m, network, chain, escrowInputs, hardwareSourcePublicKey, clientDerivedPublicKey) {
$.checkArgument(Object.values(constants_1.Constants.SCRIPT_TYPES).includes(scriptType));
const externSourcePublicKey = hardwareSourcePublicKey || clientDerivedPublicKey;
if (externSourcePublicKey) {
const bitcoreAddress = crypto_wallet_core_1.Deriver.getAddress(chain.toUpperCase(), network, externSourcePublicKey, scriptType);
return {
address: bitcoreAddress.toString(),
path,
publicKeys: [externSourcePublicKey]
};
}
chain = chain || 'btc';
const bitcore = Bitcore_[chain];
let publicKeys = (publicKeyRing || []).map(item => {
var xpub = new bitcore.HDPublicKey(item.xPubKey);
return xpub.deriveChild(path).publicKey;
});
var bitcoreAddress;
switch (scriptType) {
case constants_1.Constants.SCRIPT_TYPES.P2WSH:
const nestedWitness = false;
bitcoreAddress = bitcore.Address.createMultisig(publicKeys, m, network, nestedWitness, 'witnessscripthash');
break;
case constants_1.Constants.SCRIPT_TYPES.P2SH:
if (escrowInputs) {
var xpub = new bitcore.HDPublicKey(publicKeyRing[0].xPubKey);
const inputPublicKeys = escrowInputs.map(input => xpub.deriveChild(input.path).publicKey);
bitcoreAddress = bitcore.Address.createEscrow(inputPublicKeys, publicKeys[0], network);
publicKeys = [publicKeys[0], ...inputPublicKeys];
}
else {
bitcoreAddress = bitcore.Address.createMultisig(publicKeys, m, network);
}
break;
case constants_1.Constants.SCRIPT_TYPES.P2WPKH:
bitcoreAddress = bitcore.Address.fromPublicKey(publicKeys[0], network, 'witnesspubkeyhash');
break;
case constants_1.Constants.SCRIPT_TYPES.P2PKH:
$.checkState(Array.isArray(publicKeys) && publicKeys.length == 1, 'publicKeys array undefined');
if (constants_1.Constants.UTXO_CHAINS.includes(chain)) {
bitcoreAddress = bitcore.Address.fromPublicKey(publicKeys[0], network);
}
else {
const { addressIndex, isChange } = this.parseDerivationPath(path);
const [{ xPubKey }] = publicKeyRing;
bitcoreAddress = crypto_wallet_core_1.Deriver.deriveAddress(chain.toUpperCase(), network, xPubKey, addressIndex, isChange);
}
break;
case constants_1.Constants.SCRIPT_TYPES.P2TR:
bitcoreAddress = bitcore.Address.fromPublicKey(publicKeys[0], network, 'taproot');
break;
}
return {
address: bitcoreAddress.toString(true),
path,
publicKeys: publicKeys.map(p => p.toString())
};
}
static xPubToCopayerId(_chain, xpub) {
const chain = _chain.toLowerCase();
var str = chain == 'btc' ? xpub : chain + xpub;
var hash = sjcl_1.default.hash.sha256.hash(str);
return sjcl_1.default.codec.hex.fromBits(hash);
}
static signRequestPubKey(requestPubKey, xPrivKey) {
var priv = new crypto_wallet_core_1.BitcoreLib.HDPrivateKey(xPrivKey).deriveChild(constants_1.Constants.PATHS.REQUEST_KEY_AUTH).privateKey;
return this.signMessage(requestPubKey, priv);
}
static verifyRequestPubKey(requestPubKey, signature, xPubKey) {
var pub = new crypto_wallet_core_1.BitcoreLib.HDPublicKey(xPubKey).deriveChild(constants_1.Constants.PATHS.REQUEST_KEY_AUTH).publicKey;
return this.verifyMessage(requestPubKey, signature, pub.toString());
}
static formatAmount(satoshis, unit, opts) {
$.shouldBeNumber(satoshis);
var clipDecimals = (number, decimals) => {
let str = number.toString();
if (str.indexOf('e') >= 0) {
str = number.toFixed(MAX_DECIMAL_ANY_CHAIN);
}
var x = str.split('.');
var d = (x[1] || '0').substring(0, decimals);
const ret = parseFloat(x[0] + '.' + d);
return ret;
};
var addSeparators = (nStr, thousands, decimal, minDecimals) => {
nStr = nStr.replace('.', decimal);
var x = nStr.split(decimal);
var x0 = x[0];
var x1 = x[1] || '';
while (x1.endsWith('0') && x1.length > minDecimals) {
x1 = x1.slice(0, -1);
}
var x2 = x.length > 1 ? decimal + x1 : '';
x0 = x0.replace(/\B(?=(\d{3})+(?!\d))/g, thousands);
return x0 + x2;
};
opts = opts || {};
var u = constants_1.Constants.UNITS[unit];
var precision = opts.fullPrecision ? 'full' : 'short';
var decimals = opts.decimals ? opts.decimals[precision] : u[precision];
var toSatoshis = opts.toSatoshis ? opts.toSatoshis : u.toSatoshis;
var amount = clipDecimals(satoshis / toSatoshis, decimals.maxDecimals).toFixed(decimals.maxDecimals);
return addSeparators(amount, opts.thousandsSeparator || ',', opts.decimalSeparator || '.', decimals.minDecimals);
}
static buildTx(txp) {
var _a;
var chain = ((_a = txp.chain) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || Utils.getChain(txp.coin);
if (constants_1.Constants.UTXO_CHAINS.includes(chain)) {
var bitcore = Bitcore_[chain];
var t = new bitcore.Transaction();
if (txp.version >= 4) {
t.setVersion(2);
}
else {
t.setVersion(1);
}
$.checkState(Object.values(constants_1.Constants.SCRIPT_TYPES).includes(txp.addressType), 'Failed state: addressType not in SCRIPT_TYPES');
switch (txp.addressType) {
case constants_1.Constants.SCRIPT_TYPES.P2WSH:
case constants_1.Constants.SCRIPT_TYPES.P2SH:
for (const i of txp.inputs || []) {
t.from(i, i.publicKeys, txp.requiredSignatures);
}
break;
case constants_1.Constants.SCRIPT_TYPES.P2WPKH:
case constants_1.Constants.SCRIPT_TYPES.P2PKH:
case constants_1.Constants.SCRIPT_TYPES.P2TR:
t.from(txp.inputs);
break;
}
if (txp.toAddress && txp.amount && !txp.outputs) {
t.to(txp.toAddress, txp.amount);
}
else if (txp.outputs) {
for (const o of (txp.outputs || [])) {
$.checkState(o.script || o.toAddress, 'Output should have either toAddress or script specified');
if (o.script) {
t.addOutput(new bitcore.Transaction.Output({
script: o.script,
satoshis: o.amount
}));
}
else {
t.to(o.toAddress, o.amount);
}
}
}
t.fee(txp.fee);
if (txp.instantAcceptanceEscrow && txp.escrowAddress) {
t.escrow(txp.escrowAddress.address, txp.instantAcceptanceEscrow + txp.fee);
}
t.change(txp.changeAddress.address);
if (txp.enableRBF)
t.enableRBF();
if (t.outputs.length > 1) {
const outputOrder = (txp.outputOrder || []).filter(order => order < t.outputs.length);
$.checkState(t.outputs.length === outputOrder.length, 'Failed state: t.ouputs.length == outputOrder.length at buildTx()');
t.sortOutputs(outputs => outputOrder.map(i => outputs[i]));
}
const totalInputs = (txp.inputs || []).reduce((memo, i) => {
return +i.satoshis + memo;
}, 0);
const totalOutputs = (t.outputs || []).reduce((memo, o) => {
return +o.satoshis + memo;
}, 0);
$.checkState(totalInputs - totalOutputs >= 0, 'Failed state: totalInputs - totalOutputs >= 0 at buildTx');
$.checkState(totalInputs - totalOutputs <= defaults_1.Defaults.MAX_TX_FEE(chain), 'Failed state: totalInputs - totalOutputs <= Defaults.MAX_TX_FEE(chain) at buildTx');
return t;
}
else {
const { data, destinationTag, outputs, payProUrl, tokenAddress, multisigContractAddress, multiSendContractAddress, isTokenSwap, gasLimit, multiTx, outputOrder } = txp;
const recipients = outputs.map(output => {
return {
amount: output.amount,
address: output.toAddress,
data: output.data,
gasLimit: output.gasLimit
};
});
if (data) {
recipients[0].data = data;
}
const unsignedTxs = [];
const isToken = tokenAddress && !payProUrl && !isTokenSwap;
const isMULTISIG = multisigContractAddress;
const chainName = chain.toUpperCase();
const tokenType = chainName === 'SOL' ? 'SPL' : 'ERC20';
const _chain = isMULTISIG
? chainName + 'MULTISIG'
: isToken
? chainName + tokenType
: chainName;
if (multiSendContractAddress) {
let multiSendParams = {
nonce: Number(txp.nonce),
recipients,
chain: _chain,
contractAddress: multiSendContractAddress,
gasLimit
};
unsignedTxs.push(crypto_wallet_core_1.Transactions.create(Object.assign(Object.assign({}, txp), multiSendParams)));
}
else if (multiTx) {
for (let index = 0; index < outputOrder.length; index++) {
const outputIdx = outputOrder[index];
if (!(outputs === null || outputs === void 0 ? void 0 : outputs[outputIdx])) {
throw new Error('Output index out of range');
}
const recepient = {
amount: outputs[outputIdx].amount,
address: outputs[outputIdx].toAddress,
tag: outputs[outputIdx].tag
};
const _tag = (recepient === null || recepient === void 0 ? void 0 : recepient.tag) || destinationTag;
const rawTx = crypto_wallet_core_1.Transactions.create(Object.assign(Object.assign(Object.assign({}, txp), recepient), { tag: _tag ? Number(_tag) : undefined, chain: _chain, nonce: this.formatNonce(chainName, txp.nonce, index), recipients: [recepient] }));
unsignedTxs.push(rawTx);
}
}
else if (chainName === 'SOL') {
const rawTx = crypto_wallet_core_1.Transactions.create(Object.assign(Object.assign({}, txp), { chain: _chain, recipients }));
unsignedTxs.push(rawTx);
}
else {
for (let index = 0; index < recipients.length; index++) {
const rawTx = crypto_wallet_core_1.Transactions.create(Object.assign(Object.assign(Object.assign({}, txp), recipients[index]), { tag: destinationTag ? Number(destinationTag) : undefined, chain: _chain, nonce: this.formatNonce(chainName, txp.nonce, index), recipients: [recipients[index]] }));
unsignedTxs.push(rawTx);
}
}
return { uncheckedSerialize: () => unsignedTxs };
}
}
static formatNonce(chain, nonce, index) {
if (constants_1.Constants.SVM_CHAINS.includes(chain.toLowerCase())) {
return nonce;
}
else {
return Number(nonce) + Number(index);
}
}
static getCurrencyCodeFromCoinAndChain(coin, chain) {
if (coin.toLowerCase() === chain.toLowerCase()) {
return coin.toUpperCase();
}
if (coin.toLowerCase() === 'pol') {
return 'MATIC';
}
if (coin.toLowerCase() === 'usdt' && chain.toLowerCase() === 'arb') {
return 'USDTe_arb';
}
if (coin.toLowerCase() === 'usdt' && chain.toLowerCase() === 'op') {
return 'USDTe_op';
}
const suffix = constants_1.Constants.EVM_CHAINSUFFIXMAP[chain.toLowerCase()];
const coinIsAChain = !!constants_1.Constants.EVM_CHAINSUFFIXMAP[coin.toLowerCase()];
if (suffix && (coinIsAChain || chain.toLowerCase() !== 'eth')) {
if (chain.toLowerCase() === 'matic' && coin.toLowerCase() === 'usdc.e') {
return 'USDC_m';
}
else if (chain.toLowerCase() === 'matic' && coin.toLowerCase() === 'usdc') {
return 'USDCn_m';
}
return `${coin.toUpperCase()}_${suffix}`;
}
return coin.toUpperCase();
}
static isNativeSegwit(addressType) {
return [
constants_1.Constants.SCRIPT_TYPES.P2WPKH,
constants_1.Constants.SCRIPT_TYPES.P2WSH,
constants_1.Constants.SCRIPT_TYPES.P2TR,
].includes(addressType);
}
static getSegwitVersion(addressType) {
switch (addressType) {
case constants_1.Constants.SCRIPT_TYPES.P2WPKH:
case constants_1.Constants.SCRIPT_TYPES.P2WSH:
return 0;
case constants_1.Constants.SCRIPT_TYPES.P2TR:
return 1;
default:
return undefined;
}
}
}
exports.Utils = Utils;
//# sourceMappingURL=utils.js.map