You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

389 lines
12 KiB

  1. // Copyright (C) 2018-2020, Zpalmtree
  2. //
  3. // Please see the included LICENSE file for more information.
  4. import * as _ from 'lodash';
  5. import { Address } from 'fedoragold-utils';
  6. import { IConfig, Config, MergeConfig } from './Config';
  7. import { CryptoUtils} from './CnUtils';
  8. import {
  9. CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE, MAX_BLOCK_NUMBER,
  10. MAX_BLOCK_SIZE_GROWTH_SPEED_DENOMINATOR,
  11. MAX_BLOCK_SIZE_GROWTH_SPEED_NUMERATOR, MAX_BLOCK_SIZE_INITIAL,
  12. MAX_OUTPUT_SIZE_CLIENT,
  13. } from './Constants';
  14. import { validateAddresses, validatePaymentID } from './ValidateParameters';
  15. import { SUCCESS } from './WalletError';
  16. import { English } from './WordList';
  17. import { assertString, assertNumber } from './Assert';
  18. /**
  19. * Creates an integrated address from a standard address, and a payment ID.
  20. *
  21. * Throws if either address or payment ID is invalid.
  22. */
  23. export async function createIntegratedAddress(
  24. address: string,
  25. paymentID: string,
  26. config: IConfig = new Config()): Promise<string> {
  27. assertString(address, 'address');
  28. assertString(paymentID, 'paymentID');
  29. const tempConfig: Config = MergeConfig(config);
  30. let error = await validateAddresses([address], false, tempConfig);
  31. if (!_.isEqual(error, SUCCESS)) {
  32. throw error;
  33. }
  34. error = validatePaymentID(paymentID);
  35. if (!_.isEqual(error, SUCCESS)) {
  36. throw error;
  37. }
  38. /* Validate payment ID allows empty payment ID's */
  39. if (paymentID === '') {
  40. throw new Error('Payment ID is empty string!');
  41. }
  42. return CryptoUtils(tempConfig).createIntegratedAddress(address, paymentID);
  43. }
  44. /**
  45. * Verifies if a key or payment ID is valid (64 char hex)
  46. */
  47. export function isHex64(val: string): boolean {
  48. assertString(val, 'val');
  49. const regex = new RegExp('^[0-9a-fA-F]{64}$');
  50. return regex.test(val);
  51. }
  52. /**
  53. * Converts an address to the corresponding public view and public spend key
  54. * Precondition: address is valid
  55. *
  56. * @hidden
  57. */
  58. export async function addressToKeys(address: string, config: IConfig = new Config()): Promise<[string, string]> {
  59. const tempConfig: Config = MergeConfig(config);
  60. const parsed = await Address.fromAddress(address, tempConfig.addressPrefix);
  61. return [parsed.view.publicKey, parsed.spend.publicKey];
  62. }
  63. /**
  64. * Get the nearest multiple of the given value, rounded down.
  65. *
  66. * @hidden
  67. */
  68. export function getLowerBound(val: number, nearestMultiple: number): number {
  69. const remainder = val % nearestMultiple;
  70. return val - remainder;
  71. }
  72. /**
  73. * Get the nearest multiple of the given value, rounded up
  74. *
  75. * @hidden
  76. */
  77. export function getUpperBound(val: number, nearestMultiple: number): number {
  78. return getLowerBound(val, nearestMultiple) + nearestMultiple;
  79. }
  80. /**
  81. * Get a decent value to start the sync process at
  82. *
  83. * @hidden
  84. */
  85. export function getCurrentTimestampAdjusted(): number {
  86. const timestamp = Math.floor(Date.now() / 1000);
  87. return timestamp - (60 * 60 * 6);
  88. }
  89. /**
  90. * Is an input unlocked for spending at this height
  91. *
  92. * @hidden
  93. */
  94. export function isInputUnlocked(unlockTime: number, currentHeight: number): boolean {
  95. /* Might as well return fast with the case that is true for nearly all
  96. transactions (excluding coinbase) */
  97. if (unlockTime === 0) {
  98. return true;
  99. }
  100. if (unlockTime >= MAX_BLOCK_NUMBER) {
  101. return (Math.floor(Date.now() / 1000)) >= unlockTime;
  102. /* Plus one for CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS */
  103. } else {
  104. return currentHeight + 1 >= unlockTime;
  105. }
  106. }
  107. /**
  108. * Takes an amount in atomic units and pretty prints it.
  109. * Example: 12345607 -> 123,456.07 FED
  110. */
  111. export function prettyPrintAmount(amount: number, config: IConfig = new Config()): string {
  112. assertNumber(amount, 'amount');
  113. const tempConfig: Config = MergeConfig(config);
  114. /* Get the amount we need to divide atomic units by. 8 decimal places = 100000000 */
  115. const divisor: number = Math.pow(10000000, tempConfig.decimalPlaces);
  116. const dollars: number = amount >= 0 ? Math.floor(amount / divisor) : Math.ceil(amount / divisor);
  117. /* Make sure 1 is displaced as 01 */
  118. const cents: string = (Math.abs(amount % divisor)).toString().padStart(tempConfig.decimalPlaces, '0');
  119. /* Makes our numbers thousand separated. https://stackoverflow.com/a/2901298/8737306 */
  120. const formatted: string = dollars.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
  121. return formatted + '.' + cents + ' ' + tempConfig.ticker;
  122. }
  123. /**
  124. * Sleep for the given amount of milliseconds, async
  125. *
  126. * @hidden
  127. */
  128. export function delay(ms: number): Promise<void> {
  129. return new Promise((resolve) => setTimeout(resolve, ms));
  130. }
  131. /**
  132. * Split each amount into uniform amounts, e.g.
  133. * 1234567 = 1000000 + 200000 + 30000 + 4000 + 500 + 60 + 7
  134. *
  135. * @hidden
  136. */
  137. export function splitAmountIntoDenominations(amount: number, preventTooLargeOutputs: boolean = true): number[] {
  138. let multiplier: number = 1;
  139. let splitAmounts: number[] = [];
  140. while (amount >= 1) {
  141. const denomination: number = multiplier * (amount % 10);
  142. if (denomination > MAX_OUTPUT_SIZE_CLIENT && preventTooLargeOutputs) {
  143. /* Split amounts into ten chunks */
  144. let numSplitAmounts = 10;
  145. let splitAmount = denomination / 10;
  146. while (splitAmount > MAX_OUTPUT_SIZE_CLIENT) {
  147. splitAmount = Math.floor(splitAmount / 10);
  148. numSplitAmounts *= 10;
  149. }
  150. splitAmounts = splitAmounts.concat(Array(numSplitAmounts).fill(splitAmount));
  151. } else if (denomination !== 0) {
  152. splitAmounts.push(denomination);
  153. }
  154. amount = Math.floor(amount / 10);
  155. multiplier *= 10;
  156. }
  157. return splitAmounts;
  158. }
  159. /**
  160. * The formula for the block size is as follows. Calculate the
  161. * maxBlockCumulativeSize. This is equal to:
  162. * 100,000 + ((height * 102,400) / 1,051,200)
  163. * At a block height of 400k, this gives us a size of 138,964.
  164. * The constants this calculation arise from can be seen below, or in
  165. * src/CryptoNoteCore/Currency.cpp::maxBlockCumulativeSize(). Call this value
  166. * x.
  167. *
  168. * Next, calculate the median size of the last 100 blocks. Take the max of
  169. * this value, and 100,000. Multiply this value by 1.25. Call this value y.
  170. *
  171. * Finally, return the minimum of x and y.
  172. *
  173. * Or, in short: min(140k (slowly rising), 1.25 * max(100k, median(last 100 blocks size)))
  174. * Block size will always be 125k or greater (Assuming non testnet)
  175. *
  176. * To get the max transaction size, remove 600 from this value, for the
  177. * reserved miner transaction.
  178. *
  179. * We are going to ignore the median(last 100 blocks size), as it is possible
  180. * for a transaction to be valid for inclusion in a block when it is submitted,
  181. * but not when it actually comes to be mined, for example if the median
  182. * block size suddenly decreases. This gives a bit of a lower cap of max
  183. * tx sizes, but prevents anything getting stuck in the pool.
  184. *
  185. * @hidden
  186. */
  187. export function getMaxTxSize(currentHeight: number, blockTime: number = 30): number {
  188. const numerator: number = currentHeight * MAX_BLOCK_SIZE_GROWTH_SPEED_NUMERATOR;
  189. const denominator: number = (MAX_BLOCK_SIZE_GROWTH_SPEED_DENOMINATOR / blockTime);
  190. const growth: number = numerator / denominator;
  191. const x: number = MAX_BLOCK_SIZE_INITIAL + growth;
  192. const y: number = 125000;
  193. /* Need space for the miner transaction */
  194. return Math.min(x, y) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE;
  195. }
  196. /**
  197. * Converts an amount in bytes, say, 10000, into 9.76 KB
  198. *
  199. * @hidden
  200. */
  201. export function prettyPrintBytes(bytes: number): string {
  202. const suffixes: string[] = ['B', 'KB', 'MB', 'GB', 'TB'];
  203. let selectedSuffix: number = 0;
  204. while (bytes >= 1024 && selectedSuffix < suffixes.length - 1) {
  205. selectedSuffix++;
  206. bytes /= 1024;
  207. }
  208. return bytes.toFixed(2) + ' ' + suffixes[selectedSuffix];
  209. }
  210. /**
  211. * Returns whether the given word is in the mnemonic english dictionary. Note that
  212. * just because all the words are valid, does not mean the mnemonic is valid.
  213. *
  214. * Use isValidMnemonic to verify that.
  215. */
  216. export function isValidMnemonicWord(word: string): boolean {
  217. assertString(word, 'word');
  218. return English.includes(word);
  219. }
  220. /**
  221. * Verifies whether a mnemonic is valid. Returns a boolean, and an error messsage
  222. * describing what is invalid.
  223. */
  224. export async function isValidMnemonic(mnemonic: string, config: IConfig = new Config()): Promise<[boolean, string]> {
  225. assertString(mnemonic, 'mnemonic');
  226. const tempConfig: Config = MergeConfig(config);
  227. const words = mnemonic.split(' ').map((x) => x.toLowerCase());
  228. if (words.length !== 25) {
  229. return [false, 'The mnemonic seed given is the wrong length.'];
  230. }
  231. const invalidWords = [];
  232. for (const word of words) {
  233. if (!isValidMnemonicWord(word)) {
  234. invalidWords.push(word);
  235. }
  236. }
  237. if (invalidWords.length !== 0) {
  238. return [
  239. false,
  240. 'The following mnemonic words are not in the english word list: '
  241. + invalidWords.join(', '),
  242. ];
  243. }
  244. try {
  245. await Address.fromMnemonic(words.join(' '), undefined, tempConfig.addressPrefix);
  246. return [true, ''];
  247. } catch (err) {
  248. return [false, 'Mnemonic checksum word is invalid'];
  249. }
  250. }
  251. export function getMinimumTransactionFee(
  252. transactionSize: number,
  253. height: number,
  254. config: IConfig = new Config()): number {
  255. const tempConfig: Config = MergeConfig(config);
  256. return getTransactionFee(
  257. transactionSize,
  258. height,
  259. tempConfig.minimumFeePerByte,
  260. tempConfig,
  261. );
  262. }
  263. export function getTransactionFee(
  264. transactionSize: number,
  265. height: number,
  266. feePerByte: number,
  267. config: IConfig = new Config()): number {
  268. const tempConfig: Config = MergeConfig(config);
  269. const numChunks: number = Math.ceil(
  270. transactionSize / tempConfig.feePerByteChunkSize,
  271. );
  272. return numChunks * feePerByte * tempConfig.feePerByteChunkSize;
  273. }
  274. export function estimateTransactionSize(
  275. mixin: number,
  276. numInputs: number,
  277. numOutputs: number,
  278. havePaymentID: boolean,
  279. extraDataSize: number): number {
  280. const KEY_IMAGE_SIZE: number = 32;
  281. const OUTPUT_KEY_SIZE: number = 32;
  282. const AMOUNT_SIZE = 8 + 2; // varint
  283. const GLOBAL_INDEXES_VECTOR_SIZE_SIZE: number = 1 // varint
  284. const GLOBAL_INDEXES_INITIAL_VALUE_SIZE: number = 4; // varint
  285. const SIGNATURE_SIZE: number = 64;
  286. const EXTRA_TAG_SIZE: number = 1;
  287. const INPUT_TAG_SIZE: number = 1;
  288. const OUTPUT_TAG_SIZE: number = 1;
  289. const PUBLIC_KEY_SIZE: number = 32;
  290. const TRANSACTION_VERSION_SIZE: number = 1;
  291. const TRANSACTION_UNLOCK_TIME_SIZE: number = 8 + 2; // varint
  292. const EXTRA_DATA_SIZE: number = extraDataSize > 0 ? extraDataSize + 4 : 0;
  293. const PAYMENT_ID_SIZE: number = havePaymentID ? 34 : 0;
  294. /* The size of the transaction header */
  295. const headerSize: number = TRANSACTION_VERSION_SIZE
  296. + TRANSACTION_UNLOCK_TIME_SIZE
  297. + EXTRA_TAG_SIZE
  298. + EXTRA_DATA_SIZE
  299. + PUBLIC_KEY_SIZE
  300. + PAYMENT_ID_SIZE;
  301. /* The size of each transaction input */
  302. const inputSize: number = INPUT_TAG_SIZE
  303. + AMOUNT_SIZE
  304. + KEY_IMAGE_SIZE
  305. + SIGNATURE_SIZE
  306. + GLOBAL_INDEXES_VECTOR_SIZE_SIZE
  307. + GLOBAL_INDEXES_INITIAL_VALUE_SIZE
  308. + mixin * SIGNATURE_SIZE;
  309. const inputsSize: number = inputSize * numInputs;
  310. /* The size of each transaction output. */
  311. const outputSize: number = OUTPUT_TAG_SIZE
  312. + OUTPUT_KEY_SIZE
  313. + AMOUNT_SIZE;
  314. const outputsSize: number = outputSize * numOutputs;
  315. return headerSize + inputsSize + outputsSize;
  316. }