@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.
- package/.github/workflows/ci.yml +27 -0
- package/.idea/codeStyles/codeStyleConfig.xml +5 -0
- package/.idea/inspectionProfiles/Project_Default.xml +7 -0
- package/.idea/misc.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/turtlecoin-utils.iml +12 -0
- package/.idea/vcs.xml +6 -0
- package/.travis.yml +11 -0
- package/CONTRIBUTING.md +3 -0
- package/LICENSE +674 -0
- package/README.md +203 -0
- package/config.json +7 -0
- package/docs/.nojekyll +0 -0
- package/docs/CNAME +1 -0
- package/docs/assets/css/main.css +2321 -0
- package/docs/assets/images/icons.png +0 -0
- package/docs/assets/images/icons@2x.png +0 -0
- package/docs/assets/images/widgets.png +0 -0
- package/docs/assets/images/widgets@2x.png +0 -0
- package/docs/assets/js/main.js +1 -0
- package/docs/assets/js/search.js +3 -0
- package/docs/classes/address.html +964 -0
- package/docs/classes/addressprefix.html +431 -0
- package/docs/classes/block.html +965 -0
- package/docs/classes/blocktemplate.html +695 -0
- package/docs/classes/cryptonote.html +1137 -0
- package/docs/classes/ed25519.keypair.html +400 -0
- package/docs/classes/ed25519.keys.html +373 -0
- package/docs/classes/extranoncetag.extranoncedata.html +454 -0
- package/docs/classes/extranoncetag.extranoncepaymentid.html +453 -0
- package/docs/classes/extranoncetag.iextranonce.html +347 -0
- package/docs/classes/extratag.extramergedmining.html +494 -0
- package/docs/classes/extratag.extranonce.html +530 -0
- package/docs/classes/extratag.extrapadding.html +456 -0
- package/docs/classes/extratag.extrapublickey.html +460 -0
- package/docs/classes/extratag.iextratag.html +355 -0
- package/docs/classes/levinpacket.html +674 -0
- package/docs/classes/levinpayloads.handshake.html +731 -0
- package/docs/classes/levinpayloads.ilevinpayload.html +318 -0
- package/docs/classes/levinpayloads.liteblock.html +494 -0
- package/docs/classes/levinpayloads.missingtransactions.html +494 -0
- package/docs/classes/levinpayloads.newblock.html +540 -0
- package/docs/classes/levinpayloads.newtransactions.html +402 -0
- package/docs/classes/levinpayloads.peerentry.html +610 -0
- package/docs/classes/levinpayloads.ping.html +450 -0
- package/docs/classes/levinpayloads.rawblock.html +344 -0
- package/docs/classes/levinpayloads.requestchain.html +402 -0
- package/docs/classes/levinpayloads.requestgetobjects.html +448 -0
- package/docs/classes/levinpayloads.requesttxpool.html +402 -0
- package/docs/classes/levinpayloads.responsechain.html +494 -0
- package/docs/classes/levinpayloads.responsegetobjects.html +540 -0
- package/docs/classes/levinpayloads.timedsync.html +540 -0
- package/docs/classes/multisig.html +930 -0
- package/docs/classes/multisigmessage.html +694 -0
- package/docs/classes/parentblock.html +347 -0
- package/docs/classes/transaction.html +925 -0
- package/docs/classes/transactioninputs.coinbaseinput.html +390 -0
- package/docs/classes/transactioninputs.itransactioninput.html +321 -0
- package/docs/classes/transactioninputs.keyinput.html +459 -0
- package/docs/classes/transactionoutputs.itransactionoutput.html +317 -0
- package/docs/classes/transactionoutputs.keyoutput.html +422 -0
- package/docs/enums/extranoncetag.noncetagtype.html +246 -0
- package/docs/enums/extratag.extratagtype.html +280 -0
- package/docs/enums/levinprotocol.commandtype.html +391 -0
- package/docs/enums/transactioninputs.inputtype.html +246 -0
- package/docs/enums/transactionoutputs.outputtype.html +229 -0
- package/docs/globals.html +238 -0
- package/docs/index.html +271 -0
- package/docs/interfaces/interfaces.config.html +590 -0
- package/docs/interfaces/interfaces.daemonblocktemplateresponse.html +323 -0
- package/docs/interfaces/interfaces.generatedinput.html +304 -0
- package/docs/interfaces/interfaces.generatedoutput.html +285 -0
- package/docs/interfaces/interfaces.inputkeys.html +304 -0
- package/docs/interfaces/interfaces.ipreparedtransaction.html +268 -0
- package/docs/interfaces/interfaces.output.html +399 -0
- package/docs/interfaces/interfaces.preparedringsignature.html +377 -0
- package/docs/interfaces/interfaces.preparedtransaction.html +329 -0
- package/docs/interfaces/interfaces.randomoutput.html +285 -0
- package/docs/interfaces/interfaces.transactionrecipient.html +285 -0
- package/docs/interfaces/multisiginterfaces.partialkeyimage.html +277 -0
- package/docs/interfaces/multisiginterfaces.partialsigningkey.html +277 -0
- package/docs/modules/ed25519.html +195 -0
- package/docs/modules/extranoncetag.html +208 -0
- package/docs/modules/extratag.html +216 -0
- package/docs/modules/interfaces.html +231 -0
- package/docs/modules/levinpayloads.html +247 -0
- package/docs/modules/levinprotocol.html +191 -0
- package/docs/modules/multisiginterfaces.html +195 -0
- package/docs/modules/transactioninputs.html +208 -0
- package/docs/modules/transactionoutputs.html +204 -0
- package/index.d.ts +417 -0
- package/index.js +1508 -0
- package/lib/base58.js +220 -0
- package/lib/biginteger.js +1591 -0
- package/lib/blocktemplate.js +408 -0
- package/lib/crypto.js +19698 -0
- package/lib/mnemonic.js +1204 -0
- package/lib/nacl-fast-cn.js +608 -0
- package/lib/ringsigs.js +24262 -0
- package/lib/sha3.js +477 -0
- package/package.json +58 -0
- package/src/Address.ts +433 -0
- package/src/AddressPrefix.ts +117 -0
- package/src/Block.ts +556 -0
- package/src/BlockTemplate.ts +289 -0
- package/src/Common.ts +105 -0
- package/src/Config.ts +66 -0
- package/src/CryptoNote.ts +1072 -0
- package/src/LevinPacket.ts +366 -0
- package/src/Multisig.ts +600 -0
- package/src/MultisigMessage.ts +374 -0
- package/src/ParentBlock.ts +39 -0
- package/src/Transaction.ts +628 -0
- package/src/Types/ED25519.ts +187 -0
- package/src/Types/IExtraNonce.ts +225 -0
- package/src/Types/IExtraTag.ts +507 -0
- package/src/Types/ITransaction.ts +230 -0
- package/src/Types/ITransactionInput.ts +190 -0
- package/src/Types/ITransactionOutput.ts +108 -0
- package/src/Types/LevinPayloads.ts +1576 -0
- package/src/Types/MultisigInterfaces.ts +65 -0
- package/src/Types/PortableStorage.ts +289 -0
- package/src/Types.ts +36 -0
- package/src/index.ts +36 -0
- package/test/template.json +6 -0
- package/test/test.js +1457 -0
- package/tests/blocktemplate.json +6 -0
- package/tests/tests.js +215 -0
- package/tsconfig.json +15 -0
- package/tslint.json +36 -0
- package/typedoc.json +10 -0
- 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
|
+
}
|