@wishknish/knishio-client-js 0.5.2 → 0.6.1

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 (133) hide show
  1. package/README.md +207 -274
  2. package/dist/client.iife.js +533 -0
  3. package/package.json +37 -79
  4. package/src/.babelrc +0 -22
  5. package/src/Atom.js +171 -132
  6. package/src/AtomMeta.js +76 -50
  7. package/src/AuthToken.js +38 -47
  8. package/src/KnishIOClient.js +934 -987
  9. package/src/Meta.js +15 -17
  10. package/src/Molecule.js +423 -494
  11. package/src/PolicyMeta.js +32 -41
  12. package/src/TokenUnit.js +30 -32
  13. package/src/Wallet.js +275 -265
  14. package/src/exception/AtomIndexException.js +4 -8
  15. package/src/exception/AtomsMissingException.js +4 -6
  16. package/src/exception/AuthorizationRejectedException.js +4 -5
  17. package/src/exception/BalanceInsufficientException.js +4 -8
  18. package/src/exception/BaseException.js +6 -8
  19. package/src/exception/BatchIdException.js +5 -7
  20. package/src/exception/CodeException.js +4 -8
  21. package/src/{libraries/ApolloLink/HttpLink.js → exception/DecryptionKeyException.js} +12 -15
  22. package/src/exception/InvalidResponseException.js +4 -5
  23. package/src/exception/MetaMissingException.js +4 -6
  24. package/src/exception/MolecularHashMismatchException.js +4 -6
  25. package/src/exception/MolecularHashMissingException.js +4 -5
  26. package/src/exception/NegativeAmountException.js +4 -5
  27. package/src/exception/PolicyInvalidException.js +4 -4
  28. package/src/exception/SignatureMalformedException.js +4 -5
  29. package/src/exception/SignatureMismatchException.js +4 -5
  30. package/src/exception/StackableUnitAmountException.js +4 -5
  31. package/src/exception/StackableUnitDecimalsException.js +4 -5
  32. package/src/exception/TransferBalanceException.js +4 -5
  33. package/src/exception/TransferMalformedException.js +4 -5
  34. package/src/exception/TransferMismatchedException.js +4 -5
  35. package/src/exception/TransferRemainderException.js +4 -5
  36. package/src/exception/TransferToSelfException.js +4 -5
  37. package/src/exception/TransferUnbalancedException.js +4 -5
  38. package/src/exception/UnauthenticatedException.js +4 -5
  39. package/src/{libraries/ApolloLink/AuthLink.js → exception/WalletCredentialException.js} +12 -40
  40. package/src/exception/WalletShadowException.js +4 -5
  41. package/src/exception/WrongTokenTypeException.js +4 -5
  42. package/src/exception/index.js +26 -26
  43. package/src/index.js +8 -10
  44. package/src/instance/Rules/Callback.js +91 -93
  45. package/src/instance/Rules/Condition.js +21 -23
  46. package/src/instance/Rules/Meta.js +13 -14
  47. package/src/instance/Rules/Rule.js +39 -43
  48. package/src/instance/Rules/exception/RuleArgumentException.js +4 -4
  49. package/src/libraries/CheckMolecule.js +253 -232
  50. package/src/libraries/Decimal.js +13 -17
  51. package/src/libraries/Dot.js +74 -48
  52. package/src/libraries/Hex.js +49 -54
  53. package/src/libraries/array.js +50 -41
  54. package/src/libraries/crypto.js +20 -27
  55. package/src/libraries/strings.js +58 -91
  56. package/src/libraries/urql/UrqlClientWrapper.js +166 -0
  57. package/src/mutation/Mutation.js +44 -25
  58. package/src/mutation/MutationActiveSession.js +12 -12
  59. package/src/mutation/MutationClaimShadowWallet.js +15 -17
  60. package/src/mutation/MutationCreateIdentifier.js +11 -12
  61. package/src/mutation/MutationCreateMeta.js +11 -12
  62. package/src/mutation/MutationCreateRule.js +11 -12
  63. package/src/mutation/MutationCreateToken.js +18 -13
  64. package/src/mutation/MutationCreateWallet.js +9 -11
  65. package/src/mutation/MutationDepositBufferToken.js +7 -9
  66. package/src/mutation/MutationLinkIdentifier.js +12 -14
  67. package/src/mutation/MutationProposeMolecule.js +24 -25
  68. package/src/mutation/MutationRequestAuthorization.js +9 -10
  69. package/src/mutation/MutationRequestAuthorizationGuest.js +12 -15
  70. package/src/mutation/MutationRequestTokens.js +11 -14
  71. package/src/mutation/MutationTransferTokens.js +11 -14
  72. package/src/mutation/MutationWithdrawBufferToken.js +7 -10
  73. package/src/query/Query.js +62 -36
  74. package/src/query/QueryActiveSession.js +11 -13
  75. package/src/query/QueryAtom.js +75 -76
  76. package/src/query/QueryBalance.js +11 -12
  77. package/src/query/QueryBatch.js +17 -14
  78. package/src/query/QueryBatchHistory.js +16 -13
  79. package/src/query/QueryContinuId.js +13 -10
  80. package/src/query/QueryMetaType.js +45 -57
  81. package/src/query/QueryMetaTypeViaAtom.js +49 -57
  82. package/src/query/QueryPolicy.js +11 -12
  83. package/src/query/QueryToken.js +11 -13
  84. package/src/query/QueryUserActivity.js +11 -13
  85. package/src/query/QueryWalletBundle.js +15 -47
  86. package/src/query/QueryWalletList.js +15 -16
  87. package/src/response/Response.js +29 -34
  88. package/src/response/ResponseActiveSession.js +6 -6
  89. package/src/response/ResponseAtom.js +29 -30
  90. package/src/response/ResponseAuthorizationGuest.js +17 -18
  91. package/src/response/ResponseBalance.js +12 -13
  92. package/src/response/ResponseClaimShadowWallet.js +1 -1
  93. package/src/response/ResponseContinuId.js +21 -22
  94. package/src/response/ResponseCreateIdentifier.js +1 -1
  95. package/src/response/ResponseCreateMeta.js +1 -1
  96. package/src/response/ResponseCreateRule.js +1 -1
  97. package/src/response/ResponseCreateToken.js +1 -1
  98. package/src/response/ResponseCreateWallet.js +1 -1
  99. package/src/response/ResponseLinkIdentifier.js +9 -10
  100. package/src/response/ResponseMetaBatch.js +6 -8
  101. package/src/response/ResponseMetaType.js +19 -20
  102. package/src/response/ResponseMetaTypeViaAtom.js +19 -19
  103. package/src/response/ResponsePolicy.js +14 -15
  104. package/src/response/ResponseProposeMolecule.js +27 -30
  105. package/src/response/ResponseQueryActiveSession.js +20 -23
  106. package/src/response/ResponseQueryUserActivity.js +11 -12
  107. package/src/response/ResponseRequestAuthorization.js +11 -16
  108. package/src/response/ResponseRequestAuthorizationGuest.js +18 -21
  109. package/src/response/ResponseRequestTokens.js +1 -1
  110. package/src/response/ResponseTransferTokens.js +8 -9
  111. package/src/response/ResponseWalletBundle.js +16 -17
  112. package/src/response/ResponseWalletList.js +44 -47
  113. package/src/subscribe/ActiveSessionSubscribe.js +5 -6
  114. package/src/subscribe/ActiveWalletSubscribe.js +5 -6
  115. package/src/subscribe/CreateMoleculeSubscribe.js +5 -5
  116. package/src/subscribe/Subscribe.js +26 -26
  117. package/src/subscribe/WalletStatusSubscribe.js +5 -6
  118. package/src/versions/HashAtom.js +78 -0
  119. package/src/versions/Version4.js +34 -0
  120. package/src/versions/index.js +5 -0
  121. package/dist/client.umd.js +0 -453
  122. package/src/httpClient/ApolloClient.js +0 -245
  123. package/src/libraries/ApolloLink/CipherLink.js +0 -117
  124. package/src/libraries/ApolloLink/Client.js +0 -231
  125. package/src/libraries/ApolloLink/PusherLink.js +0 -234
  126. package/src/libraries/ApolloLink/handler.js +0 -106
  127. package/src/libraries/Base58.js +0 -71
  128. package/src/libraries/Base64.js +0 -40
  129. package/src/libraries/BaseX.js +0 -91
  130. package/src/libraries/Soda.js +0 -93
  131. package/src/query/QueryMetaInstance.js +0 -99
  132. package/src/test/Test.js +0 -670
  133. package/src/test/TestTokenUnit.js +0 -340
package/src/Wallet.js CHANGED
@@ -45,119 +45,125 @@ Please visit https://github.com/WishKnish/KnishIO-Client-JS for information.
45
45
 
46
46
  License: https://github.com/WishKnish/KnishIO-Client-JS/blob/master/LICENSE
47
47
  */
48
- import { shake256 } from 'js-sha3';
49
- import bigInt from 'big-integer/BigInteger';
48
+ import JsSHA from 'jssha'
50
49
  import {
51
50
  chunkSubstr,
52
51
  isHex,
53
52
  randomString
54
- } from './libraries/strings';
53
+ } from './libraries/strings'
55
54
  import {
56
55
  generateBatchId,
57
- generateBundleHash
58
- } from './libraries/crypto';
59
- import BaseX from './libraries/BaseX';
60
- import TokenUnit from './TokenUnit';
61
- import Soda from './libraries/Soda';
56
+ generateBundleHash,
57
+ generateSecret
58
+ } from './libraries/crypto'
59
+ import TokenUnit from './TokenUnit'
60
+ import WalletCredentialException from './exception/WalletCredentialException'
61
+ import { ml_kem768 as MlKEM768 } from '@noble/post-quantum/ml-kem'
62
62
 
63
63
  /**
64
64
  * Wallet class represents the set of public and private
65
65
  * keys to sign Molecules
66
66
  */
67
67
  export default class Wallet {
68
-
69
68
  /**
70
69
  * Class constructor
71
70
  *
72
71
  * @param {string|null} secret - typically a 2048-character biometric hash
72
+ * @param {string|null} bundle - 64-character hexadecimal user identifier
73
73
  * @param {string} token - slug for the token this wallet is intended for
74
+ * @param {string|null} address - hexadecimal public key for the signature of this wallet
74
75
  * @param {string|null} position - hexadecimal string used to salt the secret and produce one-time signatures
75
76
  * @param {string|null} batchId
76
77
  * @param {string|null} characters
77
78
  */
78
- constructor ( {
79
+ constructor ({
79
80
  secret = null,
81
+ bundle = null,
80
82
  token = 'USER',
83
+ address = null,
81
84
  position = null,
82
85
  batchId = null,
83
86
  characters = null
84
- } ) {
85
-
86
- this.token = token;
87
- this.balance = 0;
88
- this.molecules = {};
87
+ }) {
88
+ this.token = token
89
+ this.balance = 0
90
+ this.molecules = {}
89
91
 
90
92
  // Empty values
91
- this.key = null;
92
- this.address = null;
93
- this.privkey = null;
94
- this.pubkey = null;
95
- this.tokenUnits = [];
96
- this.tradeRates = {};
97
-
98
- this.bundle = null;
99
- this.batchId = batchId;
100
- this.position = position;
101
- this.characters = characters;
102
-
103
- if ( secret ) {
104
-
93
+ this.key = null
94
+ this.privkey = null
95
+ this.pubkey = null
96
+ this.tokenUnits = []
97
+ this.tradeRates = {}
98
+
99
+ this.address = address
100
+ this.position = position
101
+ this.bundle = bundle
102
+ this.batchId = batchId
103
+ this.characters = characters
104
+
105
+ if (secret) {
105
106
  // Set bundle from the secret
106
- this.bundle = generateBundleHash( secret );
107
+ this.bundle = this.bundle || generateBundleHash(secret, 'Wallet::constructor')
107
108
 
108
109
  // Generate a position for non-shadow wallet if not initialized
109
- this.position = this.position || Wallet.generatePosition();
110
+ this.position = this.position || Wallet.generatePosition()
110
111
 
111
112
  // Key & address initialization
112
- this.key = Wallet.generateKey( {
113
+ this.key = Wallet.generateKey({
113
114
  secret,
114
115
  token: this.token,
115
116
  position: this.position
116
- } );
117
- this.address = Wallet.generateAddress( this.key );
118
-
119
- // Soda object initialization
120
- this.soda = new Soda( characters );
121
-
122
- // Private & pubkey initialization
123
- this.privkey = this.soda.generatePrivateKey( this.key );
124
- this.pubkey = this.soda.generatePublicKey( this.privkey );
117
+ })
118
+ this.address = this.address || Wallet.generateAddress(this.key)
125
119
 
126
120
  // Set characters
127
- this.characters = this.characters || 'BASE64';
121
+ this.characters = this.characters || 'BASE64'
122
+
123
+ // Initialize ML-KEM keys
124
+ this.initializeMLKEM()
128
125
  }
129
126
  }
130
127
 
131
128
  /**
132
129
  * Creates a new Wallet instance
133
130
  *
134
- * @param {string} secretOrBundle
131
+ * @param {string|null} secret
132
+ * @param {string|null} bundle
135
133
  * @param {string} token
136
134
  * @param {string|null} batchId
137
135
  * @param {string|null} characters
138
136
  * @return {Wallet}
139
137
  */
140
- static create ( {
141
- secretOrBundle,
138
+ static create ({
139
+ secret = null,
140
+ bundle = null,
142
141
  token,
143
142
  batchId = null,
144
143
  characters = null
145
- } ) {
144
+ }) {
145
+ let position = null
146
+
147
+ // No credentials parameters provided?
148
+ if (!secret && !bundle) {
149
+ throw new WalletCredentialException()
150
+ }
146
151
 
147
- let secret = Wallet.isBundleHash( secretOrBundle ) ? null : secretOrBundle;
148
- let bundle = secret ? generateBundleHash( secret ) : secretOrBundle;
149
- let position = secret ? Wallet.generatePosition() : null;
152
+ // Secret, but no bundle?
153
+ if (secret && !bundle) {
154
+ position = Wallet.generatePosition()
155
+ bundle = generateBundleHash(secret, 'Wallet::create')
156
+ }
150
157
 
151
158
  // Wallet initialization
152
- let wallet = new Wallet( {
159
+ return new Wallet({
153
160
  secret,
161
+ bundle,
154
162
  token,
155
163
  position,
156
164
  batchId,
157
165
  characters
158
- } );
159
- wallet.bundle = bundle;
160
- return wallet;
166
+ })
161
167
  }
162
168
 
163
169
  /**
@@ -166,13 +172,12 @@ export default class Wallet {
166
172
  * @param {string} maybeBundleHash
167
173
  * @return {boolean}
168
174
  */
169
- static isBundleHash ( maybeBundleHash ) {
170
-
171
- if ( typeof maybeBundleHash !== 'string' ) {
172
- return false;
175
+ static isBundleHash (maybeBundleHash) {
176
+ if (typeof maybeBundleHash !== 'string') {
177
+ return false
173
178
  }
174
179
 
175
- return maybeBundleHash.length === 64 && isHex( maybeBundleHash );
180
+ return maybeBundleHash.length === 64 && isHex(maybeBundleHash)
176
181
  }
177
182
 
178
183
  /**
@@ -181,278 +186,283 @@ export default class Wallet {
181
186
  * @param unitsData
182
187
  * @return {[]}
183
188
  */
184
- static getTokenUnits ( unitsData ) {
185
- let result = [];
186
- unitsData.forEach( unitData => {
187
- result.push( TokenUnit.createFromDB( unitData ) );
188
- } );
189
- return result;
190
- }
191
-
192
- /**
193
- *
194
- * @returns {*[]}
195
- */
196
- getTokenUnitsData () {
197
- const result = [];
198
- this.tokenUnits.forEach( tokenUnit => {
199
- result.push( tokenUnit.toData() );
200
- } );
201
- return result;
189
+ static getTokenUnits (unitsData) {
190
+ const result = []
191
+ unitsData.forEach(unitData => {
192
+ result.push(TokenUnit.createFromDB(unitData))
193
+ })
194
+ return result
202
195
  }
203
196
 
204
-
205
197
  /**
206
- * Split token units
198
+ * Generates a private key for the given parameters
207
199
  *
208
- * @param {array} units
209
- * @param remainderWallet
210
- * @param recipientWallet
200
+ * @param {string} secret
201
+ * @param {string} token
202
+ * @param {string} position
203
+ * @return {string}
211
204
  */
212
- splitUnits (
213
- units, // Array: token unit IDs
214
- remainderWallet,
215
- recipientWallet = null
216
- ) {
217
-
218
- // No units supplied, nothing to split
219
- if ( units.length === 0 ) {
220
- return;
221
- }
205
+ static generateKey ({
206
+ secret,
207
+ token,
208
+ position
209
+ }) {
210
+ // Converting secret to bigInt
211
+ const bigIntSecret = BigInt(`0x${ secret }`)
222
212
 
223
- // Init recipient & remainder token units
224
- let recipientTokenUnits = [];
225
- let remainderTokenUnits = [];
226
- this.tokenUnits.forEach( tokenUnit => {
227
- if ( units.includes( tokenUnit.id ) ) {
228
- recipientTokenUnits.push( tokenUnit );
229
- } else {
230
- remainderTokenUnits.push( tokenUnit );
231
- }
232
- } );
213
+ // Adding new position to the user secret to produce the indexed key
214
+ const indexedKey = bigIntSecret + BigInt(`0x${ position }`)
233
215
 
216
+ // Hashing the indexed key to produce the intermediate key
217
+ const intermediateKeySponge = new JsSHA('SHAKE256', 'TEXT')
234
218
 
235
- // Reset token units to the sending value
236
- this.tokenUnits = recipientTokenUnits;
219
+ intermediateKeySponge.update(indexedKey.toString(16))
237
220
 
238
- // Set token units to recipient & remainder
239
- if ( recipientWallet !== null ) {
240
- recipientWallet.tokenUnits = recipientTokenUnits;
221
+ if (token) {
222
+ intermediateKeySponge.update(token)
241
223
  }
242
- remainderWallet.tokenUnits = remainderTokenUnits;
243
- }
244
224
 
245
- /**
246
- * @return boolean
247
- */
248
- isShadow () {
249
- return (
250
- ( typeof this.position === 'undefined' || null === this.position ) &&
251
- ( typeof this.address === 'undefined' || null === this.address )
252
- );
225
+ // Hashing the intermediate key to produce the private key
226
+ const privateKeySponge = new JsSHA('SHAKE256', 'TEXT')
227
+ privateKeySponge.update(intermediateKeySponge.getHash('HEX', { outputLen: 8192 }))
228
+ return privateKeySponge.getHash('HEX', { outputLen: 8192 })
253
229
  }
254
230
 
255
231
  /**
256
- * Sets up a batch ID - either using the sender's, or a new one
232
+ * Generates a wallet address
257
233
  *
258
- * @param {Wallet} sourceWallet
259
- * @param {boolean} isRemainder
234
+ * @param {string} key
235
+ * @return {string}
260
236
  */
261
- initBatchId ( {
262
- sourceWallet,
263
- isRemainder = false
264
- } ) {
237
+ static generateAddress (key) {
238
+ // Subdivide private key into 16 fragments of 128 characters each
239
+ const keyFragments = chunkSubstr(key, 128)
240
+ // Generating wallet digest
241
+ const digestSponge = new JsSHA('SHAKE256', 'TEXT')
242
+
243
+ for (const index in keyFragments) {
244
+ let workingFragment = keyFragments[index]
245
+ for (let fragmentCount = 1; fragmentCount <= 16; fragmentCount++) {
246
+ const workingSponge = new JsSHA('SHAKE256', 'TEXT')
247
+ workingSponge.update(workingFragment)
248
+ workingFragment = workingSponge.getHash('HEX', { outputLen: 512 })
249
+ }
265
250
 
266
- if ( sourceWallet.batchId ) {
267
- this.batchId = isRemainder ? sourceWallet.batchId : generateBatchId( {} );
251
+ digestSponge.update(workingFragment)
268
252
  }
253
+
254
+ // Producing wallet address
255
+ const outputSponge = new JsSHA('SHAKE256', 'TEXT')
256
+ outputSponge.update(digestSponge.getHash('HEX', { outputLen: 8192 }))
257
+ return outputSponge.getHash('HEX', { outputLen: 256 })
269
258
  }
270
259
 
271
260
  /**
272
- * Encrypts a message for this wallet instance
273
261
  *
274
- * @param {object|array} message
275
- * @return {object}
262
+ * @param saltLength
263
+ * @returns {string}
276
264
  */
277
- encryptMessage ( message ) {
278
-
279
- const encrypt = {};
280
-
281
- for ( let index = 1, length = arguments.length; index < length; index++ ) {
282
- encrypt[ this.soda.shortHash( arguments[ index ] ) ] = this.soda.encrypt( message, arguments[ index ] );
283
- }
284
-
285
- return encrypt;
265
+ static generatePosition (saltLength = 64) {
266
+ return randomString(saltLength, 'abcdef0123456789')
286
267
  }
287
268
 
288
269
  /**
289
- * Uses the current wallet's private key to decrypt the given message
290
- *
291
- * @param {string|object} message
292
- * @return {null|any}
270
+ * Initializes the ML-KEM key pair
293
271
  */
294
- decryptMessage ( message ) {
295
-
296
- const pubKey = this.pubkey;
297
- let encrypt = message;
298
-
299
- if ( message !== null
300
- && typeof message === 'object'
301
- && Object.prototype.toString.call( message ) === '[object Object]' ) {
302
-
303
- encrypt = message[ this.soda.shortHash( pubKey ) ] || '';
272
+ initializeMLKEM () {
273
+ // Generate a 64-byte (512-bit) seed from the Knish.IO private key
274
+ const seedHex = generateSecret(this.key, 64)
275
+
276
+ // Convert the hex string to a Uint8Array
277
+ const seed = new Uint8Array(64)
278
+ for (let i = 0; i < 64; i++) {
279
+ seed[i] = parseInt(seedHex.substr(i * 2, 2), 16)
304
280
  }
305
281
 
306
- return this.soda.decrypt( encrypt, this.privkey, pubKey );
282
+ const { publicKey, secretKey } = MlKEM768.keygen(seed)
283
+ this.pubkey = this.serializeKey(publicKey)
284
+ this.privkey = secretKey // Note: We're keeping privkey as UInt8Array for security
307
285
  }
308
286
 
309
- /**
310
- * @param {string|object} message
311
- *
312
- * @returns {Buffer|ArrayBuffer|Uint8Array}
313
- */
314
- decryptBinary ( message ) {
315
- const decrypt = this.decryptMessage( message );
316
- return ( new BaseX( { characters: 'BASE64' } ) ).decode( decrypt );
287
+ serializeKey (key) {
288
+ return btoa(String.fromCharCode.apply(null, key))
289
+ }
290
+
291
+ deserializeKey (serializedKey) {
292
+ const binaryString = atob(serializedKey)
293
+ return new Uint8Array(binaryString.length).map((_, i) => binaryString.charCodeAt(i))
317
294
  }
318
295
 
319
296
  /**
320
- * @param {Buffer|ArrayBuffer|Uint8Array} message
321
- * @returns {{string}}
297
+ *
298
+ * @returns {*[]}
322
299
  */
323
- encryptBinary ( message ) {
324
- const arg = Array.from( arguments ).slice( 1 );
325
-
326
- const messageBase64 = ( new BaseX( { characters: 'BASE64' } ) ).encode( message );
327
-
328
- return this.encryptMessage( messageBase64, ...arg );
300
+ getTokenUnitsData () {
301
+ const result = []
302
+ this.tokenUnits.forEach(tokenUnit => {
303
+ result.push(tokenUnit.toData())
304
+ })
305
+ return result
329
306
  }
330
307
 
331
308
  /**
332
- * Encrypts a string for the given public keys
309
+ * Split token units
333
310
  *
334
- * @param {string} data
335
- * @param {string|array} publicKeys
336
- * @return {string}
311
+ * @param {array} units
312
+ * @param remainderWallet
313
+ * @param recipientWallet
337
314
  */
338
- encryptString ( {
339
- data,
340
- publicKeys
341
- } ) {
342
-
343
- if ( data ) {
344
-
345
- // Retrieving sender's encryption public key
346
- const publicKey = this.pubkey;
315
+ splitUnits (
316
+ units, // Array: token unit IDs
317
+ remainderWallet,
318
+ recipientWallet = null
319
+ ) {
320
+ // No units supplied, nothing to split
321
+ if (units.length === 0) {
322
+ return
323
+ }
347
324
 
348
- // If the additional public keys is supplied as a string, convert to array
349
- if ( typeof publicKeys === 'string' ) {
350
- publicKeys = [ publicKeys ];
325
+ // Init recipient & remainder token units
326
+ const recipientTokenUnits = []
327
+ const remainderTokenUnits = []
328
+ this.tokenUnits.forEach(tokenUnit => {
329
+ if (units.includes(tokenUnit.id)) {
330
+ recipientTokenUnits.push(tokenUnit)
331
+ } else {
332
+ remainderTokenUnits.push(tokenUnit)
351
333
  }
334
+ })
352
335
 
353
- // Encrypting message
354
- const encryptedData = this.encryptMessage( data, publicKey, ...publicKeys );
355
- return btoa( JSON.stringify( encryptedData ) );
336
+ // Reset token units to the sending value
337
+ this.tokenUnits = recipientTokenUnits
356
338
 
339
+ // Set token units to recipient & remainder
340
+ if (recipientWallet !== null) {
341
+ recipientWallet.tokenUnits = recipientTokenUnits
357
342
  }
358
- };
343
+ remainderWallet.tokenUnits = remainderTokenUnits
344
+ }
359
345
 
360
346
  /**
361
- * Attempts to decrypt the given string
347
+ * Create a remainder wallet from the source one
362
348
  *
363
- * @param {string} data
364
- * @param {string|null} fallbackValue
365
- * @return {array|object}
349
+ * @param secret
366
350
  */
367
- decryptString ( {
368
- data,
369
- fallbackValue = null
370
- } ) {
371
-
372
- if ( data ) {
373
- try {
374
-
375
- const decrypted = JSON.parse( atob( data ) );
376
- return this.decryptMessage( decrypted ) || fallbackValue;
377
-
378
- } catch ( e ) {
379
-
380
- // Probably not actually encrypted
381
- console.error( e );
382
- return fallbackValue || data;
383
-
384
- }
385
- }
386
-
387
- };
351
+ createRemainder (secret) {
352
+ const remainderWallet = Wallet.create({
353
+ secret,
354
+ token: this.token,
355
+ characters: this.characters
356
+ })
357
+ remainderWallet.initBatchId({
358
+ sourceWallet: this,
359
+ isRemainder: true
360
+ })
361
+ return remainderWallet
362
+ }
388
363
 
389
364
  /**
390
- * Generates a private key for the given parameters
391
- *
392
- * @param {string} secret
393
- * @param {string} token
394
- * @param {string} position
395
- * @return {string}
365
+ * @return boolean
396
366
  */
397
- static generateKey ( {
398
- secret,
399
- token,
400
- position
401
- } ) {
402
-
403
- // Converting secret to bigInt
404
- const bigIntSecret = bigInt( secret, 16 ),
405
- // Adding new position to the user secret to produce the indexed key
406
- indexedKey = bigIntSecret.add( bigInt( position, 16 ) ),
407
- // Hashing the indexed key to produce the intermediate key
408
- intermediateKeySponge = shake256.create( 8192 );
409
-
410
- intermediateKeySponge.update( indexedKey.toString( 16 ) );
411
-
412
- if ( token ) {
413
- intermediateKeySponge.update( token );
414
- }
415
-
416
- // Hashing the intermediate key to produce the private key
417
- return shake256.create( 8192 ).update( intermediateKeySponge.hex() ).hex();
367
+ isShadow () {
368
+ return (
369
+ (typeof this.position === 'undefined' || this.position === null) &&
370
+ (typeof this.address === 'undefined' || this.address === null)
371
+ )
418
372
  }
419
373
 
420
374
  /**
421
- * Generates a wallet address
375
+ * Sets up a batch ID - either using the sender's, or a new one
422
376
  *
423
- * @param {string} key
424
- * @return {string}
377
+ * @param {Wallet} sourceWallet
378
+ * @param {boolean} isRemainder
425
379
  */
426
- static generateAddress ( key ) {
427
-
428
- // Subdivide private key into 16 fragments of 128 characters each
429
- const keyFragments = chunkSubstr( key, 128 ),
430
- // Generating wallet digest
431
- digestSponge = shake256.create( 8192 );
432
-
433
- for ( const index in keyFragments ) {
434
-
435
- let workingFragment = keyFragments[ index ];
380
+ initBatchId ({
381
+ sourceWallet,
382
+ isRemainder = false
383
+ }) {
384
+ if (sourceWallet.batchId) {
385
+ this.batchId = isRemainder ? sourceWallet.batchId : generateBatchId({})
386
+ }
387
+ }
436
388
 
437
- for ( let fragmentCount = 1; fragmentCount <= 16; fragmentCount++ ) {
389
+ async encryptMessage (message, recipientPubkey) {
390
+ const messageString = JSON.stringify(message)
391
+ const messageUint8 = new TextEncoder().encode(messageString)
392
+ const deserializedPubkey = this.deserializeKey(recipientPubkey)
393
+ const { cipherText, sharedSecret } = MlKEM768.encapsulate(deserializedPubkey)
394
+ const encryptedMessage = await this.encryptWithSharedSecret(messageUint8, sharedSecret)
395
+ return {
396
+ cipherText: this.serializeKey(cipherText),
397
+ encryptedMessage: this.serializeKey(encryptedMessage)
398
+ }
399
+ }
438
400
 
439
- workingFragment = shake256.create( 512 ).update( workingFragment ).hex();
401
+ async decryptMessage (encryptedData) {
402
+ const { cipherText, encryptedMessage } = encryptedData
440
403
 
441
- }
404
+ const sharedSecret = MlKEM768.decapsulate(this.deserializeKey(cipherText), this.privkey)
405
+ const decryptedUint8 = await this.decryptWithSharedSecret(this.deserializeKey(encryptedMessage), sharedSecret)
442
406
 
443
- digestSponge.update( workingFragment );
444
- }
407
+ const decryptedString = new TextDecoder().decode(decryptedUint8)
408
+ return JSON.parse(decryptedString)
409
+ }
445
410
 
446
- // Producing wallet address
447
- return shake256.create( 256 ).update( digestSponge.hex() ).hex();
411
+ async encryptWithSharedSecret (message, sharedSecret) {
412
+ const iv = crypto.getRandomValues(new Uint8Array(12))
413
+ const algorithm = { name: 'AES-GCM', iv }
414
+
415
+ // Convert the shared secret to a CryptoKey
416
+ const key = await crypto.subtle.importKey(
417
+ 'raw',
418
+ sharedSecret,
419
+ { name: 'AES-GCM' },
420
+ false,
421
+ ['encrypt']
422
+ )
423
+
424
+ // Encrypt the message
425
+ const encryptedContent = await crypto.subtle.encrypt(
426
+ algorithm,
427
+ key,
428
+ message
429
+ )
430
+
431
+ // Combine IV and encrypted content
432
+ const result = new Uint8Array(iv.length + encryptedContent.byteLength)
433
+ result.set(iv)
434
+ result.set(new Uint8Array(encryptedContent), iv.length)
435
+
436
+ return result
448
437
  }
449
438
 
450
439
  /**
451
- *
452
- * @param saltLength
453
- * @returns {string}
440
+ * Decrypts the given message using the shared secret
441
+ * @param encryptedMessage
442
+ * @param sharedSecret
443
+ * @returns {Promise<Uint8Array>}
454
444
  */
455
- static generatePosition ( saltLength = 64 ) {
456
- return randomString( saltLength, 'abcdef0123456789' );
445
+ async decryptWithSharedSecret (encryptedMessage, sharedSecret) {
446
+ // Extract IV from the encrypted message
447
+ const iv = encryptedMessage.slice(0, 12)
448
+ const algorithm = { name: 'AES-GCM', iv }
449
+
450
+ // Convert the shared secret to a CryptoKey
451
+ const key = await crypto.subtle.importKey(
452
+ 'raw',
453
+ sharedSecret,
454
+ { name: 'AES-GCM' },
455
+ false,
456
+ ['decrypt']
457
+ )
458
+
459
+ // Decrypt the message
460
+ const decryptedContent = await crypto.subtle.decrypt(
461
+ algorithm,
462
+ key,
463
+ encryptedMessage.slice(12)
464
+ )
465
+
466
+ return new Uint8Array(decryptedContent)
457
467
  }
458
468
  }