@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
package/index.js ADDED
@@ -0,0 +1,1508 @@
1
+ // Copyright (c) Lucas Jones
2
+ // Copyright (c) 2014-2017, MyMonero.com
3
+ // Copyright (c) 2016, Paul Shapiro
4
+ // Copyright (c) 2017, Luigi111
5
+ // Copyright (c) 2018-2019, The TurtleCoin Developers
6
+ //
7
+ // Please see the included LICENSE file for more information.
8
+
9
+ 'use strict'
10
+
11
+ const BigInteger = require('./lib/biginteger.js')
12
+ const Base58 = require('./lib/base58.js')
13
+ const BlockTemplate = require('./lib/blocktemplate.js')
14
+ const Mnemonic = require('./lib/mnemonic.js')
15
+ const Varint = require('varint')
16
+ const SecureRandomString = require('secure-random-string')
17
+ const Numeral = require('numeral')
18
+
19
+ /* Try to load the Node C++ Addon module
20
+ so that we can use that as it's a magnitudes
21
+ faster, if not, we'll fall back to the
22
+ JS implementations of the crypto functions */
23
+ var TurtleCoinCrypto
24
+ var NACL
25
+ var CNCrypto
26
+ var SHA3
27
+ try {
28
+ TurtleCoinCrypto = require('turtlecoin-crypto')
29
+ } catch (e) {
30
+ /* Silence standardjs check */
31
+ TurtleCoinCrypto = e
32
+ TurtleCoinCrypto = false
33
+
34
+ /* These are the JS implementations of the
35
+ crypto functions that we need to do what
36
+ we are trying to do. They are slow and
37
+ painful and only used as a last ditch
38
+ attempt if we can't use the native module
39
+ for whatever reason */
40
+ NACL = require('./lib/nacl-fast-cn.js')
41
+ CNCrypto = require('./lib/crypto.js')
42
+ SHA3 = require('./lib/sha3.js')
43
+ }
44
+
45
+ /* This sets up the ability for the caller to specify
46
+ their own cryptographic functions to use for parts
47
+ of the methods used by this module. It is tracked outside
48
+ of the instance of the module instance as there are
49
+ a number of function calls that are not directly exposed
50
+ to the caller to prevent confusion */
51
+ const userCryptoFunctions = {}
52
+
53
+ const SIZES = {
54
+ HASH: 64,
55
+ KEY: 64,
56
+ PAYMENTID_HEX: 64,
57
+ CHECKSUM: 8,
58
+ ECPOINT: 32,
59
+ GEP3: 160,
60
+ GEP2: 120,
61
+ GEP1P1: 160,
62
+ GECACHED: 160,
63
+ ECSCALAR: 32,
64
+ KEYIMAGE: 32,
65
+ GEDSMP: 160 * 8,
66
+ SIGNATURE: 64
67
+ }
68
+ const TX_EXTRA_NONCE_MAX_COUNT = 255
69
+ const TX_EXTRA_TAGS = {
70
+ PADDING: '00',
71
+ PUBKEY: '01',
72
+ NONCE: '02',
73
+ MERGE_MINING: '03'
74
+ }
75
+ const TX_EXTRA_NONCE_TAGS = {
76
+ PAYMENT_ID: '00',
77
+ ENCRYPTED_PAYMENT_ID: '01'
78
+ }
79
+ const UINT64_MAX = BigInteger(2).pow(64)
80
+ const CURRENT_TX_VERSION = 1
81
+
82
+ class CryptoNote {
83
+ constructor (config) {
84
+ this.config = require('./config.json')
85
+
86
+ if (config) {
87
+ if (config.coinUnitPlaces) {
88
+ this.config.coinUnitPlaces = config.coinUnitPlaces
89
+ }
90
+
91
+ if (config.addressPrefix) {
92
+ this.config.addressPrefix = config.addressPrefix
93
+ }
94
+
95
+ if (config.keccakIterations) {
96
+ this.config.keccakIterations = config.keccakIterations
97
+ }
98
+
99
+ if (config.defaultNetworkFee) {
100
+ this.config.defaultNetworkFee = config.defaultNetworkFee
101
+ }
102
+
103
+ if (config.mmMiningBlockVersion) {
104
+ this.config.mmMiningBlockVersion = config.mmMiningBlockVersion
105
+ }
106
+
107
+ /* The checks below are for detecting customer caller
108
+ cryptographic functions and loading them into the
109
+ stack so that they can be used later throughout the
110
+ module and it's underlying functions */
111
+ if (typeof config.underivePublicKey === 'function') {
112
+ userCryptoFunctions.underivePublicKey = config.underivePublicKey
113
+ }
114
+
115
+ if (typeof config.derivePublicKey === 'function') {
116
+ userCryptoFunctions.derivePublicKey = config.derivePublicKey
117
+ }
118
+
119
+ if (typeof config.deriveSecretKey === 'function') {
120
+ userCryptoFunctions.deriveSecretKey = config.deriveSecretKey
121
+ }
122
+
123
+ if (typeof config.generateKeyImage === 'function') {
124
+ userCryptoFunctions.generateKeyImage = config.generateKeyImage
125
+ }
126
+
127
+ if (typeof config.secretKeyToPublicKey === 'function') {
128
+ userCryptoFunctions.secretKeyToPublicKey = config.secretKeyToPublicKey
129
+ }
130
+
131
+ if (typeof config.cnFastHash === 'function') {
132
+ userCryptoFunctions.cnFastHash = config.cnFastHash
133
+ }
134
+
135
+ if (typeof config.generateRingSignatures === 'function') {
136
+ userCryptoFunctions.generateRingSignatures = config.generateRingSignatures
137
+ }
138
+
139
+ if (typeof config.generateKeyDerivation === 'function') {
140
+ userCryptoFunctions.generateKeyDerivation = config.generateKeyDerivation
141
+ }
142
+ }
143
+ }
144
+
145
+ createNewSeed (entropy, iterations) {
146
+ iterations = iterations || this.config.keccakIterations
147
+
148
+ /* If you don't supply us with entropy, we'll go find our own */
149
+ entropy = entropy || SecureRandomString({ length: 256 })
150
+
151
+ /* We're going to take that entropy, throw a random value on
152
+ to it, feed it through a poor very simple PBKDF2 implementation
153
+ to create a seed using the supplied entropy */
154
+ return scReduce32(simpleKdf(entropy + rand32(), iterations))
155
+ }
156
+
157
+ createNewAddress (entropy, lang, addressPrefix) {
158
+ addressPrefix = addressPrefix || this.config.addressPrefix
159
+
160
+ /* Let's create our new seed */
161
+ const seed = this.createNewSeed(entropy)
162
+
163
+ /* Using that seed, let's create our new CryptoNote address */
164
+ return this.createAddressFromSeed(seed, lang, addressPrefix)
165
+ }
166
+
167
+ createAddressFromSeed (seed, lang, addressPrefix) {
168
+ addressPrefix = addressPrefix || this.config.addressPrefix
169
+
170
+ /* When we have a seed, then we can create a new key
171
+ pair based on that seed */
172
+ lang = lang || 'english'
173
+ var keys = {}
174
+
175
+ /* First we create the spend key pair; however,
176
+ if the seed we were supplied isn't 64 characters
177
+ long, we'll pass it through the CN Fast Hash function
178
+ to turn it into 64 characters */
179
+ var first = seed
180
+ if (first.length !== 64) {
181
+ first = cnFastHash(seed)
182
+ }
183
+ keys.spend = generateKeys(first)
184
+
185
+ /* If our seed was less than 64 characters, then we
186
+ hash our seed again to get us the necessary data
187
+ to compute our view key pair; otherwise, we use
188
+ the privateSpendKey we just created */
189
+ var second
190
+ if (seed.length !== 64) {
191
+ second = cnFastHash(first)
192
+ } else {
193
+ second = cnFastHash(keys.spend.privateKey)
194
+ }
195
+ keys.view = generateKeys(second)
196
+
197
+ /* Once we have our keys, then we can encode the public keys
198
+ out of our view and spend pairs to create our public address */
199
+ keys.address = this.encodeAddress(keys.view.publicKey, keys.spend.publicKey, false, addressPrefix)
200
+
201
+ /* As we know the seed, we can encode it to a mnemonic string */
202
+ keys.mnemonic = Mnemonic.encode(seed, lang)
203
+
204
+ /* Put the seed in there for good measure */
205
+ keys.seed = seed
206
+
207
+ return keys
208
+ }
209
+
210
+ createAddressFromMnemonic (mnemonic, lang, addressPrefix) {
211
+ addressPrefix = addressPrefix || this.config.addressPrefix
212
+
213
+ /* The mnemonic is just a string representation of the seed
214
+ that was initially used to create our key set */
215
+ lang = lang || 'english'
216
+ const seed = Mnemonic.decode(mnemonic, lang)
217
+
218
+ /* As long as we have the seed we can recreate the key pairs
219
+ pretty easily */
220
+ return this.createAddressFromSeed(seed, lang, addressPrefix)
221
+ }
222
+
223
+ createAddressFromKeys (privateSpendKey, privateViewKey, addressPrefix) {
224
+ addressPrefix = addressPrefix || this.config.addressPrefix
225
+
226
+ let derivedViewKey = scReduce32(cnFastHash(privateSpendKey))
227
+
228
+ /* We have our private keys so we can generate everything for use
229
+ later except the mnemonic as we don't have the seed */
230
+ const keys = {
231
+ spend: {
232
+ privateKey: privateSpendKey,
233
+ publicKey: privateKeyToPublicKey(privateSpendKey)
234
+ },
235
+ view: {
236
+ privateKey: privateViewKey,
237
+ publicKey: privateKeyToPublicKey(privateViewKey)
238
+ },
239
+ address: '',
240
+ /* If the view key is derived from the spend key, we can generate a seed */
241
+ mnemonic: derivedViewKey === privateViewKey ? Mnemonic.encode(privateSpendKey) : null,
242
+ seed: null
243
+ }
244
+
245
+ /* As we now have all of our keys, we can find out what our
246
+ public address is */
247
+ keys.address = this.encodeAddress(keys.view.publicKey, keys.spend.publicKey, false, addressPrefix)
248
+
249
+ return keys
250
+ }
251
+
252
+ decodeAddressPrefix (address) {
253
+ /* First we decode the address from Base58 into the raw address */
254
+ var decodedAddress = Base58.decode(address)
255
+
256
+ /* Now we need to work in reverse, starting with chopping off
257
+ the checksum which is always the same */
258
+ decodedAddress = decodedAddress.slice(0, -(SIZES.CHECKSUM))
259
+
260
+ /* Now we find out how many extra characters there are
261
+ in what's left after we find all of the keys in the address.
262
+ Remember, this works because payment IDs are the same size as keys */
263
+ var prefixLength = decodedAddress.length % SIZES.KEY
264
+
265
+ /* Great, now we that we know how long the prefix length is, we
266
+ can grab just that from the front of the address information */
267
+ var prefixDecoded = decodedAddress.slice(0, prefixLength)
268
+
269
+ /* Then we can decode it into the integer that it represents */
270
+ var prefixVarint = decodeVarint(prefixDecoded)
271
+
272
+ /* This block of code is a hack to figure out what the human readable
273
+ address prefix is. While it has been tested with a few different
274
+ cryptonote addresses from different projects, it is by no means
275
+ guaranteed to work with every project. The reason for this is that
276
+ due to the block encoding used in Base58, it's nearly impossible
277
+ to reliably find out the Base58 version of just the prefix as it
278
+ is not actually long enough to be encoded on its own and get the
279
+ prefix we expect. */
280
+
281
+ /* First we need the need to know how long the varint representation
282
+ of the prefix is, we're going to need it later */
283
+ var prefixVarintLength = prefixVarint.toString().length
284
+
285
+ /* This is where it starts to get funny. If the length is an even
286
+ number of characters, we'll need to grab the one extra character
287
+ from the address we passed in to get the prefix that we all know
288
+ and love */
289
+ var offset = (prefixVarintLength % 2 === 0) ? 1 : 0
290
+
291
+ /* This is kind of goofy. If the address prefix varint is longer
292
+ than 10 characters, then we need to adjust the offset amount
293
+ by the count of remaining characters. This is undoubtedly a
294
+ hack to support obnoxiously long address prefixes */
295
+ if (prefixVarintLength > 10) {
296
+ offset += Math.floor((prefixVarintLength % 10) / 2)
297
+ }
298
+
299
+ /* Using all of that above, we can chop off the first couple of
300
+ characters from the supplied address and get something that looks
301
+ like the Base58 prefix we expected. */
302
+ var prefixEncoded = address.slice(0, Math.ceil(prefixVarintLength / 2) + offset)
303
+
304
+ return {
305
+ prefix: prefixDecoded,
306
+ base58: prefixEncoded,
307
+ decimal: prefixVarint,
308
+ hexadecimal: prefixVarint.toString(16)
309
+ }
310
+ }
311
+
312
+ decodeAddress (address, addressPrefix) {
313
+ addressPrefix = addressPrefix || this.config.addressPrefix
314
+
315
+ /* First, we decode the base58 string to hex */
316
+ var decodedAddress = Base58.decode(address)
317
+
318
+ /* We need to encode the address prefix from our config
319
+ so that we can compare it later */
320
+ const encodedPrefix = encodeVarint(addressPrefix)
321
+
322
+ /* Let's chop off the prefix from the address we decoded */
323
+ var prefix = decodedAddress.slice(0, encodedPrefix.length)
324
+
325
+ /* Do they match? They better... */
326
+ if (prefix !== encodedPrefix) {
327
+ throw new Error('Invalid address prefix')
328
+ }
329
+
330
+ /* We don't need the prefix in our working space any more */
331
+ decodedAddress = decodedAddress.slice(encodedPrefix.length)
332
+
333
+ var paymentId = ''
334
+
335
+ /* If the decoded address is longer than a
336
+ public spend key + public view key + checksum
337
+ then it's very likely an integrated address and as
338
+ such, we need to get the payment ID out of there for
339
+ use later otherwise the resulting data does not make
340
+ any sense whatsoever */
341
+ if (decodedAddress.length > ((SIZES.KEY * 2) + SIZES.CHECKSUM)) {
342
+ paymentId = decodedAddress.slice(0, (SIZES.KEY * 2))
343
+ decodedAddress = decodedAddress.slice((SIZES.KEY * 2))
344
+ }
345
+
346
+ /* Finish decomposing the decoded address */
347
+ const publicSpend = decodedAddress.slice(0, SIZES.KEY)
348
+ const publicView = decodedAddress.slice(SIZES.KEY, (SIZES.KEY * 2))
349
+ const expectedCheckum = decodedAddress.slice(-(SIZES.CHECKSUM))
350
+
351
+ var checksum
352
+ /* Calculate our address checksum */
353
+ if (paymentId.length === 0) {
354
+ /* If there is no payment ID it's pretty simple */
355
+ checksum = cnFastHash(prefix + publicSpend + publicView).slice(0, SIZES.CHECKSUM)
356
+ } else {
357
+ /* If there is a payment ID it's pretty simple as well */
358
+ checksum = cnFastHash(prefix + paymentId + publicSpend + publicView).slice(0, SIZES.CHECKSUM)
359
+
360
+ /* As goofy as this sounds, we need to convert the payment
361
+ ID from hex into a string representation so that it returns
362
+ to a human readable form */
363
+ paymentId = Base58.hextostr(paymentId)
364
+ }
365
+
366
+ /* If the checksum we found in the address doesn't match the
367
+ checksum that we just computed, then the address is bad */
368
+ if (expectedCheckum !== checksum) {
369
+ throw new Error('Could not parse address: checksum mismatch')
370
+ }
371
+
372
+ return {
373
+ publicViewKey: publicView,
374
+ publicSpendKey: publicSpend,
375
+ paymentId: paymentId,
376
+ encodedPrefix: prefix,
377
+ prefix: addressPrefix,
378
+ rawAddress: Base58.decode(address)
379
+ }
380
+ }
381
+
382
+ encodeRawAddress (rawAddress) {
383
+ return Base58.encode(rawAddress)
384
+ }
385
+
386
+ encodeAddress (publicViewKey, publicSpendKey, paymentId, addressPrefix) {
387
+ addressPrefix = addressPrefix || this.config.addressPrefix
388
+ paymentId = paymentId || false
389
+
390
+ if (!isHex64(publicViewKey)) {
391
+ throw new Error('Invalid public view key format')
392
+ }
393
+
394
+ if (!isHex64(publicSpendKey)) {
395
+ throw new Error('Invalid public spend key format')
396
+ }
397
+
398
+ /* If we included a payment ID it needs to be
399
+ 64 hexadecimal characters */
400
+ if (paymentId && !isHex64(paymentId)) {
401
+ throw new Error('Invalid payment ID format')
402
+ }
403
+
404
+ var rawAddress = []
405
+
406
+ /* Encode our configured address prefix so that we can throw
407
+ it on the front of our address */
408
+ const encodedPrefix = encodeVarint(addressPrefix)
409
+ rawAddress.push(encodedPrefix)
410
+
411
+ /* Is there a payment ID? If so, that comes next */
412
+ if (paymentId) {
413
+ paymentId = Base58.strtohex(paymentId)
414
+ rawAddress.push(paymentId)
415
+ }
416
+
417
+ /* Then toss on our publicSpendKey followed by our public
418
+ view key */
419
+ rawAddress.push(publicSpendKey.toString())
420
+ rawAddress.push(publicViewKey.toString())
421
+ rawAddress = rawAddress.join('')
422
+
423
+ /* Generate the checksum and toss that on the end */
424
+ const checksum = cnFastHash(rawAddress).slice(0, 8)
425
+ rawAddress += checksum
426
+
427
+ /* Finally, encode all that to Base58 */
428
+ return Base58.encode(rawAddress)
429
+ }
430
+
431
+ createIntegratedAddress (address, paymentId, addressPrefix) {
432
+ addressPrefix = addressPrefix || this.config.addressPrefix
433
+
434
+ /* Decode our address */
435
+ var addr = this.decodeAddress(address)
436
+ /* Encode the address but this time include the payment ID */
437
+ return this.encodeAddress(addr.publicViewKey, addr.publicSpendKey, paymentId, addressPrefix)
438
+ }
439
+
440
+ privateKeyToPublicKey (privateKey) {
441
+ return privateKeyToPublicKey(privateKey)
442
+ }
443
+
444
+ scanTransactionOutputs (transactionPublicKey, outputs, privateViewKey, publicSpendKey, privateSpendKey) {
445
+ /* Given the transaction public key and the array of outputs, let's see if
446
+ any of the outputs belong to us */
447
+
448
+ var ourOutputs = []
449
+
450
+ for (var i = 0; i < outputs.length; i++) {
451
+ var output = outputs[i]
452
+
453
+ /* Check to see if this output belongs to us */
454
+ var ourOutput = this.isOurTransactionOutput(transactionPublicKey, output, privateViewKey, publicSpendKey, privateSpendKey)
455
+ if (ourOutput) {
456
+ ourOutputs.push(ourOutput)
457
+ }
458
+ }
459
+
460
+ return ourOutputs
461
+ }
462
+
463
+ isOurTransactionOutput (transactionPublicKey, output, privateViewKey, publicSpendKey, privateSpendKey) {
464
+ privateSpendKey = privateSpendKey || false
465
+ output = output || {}
466
+
467
+ if (!isHex64(transactionPublicKey)) {
468
+ throw new Error('Invalid transaction public key format')
469
+ }
470
+
471
+ if (!isHex64(privateViewKey)) {
472
+ throw new Error('Invalid private view key format')
473
+ }
474
+
475
+ if (!isHex64(publicSpendKey)) {
476
+ throw new Error('Invalid public spend key format')
477
+ }
478
+
479
+ if (privateSpendKey && !isHex64(privateSpendKey)) {
480
+ throw new Error('Invalid private spend key format')
481
+ }
482
+
483
+ if (typeof output.index === 'undefined' || typeof output.key === 'undefined') {
484
+ throw new Error('Output object not of correct type')
485
+ }
486
+
487
+ /* Generate the key deriviation from the random transaction public key and our private view key */
488
+ const derivedKey = this.generateKeyDerivation(transactionPublicKey, privateViewKey)
489
+
490
+ /* Derive the transfer public key from the derived key, the output index, and our public spend key */
491
+ const publicEphemeral = derivePublicKey(derivedKey, output.index, publicSpendKey)
492
+
493
+ /* If the derived transfer public key matches the output key then this output belongs to us */
494
+ if (output.key === publicEphemeral) {
495
+ output.input = {}
496
+ output.input.transactionKey = {
497
+ publicKey: transactionPublicKey,
498
+ privateKey: derivedKey
499
+ }
500
+ output.input.publicEphemeral = publicEphemeral
501
+
502
+ if (privateSpendKey) {
503
+ /* Derive the key image private key from the derived key, the output index, and our spend secret key */
504
+ const privateEphemeral = deriveSecretKey(derivedKey, output.index, privateSpendKey)
505
+
506
+ /* Generate the key image */
507
+ const keyImage = generateKeyImage(publicEphemeral, privateEphemeral)
508
+
509
+ output.input.privateEphemeral = privateEphemeral
510
+ output.keyImage = keyImage
511
+ }
512
+
513
+ return output
514
+ }
515
+
516
+ return false
517
+ }
518
+
519
+ generateKeyImage (transactionPublicKey, privateViewKey, publicSpendKey, privateSpendKey, outputIndex) {
520
+ if (!isHex64(transactionPublicKey)) {
521
+ throw new Error('Invalid transaction public key format')
522
+ }
523
+
524
+ if (!isHex64(privateViewKey)) {
525
+ throw new Error('Invalid private view key format')
526
+ }
527
+ /* Generate the key deriviation from the random transaction public key and our private view key */
528
+ let derivation = this.generateKeyDerivation(transactionPublicKey, privateViewKey)
529
+
530
+ return this.generateKeyImagePrimitive(publicSpendKey, privateSpendKey, outputIndex, derivation)
531
+ }
532
+
533
+ /* If the user already has a derivation, they can pass that in instead of
534
+ the privateViewKey and transactionPublicKey */
535
+ generateKeyImagePrimitive (publicSpendKey, privateSpendKey, outputIndex, derivation) {
536
+ if (!isHex64(publicSpendKey)) {
537
+ throw new Error('Invalid public spend key format')
538
+ }
539
+
540
+ if (!isHex64(privateSpendKey)) {
541
+ throw new Error('Invalid private spend key format')
542
+ }
543
+
544
+ /* Derive the transfer public key from the derived key, the output index, and our public spend key */
545
+ const publicEphemeral = derivePublicKey(derivation, outputIndex, publicSpendKey)
546
+
547
+ /* Derive the key image private key from the derived key, the output index, and our spend secret key */
548
+ const privateEphemeral = deriveSecretKey(derivation, outputIndex, privateSpendKey)
549
+
550
+ /* Generate the key image */
551
+ const keyImage = generateKeyImage(publicEphemeral, privateEphemeral)
552
+
553
+ return [keyImage, privateEphemeral]
554
+ }
555
+
556
+ /* This method is designed to create new outputs for use
557
+ during transaction creation */
558
+ createTransactionOutputs (address, amount) {
559
+ amount = amount || false
560
+
561
+ /* If we didn't specify an amount we can't send anything */
562
+ if (!amount || amount < 0) {
563
+ throw new Error('You must specify a valid amount')
564
+ }
565
+
566
+ const result = []
567
+
568
+ /* Decode the address into it's important bits */
569
+ var addressDecoded = this.decodeAddress(address)
570
+
571
+ /* Now we need to decompose the amount into "pretty" amounts
572
+ that we can actually mix later. We're doing this by
573
+ converting the amount to a character array and reversing
574
+ it so that we have the digits in each place */
575
+ var amountChars = amount.toString().split('').reverse()
576
+
577
+ /* Loop through the amount characters */
578
+ for (var i = 0; i < amountChars.length; i++) {
579
+ /* Create pretty amounts */
580
+ var amt = parseInt(amountChars[i]) * Math.pow(10, i)
581
+
582
+ if (amt !== 0) {
583
+ result.push({
584
+ amount: amt,
585
+ keys: addressDecoded
586
+ })
587
+ }
588
+ }
589
+
590
+ return result
591
+ }
592
+
593
+ createTransactionStructure (newOutputs, ourOutputs, randomOuts, mixin, feeAmount, paymentId, unlockTime, _async) {
594
+ return createTransaction(newOutputs, ourOutputs, randomOuts, mixin, feeAmount, paymentId, unlockTime, _async)
595
+ }
596
+
597
+ createTransaction (newOutputs, ourOutputs, randomOuts, mixin, feeAmount, paymentId, unlockTime) {
598
+ var tx = this.createTransactionStructure(newOutputs, ourOutputs, randomOuts, mixin, feeAmount, paymentId, unlockTime, false)
599
+ var serializedTransaction = serializeTransaction(tx)
600
+ var txnHash = cnFastHash(serializedTransaction)
601
+
602
+ return {
603
+ transaction: tx,
604
+ rawTransaction: serializedTransaction,
605
+ hash: txnHash
606
+ }
607
+ }
608
+
609
+ createTransactionAsync (newOutputs, ourOutputs, randomOuts, mixin, feeAmount, paymentId, unlockTime) {
610
+ return this.createTransactionStructure(
611
+ newOutputs, ourOutputs, randomOuts, mixin, feeAmount, paymentId, unlockTime, true
612
+ ).then((tx) => {
613
+ var serializedTransaction = serializeTransaction(tx)
614
+ var txnHash = cnFastHash(serializedTransaction)
615
+
616
+ return {
617
+ transaction: tx,
618
+ rawTransaction: serializedTransaction,
619
+ hash: txnHash
620
+ }
621
+ })
622
+ }
623
+
624
+ serializeTransaction (transaction) {
625
+ return serializeTransaction(transaction, false)
626
+ }
627
+
628
+ formatMoney (amount) {
629
+ var places = ''
630
+ for (var i = 0; i < this.config.coinUnitPlaces; i++) {
631
+ places += '0'
632
+ }
633
+ return Numeral(amount / Math.pow(10, this.config.coinUnitPlaces)).format('0,0.' + places)
634
+ }
635
+
636
+ generateKeyDerivation (transactionPublicKey, privateViewKey) {
637
+ return generateKeyDerivation(transactionPublicKey, privateViewKey)
638
+ }
639
+
640
+ underivePublicKey (derivation, outputIndex, outputKey) {
641
+ if (!isHex64(derivation)) {
642
+ throw new Error('Invalid derivation key format')
643
+ }
644
+
645
+ if (userCryptoFunctions.underivePublicKey) {
646
+ userCryptoFunctions.underivePublicKey(derivation, outputIndex, outputKey)
647
+ } else if (TurtleCoinCrypto) {
648
+ const [err, key] = TurtleCoinCrypto.underivePublicKey(derivation, outputIndex, outputKey)
649
+ if (err) throw new Error('Could not underive public key')
650
+
651
+ return key
652
+ } else {
653
+ const RingSigs = require('./lib/ringsigs.js')
654
+
655
+ return RingSigs.underivePublicKey(derivation, outputIndex, outputKey)
656
+ }
657
+ }
658
+
659
+ cnFastHash (data) {
660
+ return cnFastHash(data)
661
+ }
662
+
663
+ blockTemplate (payload) {
664
+ return new BlockTemplate(payload, this.mmMiningBlockVersion)
665
+ }
666
+ }
667
+
668
+ /* Internal support functions */
669
+ function isHex (str) {
670
+ const regex = new RegExp('^[0-9a-fA-F]{' + str.length + '}$')
671
+ return regex.test(str)
672
+ }
673
+
674
+ function isHex64 (str) {
675
+ const regex = new RegExp('^[0-9a-fA-F]{64}$')
676
+ return regex.test(str)
677
+ }
678
+
679
+ function swapEndian (hex) {
680
+ if (hex.length % 2 !== 0) {
681
+ throw new Error('Hex string length must be a multiple of 2!')
682
+ }
683
+ var result = ''
684
+
685
+ var loopCount = hex.length / 2
686
+ for (var i = 1; i <= loopCount; i++) {
687
+ result += hex.substr(0 - 2 * i, 2)
688
+ }
689
+
690
+ return result
691
+ }
692
+
693
+ function d2h (integer) {
694
+ if (typeof integer !== 'string' && integer.toString().length > 15) {
695
+ throw new Error('Integer should be entered as a string for precision')
696
+ }
697
+
698
+ var padding = ''
699
+ for (var i = 0; i < 64; i++) {
700
+ padding += '0'
701
+ }
702
+
703
+ const result = (padding + BigInteger(integer).toString(16).toLowerCase()).slice(-(SIZES.KEY))
704
+ return result
705
+ }
706
+
707
+ function d2s (integer) {
708
+ return swapEndian(d2h(integer))
709
+ }
710
+
711
+ function hex2bin (hex) {
712
+ if (hex.length % 2 !== 0) {
713
+ throw new Error('Hex string has invalid length')
714
+ }
715
+
716
+ var result = new Uint8Array(hex.length / 2)
717
+ var hexLength = hex.length / 2
718
+ for (var i = 0; i < hexLength; i++) {
719
+ result[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16)
720
+ }
721
+
722
+ return result
723
+ }
724
+
725
+ function bin2hex (bin) {
726
+ var result = []
727
+ for (var i = 0; i < bin.length; ++i) {
728
+ result.push(('0' + bin[i].toString(16)).slice(-2))
729
+ }
730
+
731
+ return result.join('')
732
+ }
733
+
734
+ function str2bin (str) {
735
+ var result = new Uint8Array(str.length)
736
+ for (var i = 0; i < str.length; i++) {
737
+ result[i] = str.charCodeAt(i)
738
+ }
739
+
740
+ return result
741
+ }
742
+
743
+ function rand32 () {
744
+ /* Go get 256-bits (32 bytes) of random data */
745
+ return Mnemonic.random(256)
746
+ }
747
+
748
+ function encodeVarint (i) {
749
+ i = BigInteger(i)
750
+
751
+ var result = ''
752
+ while (i.compare(0x80) >= 0) {
753
+ result += ('0' + ((i.lowVal() & 0x7f) | 0x80).toString(16)).slice(-2)
754
+ i = i.divide(BigInteger(2).pow(7))
755
+ }
756
+ result += ('0' + i.toJSValue().toString(16)).slice(-2)
757
+
758
+ return result
759
+ }
760
+
761
+ function decodeVarint (hex) {
762
+ const buffer = Buffer.from(hex, 'hex')
763
+ return parseInt(Varint.decode(buffer))
764
+ }
765
+
766
+ function scReduce (hex, size) {
767
+ size = size || 64
768
+ var input = hex2bin(hex)
769
+ if (input.length !== size) {
770
+ throw new Error('Invalid input length')
771
+ }
772
+
773
+ var memory = CNCrypto._malloc(size)
774
+ CNCrypto.HEAPU8.set(input, memory)
775
+ CNCrypto.ccall('sc_reduce32', 'void', ['number'], [memory])
776
+
777
+ var result = CNCrypto.HEAPU8.subarray(memory, memory + size)
778
+ CNCrypto._free(memory)
779
+
780
+ return bin2hex(result)
781
+ }
782
+
783
+ function scReduce32 (hex) {
784
+ if (TurtleCoinCrypto) {
785
+ const [err, result] = TurtleCoinCrypto.scReduce32(hex)
786
+ if (err) throw new Error('Could not scReduce32')
787
+
788
+ return result
789
+ } else {
790
+ return scReduce(hex, 32)
791
+ }
792
+ }
793
+
794
+ function geScalarMult (publicKey, privateKey) {
795
+ if (!isHex64(publicKey)) {
796
+ throw new Error('Invalid public key format')
797
+ }
798
+
799
+ if (!isHex64(privateKey)) {
800
+ throw new Error('Invalid secret key format')
801
+ }
802
+
803
+ return bin2hex(NACL.ll.geScalarmult(hex2bin(publicKey), hex2bin(privateKey)))
804
+ }
805
+
806
+ function getScalarMultBase (privateKey) {
807
+ return privateKeyToPublicKey(privateKey)
808
+ }
809
+
810
+ function derivePublicKey (derivation, outputIndex, publicKey) {
811
+ if (derivation.length !== (SIZES.ECPOINT * 2)) {
812
+ throw new Error('Invalid derivation length')
813
+ }
814
+
815
+ if (!isHex64(publicKey)) {
816
+ throw new Error('Invalid public key format')
817
+ }
818
+
819
+ if (userCryptoFunctions.derivePublicKey) {
820
+ return userCryptoFunctions.derivePublicKey(derivation, outputIndex, publicKey)
821
+ } else if (TurtleCoinCrypto) {
822
+ const [err, key] = TurtleCoinCrypto.derivePublicKey(derivation, outputIndex, publicKey)
823
+ if (err) throw new Error('Could not derive public key')
824
+
825
+ return key
826
+ } else {
827
+ var s = derivationToScalar(derivation, outputIndex)
828
+ return bin2hex(NACL.ll.geAdd(hex2bin(publicKey), hex2bin(getScalarMultBase(s))))
829
+ }
830
+ }
831
+
832
+ function deriveSecretKey (derivation, outputIndex, privateKey) {
833
+ if (derivation.length !== (SIZES.ECPOINT * 2)) {
834
+ throw new Error('Invalid derivation length')
835
+ }
836
+
837
+ if (!isHex64(privateKey)) {
838
+ throw new Error('Invalid secret key format')
839
+ }
840
+
841
+ if (userCryptoFunctions.deriveSecretKey) {
842
+ return userCryptoFunctions.deriveSecretKey(derivation, outputIndex, privateKey)
843
+ } else if (TurtleCoinCrypto) {
844
+ const [err, key] = TurtleCoinCrypto.deriveSecretKey(derivation, outputIndex, privateKey)
845
+ if (err) throw new Error('Could not derive secret key')
846
+
847
+ return key
848
+ } else {
849
+ var m = CNCrypto._malloc(SIZES.ECSCALAR)
850
+ var b = hex2bin(derivationToScalar(derivation, outputIndex))
851
+ CNCrypto.HEAPU8.set(b, m)
852
+
853
+ var baseM = CNCrypto._malloc(SIZES.ECSCALAR)
854
+ CNCrypto.HEAPU8.set(hex2bin(privateKey), baseM)
855
+
856
+ var derivedM = CNCrypto._malloc(SIZES.ECSCALAR)
857
+ CNCrypto.ccall('sc_add', 'void', ['number', 'number', 'number'], [derivedM, baseM, m])
858
+
859
+ var result = CNCrypto.HEAPU8.subarray(derivedM, derivedM + SIZES.ECSCALAR)
860
+ CNCrypto._free(m)
861
+ CNCrypto._free(baseM)
862
+ CNCrypto._free(derivedM)
863
+
864
+ return bin2hex(result)
865
+ }
866
+ }
867
+
868
+ function generateKeyImage (publicKey, privateKey) {
869
+ if (!isHex64(publicKey)) {
870
+ throw new Error('Invalid public key format')
871
+ }
872
+
873
+ if (!isHex64(privateKey)) {
874
+ throw new Error('Invalid secret key format')
875
+ }
876
+
877
+ if (userCryptoFunctions.generateKeyImage) {
878
+ return userCryptoFunctions.generateKeyImage(publicKey, privateKey)
879
+ } else if (TurtleCoinCrypto) {
880
+ const [err, keyImage] = TurtleCoinCrypto.generateKeyImage(publicKey, privateKey)
881
+ if (err) throw new Error('Could not generate key image')
882
+
883
+ return keyImage
884
+ } else {
885
+ const RingSigs = require('./lib/ringsigs.js')
886
+
887
+ return RingSigs.generate_key_image(publicKey, privateKey)
888
+ }
889
+ }
890
+
891
+ function hashToScalar (buf) {
892
+ const hash = cnFastHash(buf)
893
+ return scReduce32(hash)
894
+ }
895
+
896
+ function derivationToScalar (derivation, outputIndex) {
897
+ var buf = ''
898
+
899
+ if (derivation.length !== (SIZES.ECPOINT * 2)) {
900
+ throw new Error('Invalid derivation length')
901
+ }
902
+
903
+ buf += derivation
904
+
905
+ var enc = encodeVarint(outputIndex)
906
+ if (enc.length > (10 * 2)) {
907
+ throw new Error('outputIndex does not fit in 64-bit varint')
908
+ }
909
+
910
+ buf += enc
911
+ return hashToScalar(buf)
912
+ }
913
+
914
+ function privateKeyToPublicKey (privateKey) {
915
+ if (privateKey.length !== SIZES.KEY) {
916
+ throw new Error('Invalid secret key length')
917
+ }
918
+
919
+ if (userCryptoFunctions.secretKeyToPublicKey) {
920
+ return userCryptoFunctions.secretKeyToPublicKey(privateKey)
921
+ } else if (TurtleCoinCrypto) {
922
+ const [err, key] = TurtleCoinCrypto.secretKeyToPublicKey(privateKey)
923
+ if (err) throw new Error('Could not derive public key from secret key')
924
+
925
+ return key
926
+ } else {
927
+ return bin2hex(NACL.ll.geScalarmultBase(hex2bin(privateKey)))
928
+ }
929
+ }
930
+
931
+ function cnFastHash (input) {
932
+ if (input.length % 2 !== 0 || !isHex(input)) {
933
+ throw new Error('Invalid input: ' + input)
934
+ }
935
+
936
+ if (userCryptoFunctions.cnFastHash) {
937
+ return userCryptoFunctions.cnFastHash(input)
938
+ } else if (TurtleCoinCrypto) {
939
+ const [err, hash] = TurtleCoinCrypto.cnFastHash(input)
940
+ if (err) throw new Error('Could not calculate CN Fast Hash')
941
+
942
+ return hash
943
+ } else {
944
+ return SHA3.keccak_256(hex2bin(input))
945
+ }
946
+ }
947
+
948
+ function simpleKdf (str, iterations) {
949
+ /* This is a very simple implementation of a
950
+ psuedo PBKDF2 function */
951
+ var hex = bin2hex(str2bin(str))
952
+ for (var n = 0; n < iterations; ++n) {
953
+ hex = cnFastHash(hex)
954
+ }
955
+ return hex
956
+ }
957
+
958
+ function generateKeys (seed) {
959
+ if (seed.length !== 64) {
960
+ throw new Error('Invalid seed length')
961
+ }
962
+
963
+ var privateKey = scReduce32(seed)
964
+ var publicKey = privateKeyToPublicKey(privateKey)
965
+
966
+ return {
967
+ privateKey: privateKey,
968
+ publicKey: publicKey
969
+ }
970
+ }
971
+
972
+ function randomKeypair () {
973
+ /* Generate a random key pair */
974
+ return generateKeys(simpleKdf(rand32(), 1))
975
+ }
976
+
977
+ /* This method calculates our relative offset positions for
978
+ the globalIndexes for inclusion in a new transaction */
979
+ function absoluteToRelativeOffsets (offsets) {
980
+ if (offsets.length === 0) {
981
+ return offsets
982
+ }
983
+
984
+ for (var i = offsets.length - 1; i >= 1; --i) {
985
+ offsets[i] = BigInteger(offsets[i]).subtract(offsets[i - 1]).toString()
986
+ }
987
+
988
+ /* All the other offsets are strings, not numbers. It still works, but, muh
989
+ autism */
990
+ offsets[0] = offsets[0].toString()
991
+
992
+ return offsets
993
+ }
994
+
995
+ function addTransactionPublicKeyToExtra (extra, transactionPublicKey) {
996
+ if (!isHex64(transactionPublicKey)) {
997
+ throw new Error('Invalid Transaction Public Key Format')
998
+ }
999
+
1000
+ extra += TX_EXTRA_TAGS.PUBKEY
1001
+ extra += transactionPublicKey
1002
+
1003
+ return extra
1004
+ }
1005
+
1006
+ function getPaymentIdNonce (paymentId) {
1007
+ if (!isHex64(paymentId)) {
1008
+ throw new Error('Payment ID must be 64 hexadecimal characters')
1009
+ }
1010
+
1011
+ return TX_EXTRA_NONCE_TAGS.PAYMENT_ID + paymentId
1012
+ }
1013
+
1014
+ function addNonceToExtra (extra, nonce) {
1015
+ if ((nonce.length % 2) !== 0) {
1016
+ throw new Error('Invalid extra nonce')
1017
+ }
1018
+
1019
+ if ((nonce.length / 2) > TX_EXTRA_NONCE_MAX_COUNT) {
1020
+ throw new Error('Extra nonce must be at most ' + TX_EXTRA_NONCE_MAX_COUNT + ' bytes')
1021
+ }
1022
+
1023
+ /* Add the NONCE tag */
1024
+ extra += TX_EXTRA_TAGS.NONCE
1025
+
1026
+ /* Encode the length of the NONCE */
1027
+ extra += ('0' + (nonce.length / 2).toString(16)).slice(-2)
1028
+
1029
+ /* Add the NONCE */
1030
+ extra += nonce
1031
+
1032
+ return extra
1033
+ }
1034
+
1035
+ function generateRingSignature (transactionPrefixHash, keyImage, inputKeys, privateKey, realIndex) {
1036
+ var sigs = []
1037
+
1038
+ if (!isHex64(keyImage)) {
1039
+ throw new Error('Invalid Key Image format')
1040
+ }
1041
+
1042
+ if (!isHex64(privateKey)) {
1043
+ throw new Error('Invalid secret key format')
1044
+ }
1045
+
1046
+ if (!isHex64(transactionPrefixHash)) {
1047
+ throw new Error('Invalid transaction prefix hash format')
1048
+ }
1049
+
1050
+ if (realIndex >= inputKeys.length || realIndex < 0) {
1051
+ throw new Error('Invalid realIndex supplied')
1052
+ }
1053
+
1054
+ if (userCryptoFunctions.generateRingSignatures) {
1055
+ return userCryptoFunctions.generateRingSignatures(transactionPrefixHash, keyImage, inputKeys, privateKey, realIndex)
1056
+ } else if (TurtleCoinCrypto) {
1057
+ const [err, signatures] = TurtleCoinCrypto.generateRingSignatures(transactionPrefixHash, keyImage, inputKeys, privateKey, realIndex)
1058
+ if (err) return new Error('Could not generate ring signatures')
1059
+
1060
+ return signatures
1061
+ } else {
1062
+ const RingSigs = require('./lib/ringsigs.js')
1063
+
1064
+ var cSigs = new RingSigs.VectorString()
1065
+ var cInputKeys = new RingSigs.VectorString()
1066
+
1067
+ inputKeys.forEach((key) => {
1068
+ cInputKeys.push_back(key)
1069
+ })
1070
+
1071
+ cSigs = RingSigs.generateRingSignatures(transactionPrefixHash, keyImage, cInputKeys, privateKey, realIndex)
1072
+
1073
+ for (var i = 0; i < cSigs.size(); i++) {
1074
+ sigs.push(cSigs.get(i))
1075
+ }
1076
+
1077
+ return sigs
1078
+ }
1079
+ }
1080
+
1081
+ function createTransaction (newOutputs, ourOutputs, randomOutputs, mixin, feeAmount, paymentId, unlockTime, _async) {
1082
+ unlockTime = unlockTime || 0
1083
+ randomOutputs = randomOutputs || []
1084
+
1085
+ /* Verify that we've been passed an array of outputs */
1086
+ if (!Array.isArray(newOutputs)) {
1087
+ throw new Error('newOutputs must be an array')
1088
+ }
1089
+
1090
+ /* Verify that we've been passed an array of our outputs (our funds) */
1091
+ if (!Array.isArray(ourOutputs)) {
1092
+ throw new Error('ourOutputs must be an array')
1093
+ }
1094
+
1095
+ /* Make sure that if we are to use mixins that we've been given the
1096
+ correct number of sets of random outputs */
1097
+ if (randomOutputs.length !== ourOutputs.length && mixin !== 0) {
1098
+ throw new Error('Not enough random outputs sets were supplied with the transaction')
1099
+ }
1100
+
1101
+ /* Make sure that there are the correct number of random outputs
1102
+ in each one of the sets that we were passed */
1103
+ for (var i = 0; i < randomOutputs.length; i++) {
1104
+ if ((randomOutputs[i] || []).length < mixin) {
1105
+ throw new Error('There are not enough outputs to mix with in the random outputs sets')
1106
+ }
1107
+ }
1108
+
1109
+ /* Make sure that we're not trying to send more money than
1110
+ is actually possible within the confines of a uint64 */
1111
+ var neededMoney = BigInteger.ZERO
1112
+ for (i = 0; i < newOutputs.length; i++) {
1113
+ neededMoney = neededMoney.add(newOutputs[i].amount)
1114
+ if (neededMoney.compare(UINT64_MAX) !== -1) {
1115
+ throw new Error('Total output amount exceeds UINT64_MAX')
1116
+ }
1117
+ }
1118
+
1119
+ /* Make sure that we're not trying to spend more money than
1120
+ is actually possible within the confines of a uint64 */
1121
+ var foundMoney = BigInteger.ZERO
1122
+ for (i = 0; i < ourOutputs.length; i++) {
1123
+ foundMoney = foundMoney.add(ourOutputs[i].amount)
1124
+ if (foundMoney.compare(UINT64_MAX) !== -1) {
1125
+ throw new Error('Total input amount exceeds UINT64_MAX')
1126
+ }
1127
+ }
1128
+
1129
+ /* Validate that we're spending all of the necessary funds
1130
+ and that the transaction balances properly. We do this
1131
+ relatively early as everything starts to get a little
1132
+ more computationally expensive from here on out */
1133
+ var change = BigInteger.ZERO
1134
+ var cmp = neededMoney.compare(foundMoney)
1135
+ if (cmp < 0) {
1136
+ change = foundMoney.subtract(neededMoney)
1137
+ if (change.compare(feeAmount) !== 0) {
1138
+ throw new Error('We have not spent all of what we have passed in')
1139
+ }
1140
+ } else if (cmp > 0) {
1141
+ throw new Error('We need more money than was currently supplied for the transaction')
1142
+ }
1143
+
1144
+ /* Create our transaction inputs using the helper function */
1145
+ var transactionInputs = createTransactionInputs(ourOutputs, randomOutputs, mixin)
1146
+
1147
+ /* Prepare our transaction outputs using the helper function */
1148
+ var transactionOutputs = prepareTransactionOutputs(newOutputs, _async)
1149
+
1150
+ var transactionExtra = ''
1151
+ /* If we have a payment ID we need to add it to tx_extra */
1152
+ if (isHex64(paymentId)) {
1153
+ const nonce = getPaymentIdNonce(paymentId)
1154
+ transactionExtra = addNonceToExtra(transactionExtra, nonce)
1155
+ }
1156
+
1157
+ /* Start constructing our actual transaction */
1158
+ const tx = {
1159
+ unlockTime: unlockTime,
1160
+ version: CURRENT_TX_VERSION,
1161
+ extra: transactionExtra,
1162
+ transactionKeys: transactionOutputs.transactionKeys,
1163
+ vin: [],
1164
+ vout: [],
1165
+ signatures: []
1166
+ }
1167
+
1168
+ transactionInputs.sort(function (a, b) {
1169
+ return (BigInteger.parse(a.keyImage, 16).compare(BigInteger.parse(b.keyImage, 16)) * -1)
1170
+ })
1171
+
1172
+ transactionInputs.forEach((input) => {
1173
+ const inputToKey = {
1174
+ type: 'input_to_key',
1175
+ amount: input.amount,
1176
+ keyImage: input.keyImage,
1177
+ keyOffsets: []
1178
+ }
1179
+
1180
+ input.outputs.forEach((output) => {
1181
+ inputToKey.keyOffsets.push(output.index)
1182
+ })
1183
+
1184
+ inputToKey.keyOffsets = absoluteToRelativeOffsets(inputToKey.keyOffsets)
1185
+
1186
+ tx.vin.push(inputToKey)
1187
+ })
1188
+
1189
+ tx.extra = addTransactionPublicKeyToExtra(tx.extra, transactionOutputs.transactionKeys.publicKey)
1190
+
1191
+ if (_async) {
1192
+ /* Use Promise.resolve so even if the result isn't a promise, it still
1193
+ works */
1194
+ return Promise.resolve(transactionOutputs.outputs).then((outputs) => {
1195
+ outputs.forEach((output) => {
1196
+ tx.vout.push(output)
1197
+ })
1198
+
1199
+ const txPrefixHash = getTransactionPrefixHash(tx)
1200
+
1201
+ const sigPromises = []
1202
+
1203
+ for (i = 0; i < transactionInputs.length; i++) {
1204
+ var txInput = transactionInputs[i]
1205
+
1206
+ var srcKeys = []
1207
+ txInput.outputs.forEach((out) => {
1208
+ srcKeys.push(out.key)
1209
+ })
1210
+
1211
+ var sigPromise = Promise.resolve(generateRingSignature(
1212
+ txPrefixHash, txInput.keyImage, srcKeys, txInput.input.privateEphemeral, txInput.realOutputIndex
1213
+ )).then((sigs) => {
1214
+ tx.signatures.push(sigs)
1215
+ })
1216
+
1217
+ sigPromises.push(sigPromise)
1218
+ }
1219
+
1220
+ /* Wait for all the sigs to get created and added, then return the tx */
1221
+ return Promise.all(sigPromises).then(() => {
1222
+ return tx
1223
+ })
1224
+ })
1225
+ } else {
1226
+ transactionOutputs.outputs.forEach((output) => {
1227
+ tx.vout.push(output)
1228
+ })
1229
+
1230
+ const txPrefixHash = getTransactionPrefixHash(tx)
1231
+
1232
+ for (i = 0; i < transactionInputs.length; i++) {
1233
+ var txInput = transactionInputs[i]
1234
+
1235
+ var srcKeys = []
1236
+ txInput.outputs.forEach((out) => {
1237
+ srcKeys.push(out.key)
1238
+ })
1239
+
1240
+ const sigs = generateRingSignature(txPrefixHash, txInput.keyImage, srcKeys, txInput.input.privateEphemeral, txInput.realOutputIndex)
1241
+ tx.signatures.push(sigs)
1242
+ }
1243
+
1244
+ return tx
1245
+ }
1246
+ }
1247
+
1248
+ /* This method is designed to create mixed inputs for use
1249
+ during transaction construction */
1250
+ function createTransactionInputs (ourOutputs, randomOutputs, mixin) {
1251
+ /* Make sure that if we are to use mixins that we've been given the
1252
+ correct number of sets of random outputs */
1253
+ if (ourOutputs.length !== randomOutputs.length && mixin !== 0) {
1254
+ throw new Error('There are not enough random output sets to mix with the real outputs')
1255
+ }
1256
+
1257
+ /* Make sure that there are the correct number of random outputs
1258
+ in each one of the sets that we were passed */
1259
+ for (var i = 0; i < randomOutputs.length; i++) {
1260
+ if ((randomOutputs[i] || []).length < mixin) {
1261
+ throw new Error('There are not enough outputs to mix with in the random outputs sets')
1262
+ }
1263
+ }
1264
+
1265
+ var mixedInputs = []
1266
+
1267
+ /* Loop through our outputs that we're using to send funds */
1268
+ for (i = 0; i < ourOutputs.length; i++) {
1269
+ const mixedOutputs = []
1270
+ const realOutput = ourOutputs[i]
1271
+
1272
+ /* If we're using mixins, then we need to use the random outputs */
1273
+ if (mixin !== 0) {
1274
+ /* Select our set of random outputs */
1275
+ const fakeOutputs = randomOutputs[i]
1276
+
1277
+ /* Sort the random outputs by their global indexes */
1278
+ fakeOutputs.sort((a, b) => {
1279
+ return BigInteger(a.globalIndex).compare(b.globalIndex)
1280
+ })
1281
+
1282
+ /* Insert the fake outputs into our array of mixed outputs */
1283
+ fakeOutputs.forEach((output) => {
1284
+ /* User can pass in extra outputs to let us continue if we get our
1285
+ own output as one to mix with. (See below). Continue once we've
1286
+ got enough. */
1287
+ if (mixedOutputs.length === mixin) {
1288
+ return
1289
+ }
1290
+ /* Can't mix with ourself, skip this iteration. Still might be able to
1291
+ succeed if given more outputs than mixin */
1292
+ if (output.globalIndex === realOutput.globalIndex) {
1293
+ return
1294
+ }
1295
+ mixedOutputs.push({
1296
+ key: output.key,
1297
+ index: output.globalIndex
1298
+ })
1299
+ })
1300
+
1301
+ if (mixedOutputs.length < mixin) {
1302
+ throw new Error('It is impossible to mix with yourself. Find some more random outputs and try again.')
1303
+ }
1304
+ }
1305
+
1306
+ /* Insert our real output into the stack of mixed outputs */
1307
+ mixedOutputs.push({
1308
+ key: realOutput.key,
1309
+ index: realOutput.globalIndex
1310
+ })
1311
+
1312
+ /* Sort the outputs again by `globalIndex` */
1313
+ mixedOutputs.sort((a, b) => { return BigInteger(a.index).compare(b.index) })
1314
+
1315
+ /* Set up our actual input, some extra information is added here
1316
+ to save time later */
1317
+ const input = {
1318
+ amount: realOutput.amount,
1319
+ realOutputIndex: 0,
1320
+ keyImage: realOutput.keyImage || false,
1321
+ input: realOutput.input,
1322
+ outputs: mixedOutputs
1323
+ }
1324
+
1325
+ /* Loop through the mixed outputs and look for our real input
1326
+ as we'll need to know which one it is in the array later */
1327
+ for (var j = 0; j < mixedOutputs.length; j++) {
1328
+ if (mixedOutputs[j].index === realOutput.globalIndex) {
1329
+ input.realOutputIndex = j
1330
+ }
1331
+ }
1332
+
1333
+ /* Push the input on to our stack */
1334
+ mixedInputs.push(input)
1335
+ }
1336
+
1337
+ /* Return the array of mixed inputs */
1338
+ return mixedInputs
1339
+ }
1340
+
1341
+ function prepareTransactionOutputs (outputs, _async) {
1342
+ if (!Array.isArray(outputs)) {
1343
+ throw new Error('Must supply an array of outputs')
1344
+ }
1345
+
1346
+ /* Generate a transaction key pair */
1347
+ const transactionKeys = randomKeypair()
1348
+
1349
+ /* Sort our outputs by amount */
1350
+ outputs.sort((a, b) => (a.amount > b.amount) ? 1 : ((b.amount > a.amount) ? -1 : 0))
1351
+
1352
+ const preparedOutputs = []
1353
+ let promises = []
1354
+
1355
+ if (_async) {
1356
+ promises = outputs.map((output, i) => {
1357
+ if (output.amount <= 0) {
1358
+ throw new Error('Cannot have an amount <= 0')
1359
+ }
1360
+
1361
+ return Promise.resolve(generateKeyDerivation(
1362
+ output.keys.publicViewKey, transactionKeys.privateKey
1363
+ )).then((outDerivation) => {
1364
+ return Promise.resolve(derivePublicKey(outDerivation, i, output.keys.publicSpendKey))
1365
+ }).then((outEphemeralPub) => {
1366
+ return ({
1367
+ amount: output.amount,
1368
+ target: {
1369
+ data: outEphemeralPub
1370
+ },
1371
+ type: 'txout_to_key'
1372
+ })
1373
+ })
1374
+ })
1375
+ } else {
1376
+ for (var i = 0; i < outputs.length; i++) {
1377
+ var output = outputs[i]
1378
+ if (output.amount <= 0) {
1379
+ throw new Error('Cannot have an amount <= 0')
1380
+ }
1381
+
1382
+ var outDerivation = generateKeyDerivation(output.keys.publicViewKey, transactionKeys.privateKey)
1383
+
1384
+ /* Generate the one time output key */
1385
+ const outEphemeralPub = derivePublicKey(outDerivation, i, output.keys.publicSpendKey)
1386
+
1387
+ /* Push it on to our stack */
1388
+ preparedOutputs.push({
1389
+ amount: output.amount,
1390
+ target: {
1391
+ data: outEphemeralPub
1392
+ },
1393
+ type: 'txout_to_key'
1394
+ })
1395
+ }
1396
+ }
1397
+
1398
+ if (_async) {
1399
+ return {
1400
+ transactionKeys,
1401
+ outputs: Promise.all(promises).then((outputs) => {
1402
+ return outputs
1403
+ })
1404
+ }
1405
+ }
1406
+
1407
+ return { transactionKeys, outputs: preparedOutputs }
1408
+ }
1409
+
1410
+ function getTransactionPrefixHash (tx) {
1411
+ /* Serialize the transaction as a string (blob) but
1412
+ do not include the signatures */
1413
+ var prefix = serializeTransaction(tx, true)
1414
+
1415
+ /* Hash it */
1416
+ return cnFastHash(prefix)
1417
+ }
1418
+
1419
+ function serializeTransaction (tx, headerOnly) {
1420
+ headerOnly = headerOnly || false
1421
+
1422
+ var buf = ''
1423
+ buf += encodeVarint(tx.version)
1424
+ buf += encodeVarint(tx.unlockTime)
1425
+
1426
+ /* Loop through the transaction inputs and put them in the buffer */
1427
+ buf += encodeVarint(tx.vin.length)
1428
+ for (var i = 0; i < tx.vin.length; i++) {
1429
+ var vin = tx.vin[i]
1430
+ switch (vin.type.toLowerCase()) {
1431
+ case 'input_to_key':
1432
+ buf += '02'
1433
+ buf += encodeVarint(vin.amount)
1434
+ buf += encodeVarint(vin.keyOffsets.length)
1435
+ for (var j = 0; j < vin.keyOffsets.length; j++) {
1436
+ buf += encodeVarint(vin.keyOffsets[j])
1437
+ }
1438
+ buf += vin.keyImage
1439
+ break
1440
+ default:
1441
+ throw new Error('Unhandled transaction input type: ' + vin.type)
1442
+ }
1443
+ }
1444
+
1445
+ /* Loop through the transaction outputs and put them in the buffer */
1446
+ buf += encodeVarint(tx.vout.length)
1447
+ for (i = 0; i < tx.vout.length; i++) {
1448
+ var vout = tx.vout[i]
1449
+ buf += encodeVarint(vout.amount)
1450
+ switch (vout.type.toLowerCase()) {
1451
+ case 'txout_to_key':
1452
+ buf += '02'
1453
+ buf += vout.target.data
1454
+ break
1455
+ default:
1456
+ throw new Error('Unhandled transacount output type: ' + vout.type)
1457
+ }
1458
+ }
1459
+
1460
+ /* If we supplied extra data, it needs to be hexadecimal */
1461
+ if (!isHex(tx.extra)) {
1462
+ throw new Error('Transaction extra has invalid hexadecimal data')
1463
+ }
1464
+
1465
+ buf += encodeVarint(tx.extra.length / 2)
1466
+ buf += tx.extra
1467
+
1468
+ /* Loop through the transaction signatures if this is a full transaction payload
1469
+ and put them in the buffer */
1470
+ if (!headerOnly) {
1471
+ if (tx.vin.length !== tx.signatures.length) {
1472
+ throw new Error('Number of signatures supplied does not equal the number of inputs used')
1473
+ }
1474
+ for (i = 0; i < tx.vin.length; i++) {
1475
+ for (j = 0; j < tx.signatures[i].length; j++) {
1476
+ buf += tx.signatures[i][j]
1477
+ }
1478
+ }
1479
+ }
1480
+
1481
+ return buf
1482
+ }
1483
+
1484
+ function generateKeyDerivation (transactionPublicKey, privateViewKey) {
1485
+ if (!isHex64(transactionPublicKey)) {
1486
+ throw new Error('Invalid public key format')
1487
+ }
1488
+
1489
+ if (!isHex64(privateViewKey)) {
1490
+ throw new Error('Invalid secret key format')
1491
+ }
1492
+
1493
+ if (userCryptoFunctions.generateKeyDerivation) {
1494
+ return userCryptoFunctions.generateKeyDerivation(transactionPublicKey, privateViewKey)
1495
+ } else if (TurtleCoinCrypto) {
1496
+ const [err, derivation] = TurtleCoinCrypto.generateKeyDerivation(privateViewKey, transactionPublicKey)
1497
+ if (err) throw new Error('Could not generate key derivation')
1498
+
1499
+ return derivation
1500
+ } else {
1501
+ var p = geScalarMult(transactionPublicKey, privateViewKey)
1502
+ return geScalarMult(p, d2s(8))
1503
+ }
1504
+ }
1505
+
1506
+ module.exports = {
1507
+ CryptoNote
1508
+ }