@leocuvee/turtlecoin-utils 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/.github/workflows/ci.yml +27 -0
  2. package/.idea/codeStyles/codeStyleConfig.xml +5 -0
  3. package/.idea/inspectionProfiles/Project_Default.xml +7 -0
  4. package/.idea/misc.xml +6 -0
  5. package/.idea/modules.xml +8 -0
  6. package/.idea/turtlecoin-utils.iml +12 -0
  7. package/.idea/vcs.xml +6 -0
  8. package/.travis.yml +11 -0
  9. package/CONTRIBUTING.md +3 -0
  10. package/LICENSE +674 -0
  11. package/README.md +203 -0
  12. package/config.json +7 -0
  13. package/docs/.nojekyll +0 -0
  14. package/docs/CNAME +1 -0
  15. package/docs/assets/css/main.css +2321 -0
  16. package/docs/assets/images/icons.png +0 -0
  17. package/docs/assets/images/icons@2x.png +0 -0
  18. package/docs/assets/images/widgets.png +0 -0
  19. package/docs/assets/images/widgets@2x.png +0 -0
  20. package/docs/assets/js/main.js +1 -0
  21. package/docs/assets/js/search.js +3 -0
  22. package/docs/classes/address.html +964 -0
  23. package/docs/classes/addressprefix.html +431 -0
  24. package/docs/classes/block.html +965 -0
  25. package/docs/classes/blocktemplate.html +695 -0
  26. package/docs/classes/cryptonote.html +1137 -0
  27. package/docs/classes/ed25519.keypair.html +400 -0
  28. package/docs/classes/ed25519.keys.html +373 -0
  29. package/docs/classes/extranoncetag.extranoncedata.html +454 -0
  30. package/docs/classes/extranoncetag.extranoncepaymentid.html +453 -0
  31. package/docs/classes/extranoncetag.iextranonce.html +347 -0
  32. package/docs/classes/extratag.extramergedmining.html +494 -0
  33. package/docs/classes/extratag.extranonce.html +530 -0
  34. package/docs/classes/extratag.extrapadding.html +456 -0
  35. package/docs/classes/extratag.extrapublickey.html +460 -0
  36. package/docs/classes/extratag.iextratag.html +355 -0
  37. package/docs/classes/levinpacket.html +674 -0
  38. package/docs/classes/levinpayloads.handshake.html +731 -0
  39. package/docs/classes/levinpayloads.ilevinpayload.html +318 -0
  40. package/docs/classes/levinpayloads.liteblock.html +494 -0
  41. package/docs/classes/levinpayloads.missingtransactions.html +494 -0
  42. package/docs/classes/levinpayloads.newblock.html +540 -0
  43. package/docs/classes/levinpayloads.newtransactions.html +402 -0
  44. package/docs/classes/levinpayloads.peerentry.html +610 -0
  45. package/docs/classes/levinpayloads.ping.html +450 -0
  46. package/docs/classes/levinpayloads.rawblock.html +344 -0
  47. package/docs/classes/levinpayloads.requestchain.html +402 -0
  48. package/docs/classes/levinpayloads.requestgetobjects.html +448 -0
  49. package/docs/classes/levinpayloads.requesttxpool.html +402 -0
  50. package/docs/classes/levinpayloads.responsechain.html +494 -0
  51. package/docs/classes/levinpayloads.responsegetobjects.html +540 -0
  52. package/docs/classes/levinpayloads.timedsync.html +540 -0
  53. package/docs/classes/multisig.html +930 -0
  54. package/docs/classes/multisigmessage.html +694 -0
  55. package/docs/classes/parentblock.html +347 -0
  56. package/docs/classes/transaction.html +925 -0
  57. package/docs/classes/transactioninputs.coinbaseinput.html +390 -0
  58. package/docs/classes/transactioninputs.itransactioninput.html +321 -0
  59. package/docs/classes/transactioninputs.keyinput.html +459 -0
  60. package/docs/classes/transactionoutputs.itransactionoutput.html +317 -0
  61. package/docs/classes/transactionoutputs.keyoutput.html +422 -0
  62. package/docs/enums/extranoncetag.noncetagtype.html +246 -0
  63. package/docs/enums/extratag.extratagtype.html +280 -0
  64. package/docs/enums/levinprotocol.commandtype.html +391 -0
  65. package/docs/enums/transactioninputs.inputtype.html +246 -0
  66. package/docs/enums/transactionoutputs.outputtype.html +229 -0
  67. package/docs/globals.html +238 -0
  68. package/docs/index.html +271 -0
  69. package/docs/interfaces/interfaces.config.html +590 -0
  70. package/docs/interfaces/interfaces.daemonblocktemplateresponse.html +323 -0
  71. package/docs/interfaces/interfaces.generatedinput.html +304 -0
  72. package/docs/interfaces/interfaces.generatedoutput.html +285 -0
  73. package/docs/interfaces/interfaces.inputkeys.html +304 -0
  74. package/docs/interfaces/interfaces.ipreparedtransaction.html +268 -0
  75. package/docs/interfaces/interfaces.output.html +399 -0
  76. package/docs/interfaces/interfaces.preparedringsignature.html +377 -0
  77. package/docs/interfaces/interfaces.preparedtransaction.html +329 -0
  78. package/docs/interfaces/interfaces.randomoutput.html +285 -0
  79. package/docs/interfaces/interfaces.transactionrecipient.html +285 -0
  80. package/docs/interfaces/multisiginterfaces.partialkeyimage.html +277 -0
  81. package/docs/interfaces/multisiginterfaces.partialsigningkey.html +277 -0
  82. package/docs/modules/ed25519.html +195 -0
  83. package/docs/modules/extranoncetag.html +208 -0
  84. package/docs/modules/extratag.html +216 -0
  85. package/docs/modules/interfaces.html +231 -0
  86. package/docs/modules/levinpayloads.html +247 -0
  87. package/docs/modules/levinprotocol.html +191 -0
  88. package/docs/modules/multisiginterfaces.html +195 -0
  89. package/docs/modules/transactioninputs.html +208 -0
  90. package/docs/modules/transactionoutputs.html +204 -0
  91. package/index.d.ts +417 -0
  92. package/index.js +1508 -0
  93. package/lib/base58.js +220 -0
  94. package/lib/biginteger.js +1591 -0
  95. package/lib/blocktemplate.js +408 -0
  96. package/lib/crypto.js +19698 -0
  97. package/lib/mnemonic.js +1204 -0
  98. package/lib/nacl-fast-cn.js +608 -0
  99. package/lib/ringsigs.js +24262 -0
  100. package/lib/sha3.js +477 -0
  101. package/package.json +58 -0
  102. package/src/Address.ts +433 -0
  103. package/src/AddressPrefix.ts +117 -0
  104. package/src/Block.ts +556 -0
  105. package/src/BlockTemplate.ts +289 -0
  106. package/src/Common.ts +105 -0
  107. package/src/Config.ts +66 -0
  108. package/src/CryptoNote.ts +1072 -0
  109. package/src/LevinPacket.ts +366 -0
  110. package/src/Multisig.ts +600 -0
  111. package/src/MultisigMessage.ts +374 -0
  112. package/src/ParentBlock.ts +39 -0
  113. package/src/Transaction.ts +628 -0
  114. package/src/Types/ED25519.ts +187 -0
  115. package/src/Types/IExtraNonce.ts +225 -0
  116. package/src/Types/IExtraTag.ts +507 -0
  117. package/src/Types/ITransaction.ts +230 -0
  118. package/src/Types/ITransactionInput.ts +190 -0
  119. package/src/Types/ITransactionOutput.ts +108 -0
  120. package/src/Types/LevinPayloads.ts +1576 -0
  121. package/src/Types/MultisigInterfaces.ts +65 -0
  122. package/src/Types/PortableStorage.ts +289 -0
  123. package/src/Types.ts +36 -0
  124. package/src/index.ts +36 -0
  125. package/test/template.json +6 -0
  126. package/test/test.js +1457 -0
  127. package/tests/blocktemplate.json +6 -0
  128. package/tests/tests.js +215 -0
  129. package/tsconfig.json +15 -0
  130. package/tslint.json +36 -0
  131. package/typedoc.json +10 -0
  132. package/webpack.config.js +15 -0
@@ -0,0 +1,1072 @@
1
+ // Copyright (c) 2018-2020, The TurtleCoin Developers
2
+ //
3
+ // Please see the included LICENSE file for more information.
4
+
5
+ import {Address} from './Address';
6
+ import {AddressPrefix} from './AddressPrefix';
7
+ import * as ConfigInterface from './Config';
8
+ import {Common} from './Common';
9
+ import {BigInteger, ED25519, TransactionInputs, TransactionOutputs, TurtleCoinCrypto, Interfaces} from './Types';
10
+ import {Transaction} from './Transaction';
11
+ import * as Numeral from 'numeral';
12
+ import Config = ConfigInterface.Interfaces.Config;
13
+
14
+ /** @ignore */
15
+ const Config = require('../config.json');
16
+
17
+ /** @ignore */
18
+ const UINT64_MAX = BigInteger(2).pow(64);
19
+
20
+ /**
21
+ * CryptoNote helper class for constructing transactions and performing
22
+ * various other cryptographic items during the receipt or transfer
23
+ * of funds on the network
24
+ */
25
+ export class CryptoNote {
26
+
27
+ protected config: Config = require('../config.json');
28
+
29
+ /**
30
+ * Constructs a new instance of the object
31
+ * If a configuration is supplied, it is also passed to the underlying
32
+ * cryptographic library
33
+ * @param [config] the base configuration to apply to our helper
34
+ */
35
+ constructor(config?: Config) {
36
+ if (config) {
37
+ Object.keys(config).forEach((key) => {
38
+ switch (key) {
39
+ case 'coinUnitPlaces':
40
+ this.config.coinUnitPlaces = config[key];
41
+ break;
42
+ case 'addressPrefix':
43
+ this.config.addressPrefix = config[key];
44
+ break;
45
+ case 'keccakIterations':
46
+ this.config.keccakIterations = config[key];
47
+ break;
48
+ case 'defaultNetworkFee':
49
+ this.config.defaultNetworkFee = config[key];
50
+ break;
51
+ case 'fusionMinInputCount':
52
+ this.config.fusionMinInputCount = config[key];
53
+ break;
54
+ case 'fusionMinInOutCountRatio':
55
+ this.config.fusionMinInOutCountRatio = config[key];
56
+ break;
57
+ case 'mmMiningBlockVersion':
58
+ this.config.mmMiningBlockVersion = config[key];
59
+ break;
60
+ case 'maximumOutputAmount':
61
+ this.config.maximumOutputAmount = config[key];
62
+ break;
63
+ case 'maximumOutputsPerTransaction':
64
+ this.config.maximumOutputsPerTransaction = config[key];
65
+ break;
66
+ case 'maximumExtraSize':
67
+ this.config.maximumExtraSize = config[key];
68
+ break;
69
+ case 'activateFeePerByteTransactions':
70
+ this.config.activateFeePerByteTransactions = config[key];
71
+ break;
72
+ case 'feePerByte':
73
+ this.config.feePerByte = config[key];
74
+ break;
75
+ case 'feePerByteChunkSize':
76
+ this.config.feePerByteChunkSize = config[key];
77
+ break;
78
+ }
79
+ });
80
+
81
+ TurtleCoinCrypto.userCryptoFunctions = config;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Converts absolute global index offsets to relative ones
87
+ * @param offsets the absolute offsets
88
+ * @returns the relative offsets
89
+ */
90
+ public absoluteToRelativeOffsets(offsets: BigInteger.BigInteger[] | string[] | number[]): number[] {
91
+ const result: number[] = [];
92
+
93
+ const tmpOffsets = Common.absoluteToRelativeOffsets(offsets);
94
+
95
+ tmpOffsets.forEach((offset) => result.push(offset.toJSNumber()));
96
+
97
+ return result;
98
+ }
99
+
100
+ /**
101
+ * Converts relative global index offsets to absolute offsets
102
+ * @param offsets the relative offsets
103
+ * @returns the absolute offsets
104
+ */
105
+ public relativeToAbsoluteOffsets(offsets: BigInteger.BigInteger[] | string[] | number[]): number[] {
106
+ const result: number[] = [];
107
+
108
+ const tmpOffsets = Common.relativeToAbsoluteOffsets(offsets);
109
+
110
+ tmpOffsets.forEach((offset) => result.push(offset.toJSNumber()));
111
+
112
+ return result;
113
+ }
114
+
115
+ /**
116
+ * Generates a key image from the supplied values
117
+ * @async
118
+ * @param transactionPublicKey the transaction public key
119
+ * @param privateViewKey the private view key
120
+ * @param publicSpendKey the public spend key
121
+ * @param privateSpendKey the private spend key
122
+ * @param outputIndex the index of the output in the transaction
123
+ * @returns the key image
124
+ */
125
+ public async generateKeyImage(
126
+ transactionPublicKey: string,
127
+ privateViewKey: string,
128
+ publicSpendKey: string,
129
+ privateSpendKey: string,
130
+ outputIndex: number): Promise<string> {
131
+ const derivation = await TurtleCoinCrypto.generateKeyDerivation(transactionPublicKey, privateViewKey);
132
+
133
+ return this.generateKeyImagePrimitive(publicSpendKey, privateSpendKey, outputIndex, derivation);
134
+ }
135
+
136
+ /**
137
+ * Primitive method for generating a key image from the supplied values
138
+ * @async
139
+ * @param publicSpendKey the public spend key
140
+ * @param privateSpendKey the private spend key
141
+ * @param outputIndex the index of the output in the transaction
142
+ * @param derivation the key derivation
143
+ * @returns the key image
144
+ */
145
+ public async generateKeyImagePrimitive(
146
+ publicSpendKey: string,
147
+ privateSpendKey: string,
148
+ outputIndex: number,
149
+ derivation: string): Promise<string> {
150
+ const publicEphemeral = await TurtleCoinCrypto.derivePublicKey(derivation, outputIndex, publicSpendKey);
151
+
152
+ const privateEphemeral = await TurtleCoinCrypto.deriveSecretKey(derivation, outputIndex, privateSpendKey);
153
+
154
+ return TurtleCoinCrypto.generateKeyImage(publicEphemeral, privateEphemeral);
155
+ }
156
+
157
+ /**
158
+ * Provides the public key of the supplied private key
159
+ * @async
160
+ * @param privateKey the private key
161
+ * @returns the public key
162
+ */
163
+ public async privateKeyToPublicKey(privateKey: string): Promise<string> {
164
+ return TurtleCoinCrypto.secretKeyToPublicKey(privateKey);
165
+ }
166
+
167
+ /**
168
+ * Scans the provided transaction outputs and returns those outputs which belong to us.
169
+ * If the privateSpendKey is not supplied, the private ephemeral and key image will be undefined
170
+ * @async
171
+ * @param transactionPublicKey the transaction public key
172
+ * @param outputs the transaction outputs
173
+ * @param privateViewKey the private view key
174
+ * @param publicSpendKey the public spend key
175
+ * @param [privateSpendKey] the private spend key
176
+ * @param [generatePartial] whether we should generate partial key images if the output belongs to use
177
+ * @returns an list of outputs that belong to us
178
+ */
179
+ public async scanTransactionOutputs(
180
+ transactionPublicKey: string,
181
+ outputs: Interfaces.Output[],
182
+ privateViewKey: string,
183
+ publicSpendKey: string,
184
+ privateSpendKey?: string,
185
+ generatePartial?: boolean,
186
+ ): Promise<Interfaces.Output[]> {
187
+ const promises = [];
188
+
189
+ for (const output of outputs) {
190
+ promises.push(
191
+ this.isOurTransactionOutput(
192
+ transactionPublicKey,
193
+ output,
194
+ privateViewKey,
195
+ publicSpendKey,
196
+ privateSpendKey,
197
+ generatePartial).catch(),
198
+ );
199
+ }
200
+
201
+ const results = await Promise.all(promises);
202
+
203
+ const ourOutputs: Interfaces.Output[] = [];
204
+
205
+ for (const result of results) {
206
+ if (result) {
207
+ ourOutputs.push(result);
208
+ }
209
+ }
210
+
211
+ return ourOutputs;
212
+ }
213
+
214
+ /**
215
+ * Scans the given transaction output to determine if it belongs to us, if so, we return the output
216
+ * with the private ephemeral and key image if the privateSpendKey was supplied
217
+ * @async
218
+ * @param transactionPublicKey the transaction public key
219
+ * @param output the transaction output
220
+ * @param privateViewKey the private view key
221
+ * @param publicSpendKey the public spend key
222
+ * @param [privateSpendKey] the private spend key
223
+ * @param [generatePartial] whether we should generate partial key images
224
+ * @returns the output if it belongs to us
225
+ */
226
+ public async isOurTransactionOutput(
227
+ transactionPublicKey: string,
228
+ output: Interfaces.Output,
229
+ privateViewKey: string,
230
+ publicSpendKey: string,
231
+ privateSpendKey?: string,
232
+ generatePartial?: boolean,
233
+ ): Promise<Interfaces.Output> {
234
+ const derivedKey = await TurtleCoinCrypto.generateKeyDerivation(transactionPublicKey, privateViewKey);
235
+
236
+ const publicEphemeral = await TurtleCoinCrypto.derivePublicKey(derivedKey, output.index, publicSpendKey);
237
+
238
+ if (publicEphemeral === output.key) {
239
+ output.input = {
240
+ publicEphemeral,
241
+ transactionKeys: {
242
+ publicKey: transactionPublicKey,
243
+ derivedKey,
244
+ outputIndex: output.index,
245
+ },
246
+ };
247
+
248
+ if (privateSpendKey) {
249
+ /* If we are forcing the generation of a partial key image then we
250
+ use the supplied private spend key in the key generation instead of
251
+ the privateEphemeral that we don't have
252
+ */
253
+ const privateEphemeral = (generatePartial) ?
254
+ privateSpendKey :
255
+ await TurtleCoinCrypto.deriveSecretKey(
256
+ derivedKey, output.index, privateSpendKey);
257
+
258
+ const derivedPublicEphemeral = await TurtleCoinCrypto.secretKeyToPublicKey(privateEphemeral);
259
+
260
+ if (derivedPublicEphemeral !== publicEphemeral && !generatePartial) {
261
+ throw new Error('Incorrect private spend key supplied');
262
+ }
263
+
264
+ const keyImage = await TurtleCoinCrypto.generateKeyImage(publicEphemeral, privateEphemeral);
265
+
266
+ output.input.privateEphemeral = privateEphemeral;
267
+
268
+ output.keyImage = keyImage;
269
+
270
+ output.isPartialKeyImage = (generatePartial) ? generatePartial : false;
271
+ }
272
+
273
+ return output;
274
+ }
275
+
276
+ throw new Error('Not our output');
277
+ }
278
+
279
+ /**
280
+ * Calculates the minimum transaction fee given the transaction size (bytes)
281
+ * @param txSize the transaction size in bytes
282
+ * @returns the minimum transaction fee
283
+ */
284
+ public calculateMinimumTransactionFee(txSize: number) {
285
+ const chunks = Math.ceil(
286
+ txSize /
287
+ (this.config.feePerByteChunkSize || Config.feePerByteChunkSize));
288
+
289
+ return chunks *
290
+ (this.config.feePerByteChunkSize || Config.feePerByteChunkSize) *
291
+ (this.config.feePerByte || Config.feePerByte);
292
+ }
293
+
294
+ /**
295
+ * Creates an integrated address using the supplied values
296
+ * @param address the wallet address
297
+ * @param paymentId the payment ID
298
+ * @param [prefix] the address prefix
299
+ * @returns the integrated address
300
+ */
301
+ public createIntegratedAddress(address: string, paymentId: string, prefix?: AddressPrefix | number): string {
302
+ if (typeof prefix === 'number') {
303
+ prefix = new AddressPrefix(prefix);
304
+ }
305
+
306
+ if (!prefix) {
307
+ prefix = new AddressPrefix(this.config.addressPrefix || Config.addressPrefix);
308
+ }
309
+
310
+ const addr = Address.fromAddress(address);
311
+
312
+ addr.paymentId = paymentId;
313
+
314
+ if (prefix) {
315
+ addr.prefix = prefix;
316
+ }
317
+
318
+ return addr.toString();
319
+ }
320
+
321
+ /**
322
+ * Formats atomic units into human readable units
323
+ * @param amount the amount in atomic units
324
+ * @returns the amount in human readable units
325
+ */
326
+ public formatMoney(amount: BigInteger.BigInteger | number): string {
327
+ let places = '';
328
+
329
+ for (let i = 0; i < (this.config.coinUnitPlaces || Config.coinUnitPlaces); i++) {
330
+ places += '0';
331
+ }
332
+
333
+ if (typeof amount !== 'number') {
334
+ amount = amount.toJSNumber();
335
+ }
336
+
337
+ return Numeral(
338
+ amount / Math.pow(10, this.config.coinUnitPlaces || Config.coinUnitPlaces),
339
+ ).format('0,0.' + places);
340
+ }
341
+
342
+ /**
343
+ * Generates an array of transaction outputs (new destinations) for the given address
344
+ * and the given amount within the allowed rules of the network
345
+ * @param address the destination wallet address
346
+ * @param amount the amount to send
347
+ * @returns a list of transaction outputs
348
+ */
349
+ public generateTransactionOutputs(address: string, amount: number): Interfaces.GeneratedOutput[] {
350
+ if (amount < 0) {
351
+ throw new RangeError('Amount must be a positive value');
352
+ }
353
+
354
+ const result: Interfaces.GeneratedOutput[] = [];
355
+
356
+ const destination = Address.fromAddress(address);
357
+
358
+ const amountChars = amount.toString().split('').reverse();
359
+
360
+ for (let i = 0; i < amountChars.length; i++) {
361
+ const amt = parseInt(amountChars[i], 10) * Math.pow(10, i);
362
+
363
+ if (amt > (this.config.maximumOutputAmount || Config.maximumOutputAmount)) {
364
+ let splitAmt = amt;
365
+
366
+ while (splitAmt >= (this.config.maximumOutputAmount || Config.maximumOutputAmount)) {
367
+ result.push({
368
+ amount: this.config.maximumOutputAmount || Config.maximumOutputAmount,
369
+ destination: destination,
370
+ });
371
+ splitAmt -= this.config.maximumOutputAmount || Config.maximumOutputAmount;
372
+ }
373
+ } else if (amt !== 0) {
374
+ result.push({
375
+ amount: amt,
376
+ destination: destination,
377
+ });
378
+ }
379
+ }
380
+
381
+ return result;
382
+ }
383
+
384
+ /**
385
+ * Signs an arbitrary message using the supplied private key
386
+ * @async
387
+ * @param message the arbitrary message to sign
388
+ * @param privateKey the private key to sign with
389
+ * @returns the signature
390
+ */
391
+ public async signMessage(message: any, privateKey: string): Promise<string> {
392
+ if (typeof message !== 'string') {
393
+ message = JSON.stringify(message);
394
+ }
395
+
396
+ const publicKey = await TurtleCoinCrypto.secretKeyToPublicKey(privateKey);
397
+
398
+ const hex = Buffer.from(message);
399
+
400
+ const hash = await TurtleCoinCrypto.cn_fast_hash(hex.toString('hex'));
401
+
402
+ return TurtleCoinCrypto.generateSignature(hash, publicKey, privateKey);
403
+ }
404
+
405
+ /**
406
+ * Verifies the signature of an arbitrary message using the signature and the supplied public key
407
+ * @async
408
+ * @param message the arbitrary message that was signed
409
+ * @param publicKey the public key of the private key that was used to sign
410
+ * @param signature the signature
411
+ * @returns whether the signature is valid
412
+ */
413
+ public async verifyMessageSignature(message: any, publicKey: string, signature: string): Promise<void> {
414
+ if (typeof message !== 'string') {
415
+ message = JSON.stringify(message);
416
+ }
417
+
418
+ const hex = Buffer.from(message);
419
+
420
+ const hash = await TurtleCoinCrypto.cn_fast_hash(hex.toString('hex'));
421
+
422
+ const valid = await TurtleCoinCrypto.checkSignature(hash, publicKey, signature);
423
+
424
+ if (!valid) {
425
+ throw new Error('Invalid signature');
426
+ }
427
+ }
428
+
429
+ /**
430
+ * Constructs a new Transaction using the supplied values.
431
+ * The resulting transaction can be broadcasted to the TurtleCoin network
432
+ * @async
433
+ * @param outputs the new outputs for the transaction (TO)
434
+ * @param inputs outputs we will be spending (FROM)
435
+ * @param randomOutputs the random outputs to use for mixing
436
+ * @param mixin the number of mixins to use
437
+ * @param [feeAmount] the transaction fee amount to pay
438
+ * @param [paymentId] the payment ID to use in the transaction,
439
+ * @param [unlockTime] the unlock time or block height for the transaction
440
+ * @param [extraData] arbitrary extra data to include in the transaction extra field
441
+ * @returns the newly created transaction object
442
+ */
443
+ public async createTransaction(
444
+ outputs: Interfaces.GeneratedOutput[],
445
+ inputs: Interfaces.Output[],
446
+ randomOutputs: Interfaces.RandomOutput[][],
447
+ mixin: number,
448
+ feeAmount?: number,
449
+ paymentId?: string,
450
+ unlockTime?: number,
451
+ extraData?: any,
452
+ ): Promise<Transaction> {
453
+ const feePerByte =
454
+ this.config.activateFeePerByteTransactions || Config.activateFeePerByteTransactions || false;
455
+
456
+ const prepared = await this.createTransactionStructure(
457
+ outputs, inputs, randomOutputs, mixin, feeAmount, paymentId, unlockTime, extraData,
458
+ );
459
+
460
+ const txPrefixHash = prepared.transaction.prefixHash;
461
+
462
+ const promises = [];
463
+
464
+ for (let i = 0; i < prepared.inputs.length; i++) {
465
+ const input = prepared.inputs[i];
466
+ const srcKeys: string[] = [];
467
+
468
+ if (!input.input.privateEphemeral) {
469
+ throw new Error('private ephemeral missing from input');
470
+ }
471
+
472
+ input.outputs.forEach((out) => srcKeys.push(out.key));
473
+
474
+ promises.push(
475
+ generateRingSignatures(
476
+ txPrefixHash,
477
+ input.keyImage,
478
+ srcKeys,
479
+ input.input.privateEphemeral,
480
+ input.realOutputIndex,
481
+ i));
482
+ }
483
+
484
+ const tmpSignatures = await Promise.all(promises);
485
+
486
+ tmpSignatures.sort((a, b) => (a.index > b.index) ? 1 : -1);
487
+
488
+ const signatures: string[][] = [];
489
+
490
+ tmpSignatures.forEach((sigs) => {
491
+ const sigSet: string[] = [];
492
+ sigs.signatures.forEach((sig) => sigSet.push(sig));
493
+ signatures.push(sigSet);
494
+ });
495
+
496
+ prepared.transaction.signatures = signatures;
497
+
498
+ const minimumFee = this.calculateMinimumTransactionFee(prepared.transaction.size);
499
+
500
+ if (feeAmount && feeAmount !== 0 && feePerByte && feeAmount < minimumFee) {
501
+ throw new Error('Transaction fee [' + prepared.transaction.fee +
502
+ '] is not enough for network transmission: ' + minimumFee);
503
+ }
504
+
505
+ return prepared.transaction;
506
+ }
507
+
508
+ /**
509
+ * Constructs a new Transaction using the supplied values.
510
+ * Note: Does not sign the transaction
511
+ * @async
512
+ * @param outputs the new outputs for the transaction (TO)
513
+ * @param inputs outputs we will be spending (FROM)
514
+ * @param randomOutputs the random outputs to use for mixing
515
+ * @param mixin the number of mixins to use
516
+ * @param [feeAmount] the transaction fee amount to pay
517
+ * @param [paymentId] the payment ID to use in the transaction,
518
+ * @param [unlockTime] the unlock time or block height for the transaction
519
+ * @param [extraData] arbitrary extra data to include in the transaction extra field
520
+ * @returns the newly created transaction object and it's input data
521
+ */
522
+ public async createTransactionStructure(
523
+ outputs: Interfaces.GeneratedOutput[],
524
+ inputs: Interfaces.Output[],
525
+ randomOutputs: Interfaces.RandomOutput[][],
526
+ mixin: number,
527
+ feeAmount?: number,
528
+ paymentId?: string,
529
+ unlockTime?: number,
530
+ extraData?: any,
531
+ ): Promise<Interfaces.IPreparedTransaction> {
532
+ if (typeof feeAmount === 'undefined') {
533
+ feeAmount = this.config.defaultNetworkFee || Config.defaultNetworkFee;
534
+ }
535
+ unlockTime = unlockTime || 0;
536
+
537
+ const feePerByte =
538
+ this.config.activateFeePerByteTransactions || Config.activateFeePerByteTransactions || false;
539
+
540
+ if (randomOutputs.length !== inputs.length && mixin !== 0) {
541
+ throw new Error('The sets of random outputs supplied does not match the number of inputs supplied');
542
+ }
543
+
544
+ for (const randomOutput of randomOutputs) {
545
+ if (randomOutput.length < mixin) {
546
+ throw new Error('There are not enough random outputs to mix with');
547
+ }
548
+ }
549
+
550
+ const neededMoney = BigInteger.zero;
551
+ let integratedPaymentId: string | undefined;
552
+
553
+ for (const output of outputs) {
554
+ if (output.amount <= 0) {
555
+ throw new RangeError('Cannot create an output with an amount <= 0');
556
+ }
557
+ if (output.amount > (this.config.maximumOutputAmount || Config.maximumOutputAmount)) {
558
+ throw new RangeError('Cannot create an output with an amount > ' +
559
+ (this.config.maximumOutputAmount || Config.maximumOutputAmount));
560
+ }
561
+ neededMoney.add(output.amount);
562
+ if (neededMoney.greater(UINT64_MAX)) {
563
+ throw new RangeError('Total output amount exceeds UINT64_MAX');
564
+ }
565
+ /* Check to see if our destination contains differeing payment IDs via integrated addresses */
566
+ if (output.destination.paymentId) {
567
+ if (!integratedPaymentId) {
568
+ integratedPaymentId = output.destination.paymentId;
569
+ } else if (integratedPaymentId && integratedPaymentId !== output.destination.paymentId) {
570
+ throw new Error('Cannot perform multiple transfers with differing integrated addresses');
571
+ }
572
+ }
573
+ }
574
+
575
+ /* If we found an integrated payment ID in the destinations and we supplied a payment ID
576
+ in our call to this method and they do not match, this will result in a failure */
577
+ if (integratedPaymentId && paymentId && integratedPaymentId !== paymentId) {
578
+ throw new Error('Transfer destinations contains an integrated payment ID that does not match the payment' +
579
+ 'ID supplied to this method');
580
+ }
581
+
582
+ const foundMoney = BigInteger.zero;
583
+
584
+ for (const input of inputs) {
585
+ if (input.amount <= 0) {
586
+ throw new RangeError('Cannot spend outputs with an amount <= 0');
587
+ }
588
+ foundMoney.add(input.amount);
589
+ if (foundMoney.greater(UINT64_MAX)) {
590
+ throw new RangeError('Total input amount exceeds UINT64_MAX');
591
+ }
592
+ }
593
+
594
+ if (neededMoney.greater(foundMoney)) {
595
+ throw new Error('We need more funds than was currently supplied for the transaction');
596
+ }
597
+
598
+ const change = foundMoney.subtract(neededMoney);
599
+
600
+ if (!feePerByte && feeAmount && change.lesser(feeAmount)) {
601
+ throw new Error('We have not spent all of what we sent in');
602
+ }
603
+
604
+ const transactionInputs = await prepareTransactionInputs(inputs, randomOutputs, mixin);
605
+
606
+ const transactionOutputs = await prepareTransactionOutputs(outputs);
607
+
608
+ if (transactionOutputs.outputs.length >
609
+ (this.config.maximumOutputsPerTransaction || Config.maximumOutputsPerTransaction)) {
610
+ throw new RangeError('Tried to create a transaction with more outputs than permitted');
611
+ }
612
+
613
+ if (feeAmount === 0) {
614
+ if (transactionInputs.length < 12) {
615
+ throw new Error('Sending a [0] fee transaction (fusion) requires a minimum of ['
616
+ + (this.config.fusionMinInputCount || Config.fusionMinInputCount) + '] inputs');
617
+ }
618
+ const ratio = this.config.fusionMinInOutCountRatio || Config.fusionMinInOutCountRatio;
619
+ if ((transactionInputs.length / transactionOutputs.outputs.length) < ratio) {
620
+ throw new Error('Sending a [0] fee transaction (fusion) requires the ' +
621
+ 'correct input:output ratio be met');
622
+ }
623
+ }
624
+
625
+ const tx = new Transaction();
626
+ tx.unlockTime = BigInteger(unlockTime);
627
+ tx.addPublicKey(transactionOutputs.transactionKeys.publicKey);
628
+ tx.transactionKeys = transactionOutputs.transactionKeys;
629
+
630
+ if (integratedPaymentId) {
631
+ tx.addPaymentId(integratedPaymentId);
632
+ } else if (paymentId) {
633
+ tx.addPaymentId(paymentId);
634
+ }
635
+
636
+ if (extraData) {
637
+ if (!(extraData instanceof Buffer)) {
638
+ extraData = (typeof extraData === 'string') ?
639
+ Buffer.from(extraData) : Buffer.from(JSON.stringify(extraData));
640
+ }
641
+
642
+ tx.addData(extraData);
643
+ }
644
+
645
+ transactionInputs.sort((a, b) => {
646
+ return (BigInteger(a.keyImage, 16).compare(BigInteger(b.keyImage, 16)) * -1);
647
+ });
648
+
649
+ for (const input of transactionInputs) {
650
+ let offsets: BigInteger.BigInteger[] = [];
651
+
652
+ input.outputs.forEach((output) => offsets.push(BigInteger(output.index)));
653
+
654
+ offsets = Common.absoluteToRelativeOffsets(offsets);
655
+
656
+ tx.inputs.push(new TransactionInputs.KeyInput(input.amount, offsets, input.keyImage));
657
+ }
658
+
659
+ for (const output of transactionOutputs.outputs) {
660
+ tx.outputs.push(new TransactionOutputs.KeyOutput(output.amount, output.key));
661
+ }
662
+
663
+ if (tx.extra.length > (this.config.maximumExtraSize || Config.maximumExtraSize)) {
664
+ throw new Error('Transaction extra exceeds the limit of [' +
665
+ (this.config.maximumExtraSize || Config.maximumExtraSize) + '] bytes');
666
+ }
667
+
668
+ return {
669
+ transaction: tx,
670
+ inputs: transactionInputs,
671
+ };
672
+ }
673
+
674
+ /**
675
+ * Constructs a new Transaction using the supplied values.
676
+ * The resulting transaction can be broadcasted to the TurtleCoin network
677
+ * @async
678
+ * @param outputs the new outputs for the transaction (TO)
679
+ * @param inputs outputs we will be spending (FROM)
680
+ * @param randomOutputs the random outputs to use for mixing
681
+ * @param mixin the number of mixins to use
682
+ * @param [feeAmount] the transaction fee amount to pay
683
+ * @param [paymentId] the payment ID to use in the transaction,
684
+ * @param [unlockTime] the unlock time or block height for the transaction
685
+ * @param [extraData] arbitrary extra data to include in the transaction extra field
686
+ * @returns the newly created transaction object with prepared signatures
687
+ */
688
+ public async prepareTransaction(
689
+ outputs: Interfaces.GeneratedOutput[],
690
+ inputs: Interfaces.Output[],
691
+ randomOutputs: Interfaces.RandomOutput[][],
692
+ mixin: number,
693
+ feeAmount?: number,
694
+ paymentId?: string,
695
+ unlockTime?: number,
696
+ extraData?: any,
697
+ ): Promise<Interfaces.PreparedTransaction> {
698
+ const feePerByte =
699
+ this.config.activateFeePerByteTransactions || Config.activateFeePerByteTransactions || false;
700
+
701
+ const prepared = await this.createTransactionStructure(
702
+ outputs, inputs, randomOutputs, mixin, feeAmount, paymentId, unlockTime, extraData,
703
+ );
704
+
705
+ const recipients: Interfaces.TransactionRecipient[] = [];
706
+
707
+ for (const output of outputs) {
708
+ recipients.push({
709
+ address: output.destination.address,
710
+ amount: output.amount,
711
+ });
712
+ }
713
+
714
+ const txPrefixHash = prepared.transaction.prefixHash;
715
+
716
+ const promises = [];
717
+
718
+ for (let i = 0; i < prepared.inputs.length; i++) {
719
+ const input = prepared.inputs[i];
720
+ const srcKeys: string[] = [];
721
+
722
+ input.outputs.forEach((out) => srcKeys.push(out.key));
723
+
724
+ promises.push(
725
+ prepareRingSignatures(
726
+ txPrefixHash,
727
+ input.keyImage,
728
+ srcKeys,
729
+ input.realOutputIndex,
730
+ input.input.transactionKeys.derivedKey,
731
+ input.input.transactionKeys.outputIndex,
732
+ i));
733
+ }
734
+
735
+ const results = await Promise.all(promises);
736
+
737
+ results.sort((a, b) => (a.index > b.index) ? 1 : -1);
738
+
739
+ const signatures: string[][] = [];
740
+
741
+ const signatureMeta: Interfaces.PreparedRingSignature[] = [];
742
+
743
+ for (const result of results) {
744
+ const sigSet: string[] = [];
745
+ if (!result.signatures) {
746
+ throw new Error('Prepared signatures are incomplete');
747
+ }
748
+
749
+ result.signatures.forEach((sig) => sigSet.push(sig));
750
+ signatures.push(sigSet);
751
+
752
+ const meta: Interfaces.PreparedRingSignature = {
753
+ index: result.index,
754
+ realOutputIndex: result.realOutputIndex,
755
+ key: result.key,
756
+ inputKeys: result.inputKeys,
757
+ input: {
758
+ derivation: prepared.inputs[result.index].input.transactionKeys.derivedKey,
759
+ outputIndex: prepared.inputs[result.index].input.transactionKeys.outputIndex,
760
+ },
761
+ };
762
+
763
+ signatureMeta.push(meta);
764
+ }
765
+
766
+ prepared.transaction.signatures = signatures;
767
+
768
+ const minimumFee = this.calculateMinimumTransactionFee(prepared.transaction.size);
769
+
770
+ if (feeAmount && feeAmount !== 0 && feePerByte && feeAmount < minimumFee) {
771
+ throw new Error('Transaction fee [' + prepared.transaction.fee +
772
+ '] is not enough for network transmission: ' + minimumFee);
773
+ }
774
+
775
+ return {
776
+ transaction: prepared.transaction,
777
+ transactionRecipients: recipients,
778
+ transactionPrivateKey: prepared.transaction.transactionKeys.privateKey,
779
+ signatureMeta: signatureMeta,
780
+ };
781
+ }
782
+
783
+ /**
784
+ * Completes a prepared transaction using the supplied private ephemeral
785
+ * The resulting transaction can be broadcast to the network. Please note that the PreparedTransaction
786
+ * signatures meta data must be updated to include the proper private ephemeral
787
+ * @param preparedTransaction the prepared transaction
788
+ * @param privateSpendKey the private spend key of the wallet that contains the funds
789
+ * @returns the completed transaction
790
+ */
791
+ public async completeTransaction(
792
+ preparedTransaction: Interfaces.PreparedTransaction,
793
+ privateSpendKey: string,
794
+ ): Promise<Transaction> {
795
+ const promises = [];
796
+
797
+ const tx = preparedTransaction.transaction;
798
+
799
+ if (!preparedTransaction.signatureMeta) {
800
+ throw new Error('No transaction signature meta data supplied');
801
+ }
802
+
803
+ for (const meta of preparedTransaction.signatureMeta) {
804
+ if (!meta.input || !meta.input.derivation || !meta.input.outputIndex) {
805
+ throw new Error('Meta data is missing critical information');
806
+ }
807
+
808
+ promises.push(completeRingSignatures(
809
+ privateSpendKey,
810
+ meta.input.derivation,
811
+ meta.input.outputIndex,
812
+ meta.realOutputIndex,
813
+ meta.key,
814
+ tx.signatures[meta.index],
815
+ meta.index,
816
+ ));
817
+ }
818
+
819
+ const results = await Promise.all(promises);
820
+
821
+ for (const result of results) {
822
+ tx.signatures[result.index] = result.signatures;
823
+ }
824
+
825
+ const prefixHash = tx.prefixHash;
826
+
827
+ const checkPromises = [];
828
+
829
+ for (let i = 0; i < tx.inputs.length; i++) {
830
+ checkPromises.push(checkRingSignatures(
831
+ prefixHash,
832
+ (tx.inputs[i] as TransactionInputs.KeyInput).keyImage,
833
+ getInputKeys(preparedTransaction.signatureMeta, i),
834
+ tx.signatures[i],
835
+ ));
836
+ }
837
+
838
+ const validSigs = await Promise.all(checkPromises);
839
+
840
+ for (const valid of validSigs) {
841
+ if (!valid) {
842
+ throw new Error('Could not complete ring signatures');
843
+ }
844
+ }
845
+
846
+ return preparedTransaction.transaction;
847
+ }
848
+ }
849
+
850
+ /** @ignore */
851
+ async function checkRingSignatures(
852
+ hash: string,
853
+ keyImage: string,
854
+ publicKeys: string[],
855
+ signatures: string[],
856
+ ): Promise<boolean> {
857
+ return TurtleCoinCrypto.checkRingSignatures(hash, keyImage, publicKeys, signatures);
858
+ }
859
+
860
+ /** @ignore */
861
+ async function generateRingSignatures(
862
+ hash: string,
863
+ keyImage: string,
864
+ publicKeys: string[],
865
+ privateKey: string,
866
+ realOutputIndex: number,
867
+ index: number,
868
+ ): Promise<Interfaces.GeneratedRingSignatures> {
869
+ const signatures = await TurtleCoinCrypto.generateRingSignatures(
870
+ hash,
871
+ keyImage,
872
+ publicKeys,
873
+ privateKey,
874
+ realOutputIndex);
875
+
876
+ const valid = await checkRingSignatures(hash, keyImage, publicKeys, signatures);
877
+
878
+ if (!valid) {
879
+ throw new Error('Could not generate ring signatures');
880
+ }
881
+
882
+ return {signatures, index};
883
+ }
884
+
885
+ /** @ignore */
886
+ async function prepareRingSignatures(
887
+ hash: string,
888
+ keyImage: string,
889
+ publicKeys: string[],
890
+ realOutputIndex: number,
891
+ derivation: string,
892
+ outputIndex: number,
893
+ index: number,
894
+ ): Promise<Interfaces.PreparedRingSignature> {
895
+ const prepped = await TurtleCoinCrypto.prepareRingSignatures(hash, keyImage, publicKeys, realOutputIndex);
896
+
897
+ return {
898
+ index: index,
899
+ realOutputIndex: realOutputIndex,
900
+ key: prepped.key,
901
+ signatures: prepped.signatures,
902
+ inputKeys: publicKeys,
903
+ input: {
904
+ derivation,
905
+ outputIndex,
906
+ },
907
+ };
908
+ }
909
+
910
+ /** @ignore */
911
+ async function completeRingSignatures(
912
+ privateSpendKey: string,
913
+ derivation: string,
914
+ outputIndex: number,
915
+ realOutputIndex: number,
916
+ key: string,
917
+ sigs: string[],
918
+ index: number,
919
+ ): Promise<Interfaces.GeneratedRingSignatures> {
920
+ const privateEphemeral = await TurtleCoinCrypto.deriveSecretKey(derivation, outputIndex, privateSpendKey);
921
+
922
+ const signatures = await TurtleCoinCrypto.completeRingSignatures(privateEphemeral, realOutputIndex, key, sigs);
923
+
924
+ return {signatures, index};
925
+ }
926
+
927
+ /** @ignore */
928
+ function prepareTransactionInputs(
929
+ inputs: Interfaces.Output[],
930
+ randomOutputs: Interfaces.RandomOutput[][],
931
+ mixin: number): Interfaces.PreparedInput[] {
932
+ if (inputs.length !== randomOutputs.length && mixin !== 0) {
933
+ throw new Error('There are not enough random output sets to mix with the real outputs');
934
+ }
935
+
936
+ for (const randomOutput of randomOutputs) {
937
+ if (randomOutput.length < mixin) {
938
+ throw new Error('There are not enough random outputs to mix with');
939
+ }
940
+ }
941
+
942
+ const mixedInputs: Interfaces.PreparedInput[] = [];
943
+
944
+ for (let i = 0; i < inputs.length; i++) {
945
+ const mixedOutputs: Interfaces.PreparedInputOutputs[] = [];
946
+ const realOutput = inputs[i];
947
+
948
+ if (!realOutput.keyImage) {
949
+ throw new Error('input is missing its key image');
950
+ }
951
+
952
+ if (!realOutput.input) {
953
+ throw new Error('input is missing mandatory data fields');
954
+ }
955
+
956
+ if (realOutput.amount <= 0) {
957
+ throw new RangeError('Real inputs cannot have an amount <= 0');
958
+ }
959
+
960
+ if (mixin !== 0) {
961
+ const fakeOutputs = randomOutputs[i];
962
+
963
+ fakeOutputs.sort((a, b) => {
964
+ return BigInteger(a.globalIndex).compare(b.globalIndex);
965
+ });
966
+
967
+ for (const fakeOutput of fakeOutputs) {
968
+ if (mixedOutputs.length === mixin) {
969
+ continue;
970
+ }
971
+
972
+ if (fakeOutput.globalIndex === realOutput.globalIndex) {
973
+ continue;
974
+ }
975
+
976
+ mixedOutputs.push({
977
+ key: fakeOutput.key,
978
+ index: fakeOutput.globalIndex,
979
+ });
980
+ }
981
+
982
+ if (mixedOutputs.length < mixin) {
983
+ throw new Error('It is impossible to mix with yourself. Find some more random outputs and try again.');
984
+ }
985
+ }
986
+
987
+ mixedOutputs.push({
988
+ key: realOutput.key,
989
+ index: realOutput.globalIndex,
990
+ });
991
+
992
+ mixedOutputs.sort((a, b) => {
993
+ return BigInteger(a.index).compare(b.index);
994
+ });
995
+
996
+ const input = {
997
+ amount: realOutput.amount,
998
+ realOutputIndex: 0,
999
+ keyImage: realOutput.keyImage,
1000
+ input: realOutput.input,
1001
+ outputs: mixedOutputs,
1002
+ };
1003
+
1004
+ for (let j = 0; j < mixedOutputs.length; j++) {
1005
+ if (mixedOutputs[j].index === realOutput.globalIndex) {
1006
+ input.realOutputIndex = j;
1007
+ }
1008
+ }
1009
+
1010
+ mixedInputs.push(input);
1011
+ }
1012
+
1013
+ return mixedInputs;
1014
+ }
1015
+
1016
+ /** @ignore */
1017
+ async function prepareTransactionOutputs(outputs: Interfaces.GeneratedOutput[]): Promise<Interfaces.PreparedOutputs> {
1018
+ async function prepareOutput(
1019
+ destination: Address,
1020
+ amount: number,
1021
+ index: number,
1022
+ privateKey: string): Promise<Interfaces.PreparedOutput> {
1023
+ const outDerivation = await TurtleCoinCrypto.generateKeyDerivation(destination.view.publicKey, privateKey);
1024
+
1025
+ const outPublicEphemeral = await TurtleCoinCrypto.derivePublicKey(
1026
+ outDerivation,
1027
+ index,
1028
+ destination.spend.publicKey);
1029
+
1030
+ return {
1031
+ amount,
1032
+ key: outPublicEphemeral,
1033
+ };
1034
+ }
1035
+
1036
+ const keys = await TurtleCoinCrypto.generateKeys();
1037
+
1038
+ const transactionKeys: ED25519.KeyPair = new ED25519.KeyPair(keys.publicKey, keys.privateKey);
1039
+
1040
+ outputs.sort((a, b) => (a.amount > b.amount) ? 1 : ((b.amount > a.amount) ? -1 : 0));
1041
+
1042
+ const promises = [];
1043
+
1044
+ for (let i = 0; i < outputs.length; i++) {
1045
+ const output = outputs[i];
1046
+ if (output.amount <= 0) {
1047
+ throw new RangeError('Amount cannot be <= 0');
1048
+ }
1049
+
1050
+ promises.push(prepareOutput(output.destination, output.amount, i, transactionKeys.privateKey));
1051
+ }
1052
+
1053
+ const preparedOutputs = await Promise.all(promises);
1054
+
1055
+ return {
1056
+ transactionKeys,
1057
+ outputs: preparedOutputs,
1058
+ };
1059
+ }
1060
+
1061
+ /** @ignore */
1062
+ function getInputKeys(preparedSignatures: Interfaces.PreparedRingSignature[], index: number): string[] {
1063
+ for (const meta of preparedSignatures) {
1064
+ if (meta.index === index) {
1065
+ if (meta.inputKeys) {
1066
+ return meta.inputKeys;
1067
+ }
1068
+ }
1069
+ }
1070
+
1071
+ throw new Error('Could not locate input keys in the prepared signatures');
1072
+ }