UNPKG

turtlecoin-wallet-backend

Version:

[![Build Status](https://travis-ci.org/turtlecoin/turtlecoin-wallet-backend-js.svg?branch=master)](https://travis-ci.org/turtlecoin/turtlecoin-wallet-backend-js)

210 lines (209 loc) 9.23 kB
"use strict"; // Copyright (c) 2018, Zpalmtree // // Please see the included LICENSE file for more information. Object.defineProperty(exports, "__esModule", { value: true }); const _ = require("lodash"); const CnUtils_1 = require("./CnUtils"); const WalletError_1 = require("./WalletError"); const Config_1 = require("./Config"); const Assert_1 = require("./Assert"); /** * @param addresses The addresses to validate * @param integratedAddressesAllowed Should we allow integrated addresses? * * Verifies that the addresses given are valid. * @returns Returns SUCCESS if valid, otherwise a WalletError describing the error */ function validateAddresses(addresses, integratedAddressesAllowed, config = new Config_1.Config()) { Assert_1.assertArray(addresses, 'addresses'); Assert_1.assertBoolean(integratedAddressesAllowed, 'integratedAddressesAllowed'); const _config = Config_1.MergeConfig(config); const alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; for (const address of addresses) { try { /* Verify address lengths are correct */ if (address.length !== config.standardAddressLength && address.length !== config.integratedAddressLength) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.ADDRESS_WRONG_LENGTH); } /* Verify every address character is in the base58 set */ if (![...address].every((x) => alphabet.includes(x))) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.ADDRESS_NOT_BASE58); } /* Verify checksum */ const parsed = CnUtils_1.CryptoUtils(_config).decodeAddress(address); /* Verify the prefix is correct */ if (parsed.prefix !== _config.addressPrefix) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.ADDRESS_WRONG_PREFIX); } /* Verify it's not an integrated, if those aren't allowed */ if (parsed.paymentId.length !== 0 && !integratedAddressesAllowed) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.ADDRESS_IS_INTEGRATED); } } catch (err) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.ADDRESS_NOT_VALID, err.toString()); } } return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.SUCCESS); } exports.validateAddresses = validateAddresses; /** * Validate the amounts being sent are valid, and the addresses are valid. * * @returns Returns SUCCESS if valid, otherwise a WalletError describing the error * * @hidden */ function validateDestinations(destinations, config = new Config_1.Config()) { const _config = Config_1.MergeConfig(config); if (destinations.length === 0) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.NO_DESTINATIONS_GIVEN); } const destinationAddresses = []; for (const [destination, amount] of destinations) { if (amount === 0) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.AMOUNT_IS_ZERO); } if (amount < 0) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.NEGATIVE_VALUE_GIVEN); } if (!Number.isInteger(amount)) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.NON_INTEGER_GIVEN); } destinationAddresses.push(destination); } /* Validate the addresses, integrated addresses allowed */ return validateAddresses(destinationAddresses, true, _config); } exports.validateDestinations = validateDestinations; /** * Validate that the payment ID's included in integrated addresses are valid. * * You should have already called validateAddresses() before this function * * @returns Returns SUCCESS if valid, otherwise a WalletError describing the error * * @hidden */ function validateIntegratedAddresses(destinations, paymentID, config = new Config_1.Config()) { const _config = Config_1.MergeConfig(config); for (const [destination, amount] of destinations) { if (destination.length !== _config.integratedAddressLength) { continue; } /* Extract the payment ID */ const parsedAddress = CnUtils_1.CryptoUtils(_config).decodeAddress(destination); if (paymentID === '') { paymentID = parsedAddress.paymentId; } else if (paymentID !== parsedAddress.paymentId) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.CONFLICTING_PAYMENT_IDS); } } return WalletError_1.SUCCESS; } exports.validateIntegratedAddresses = validateIntegratedAddresses; /** * Validate the the addresses given are both valid, and exist in the subwallet * * @returns Returns SUCCESS if valid, otherwise a WalletError describing the error * * @hidden */ function validateOurAddresses(addresses, subWallets, config = new Config_1.Config()) { const _config = Config_1.MergeConfig(config); const error = validateAddresses(addresses, false, _config); if (!_.isEqual(error, WalletError_1.SUCCESS)) { return error; } for (const address of addresses) { const parsedAddress = CnUtils_1.CryptoUtils(_config).decodeAddress(address); const keys = subWallets.getPublicSpendKeys(); if (!keys.includes(parsedAddress.publicSpendKey)) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.ADDRESS_NOT_IN_WALLET, `The address given (${address}) does not exist in the wallet ` + `container, but it is required to exist for this operation.`); } } return WalletError_1.SUCCESS; } exports.validateOurAddresses = validateOurAddresses; /** * Validate that the transfer amount + fee is valid, and we have enough balance * Note: Does not verify amounts are positive / integer, validateDestinations * handles that. * * @returns Returns SUCCESS if valid, otherwise a WalletError describing the error * * @hidden */ function validateAmount(destinations, fee, subWalletsToTakeFrom, subWallets, currentHeight, config = new Config_1.Config()) { const _config = Config_1.MergeConfig(config); if (fee < _config.minimumFee) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.FEE_TOO_SMALL); } if (!Number.isInteger(fee)) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.NON_INTEGER_GIVEN); } /* Get available balance, given the source addresses */ const [availableBalance, lockedBalance] = subWallets.getBalance(currentHeight, subWalletsToTakeFrom); /* Get the sum of the transaction */ const totalAmount = _.sumBy(destinations, ([destination, amount]) => amount) + fee; if (totalAmount > availableBalance) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.NOT_ENOUGH_BALANCE); } /* Can't send more than 2^64 (Granted, that is larger than the entire supply, but you can still try ;) */ if (totalAmount >= Math.pow(2, 64)) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.WILL_OVERFLOW); } return WalletError_1.SUCCESS; } exports.validateAmount = validateAmount; /** * Validates mixin is valid and in allowed range * * @returns Returns SUCCESS if valid, otherwise a WalletError describing the error * * @hidden */ function validateMixin(mixin, height, config = new Config_1.Config()) { const _config = Config_1.MergeConfig(config); if (mixin < 0) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.NEGATIVE_VALUE_GIVEN); } if (!Number.isInteger(mixin)) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.NON_INTEGER_GIVEN); } const [minMixin, maxMixin] = _config.mixinLimits.getMixinLimitsByHeight(height); if (mixin < minMixin) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.MIXIN_TOO_SMALL, `The mixin value given (${mixin}) is lower than the minimum mixin ` + `allowed (${minMixin})`); } if (mixin > maxMixin) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.MIXIN_TOO_BIG, `The mixin value given (${mixin}) is greater than the maximum mixin ` + `allowed (${maxMixin})`); } return WalletError_1.SUCCESS; } exports.validateMixin = validateMixin; /** * Validates the payment ID is valid (or an empty string) * * @returns Returns SUCCESS if valid, otherwise a WalletError describing the error */ function validatePaymentID(paymentID) { Assert_1.assertString(paymentID, 'paymentID'); if (paymentID === '') { return WalletError_1.SUCCESS; } if (paymentID.length !== 64) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.PAYMENT_ID_WRONG_LENGTH); } if (paymentID.match(new RegExp(/[a-zA-Z0-9]{64}/)) === null) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.PAYMENT_ID_INVALID); } return WalletError_1.SUCCESS; } exports.validatePaymentID = validatePaymentID;