spd-lib 1.2.2 → 1.2.4
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/ai.py +66 -0
- package/dist/index.js +1 -415
- package/fatman.spd +0 -0
- package/high-zip/index.js +28 -0
- package/high-zip/node_modules/.package-lock.json +7 -0
- package/high-zip/package-lock.json +13 -0
- package/high-zip/package.json +13 -0
- package/high-zip/tool.js +78 -0
- package/img.py +44 -0
- package/obfuscate.js +131 -0
- package/package/dist/index.js +1 -0
- package/package/package-lock.json +1332 -0
- package/package/package.json +34 -0
- package/package.json +5 -2
- package/readme.md +137 -0
- package/readme.md.gz +0 -0
- package/readme.md.gz.lo +137 -0
- package/src/index.js +972 -0
- package/t.py +47 -0
- package/test.js +10 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,972 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const zlib = require('zlib');
|
|
3
|
+
const sodium = require('libsodium-wrappers');
|
|
4
|
+
const crypto = require('crypto');
|
|
5
|
+
const argon2 = require('argon2');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SPD {
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
this.data = [];
|
|
13
|
+
this.keyPair; // Generate a key pair for encryption/decryption
|
|
14
|
+
this.userKey;
|
|
15
|
+
this.salt;
|
|
16
|
+
this.hash = 'sha3-512'
|
|
17
|
+
this.compression_level = 9
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async init() {
|
|
22
|
+
await sodium.ready;
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
async changePasscode(oldPasscode, newPasscode) {
|
|
26
|
+
if (!oldPasscode || !newPasscode) {
|
|
27
|
+
throw new Error('Old and new passcodes must be provided.');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Ensure sodium is ready
|
|
31
|
+
await sodium.ready;
|
|
32
|
+
|
|
33
|
+
// Step 1: Derive key from old passcode and check MAC
|
|
34
|
+
const { pqcKey: oldPQCKey, salt: currentSalt } = await this.convertPasscodeToPQCKeySalted(oldPasscode, this.salt);
|
|
35
|
+
const oldUserKey = oldPQCKey.privateKey;
|
|
36
|
+
|
|
37
|
+
// Rebuild MAC to verify old passcode
|
|
38
|
+
const spdPayload = { data: this.data, salt: Array.from(this.salt) };
|
|
39
|
+
const spdBuffer = Buffer.from(JSON.stringify(spdPayload), 'utf8');
|
|
40
|
+
const expectedMac = crypto.createHmac('sha512', oldUserKey).update(spdBuffer).digest();
|
|
41
|
+
|
|
42
|
+
const currentMac = this.generateMAC(spdBuffer, this.userKey);
|
|
43
|
+
if (!crypto.timingSafeEqual(expectedMac, currentMac)) {
|
|
44
|
+
throw new Error('Old passcode is incorrect or data integrity check failed.');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Step 2: Decrypt everything with old key
|
|
48
|
+
const decryptedEntries = await Promise.all(
|
|
49
|
+
this.data.map(async dat => {
|
|
50
|
+
const decrypted = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
|
|
51
|
+
Buffer.from(dat.data),
|
|
52
|
+
Buffer.from(dat.nonce),
|
|
53
|
+
oldUserKey
|
|
54
|
+
);
|
|
55
|
+
const decompressed = zlib.inflateSync(decrypted);
|
|
56
|
+
const originalData = decompressed.toString('utf8');
|
|
57
|
+
return {
|
|
58
|
+
name: dat.dataName,
|
|
59
|
+
parsed: await this.CSTI(originalData, dat.dataType),
|
|
60
|
+
type: dat.dataType
|
|
61
|
+
};
|
|
62
|
+
})
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Step 3: Generate new salt + key
|
|
66
|
+
const { pqcKey: newPQCKey, salt: newSalt } = await this.convertPasscodeToPQCKey(newPasscode);
|
|
67
|
+
const newUserKey = newPQCKey.privateKey;
|
|
68
|
+
|
|
69
|
+
// Step 4: Re-encrypt data
|
|
70
|
+
this.salt = newSalt;
|
|
71
|
+
this.userKey = newUserKey;
|
|
72
|
+
this.data = [];
|
|
73
|
+
|
|
74
|
+
for (const entry of decryptedEntries) {
|
|
75
|
+
await this.addData(entry.name, entry.parsed);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Done: data is now re-encrypted under the new key
|
|
79
|
+
}
|
|
80
|
+
generateMAC(dataBuffer, key, meta = {}) {
|
|
81
|
+
const macInput = Buffer.concat([
|
|
82
|
+
dataBuffer,
|
|
83
|
+
Buffer.from(meta.dataName || '', 'utf8'),
|
|
84
|
+
Buffer.from(meta.dataType || '', 'utf8')
|
|
85
|
+
]);
|
|
86
|
+
return crypto.createHmac('sha512', key).update(macInput).digest();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
checkPasscodeStrength(passcode) {
|
|
90
|
+
const minLength = 12;
|
|
91
|
+
const entropyChecks = [
|
|
92
|
+
/[a-z]/, // lowercase
|
|
93
|
+
/[A-Z]/, // uppercase
|
|
94
|
+
/[0-9]/, // digits
|
|
95
|
+
/[^A-Za-z0-9]/ // special characters
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
if (typeof passcode !== 'string' || passcode.length < minLength) {
|
|
99
|
+
throw new Error(`Passcode must be at least ${minLength} characters long.`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const failed = entropyChecks.filter(regex => !regex.test(passcode));
|
|
103
|
+
if (failed.length > 1) {
|
|
104
|
+
throw new Error('Passcode must contain at least three of the following: lowercase letters, uppercase letters, digits, special characters.');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
#passcode
|
|
110
|
+
async setPassKey(passcode){
|
|
111
|
+
this.checkPasscodeStrength(passcode);
|
|
112
|
+
await sodium.ready;
|
|
113
|
+
await this.init()
|
|
114
|
+
const { pqcKey, salt } = await this.convertPasscodeToPQCKey(passcode);
|
|
115
|
+
const userKey = pqcKey.privateKey;
|
|
116
|
+
this.userKey = userKey;
|
|
117
|
+
this.salt = salt;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
setHash(hash="sha3-512")
|
|
122
|
+
{
|
|
123
|
+
this.hash = hash
|
|
124
|
+
}
|
|
125
|
+
setCompressionLevel(level=9)
|
|
126
|
+
{
|
|
127
|
+
this.compression_level = level
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
getSodium(){
|
|
131
|
+
return sodium
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
sanitizeName(name) {
|
|
135
|
+
if (typeof name !== 'string') throw new Error('dataName must be a string');
|
|
136
|
+
|
|
137
|
+
// Normalize Unicode, lowercase, trim, and remove unsafe characters
|
|
138
|
+
return name
|
|
139
|
+
.normalize('NFKC') // Normalize full-width, diacritics, etc.
|
|
140
|
+
.trim()
|
|
141
|
+
.toLowerCase()
|
|
142
|
+
.replace(/[^a-z0-9_\-]/g, '_'); // Keep a-z, 0-9, underscore, dash
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
async addData(name, data) {
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
const dmap = await this.CITS(data)
|
|
150
|
+
await sodium.ready;
|
|
151
|
+
const dat = Buffer.from(dmap[0]);
|
|
152
|
+
const compressedData = zlib.deflateSync(dat,{
|
|
153
|
+
level:this.compression_level
|
|
154
|
+
});
|
|
155
|
+
const safeName = this.sanitizeName(name);
|
|
156
|
+
|
|
157
|
+
const nonce = sodium.randombytes_buf(sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
const encryptedData = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
|
|
162
|
+
compressedData,
|
|
163
|
+
null,
|
|
164
|
+
null,
|
|
165
|
+
nonce,
|
|
166
|
+
this.userKey
|
|
167
|
+
)
|
|
168
|
+
//sodium.crypto_secretbox_easy(compressedData, nonce, this.userKey);
|
|
169
|
+
const hash = crypto.createHash(this.hash).update(encryptedData).digest('hex');
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
this.data.push({ dataName: name, nonce: Buffer.from(nonce), data: Buffer.from(encryptedData), hash, dataType: dmap[1] });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
async addMany(dataItems) {
|
|
178
|
+
await sodium.ready;
|
|
179
|
+
|
|
180
|
+
const tasks = dataItems.map(async ({ name, data }) => {
|
|
181
|
+
const dmap = await this.CITS(data);
|
|
182
|
+
const dat = Buffer.from(dmap[0]);
|
|
183
|
+
const compressedData = zlib.deflateSync(dat, { level: this.compression_level });
|
|
184
|
+
const nonce = sodium.randombytes_buf(sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
|
|
185
|
+
|
|
186
|
+
const encryptedData = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(compressedData, null, null, nonce, this.userKey);
|
|
187
|
+
const hash = crypto.createHash(this.hash).update(encryptedData).digest('hex');
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
dataName: name,
|
|
191
|
+
nonce: Array.from(nonce),
|
|
192
|
+
data: Array.from(encryptedData),
|
|
193
|
+
hash,
|
|
194
|
+
dataType: dmap[1],
|
|
195
|
+
};
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const results = await Promise.all(tasks);
|
|
199
|
+
this.data.push(...results);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
clearCache(){
|
|
203
|
+
this.data = []
|
|
204
|
+
this.salt = null
|
|
205
|
+
this.userKey = null
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async saveToFile(outputPath,passcode) {
|
|
209
|
+
if (
|
|
210
|
+
typeof outputPath !== 'string' ||
|
|
211
|
+
!outputPath.trim()
|
|
212
|
+
) {
|
|
213
|
+
throw new Error('Invalid output path.');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const { encryptedSalt, saltNonce, wrapSalt } = await SPD.encryptSalt(this.salt, passcode);
|
|
217
|
+
|
|
218
|
+
const spdPayload = {
|
|
219
|
+
data: this.data,
|
|
220
|
+
encryptedSalt,
|
|
221
|
+
saltNonce,
|
|
222
|
+
wrapSalt,
|
|
223
|
+
version:24
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const spdDataJSON = JSON.stringify(spdPayload);
|
|
227
|
+
const spdBuffer = Buffer.from(spdDataJSON, 'utf8');
|
|
228
|
+
const mac = this.generateMAC(spdBuffer, this.userKey, {
|
|
229
|
+
dataName: '', // Entire dataset, use blank or static label
|
|
230
|
+
dataType: 'application/json' // Or whatever label you want for top-level
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
const finalObject = {
|
|
236
|
+
payload: Buffer.from(spdBuffer),
|
|
237
|
+
mac: Buffer.from(mac),
|
|
238
|
+
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const finalBuffer = zlib.deflateSync(Buffer.from(JSON.stringify(finalObject)), {
|
|
242
|
+
level: this.compression_level
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
fs.writeFileSync(outputPath, finalBuffer, { mode: 0o600 });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
static async loadFromFile(spdPath, passcode,hash='sha3-512',compression_level=9) {
|
|
250
|
+
try{
|
|
251
|
+
if (!spdPath || typeof spdPath !== 'string' || !spdPath.trim() || !passcode || typeof passcode !== 'string' || !passcode.trim()) {
|
|
252
|
+
throw new Error('Invalid SPD path or passcode.')
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
await sodium.ready;
|
|
256
|
+
|
|
257
|
+
const compressedSpdData = fs.readFileSync(spdPath);
|
|
258
|
+
const spdData = zlib.inflateSync(compressedSpdData,{level:compression_level}).toString('utf8');
|
|
259
|
+
|
|
260
|
+
const { payload,mac } = JSON.parse(spdData);
|
|
261
|
+
const spdBuffer = Buffer.from(payload);
|
|
262
|
+
const t = new SPD()
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
const { data, encryptedSalt, saltNonce, wrapSalt,version } = JSON.parse(Buffer.from(payload).toString("utf-8"));
|
|
266
|
+
if (typeof version !== 'number' || version !== 24) {
|
|
267
|
+
throw new Error(`Unsupported SPD version: ${version}`);
|
|
268
|
+
}
|
|
269
|
+
const salt = await SPD.decryptSalt(encryptedSalt, saltNonce, wrapSalt, passcode);
|
|
270
|
+
const { pqcKey } = await t.convertPasscodeToPQCKey(passcode, new Uint8Array(salt));
|
|
271
|
+
const pbk = pqcKey.privateKey;
|
|
272
|
+
const expectedMac = crypto.createHmac('sha512', pbk).update(Buffer.concat([
|
|
273
|
+
spdBuffer,
|
|
274
|
+
Buffer.from('', 'utf8'),
|
|
275
|
+
Buffer.from('application/json', 'utf8')
|
|
276
|
+
])).digest();
|
|
277
|
+
|
|
278
|
+
if (!crypto.timingSafeEqual(Buffer.from(mac), expectedMac)) {
|
|
279
|
+
throw new Error('MAC verification failed — data may be tampered with.');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
const spd = new SPD();
|
|
284
|
+
spd.setHash(hash)
|
|
285
|
+
spd.setCompressionLevel(compression_level)
|
|
286
|
+
spd.userKey = pbk;
|
|
287
|
+
spd.keyPair = {
|
|
288
|
+
publicKey: pbk.publicKey
|
|
289
|
+
};
|
|
290
|
+
spd.data = data.map(dat => ({
|
|
291
|
+
dataName: dat.dataName,
|
|
292
|
+
nonce: Buffer.from(dat.nonce),
|
|
293
|
+
data: Buffer.from(dat.data),
|
|
294
|
+
hash: dat.hash,
|
|
295
|
+
dataType: dat.dataType
|
|
296
|
+
}));
|
|
297
|
+
await Promise.all(spd.data.map(dat => {
|
|
298
|
+
const calculatedHash = crypto.createHash(hash).update(Buffer.from(dat.data)).digest('hex');
|
|
299
|
+
if (calculatedHash !== dat.hash) {
|
|
300
|
+
throw new Error(`Data integrity check failed for ${dat.dataName}`);
|
|
301
|
+
}
|
|
302
|
+
}));
|
|
303
|
+
|
|
304
|
+
return spd;
|
|
305
|
+
}catch(err){
|
|
306
|
+
throw new Error(err)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async extractData() {
|
|
312
|
+
await sodium.ready;
|
|
313
|
+
|
|
314
|
+
const tasks = this.data.map(async dat => {
|
|
315
|
+
try {
|
|
316
|
+
const decryptedData = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(null,dat.data,null,dat.nonce, this.userKey);
|
|
317
|
+
const decompressedData = zlib.inflateSync(decryptedData, { level: this.compression_level });
|
|
318
|
+
const dt = decompressedData.toString('utf8');
|
|
319
|
+
const parsed = await this.CSTI(dt, dat.dataType);
|
|
320
|
+
return [dat.dataName, parsed];
|
|
321
|
+
} catch (err) {
|
|
322
|
+
throw new Error(`Failed to process ${dat.dataName}: ${err.message}`);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
const results = await Promise.all(tasks);
|
|
327
|
+
return Object.fromEntries(results);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
static async derivePBK(passcode, salt) {
|
|
334
|
+
if (!salt || !(salt instanceof Uint8Array) || salt.length !== 16) {
|
|
335
|
+
throw new Error('Invalid salt.');
|
|
336
|
+
}
|
|
337
|
+
if (!passcode || typeof passcode !== 'string' || !salt || !(salt instanceof Uint8Array) || salt.length !== 16) {
|
|
338
|
+
throw new Error('Invalid passcode or salt.');
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
// 2. Hash again with SHA-512 to get deterministic binary seed material
|
|
343
|
+
const derivedKey = await argon2.hash(passcode, {
|
|
344
|
+
salt: Buffer.from(salt),
|
|
345
|
+
type: argon2.argon2id,
|
|
346
|
+
raw: true, // <- raw output as Buffer
|
|
347
|
+
memoryCost: 2 ** 16, // 64 MB
|
|
348
|
+
timeCost: 5,
|
|
349
|
+
parallelism: 1,
|
|
350
|
+
hashLength: sodium.crypto_kx_SEEDBYTES // usually 32 bytes
|
|
351
|
+
});
|
|
352
|
+
// 3. Slice securely to get the 32-byte seed for libsodium
|
|
353
|
+
const seed = derivedKey.slice(0, sodium.crypto_kx_SEEDBYTES);
|
|
354
|
+
|
|
355
|
+
return { pbk: seed, salt };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async saveData(passcode="") {
|
|
359
|
+
const { encryptedSalt, saltNonce, wrapSalt } = await SPD.encryptSalt(this.salt, passcode);
|
|
360
|
+
const spdPayload = {
|
|
361
|
+
data: this.data,
|
|
362
|
+
encryptedSalt,
|
|
363
|
+
saltNonce,
|
|
364
|
+
wrapSalt,
|
|
365
|
+
version: 24
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const spdDataJSON = JSON.stringify(spdPayload);
|
|
369
|
+
const spdBuffer = Buffer.from(spdDataJSON, 'utf8');
|
|
370
|
+
const mac = this.generateMAC(spdBuffer, this.userKey, {
|
|
371
|
+
dataName: '', // Entire dataset, use blank or static label
|
|
372
|
+
dataType: 'application/json' // Or whatever label you want for top-level
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
const finalObject = {
|
|
376
|
+
payload: Buffer.from(spdBuffer),
|
|
377
|
+
mac: Buffer.from(mac),
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
const compressedSpdData = zlib.deflateSync(Buffer.from(JSON.stringify(finalObject)), {
|
|
381
|
+
level: this.compression_level
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
return compressedSpdData.toString('base64');
|
|
385
|
+
}
|
|
386
|
+
static async decryptSalt(encryptedSalt, saltNonce, wrapSalt, passcode) {
|
|
387
|
+
|
|
388
|
+
const { pbk } = await SPD.derivePBK(passcode, new Uint8Array(wrapSalt));
|
|
389
|
+
await sodium.ready;
|
|
390
|
+
|
|
391
|
+
const salt = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(null,
|
|
392
|
+
new Uint8Array(encryptedSalt),
|
|
393
|
+
null,
|
|
394
|
+
new Uint8Array(saltNonce),
|
|
395
|
+
new Uint8Array(pbk)
|
|
396
|
+
);
|
|
397
|
+
if (!salt) throw new Error('Failed to decrypt salt.');
|
|
398
|
+
return salt;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
static async encryptSalt(salt, passcode) {
|
|
402
|
+
const wrapSalt = crypto.getRandomValues(new Uint8Array(16));
|
|
403
|
+
const { pbk } = await SPD.derivePBK(passcode, wrapSalt);
|
|
404
|
+
await sodium.ready;
|
|
405
|
+
|
|
406
|
+
const nonce = sodium.randombytes_buf(sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
|
|
407
|
+
const encryptedSalt = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(salt, null,null, nonce, new Uint8Array(pbk));
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
encryptedSalt: Array.from(encryptedSalt),
|
|
411
|
+
saltNonce: Array.from(nonce),
|
|
412
|
+
wrapSalt: Array.from(wrapSalt)
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
static async loadFromString(spdData, passcode,hash='sha3-512',compression_level=9) {
|
|
418
|
+
try{
|
|
419
|
+
|
|
420
|
+
if (!spdData || typeof spdData !== 'string' || !spdData.trim() || !passcode || typeof passcode !== 'string' || !passcode.trim()) {
|
|
421
|
+
throw new Error('Invalid SPD path or passcode.')
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
await sodium.ready;
|
|
425
|
+
const spdDataBuffer = Buffer.from(spdData, 'base64');
|
|
426
|
+
const spdData2 = zlib.inflateSync(spdDataBuffer,{level:compression_level}).toString('utf8');
|
|
427
|
+
const { payload, mac } = JSON.parse(spdData2);
|
|
428
|
+
const { data, encryptedSalt, saltNonce, wrapSalt,version } = JSON.parse(Buffer.from(payload).toString("utf-8"));
|
|
429
|
+
const salt = await SPD.decryptSalt(encryptedSalt, saltNonce, wrapSalt, passcode);
|
|
430
|
+
if (typeof version !== 'number' || version !== 24) {
|
|
431
|
+
throw new Error(`Unsupported SPD version: ${version}`);
|
|
432
|
+
}
|
|
433
|
+
const spdBuffer = Buffer.from(payload);
|
|
434
|
+
const t = new SPD()
|
|
435
|
+
|
|
436
|
+
const { pqcKey } = await t.convertPasscodeToPQCKey(passcode, new Uint8Array(salt));
|
|
437
|
+
const pbk = pqcKey.privateKey;
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
const expectedMac = crypto.createHmac('sha512', pbk).update(Buffer.concat([
|
|
441
|
+
spdBuffer,
|
|
442
|
+
Buffer.from('', 'utf8'),
|
|
443
|
+
Buffer.from('application/json', 'utf8')
|
|
444
|
+
])).digest();
|
|
445
|
+
|
|
446
|
+
if (!crypto.timingSafeEqual(Buffer.from(mac), expectedMac)) {
|
|
447
|
+
throw new Error('MAC verification failed — data may be tampered with.');
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const spd = new SPD();
|
|
451
|
+
spd.setHash(hash)
|
|
452
|
+
spd.setCompressionLevel(compression_level)
|
|
453
|
+
spd.userKey = pbk;
|
|
454
|
+
spd.keyPair = {
|
|
455
|
+
publicKey: pbk.publicKey
|
|
456
|
+
};
|
|
457
|
+
spd.data = data.map(dat => ({
|
|
458
|
+
dataName: dat.dataName,
|
|
459
|
+
nonce: Buffer.from(dat.nonce),
|
|
460
|
+
data: Buffer.from(dat.data),
|
|
461
|
+
hash: dat.hash,
|
|
462
|
+
dataType: dat.dataType
|
|
463
|
+
}));
|
|
464
|
+
await Promise.all(spd.data.map(dat => {
|
|
465
|
+
const calculatedHash = crypto.createHash(hash).update(Buffer.from(dat.data)).digest('hex');
|
|
466
|
+
if (calculatedHash !== dat.hash) {
|
|
467
|
+
throw new Error(`Data integrity check failed for ${dat.dataName}`);
|
|
468
|
+
}
|
|
469
|
+
}));
|
|
470
|
+
return spd;
|
|
471
|
+
|
|
472
|
+
}catch(err){
|
|
473
|
+
throw new Error(err)
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
static toBase64(buffer) {
|
|
480
|
+
return Buffer.from(buffer).toString('base64');
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
static fromBase64(str) {
|
|
484
|
+
return new Uint8Array(Buffer.from(str, 'base64'));
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
async convertPasscodeToPQCKey(passcode, salt = crypto.getRandomValues(new Uint8Array(16))) {
|
|
489
|
+
if (typeof passcode !== 'string' || passcode.length < 8)
|
|
490
|
+
throw new Error('Invalid passcode.');
|
|
491
|
+
const { pbk } = await SPD.derivePBK(passcode, salt);
|
|
492
|
+
await this.init();
|
|
493
|
+
const keyPair = sodium.crypto_kx_seed_keypair(pbk);
|
|
494
|
+
sodium.memzero(pbk);
|
|
495
|
+
return { pqcKey: { privateKey: keyPair.privateKey }, salt };
|
|
496
|
+
}
|
|
497
|
+
async TDT(data) {
|
|
498
|
+
const classTypeMap = {
|
|
499
|
+
'[object Array]': 'Array',
|
|
500
|
+
'[object Uint8Array]': 'Uint8Array',
|
|
501
|
+
'[object Uint16Array]': 'Uint16Array',
|
|
502
|
+
'[object Uint32Array]': 'Uint32Array',
|
|
503
|
+
'[object BigInt64Array]': 'BigInt64Array',
|
|
504
|
+
'[object BigUint64Array]': 'BigUint64Array',
|
|
505
|
+
'[object Float32Array]': 'Float32Array',
|
|
506
|
+
'[object Float64Array]': 'Float64Array',
|
|
507
|
+
'[object Map]': 'Map',
|
|
508
|
+
'[object Set]': 'Set',
|
|
509
|
+
'[object Date]': 'Date',
|
|
510
|
+
'[object RegExp]': 'RegExp',
|
|
511
|
+
'[object Error]': 'Error'
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
const objectType = Object.prototype.toString.call(data);
|
|
515
|
+
const mappedType = classTypeMap[objectType];
|
|
516
|
+
return mappedType;
|
|
517
|
+
}
|
|
518
|
+
async isNumArr(dataType) {
|
|
519
|
+
if(dataType === 'Uint8Array' || dataType === 'Uint16Array' || dataType === 'Uint32Array' || dataType === 'BigInt64Array' || dataType === 'BigUint64Array' || dataType === 'Float32Array' || dataType === 'Float64Array'){
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
async isSWM(dataType) {
|
|
524
|
+
if(dataType === 'Map' || dataType === 'Set' || dataType === 'WeakMap' || dataType === 'WeakSet'){
|
|
525
|
+
return true;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
async isDRE(dataType) {
|
|
529
|
+
if(dataType === 'Date' || dataType === 'RegExp' || dataType === 'Error'){
|
|
530
|
+
return true;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
async CITS(data) {
|
|
534
|
+
const dataType = typeof data;
|
|
535
|
+
if (dataType === 'string' || dataType === 'number' || dataType === 'boolean') {
|
|
536
|
+
return [data.toString(), dataType];
|
|
537
|
+
}
|
|
538
|
+
if(typeof data === 'object'){
|
|
539
|
+
const type = await this.TDT(data)
|
|
540
|
+
if(type === 'Array'){
|
|
541
|
+
return [JSON.stringify(data),'Array'];
|
|
542
|
+
}
|
|
543
|
+
if(await this.isNumArr(type)){
|
|
544
|
+
return [JSON.stringify(Array.from(data)),type];
|
|
545
|
+
}
|
|
546
|
+
if(await this.isSWM(type)){
|
|
547
|
+
|
|
548
|
+
return [JSON.stringify([...data]),type];
|
|
549
|
+
}
|
|
550
|
+
if(await this.isDRE(type)){
|
|
551
|
+
return [data.toString(),type];
|
|
552
|
+
}
|
|
553
|
+
return [JSON.stringify(data), typeof data];
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
async CSTI(data, type) {
|
|
557
|
+
try {
|
|
558
|
+
switch (type) {
|
|
559
|
+
case 'string':
|
|
560
|
+
if (typeof data !== 'string') throw new Error('Expected string');
|
|
561
|
+
return data;
|
|
562
|
+
|
|
563
|
+
case 'number':
|
|
564
|
+
const num = parseFloat(data);
|
|
565
|
+
if (isNaN(num)) throw new Error('Invalid number');
|
|
566
|
+
return num;
|
|
567
|
+
|
|
568
|
+
case 'boolean':
|
|
569
|
+
if (data !== 'true' && data !== 'false') throw new Error('Invalid boolean');
|
|
570
|
+
return data === 'true';
|
|
571
|
+
|
|
572
|
+
case 'Array':
|
|
573
|
+
case 'object':
|
|
574
|
+
const obj = JSON.parse(data);
|
|
575
|
+
if (typeof obj !== 'object' || obj === null) throw new Error('Invalid object');
|
|
576
|
+
return obj;
|
|
577
|
+
|
|
578
|
+
case 'Uint8Array':
|
|
579
|
+
return new Uint8Array(JSON.parse(data));
|
|
580
|
+
|
|
581
|
+
case 'Uint16Array':
|
|
582
|
+
return new Uint16Array(JSON.parse(data));
|
|
583
|
+
|
|
584
|
+
case 'Uint32Array':
|
|
585
|
+
return new Uint32Array(JSON.parse(data));
|
|
586
|
+
|
|
587
|
+
case 'BigInt64Array':
|
|
588
|
+
return new BigInt64Array(JSON.parse(data));
|
|
589
|
+
|
|
590
|
+
case 'BigUint64Array':
|
|
591
|
+
return new BigUint64Array(JSON.parse(data));
|
|
592
|
+
|
|
593
|
+
case 'Float32Array':
|
|
594
|
+
return new Float32Array(JSON.parse(data));
|
|
595
|
+
|
|
596
|
+
case 'Float64Array':
|
|
597
|
+
return new Float64Array(JSON.parse(data));
|
|
598
|
+
|
|
599
|
+
case 'Map':
|
|
600
|
+
return new Map(JSON.parse(data));
|
|
601
|
+
|
|
602
|
+
case 'Set':
|
|
603
|
+
return new Set(JSON.parse(data));
|
|
604
|
+
|
|
605
|
+
case 'Date':
|
|
606
|
+
const d = new Date(data);
|
|
607
|
+
if (isNaN(d.getTime())) throw new Error('Invalid Date');
|
|
608
|
+
return d;
|
|
609
|
+
|
|
610
|
+
case 'RegExp':
|
|
611
|
+
return new RegExp(data);
|
|
612
|
+
|
|
613
|
+
case 'Error':
|
|
614
|
+
return new Error(data);
|
|
615
|
+
|
|
616
|
+
default:
|
|
617
|
+
throw new Error(`Unknown or unsupported type: ${type}`);
|
|
618
|
+
}
|
|
619
|
+
} catch (err) {
|
|
620
|
+
throw new Error(`Failed to restore data of type "${type}": ${err.message}`);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
class SPD_Legacy {
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
constructor() {
|
|
630
|
+
this.data = [];
|
|
631
|
+
this.keyPair; // Generate a key pair for encryption/decryption
|
|
632
|
+
this.userKey;
|
|
633
|
+
this.salt;
|
|
634
|
+
this.init();
|
|
635
|
+
}
|
|
636
|
+
async init() {
|
|
637
|
+
await sodium.ready;
|
|
638
|
+
this.keyPair = sodium.crypto_box_keypair()
|
|
639
|
+
}
|
|
640
|
+
async setPassKey(passcode){
|
|
641
|
+
await sodium.ready;
|
|
642
|
+
this.init()
|
|
643
|
+
const { pqcKey, salt } = await new SPD_Legacy().convertPasscodeToPQCKey(passcode);
|
|
644
|
+
const userKey = pqcKey.publicKey;
|
|
645
|
+
this.userKey = userKey;
|
|
646
|
+
this.salt = salt;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
async addData(name, data) {
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
const dmap = await this.CITS(data)
|
|
653
|
+
await sodium.ready;
|
|
654
|
+
const dat = Buffer.from(dmap[0]);
|
|
655
|
+
const compressedData = zlib.deflateSync(dat,{
|
|
656
|
+
level:9
|
|
657
|
+
});
|
|
658
|
+
const nonce = sodium.randombytes_buf(sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
|
|
659
|
+
const encryptedData = sodium.crypto_secretbox_easy(compressedData, nonce, this.userKey);
|
|
660
|
+
const hash = crypto.createHash('sha256').update(encryptedData).digest('hex');
|
|
661
|
+
this.data.push({ dataName: name, nonce: Array.from(nonce), data: Array.from(encryptedData), hash, dataType: dmap[1] });
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
saveToFile(outputPath) {
|
|
665
|
+
if (!outputPath || typeof outputPath !== 'string' || !outputPath.trim() || !this.salt || !(this.salt instanceof Uint8Array) || this.salt.length !== 16) {
|
|
666
|
+
throw new Error('Invalid output path or salt.');
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const spdData = JSON.stringify({ data: this.data, salt: Array.from(this.salt) });
|
|
670
|
+
const compressedSpdData = zlib.deflateSync(spdData,{
|
|
671
|
+
level:9
|
|
672
|
+
});
|
|
673
|
+
fs.writeFileSync(outputPath, compressedSpdData, { mode: 0o600 });
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
static async loadFromFile(spdPath, passcode) {
|
|
678
|
+
if (
|
|
679
|
+
!spdPath || typeof spdPath !== 'string' || !spdPath.trim() ||
|
|
680
|
+
!passcode || typeof passcode !== 'string' || !passcode.trim()
|
|
681
|
+
) {
|
|
682
|
+
throw new Error('Invalid SPD path or passcode.');
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
await sodium.ready;
|
|
686
|
+
|
|
687
|
+
try {
|
|
688
|
+
const compressedSpdData = fs.readFileSync(spdPath);
|
|
689
|
+
const spdData = zlib.inflateSync(compressedSpdData, { level: 9 }).toString('utf8');
|
|
690
|
+
const { data, salt } = JSON.parse(spdData);
|
|
691
|
+
|
|
692
|
+
const legacyInstance = new SPD_Legacy();
|
|
693
|
+
const { pqcKey } = await legacyInstance.convertPasscodeToPQCKeySalted(passcode, new Uint8Array(salt));
|
|
694
|
+
const pbk = pqcKey.publicKey;
|
|
695
|
+
|
|
696
|
+
const spd = new SPD_Legacy();
|
|
697
|
+
spd.userKey = pbk;
|
|
698
|
+
spd.keyPair = { publicKey: pbk.publicKey };
|
|
699
|
+
|
|
700
|
+
spd.data = data.map(dat => ({
|
|
701
|
+
dataName: dat.dataName,
|
|
702
|
+
nonce: Buffer.from(dat.nonce),
|
|
703
|
+
data: Buffer.from(dat.data),
|
|
704
|
+
hash: dat.hash,
|
|
705
|
+
dataType: dat.dataType
|
|
706
|
+
}));
|
|
707
|
+
|
|
708
|
+
// ✅ Run hash validation in parallel
|
|
709
|
+
const validationTasks = spd.data.map(dat => {
|
|
710
|
+
return new Promise((resolve, reject) => {
|
|
711
|
+
const calculatedHash = crypto.createHash('sha256').update(dat.data).digest('hex');
|
|
712
|
+
if (calculatedHash !== dat.hash) {
|
|
713
|
+
reject(new Error(`Data integrity check failed for ${dat.dataName}`));
|
|
714
|
+
} else {
|
|
715
|
+
resolve();
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
await Promise.all(validationTasks);
|
|
721
|
+
|
|
722
|
+
return spd;
|
|
723
|
+
|
|
724
|
+
} catch (err) {
|
|
725
|
+
throw new Error(`Failed to load SPD file: ${err.message}`);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
async extractData() {
|
|
731
|
+
await sodium.ready;
|
|
732
|
+
|
|
733
|
+
try {
|
|
734
|
+
const extractionTasks = this.data.map(async dat => {
|
|
735
|
+
try {
|
|
736
|
+
const decryptedData = sodium.crypto_secretbox_open_easy(dat.data, dat.nonce, this.userKey);
|
|
737
|
+
const decompressedData = zlib.inflateSync(decryptedData, { level: 9 });
|
|
738
|
+
const dt = decompressedData.toString('utf8');
|
|
739
|
+
const parsed = await this.CSTI(dt, dat.dataType);
|
|
740
|
+
return [dat.dataName, parsed];
|
|
741
|
+
} catch (err) {
|
|
742
|
+
throw new Error(`Failed to process ${dat.dataName}: ${err.message}`);
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
const results = await Promise.all(extractionTasks);
|
|
747
|
+
return Object.fromEntries(results);
|
|
748
|
+
|
|
749
|
+
} catch (err) {
|
|
750
|
+
throw new Error(`Data extraction failed: ${err.message}`);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
static async derivePBK(passcode, salt) {
|
|
758
|
+
if (!passcode || typeof passcode !== 'string' || !passcode.trim() || !salt || !(salt instanceof Uint8Array) || salt.length !== 16) {
|
|
759
|
+
throw new Error('Invalid passcode or salt.');
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
return new Promise((resolve, reject) => {
|
|
763
|
+
crypto.pbkdf2(passcode, salt, 100000, 32, 'sha256', (err, derivedKey) => {
|
|
764
|
+
if (err) {
|
|
765
|
+
reject(err);
|
|
766
|
+
} else {
|
|
767
|
+
resolve({ pbk: derivedKey, salt: salt });
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
saveData() {
|
|
774
|
+
const spdData = JSON.stringify({ data: this.data, salt: Array.from(this.salt) });
|
|
775
|
+
const compressedSpdData = zlib.deflateSync(spdData,{level:9});
|
|
776
|
+
return compressedSpdData;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
static async loadFromString(spdData, passcode) {
|
|
780
|
+
if (!spdData || typeof spdData !== 'string' || !spdData.trim() ||
|
|
781
|
+
!passcode || typeof passcode !== 'string' || !passcode.trim()) {
|
|
782
|
+
throw new Error('Invalid SPD data or passcode.');
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
await sodium.ready;
|
|
786
|
+
|
|
787
|
+
try {
|
|
788
|
+
const spdDataBuffer = Buffer.from(spdData, 'base64');
|
|
789
|
+
const decompressed = zlib.inflateSync(spdDataBuffer, { level: 9 }).toString('utf8');
|
|
790
|
+
const { data, salt } = JSON.parse(decompressed);
|
|
791
|
+
|
|
792
|
+
const legacyInstance = new SPD_Legacy();
|
|
793
|
+
const { pqcKey } = await legacyInstance.convertPasscodeToPQCKeySalted(passcode, new Uint8Array(salt));
|
|
794
|
+
const pbk = pqcKey.publicKey;
|
|
795
|
+
|
|
796
|
+
const spd = new SPD_Legacy();
|
|
797
|
+
spd.userKey = pbk;
|
|
798
|
+
spd.keyPair = { publicKey: pbk.publicKey };
|
|
799
|
+
|
|
800
|
+
// Deserialize data
|
|
801
|
+
spd.data = data.map(dat => ({
|
|
802
|
+
dataName: dat.dataName,
|
|
803
|
+
nonce: Buffer.from(dat.nonce),
|
|
804
|
+
data: Buffer.from(dat.data),
|
|
805
|
+
hash: dat.hash,
|
|
806
|
+
dataType: dat.dataType
|
|
807
|
+
}));
|
|
808
|
+
|
|
809
|
+
// ✅ Validate hashes in parallel
|
|
810
|
+
const validationTasks = spd.data.map(dat => {
|
|
811
|
+
return new Promise((resolve, reject) => {
|
|
812
|
+
const calculatedHash = crypto.createHash('sha256').update(dat.data).digest('hex');
|
|
813
|
+
if (calculatedHash !== dat.hash) {
|
|
814
|
+
reject(new Error(`Data integrity check failed for ${dat.dataName}`));
|
|
815
|
+
} else {
|
|
816
|
+
resolve();
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
await Promise.all(validationTasks);
|
|
822
|
+
|
|
823
|
+
return spd;
|
|
824
|
+
} catch (err) {
|
|
825
|
+
throw new Error(`Failed to load SPD data: ${err.message}`);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
async convertPasscodeToPQCKeySalted(passcode, salt) {
|
|
831
|
+
if (!passcode || typeof passcode !== 'string' || !passcode.trim() || passcode.length < 8 || !salt || !(salt instanceof Uint8Array) || salt.length !== 16) {
|
|
832
|
+
throw new Error('Invalid passcode or salt.');
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const { pbk } = await SPD.derivePBK(passcode, salt);
|
|
836
|
+
await sodium.ready;
|
|
837
|
+
const keyPair = sodium.crypto_kx_seed_keypair(pbk)
|
|
838
|
+
return { pqcKey: { publicKey: keyPair.publicKey }, salt };
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
async convertPasscodeToPQCKey(passcode) {
|
|
842
|
+
if (!passcode || typeof passcode !== 'string' || !passcode.trim() || passcode.length < 8) {
|
|
843
|
+
throw new Error('Invalid passcode.');
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
const { pbk, salt } = await SPD.derivePBK(passcode, crypto.getRandomValues(new Uint8Array(16)));
|
|
847
|
+
await sodium.ready;
|
|
848
|
+
const keyPair = sodium.crypto_kx_seed_keypair(pbk.slice(0, sodium.crypto_kx_SEEDBYTES));
|
|
849
|
+
return { pqcKey: { publicKey: keyPair.publicKey }, salt };
|
|
850
|
+
}
|
|
851
|
+
async TDT(data) {
|
|
852
|
+
const classTypeMap = {
|
|
853
|
+
'[object Array]': 'Array',
|
|
854
|
+
'[object Uint8Array]': 'Uint8Array',
|
|
855
|
+
'[object Uint16Array]': 'Uint16Array',
|
|
856
|
+
'[object Uint32Array]': 'Uint32Array',
|
|
857
|
+
'[object BigInt64Array]': 'BigInt64Array',
|
|
858
|
+
'[object BigUint64Array]': 'BigUint64Array',
|
|
859
|
+
'[object Float32Array]': 'Float32Array',
|
|
860
|
+
'[object Float64Array]': 'Float64Array',
|
|
861
|
+
'[object Map]': 'Map',
|
|
862
|
+
'[object Set]': 'Set',
|
|
863
|
+
'[object Date]': 'Date',
|
|
864
|
+
'[object RegExp]': 'RegExp',
|
|
865
|
+
'[object Error]': 'Error'
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
const objectType = Object.prototype.toString.call(data);
|
|
869
|
+
const mappedType = classTypeMap[objectType];
|
|
870
|
+
return mappedType;
|
|
871
|
+
}
|
|
872
|
+
async isNumArr(dataType) {
|
|
873
|
+
if(dataType === 'Uint8Array' || dataType === 'Uint16Array' || dataType === 'Uint32Array' || dataType === 'BigInt64Array' || dataType === 'BigUint64Array' || dataType === 'Float32Array' || dataType === 'Float64Array'){
|
|
874
|
+
return true;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
async isSWM(dataType) {
|
|
878
|
+
if(dataType === 'Map' || dataType === 'Set' || dataType === 'WeakMap' || dataType === 'WeakSet'){
|
|
879
|
+
return true;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
async isDRE(dataType) {
|
|
883
|
+
if(dataType === 'Date' || dataType === 'RegExp' || dataType === 'Error'){
|
|
884
|
+
return true;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
async CITS(data) {
|
|
888
|
+
const dataType = typeof data;
|
|
889
|
+
if (dataType === 'string' || dataType === 'number' || dataType === 'boolean') {
|
|
890
|
+
return [data.toString(), dataType];
|
|
891
|
+
}
|
|
892
|
+
if(typeof data === 'object'){
|
|
893
|
+
const type = await this.TDT(data)
|
|
894
|
+
if(type === 'Array'){
|
|
895
|
+
return [JSON.stringify(data),'Array'];
|
|
896
|
+
}
|
|
897
|
+
if(await this.isNumArr(type)){
|
|
898
|
+
return [JSON.stringify(Array.from(data)),type];
|
|
899
|
+
}
|
|
900
|
+
if(await this.isSWM(type)){
|
|
901
|
+
|
|
902
|
+
return [JSON.stringify([...data]),type];
|
|
903
|
+
}
|
|
904
|
+
if(await this.isDRE(type)){
|
|
905
|
+
return [data.toString(),type];
|
|
906
|
+
}
|
|
907
|
+
return [JSON.stringify(data), typeof data];
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
async CSTI(data,type) {
|
|
911
|
+
if(type === 'string') {
|
|
912
|
+
return data
|
|
913
|
+
}
|
|
914
|
+
if(type === 'number'){
|
|
915
|
+
return parseFloat(data);
|
|
916
|
+
}
|
|
917
|
+
if(type === 'boolean'){
|
|
918
|
+
return (data === 'true')&&(data !== 'false');
|
|
919
|
+
}
|
|
920
|
+
if(type === 'object' || type === 'Array'){
|
|
921
|
+
return JSON.parse(data);
|
|
922
|
+
}
|
|
923
|
+
if(type === 'Uint8Array'){
|
|
924
|
+
return new Uint8Array(JSON.parse(data));
|
|
925
|
+
}
|
|
926
|
+
if(type === 'Uint16Array'){
|
|
927
|
+
return new Uint16Array(JSON.parse(data));
|
|
928
|
+
}
|
|
929
|
+
if(type === 'Uint32Array'){
|
|
930
|
+
|
|
931
|
+
return new Uint32Array(JSON.parse(data));
|
|
932
|
+
}
|
|
933
|
+
if(type === 'BigInt64Array'){
|
|
934
|
+
return new BigInt64Array(JSON.parse(data));
|
|
935
|
+
}
|
|
936
|
+
if(type === 'BigUint64Array'){
|
|
937
|
+
return new BigUint64Array(JSON.parse(data));
|
|
938
|
+
}
|
|
939
|
+
if(type === 'Float32Array'){
|
|
940
|
+
return new Float32Array(JSON.parse(data));
|
|
941
|
+
}
|
|
942
|
+
if(type === 'Float64Array'){
|
|
943
|
+
return new Float64Array(JSON.parse(data));
|
|
944
|
+
}
|
|
945
|
+
if(type === 'Map'){
|
|
946
|
+
return new Map(JSON.parse(data));
|
|
947
|
+
}
|
|
948
|
+
if(type === 'Set'){
|
|
949
|
+
return new Set(JSON.parse(data));
|
|
950
|
+
}
|
|
951
|
+
if(type === 'WeakMap'){
|
|
952
|
+
return new WeakMap(JSON.parse(data));
|
|
953
|
+
}
|
|
954
|
+
if(type === 'WeakSet'){
|
|
955
|
+
return new WeakSet(JSON.parse(data));
|
|
956
|
+
}
|
|
957
|
+
if(type === 'Date'){
|
|
958
|
+
return new Date(data);
|
|
959
|
+
}
|
|
960
|
+
if(type === 'RegExp'){
|
|
961
|
+
return new RegExp(data);
|
|
962
|
+
}
|
|
963
|
+
if(type === 'Error'){
|
|
964
|
+
return new Error(data);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
module.exports = {
|
|
970
|
+
SPD,
|
|
971
|
+
SPD_LEG:SPD_Legacy
|
|
972
|
+
};
|