@mybucks.online/core 2.0.0 → 2.1.0
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/README.md +3 -3
- package/index.js +29 -18
- package/package.json +2 -2
- package/test/index.test.js +46 -0
package/README.md
CHANGED
|
@@ -99,7 +99,7 @@ To make the wallet more secure and resilient against attacks, and to meet standa
|
|
|
99
99
|
|
|
100
100
|
**Token generation (default)**
|
|
101
101
|
- Legacy encoded the transfer-link token by **concatenating** passphrase, pin and network with a delimiter, which is ambiguous for some inputs.
|
|
102
|
-
- The default uses **
|
|
102
|
+
- The default uses a **compact length-prefixed** payload (version byte + lengths + UTF-8 bytes) so there is no concatenation ambiguity and the URL fragment stays short.
|
|
103
103
|
- `parseToken` accepts both legacy and default token formats automatically and returns `[passphrase, pin, network, legacy]`, where `legacy` is `true` if the token was in legacy format.
|
|
104
104
|
|
|
105
105
|
Use `generateHash(passphrase, pin, cb, true)` or `generateToken(passphrase, pin, network, true)` only when you need to match existing legacy wallets or tokens.
|
|
@@ -119,6 +119,6 @@ Find the docs [here](https://docs.mybucks.online).
|
|
|
119
119
|
- https://app.mybucks.online
|
|
120
120
|
passphrase: **DemoAccount5&**
|
|
121
121
|
PIN: **112324**
|
|
122
|
-
- https://app.mybucks.online/#wallet=
|
|
123
|
-
- https://app.mybucks.online/#wallet=
|
|
122
|
+
- https://app.mybucks.online/#wallet=Db1zfXAg1EZW1vQWNjb3VudDUmBjExMjMyNAhvcHRpbWlzbQ==mlUEbO (default)
|
|
123
|
+
- https://app.mybucks.online/#wallet=VWnsSGRGVtb0FjY291bnQ1JgIxMTIzMjQCb3B0aW1pc20=_wNovT (legacy)
|
|
124
124
|
- https://codesandbox.io/p/sandbox/mybucks-online-key-generation-sandbox-lt53c3
|
package/index.js
CHANGED
|
@@ -135,7 +135,9 @@ export function getTronWalletAddress(hash) {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
const URL_DELIMITER = "\u0002";
|
|
138
|
-
|
|
138
|
+
// Version byte for the default (non-legacy) token format.
|
|
139
|
+
// Uses a compact length-prefixed encoding for passphrase, pin and network.
|
|
140
|
+
const TOKEN_VERSION_COMPACT = 0x02;
|
|
139
141
|
|
|
140
142
|
const NETWORKS = [
|
|
141
143
|
"ethereum",
|
|
@@ -151,12 +153,12 @@ const NETWORKS = [
|
|
|
151
153
|
* This function generates a transfer-link token by encoding passphrase, pin and network, and adding random padding.
|
|
152
154
|
* The transfer-link enables users to send their full ownership of a wallet account to another user for gifting or airdropping.
|
|
153
155
|
* Passphrase and PIN are validated by length (see PASSPHRASE_MIN/MAX_LENGTH, PIN_MIN/MAX_LENGTH) and zxcvbn; invalid or weak values are rejected (returns null).
|
|
154
|
-
* When legacy is false, payload is
|
|
156
|
+
* When legacy is false, payload is compact length-prefixed (version 0x02) to avoid concatenation ambiguity and keep the URL fragment short; when true, uses URL_DELIMITER concatenation.
|
|
155
157
|
*
|
|
156
158
|
* @param {string} passphrase - Length in [PASSPHRASE_MIN_LENGTH, PASSPHRASE_MAX_LENGTH], zxcvbn score >= 3
|
|
157
159
|
* @param {string} pin - Length in [PIN_MIN_LENGTH, PIN_MAX_LENGTH], zxcvbn score >= 1
|
|
158
160
|
* @param {string} network - ethereum | polygon | arbitrum | optimism | bsc | avalanche | base | tron
|
|
159
|
-
* @param {boolean} legacy - when true, use URL_DELIMITER concatenation; when false, use
|
|
161
|
+
* @param {boolean} legacy - when true, use URL_DELIMITER concatenation; when false, use compact length-prefixed encoding for the payload
|
|
160
162
|
* @returns A string formatted as a transfer-link token, which can be appended to `https://app.mybucks.online#wallet=`, or null if invalid/weak
|
|
161
163
|
*/
|
|
162
164
|
export function generateToken(passphrase, pin, network, legacy = false) {
|
|
@@ -194,14 +196,19 @@ export function generateToken(passphrase, pin, network, legacy = false) {
|
|
|
194
196
|
"utf-8",
|
|
195
197
|
);
|
|
196
198
|
} else {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
);
|
|
201
|
-
|
|
199
|
+
// Default format: compact length-prefixed encoding.
|
|
200
|
+
const passphraseBytes = Buffer.from(passphrase, "utf-8");
|
|
201
|
+
const pinBytes = Buffer.from(pin, "utf-8");
|
|
202
|
+
const networkBytes = Buffer.from(network, "utf-8");
|
|
203
|
+
|
|
202
204
|
payloadBuffer = Buffer.concat([
|
|
203
|
-
Buffer.from([
|
|
204
|
-
|
|
205
|
+
Buffer.from([TOKEN_VERSION_COMPACT]),
|
|
206
|
+
Buffer.from([passphraseBytes.length]),
|
|
207
|
+
passphraseBytes,
|
|
208
|
+
Buffer.from([pinBytes.length]),
|
|
209
|
+
pinBytes,
|
|
210
|
+
Buffer.from([networkBytes.length]),
|
|
211
|
+
networkBytes,
|
|
205
212
|
]);
|
|
206
213
|
}
|
|
207
214
|
|
|
@@ -212,20 +219,24 @@ export function generateToken(passphrase, pin, network, legacy = false) {
|
|
|
212
219
|
|
|
213
220
|
/**
|
|
214
221
|
* This function parses a transfer-link token generated by generateToken().
|
|
215
|
-
* Tokens
|
|
222
|
+
* Tokens whose payload starts with 0x02 are decoded as compact length-prefixed; otherwise payload is treated as legacy (UTF-8 + URL_DELIMITER).
|
|
216
223
|
* @param {string} token - token string returned by generateToken()
|
|
217
|
-
* @returns {[string, string, string, boolean]} [passphrase, pin, network, legacy] - legacy is true if token was in legacy format, false if
|
|
224
|
+
* @returns {[string, string, string, boolean]} [passphrase, pin, network, legacy] - legacy is true if token was in legacy format, false if compact-encoded
|
|
218
225
|
*/
|
|
219
226
|
export function parseToken(token) {
|
|
220
227
|
const payload = token.slice(6, token.length - 6);
|
|
221
228
|
const decoded = Buffer.from(payload, "base64");
|
|
222
229
|
|
|
223
|
-
if (decoded[0] ===
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
230
|
+
if (decoded[0] === TOKEN_VERSION_COMPACT) {
|
|
231
|
+
let i = 1;
|
|
232
|
+
const lenP = decoded[i++];
|
|
233
|
+
const passphrase = decoded.subarray(i, i + lenP).toString("utf-8");
|
|
234
|
+
i += lenP;
|
|
235
|
+
const lenI = decoded[i++];
|
|
236
|
+
const pin = decoded.subarray(i, i + lenI).toString("utf-8");
|
|
237
|
+
i += lenI;
|
|
238
|
+
const lenN = decoded[i++];
|
|
239
|
+
const network = decoded.subarray(i, i + lenN).toString("utf-8");
|
|
229
240
|
return [passphrase, pin, network, false];
|
|
230
241
|
}
|
|
231
242
|
const str = decoded.toString("utf-8");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mybucks.online/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Core module of Mybucks.online Crypto Wallet",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"ethers": "^6.13.5",
|
|
39
39
|
"nanoid": "^5.0.9",
|
|
40
40
|
"scrypt-js": "^3.0.1",
|
|
41
|
-
"tronweb": "^6.
|
|
41
|
+
"tronweb": "^6.2.2",
|
|
42
42
|
"zxcvbn": "^4.4.2"
|
|
43
43
|
}
|
|
44
44
|
}
|
package/test/index.test.js
CHANGED
|
@@ -21,6 +21,7 @@ const DEMO_WALLET_EVM_ADDRESS = "0xC3Bb18Ed137577e5482fA7C6AEaca8b3F68Dafba";
|
|
|
21
21
|
const DEMO_WALLET_TRON_ADDRESS = "TTp8uDeig42XdefAoTGWTj61uNMYvEVnXR";
|
|
22
22
|
|
|
23
23
|
const DEMO_LEGACY_TOKEN = "VWnsSGRGVtb0FjY291bnQ1JgIxMTIzMjQCb3B0aW1pc20=_wNovT";
|
|
24
|
+
const DEMO_DEFAULT_TOKEN = "Db1zfXAg1EZW1vQWNjb3VudDUmBjExMjMyNAhvcHRpbWlzbQ==mlUEbO";
|
|
24
25
|
|
|
25
26
|
describe("generateHash (default)", () => {
|
|
26
27
|
test("should return empty string if passphrase or pin is blank", async () => {
|
|
@@ -282,6 +283,13 @@ describe("generateToken", () => {
|
|
|
282
283
|
);
|
|
283
284
|
}
|
|
284
285
|
});
|
|
286
|
+
|
|
287
|
+
test("should handle passphrase containing URL_DELIMITER when legacy=false", () => {
|
|
288
|
+
const delimiter = String.fromCharCode(2);
|
|
289
|
+
const trickyPassphrase = `Demo${delimiter}Account5&`;
|
|
290
|
+
const token = generateToken(trickyPassphrase, DEMO_PIN, DEMO_NETWORK, false);
|
|
291
|
+
assert.ok(token !== null);
|
|
292
|
+
});
|
|
285
293
|
});
|
|
286
294
|
|
|
287
295
|
describe("parseToken", () => {
|
|
@@ -310,6 +318,14 @@ describe("parseToken", () => {
|
|
|
310
318
|
assert.strictEqual(network, DEMO_NETWORK);
|
|
311
319
|
assert.strictEqual(legacy, true);
|
|
312
320
|
});
|
|
321
|
+
|
|
322
|
+
test("should parse DEMO_DEFAULT_TOKEN and return DEMO_PASSPHRASE, DEMO_PIN, DEMO_NETWORK and legacy=false", () => {
|
|
323
|
+
const [passphrase, pin, network, legacy] = parseToken(DEMO_DEFAULT_TOKEN);
|
|
324
|
+
assert.strictEqual(passphrase, DEMO_PASSPHRASE);
|
|
325
|
+
assert.strictEqual(pin, DEMO_PIN);
|
|
326
|
+
assert.strictEqual(network, DEMO_NETWORK);
|
|
327
|
+
assert.strictEqual(legacy, false);
|
|
328
|
+
});
|
|
313
329
|
});
|
|
314
330
|
|
|
315
331
|
describe("generateToken and parseToken", () => {
|
|
@@ -325,6 +341,20 @@ describe("generateToken and parseToken", () => {
|
|
|
325
341
|
assert.strictEqual(network, testNetwork);
|
|
326
342
|
});
|
|
327
343
|
|
|
344
|
+
test("should round-trip passphrase containing URL_DELIMITER when legacy=false", () => {
|
|
345
|
+
const delimiter = String.fromCharCode(2);
|
|
346
|
+
const testPassphrase = `My${delimiter}-1st-car-was-a-red-Ford-2005!`;
|
|
347
|
+
const testPin = "909011";
|
|
348
|
+
const testNetwork = "polygon";
|
|
349
|
+
const token = generateToken(testPassphrase, testPin, testNetwork, false);
|
|
350
|
+
|
|
351
|
+
const [passphrase, pin, network, legacy] = parseToken(token);
|
|
352
|
+
assert.strictEqual(passphrase, testPassphrase);
|
|
353
|
+
assert.strictEqual(pin, testPin);
|
|
354
|
+
assert.strictEqual(network, testNetwork);
|
|
355
|
+
assert.strictEqual(legacy, false);
|
|
356
|
+
});
|
|
357
|
+
|
|
328
358
|
test("should round-trip when legacy=true", () => {
|
|
329
359
|
const testPassphrase = "My-1st-car-was-a-red-Ford-2005!";
|
|
330
360
|
const testPin = "909011";
|
|
@@ -336,4 +366,20 @@ describe("generateToken and parseToken", () => {
|
|
|
336
366
|
assert.strictEqual(pin, testPin);
|
|
337
367
|
assert.strictEqual(network, testNetwork);
|
|
338
368
|
});
|
|
369
|
+
|
|
370
|
+
test("should not safely round-trip passphrase containing URL_DELIMITER when legacy=true", () => {
|
|
371
|
+
const delimiter = String.fromCharCode(2);
|
|
372
|
+
const testPassphrase = `My${delimiter}-1st-car-was-a-red-Ford-2005!`;
|
|
373
|
+
const testPin = "909011";
|
|
374
|
+
const testNetwork = "polygon";
|
|
375
|
+
const token = generateToken(testPassphrase, testPin, testNetwork, true);
|
|
376
|
+
|
|
377
|
+
const [passphrase, pin, network, legacy] = parseToken(token);
|
|
378
|
+
assert.notStrictEqual(
|
|
379
|
+
passphrase,
|
|
380
|
+
testPassphrase,
|
|
381
|
+
"legacy format cannot safely encode passphrase containing URL_DELIMITER",
|
|
382
|
+
);
|
|
383
|
+
assert.strictEqual(legacy, true);
|
|
384
|
+
});
|
|
339
385
|
});
|