@theqrl/wallet.js 0.1.3 → 0.2.2

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 (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +195 -1
  3. package/dist/cjs/package.json +1 -0
  4. package/dist/cjs/wallet.js +4856 -0
  5. package/dist/mjs/package.json +1 -0
  6. package/dist/mjs/wallet.js +4841 -0
  7. package/package.json +27 -7
  8. package/src/index.js +33 -13
  9. package/src/qrl/wordlist.js +11 -5
  10. package/src/utils/bytes.js +59 -0
  11. package/src/wallet/common/address.js +94 -0
  12. package/src/wallet/common/constants.js +16 -0
  13. package/src/wallet/common/descriptor.js +70 -0
  14. package/src/wallet/common/seed.js +123 -0
  15. package/src/wallet/common/wallettype.js +21 -0
  16. package/src/wallet/factory.js +39 -0
  17. package/src/wallet/misc/mnemonic.js +77 -0
  18. package/src/wallet/ml_dsa_87/crypto.js +90 -0
  19. package/src/wallet/ml_dsa_87/descriptor.js +18 -0
  20. package/src/wallet/ml_dsa_87/wallet.js +158 -0
  21. package/types/index.d.ts +12 -11
  22. package/types/qrl/wordlist.d.ts +9 -0
  23. package/types/qrl/wordlist.d.ts.map +1 -1
  24. package/types/utils/bytes.d.ts +27 -0
  25. package/types/utils/bytes.d.ts.map +1 -0
  26. package/types/wallet/common/address.d.ts +17 -0
  27. package/types/wallet/common/address.d.ts.map +1 -0
  28. package/types/wallet/common/constants.d.ts +13 -0
  29. package/types/wallet/common/constants.d.ts.map +1 -0
  30. package/types/wallet/common/descriptor.d.ts +32 -0
  31. package/types/wallet/common/descriptor.d.ts.map +1 -0
  32. package/types/wallet/common/seed.d.ts +67 -0
  33. package/types/wallet/common/seed.d.ts.map +1 -0
  34. package/types/wallet/common/wallettype.d.ts +19 -0
  35. package/types/wallet/common/wallettype.d.ts.map +1 -0
  36. package/types/wallet/factory.d.ts +9 -0
  37. package/types/wallet/factory.d.ts.map +1 -0
  38. package/types/wallet/misc/mnemonic.d.ts +13 -0
  39. package/types/wallet/misc/mnemonic.d.ts.map +1 -0
  40. package/types/wallet/ml_dsa_87/crypto.d.ts +24 -0
  41. package/types/wallet/ml_dsa_87/crypto.d.ts.map +1 -0
  42. package/types/wallet/ml_dsa_87/descriptor.d.ts +8 -0
  43. package/types/wallet/ml_dsa_87/descriptor.d.ts.map +1 -0
  44. package/types/wallet/ml_dsa_87/wallet.d.ts +74 -0
  45. package/types/wallet/ml_dsa_87/wallet.d.ts.map +1 -0
  46. package/src/dilithium.js +0 -158
  47. package/src/utils/mnemonic.js +0 -93
  48. package/types/dilithium.d.ts +0 -25
  49. package/types/dilithium.d.ts.map +0 -1
  50. package/types/utils/mnemonic.d.ts +0 -3
  51. package/types/utils/mnemonic.d.ts.map +0 -1
package/package.json CHANGED
@@ -1,10 +1,21 @@
1
1
  {
2
2
  "name": "@theqrl/wallet.js",
3
- "version": "0.1.3",
4
- "description": "",
5
- "main": "src/index.js",
3
+ "version": "0.2.2",
4
+ "description": "Quantum-resistant wallet library for The QRL using ML-DSA-87 (FIPS 204)",
5
+ "type": "module",
6
+ "main": "dist/cjs/wallet.js",
7
+ "module": "dist/mjs/wallet.js",
6
8
  "types": "types/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./types/index.d.ts",
12
+ "import": "./dist/mjs/wallet.js",
13
+ "require": "./dist/cjs/wallet.js"
14
+ }
15
+ },
7
16
  "scripts": {
17
+ "build": "rollup -c && ./fixup",
18
+ "prepublishOnly": "npm run build",
8
19
  "test": "mocha",
9
20
  "lint-check": "eslint 'src/**/*.js' 'test/**/*.js'",
10
21
  "lint": "eslint --fix 'src/**/*.js' 'test/**/*.js'",
@@ -13,31 +24,40 @@
13
24
  },
14
25
  "author": "QRL contributors <info@theqrl.org> (https://theqrl.org)",
15
26
  "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/theQRL/wallet.js"
30
+ },
16
31
  "dependencies": {
17
- "@theqrl/dilithium5": "^0.0.9",
18
- "randombytes": "^2.1.0",
19
- "sha3": "^2.1.4"
32
+ "@noble/hashes": "^1.8.0",
33
+ "@theqrl/mldsa87": "^1.0.3",
34
+ "randombytes": "^2.1.0"
20
35
  },
21
36
  "directories": {
22
37
  "lib": "src",
23
38
  "test": "test"
24
39
  },
25
40
  "files": [
41
+ "dist",
26
42
  "src",
27
43
  "types"
28
44
  ],
29
45
  "devDependencies": {
46
+ "@semantic-release/changelog": "^6.0.3",
47
+ "@semantic-release/exec": "^7.1.0",
48
+ "@semantic-release/git": "^10.0.1",
30
49
  "@types/node": "^20.14.12",
31
50
  "c8": "^7.13.0",
32
51
  "chai": "^4.3.7",
33
- "codecov": "^3.8.3",
34
52
  "eslint": "^8.37.0",
35
53
  "eslint-config-airbnb": "^19.0.4",
36
54
  "eslint-config-prettier": "^8.8.0",
37
55
  "eslint-plugin-import": "^2.27.5",
38
56
  "eslint-plugin-prettier": "^4.2.1",
57
+ "fast-check": "^4.5.3",
39
58
  "mocha": "^10.2.0",
40
59
  "prettier": "^2.8.7",
60
+ "rollup": "^4.55.1",
41
61
  "typescript": "^5.5.4"
42
62
  }
43
63
  }
package/src/index.js CHANGED
@@ -1,15 +1,35 @@
1
- const dilithium = require('./dilithium.js');
2
- const mnemonic = require('./utils/mnemonic.js');
3
- const wordlist = require('./qrl/wordlist.js');
1
+ /**
2
+ * Package entry: re-export modules at the root for convenience.
3
+ * @module index
4
+ */
4
5
 
5
- module.exports = {
6
- Dilithium: dilithium.Dilithium,
7
- extractMessage: dilithium.extractMessage,
8
- extractSignature: dilithium.extractSignature,
9
- getDilithiumAddressFromPK: dilithium.getDilithiumAddressFromPK,
10
- getDilithiumDescriptor: dilithium.getDilithiumDescriptor,
11
- isValidDilithiumAddress: dilithium.isValidDilithiumAddress,
12
- MnemonicToSeedBin: mnemonic.MnemonicToSeedBin,
13
- SeedBinToMnemonic: mnemonic.SeedBinToMnemonic,
14
- WORD_LIST: wordlist.WordList,
6
+ import { DESCRIPTOR_SIZE, EXTENDED_SEED_SIZE, SEED_SIZE } from './wallet/common/constants.js';
7
+ import {
8
+ getAddressFromPKAndDescriptor,
9
+ stringToAddress,
10
+ isValidAddress,
11
+ addressToString,
12
+ } from './wallet/common/address.js';
13
+ import { ExtendedSeed, Seed } from './wallet/common/seed.js';
14
+ import { newMLDSA87Descriptor } from './wallet/ml_dsa_87/descriptor.js';
15
+ import { Descriptor } from './wallet/common/descriptor.js';
16
+ import { newWalletFromExtendedSeed } from './wallet/factory.js';
17
+ import { Wallet as MLDSA87 } from './wallet/ml_dsa_87/wallet.js';
18
+ import { WalletType } from './wallet/common/wallettype.js';
19
+
20
+ export {
21
+ Seed,
22
+ SEED_SIZE,
23
+ ExtendedSeed,
24
+ EXTENDED_SEED_SIZE,
25
+ Descriptor,
26
+ DESCRIPTOR_SIZE,
27
+ newMLDSA87Descriptor,
28
+ getAddressFromPKAndDescriptor,
29
+ addressToString,
30
+ stringToAddress,
31
+ isValidAddress,
32
+ WalletType,
33
+ newWalletFromExtendedSeed,
34
+ MLDSA87,
15
35
  };
@@ -1,4 +1,14 @@
1
- const WordList = [
1
+ /**
2
+ * Mnemonic word list used by encoding/decoding utilities.
3
+ * @module qrl/wordlist
4
+ */
5
+
6
+ /**
7
+ * Ordered list of mnemonic words.
8
+ * @readonly
9
+ * @type {string[]}
10
+ */
11
+ export const WordList = [
2
12
  'aback',
3
13
  'abbey',
4
14
  'abbot',
@@ -4096,7 +4106,3 @@ const WordList = [
4096
4106
  'zone',
4097
4107
  'zurich',
4098
4108
  ];
4099
-
4100
- module.exports = {
4101
- WordList,
4102
- };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Shared byte/hex utils used across modules.
3
+ * @module utils/bytes
4
+ */
5
+
6
+ import { hexToBytes } from '@noble/hashes/utils';
7
+
8
+ /**
9
+ * @param {unknown} input
10
+ * @returns {boolean}
11
+ */
12
+ export function isUint8(input) {
13
+ return input instanceof Uint8Array;
14
+ }
15
+
16
+ /**
17
+ * Accepts strings with optional 0x/0X prefix and separators(space, :, _, -).
18
+ * @param {unknown} input
19
+ * @returns {boolean}
20
+ */
21
+ export function isHexLike(input) {
22
+ if (typeof input !== 'string') return false;
23
+ const s = input.trim().replace(/^0x/i, '');
24
+ return /^[0-9a-fA-F\s:_-]*$/.test(s);
25
+ }
26
+
27
+ /**
28
+ * Remove 0x prefix and all non-hex chars.
29
+ * @param {string} hex
30
+ * @returns {string}
31
+ */
32
+ export function cleanHex(hex) {
33
+ return hex.replace(/^0x/i, '').replace(/[^0-9a-fA-F]/g, '');
34
+ }
35
+
36
+ /**
37
+ * Convert various inputs to a fixed-length byte array.
38
+ * Supports hex string(with/without 0x), Uint8Array, Buffer, number[].
39
+ * @param {string|Uint8Array|Buffer|number[]} input
40
+ * @param {number} expectedLen
41
+ * @param {string} [label='bytes']
42
+ * @returns {Uint8Array}
43
+ */
44
+ export function toFixedU8(input, expectedLen, label = 'bytes') {
45
+ let bytes;
46
+ if (isUint8(input)) {
47
+ bytes = new Uint8Array(input);
48
+ } else if (isHexLike(input)) {
49
+ bytes = hexToBytes(cleanHex(input));
50
+ } else if (Array.isArray(input)) {
51
+ bytes = Uint8Array.from(input);
52
+ } else {
53
+ throw new Error(`${label}: unsupported input type; pass hex string or Uint8Array/Buffer`);
54
+ }
55
+ if (bytes.length !== expectedLen) {
56
+ throw new Error(`${label}: expected ${expectedLen} bytes, got ${bytes.length}`);
57
+ }
58
+ return bytes;
59
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Address helpers.
3
+ * @module wallet/common/address
4
+ */
5
+
6
+ /** @typedef {import('./descriptor.js').Descriptor} Descriptor */
7
+ import { shake256 } from '@noble/hashes/sha3';
8
+ import { CryptoPublicKeyBytes } from '@theqrl/mldsa87';
9
+ import { ADDRESS_SIZE } from './constants.js';
10
+
11
+ /**
12
+ * Convert address bytes to string form.
13
+ * @param {Uint8Array} addrBytes
14
+ * @returns {string}
15
+ * @throws {Error} If length mismatch.
16
+ */
17
+ function addressToString(addrBytes) {
18
+ if (!addrBytes || addrBytes.length !== ADDRESS_SIZE) {
19
+ throw new Error(`address must be ${ADDRESS_SIZE} bytes`);
20
+ }
21
+ const hex = [...addrBytes].map((b) => b.toString(16).padStart(2, '0')).join('');
22
+ return `Q${hex}`;
23
+ }
24
+
25
+ /**
26
+ * Convert address string to bytes.
27
+ * @param {string} addrStr - Address string starting with 'Q' followed by 40 hex characters.
28
+ * @returns {Uint8Array} 20-byte address.
29
+ * @throws {Error} If address format is invalid.
30
+ */
31
+ function stringToAddress(addrStr) {
32
+ if (typeof addrStr !== 'string') {
33
+ throw new Error('address must be a string');
34
+ }
35
+ const trimmed = addrStr.trim();
36
+ if (!trimmed.startsWith('Q') && !trimmed.startsWith('q')) {
37
+ throw new Error('address must start with Q');
38
+ }
39
+ const hex = trimmed.slice(1);
40
+ if (hex.length !== ADDRESS_SIZE * 2) {
41
+ throw new Error(`address must be Q + ${ADDRESS_SIZE * 2} hex characters, got ${hex.length}`);
42
+ }
43
+ if (!/^[0-9a-fA-F]+$/.test(hex)) {
44
+ throw new Error('address contains invalid characters');
45
+ }
46
+ const bytes = new Uint8Array(ADDRESS_SIZE);
47
+ for (let i = 0; i < ADDRESS_SIZE; i += 1) {
48
+ bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
49
+ }
50
+ return bytes;
51
+ }
52
+
53
+ /**
54
+ * Check if a string is a valid QRL address format.
55
+ * @param {string} addrStr - Address string to validate.
56
+ * @returns {boolean} True if valid address format.
57
+ */
58
+ function isValidAddress(addrStr) {
59
+ try {
60
+ stringToAddress(addrStr);
61
+ return true;
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Derive an address from a public key and descriptor.
69
+ * @param {Uint8Array} pk
70
+ * @param {Descriptor} descriptor
71
+ * @returns {Uint8Array} 20-byte address.
72
+ * @throws {Error} If pk length mismatch.
73
+ */
74
+ function getAddressFromPKAndDescriptor(pk, descriptor) {
75
+ if (!(pk instanceof Uint8Array)) throw new Error('pk must be Uint8Array');
76
+
77
+ const walletType = descriptor.type();
78
+ let expectedPKLen;
79
+ switch (walletType) {
80
+ default:
81
+ expectedPKLen = CryptoPublicKeyBytes;
82
+ }
83
+ if (pk.length !== expectedPKLen) {
84
+ throw new Error(`pk must be ${expectedPKLen} bytes for wallet type ${walletType}`);
85
+ }
86
+
87
+ const descBytes = descriptor.toBytes();
88
+ const input = new Uint8Array(descBytes.length + pk.length);
89
+ input.set(descBytes, 0);
90
+ input.set(pk, descBytes.length);
91
+ return shake256.create({ dkLen: ADDRESS_SIZE }).update(input).digest();
92
+ }
93
+
94
+ export { addressToString, stringToAddress, isValidAddress, getAddressFromPKAndDescriptor };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Constants used across wallet components.
3
+ * @module wallet/common/constants
4
+ */
5
+
6
+ /** @type {number} Size in bytes of the 3-byte descriptor */
7
+ export const DESCRIPTOR_SIZE = 3;
8
+
9
+ /** @type {number} Address length in bytes */
10
+ export const ADDRESS_SIZE = 20;
11
+
12
+ /** @type {number} Seed length in bytes */
13
+ export const SEED_SIZE = 48;
14
+
15
+ /** @type {number} Extended seed length in bytes */
16
+ export const EXTENDED_SEED_SIZE = DESCRIPTOR_SIZE + SEED_SIZE;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * 3-byte descriptor for a wallet:
3
+ * - byte 0: wallet type (e.g. ML_DSA_87)
4
+ * - bytes 1..2: 2 bytes metadata
5
+ * @module wallet/common/descriptor
6
+ */
7
+
8
+ import { DESCRIPTOR_SIZE } from './constants.js';
9
+ import { isValidWalletType } from './wallettype.js';
10
+ import { toFixedU8 } from '../../utils/bytes.js';
11
+
12
+ class Descriptor {
13
+ /**
14
+ * @param {Uint8Array|number[]} bytes Must be exactly 3 bytes.
15
+ * @throws {Error} If size is not 3 or wallet type is invalid.
16
+ */
17
+ constructor(bytes) {
18
+ if (!bytes || bytes.length !== DESCRIPTOR_SIZE) {
19
+ throw new Error(`Descriptor must be ${DESCRIPTOR_SIZE} bytes`);
20
+ }
21
+ /** @private @type {Uint8Array} */
22
+ this.bytes = Uint8Array.from(bytes);
23
+ if (!isValidWalletType(this.bytes[0])) {
24
+ throw new Error('Invalid wallet type in descriptor');
25
+ }
26
+ }
27
+
28
+ /**
29
+ * @returns {number}
30
+ */
31
+ type() {
32
+ return this.bytes[0] >>> 0;
33
+ }
34
+
35
+ /**
36
+ * Copy of internal bytes.
37
+ * @returns {Uint8Array}
38
+ */
39
+ toBytes() {
40
+ return this.bytes.slice();
41
+ }
42
+
43
+ /**
44
+ * Constructor: accepts hex string / Uint8Array / Buffer / number[].
45
+ * @param {string|Uint8Array|Buffer|number[]} input
46
+ * @returns {Descriptor}
47
+ */
48
+ static from(input) {
49
+ return new Descriptor(toFixedU8(input, DESCRIPTOR_SIZE, 'Descriptor'));
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Build descriptor bytes from parts.
55
+ * @param {number} walletType byte.
56
+ * @param {[number, number]} [metadata=[0,0]] Two metadata bytes.
57
+ * @returns {Uint8Array} 3 bytes.
58
+ */
59
+ function getDescriptorBytes(walletType, metadata = [0, 0]) {
60
+ if (!isValidWalletType(walletType)) {
61
+ throw new Error('Invalid wallet type in descriptor');
62
+ }
63
+ const out = new Uint8Array(DESCRIPTOR_SIZE);
64
+ out[0] = walletType >>> 0;
65
+ out[1] = (metadata?.[0] ?? 0) >>> 0;
66
+ out[2] = (metadata?.[1] ?? 0) >>> 0;
67
+ return out;
68
+ }
69
+
70
+ export { Descriptor, getDescriptorBytes };
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Seed(48 bytes) and ExtendedSeed(51 bytes) with constructors.
3
+ * @module wallet/common/seed
4
+ */
5
+
6
+ import { sha256 } from '@noble/hashes/sha2.js';
7
+ import { SEED_SIZE, EXTENDED_SEED_SIZE, DESCRIPTOR_SIZE } from './constants.js';
8
+ import { toFixedU8 } from '../../utils/bytes.js';
9
+ import { Descriptor } from './descriptor.js';
10
+ import { isValidWalletType } from './wallettype.js';
11
+
12
+ class Seed {
13
+ /**
14
+ * @param {Uint8Array} bytes Exactly 48 bytes.
15
+ * @throws {Error} If size mismatch.
16
+ */
17
+ constructor(bytes) {
18
+ if (!bytes || bytes.length !== SEED_SIZE) {
19
+ throw new Error(`Seed must be ${SEED_SIZE} bytes`);
20
+ }
21
+ this.bytes = Uint8Array.from(bytes);
22
+ }
23
+
24
+ /** @returns {Uint8Array} */
25
+ hashSHA256() {
26
+ return Uint8Array.from(sha256(this.bytes));
27
+ }
28
+
29
+ /**
30
+ * Copy of internal seed bytes.
31
+ * @returns {Uint8Array}
32
+ */
33
+ toBytes() {
34
+ return this.bytes.slice();
35
+ }
36
+
37
+ /**
38
+ * Constructor: accepts hex string / Uint8Array / Buffer / number[].
39
+ * @param {string|Uint8Array|Buffer|number[]} input
40
+ * @returns {Seed}
41
+ */
42
+ static from(input) {
43
+ return new Seed(toFixedU8(input, SEED_SIZE, 'Seed'));
44
+ }
45
+ }
46
+
47
+ class ExtendedSeed {
48
+ /**
49
+ * Layout: [3 bytes descriptor] || [48 bytes seed].
50
+ * @param {Uint8Array} bytes Exactly 51 bytes.
51
+ * @throws {Error} If size mismatch.
52
+ */
53
+ constructor(bytes) {
54
+ if (!bytes || bytes.length !== EXTENDED_SEED_SIZE) {
55
+ throw new Error(`ExtendedSeed must be ${EXTENDED_SEED_SIZE} bytes`);
56
+ }
57
+ /** @private @type {Uint8Array} */
58
+ this.bytes = Uint8Array.from(bytes);
59
+ if (!isValidWalletType(this.bytes[0])) {
60
+ throw new Error('Invalid wallet type in descriptor');
61
+ }
62
+ }
63
+
64
+ /**
65
+ * @returns {Descriptor}
66
+ */
67
+ getDescriptor() {
68
+ return new Descriptor(this.getDescriptorBytes());
69
+ }
70
+
71
+ /**
72
+ * @returns {Uint8Array} Descriptor(3 bytes).
73
+ */
74
+ getDescriptorBytes() {
75
+ return this.bytes.slice(0, DESCRIPTOR_SIZE);
76
+ }
77
+
78
+ /**
79
+ * @returns {Uint8Array} Seed bytes(48 bytes).
80
+ */
81
+ getSeedBytes() {
82
+ return this.bytes.slice(DESCRIPTOR_SIZE);
83
+ }
84
+
85
+ /**
86
+ * @returns {Seed}
87
+ */
88
+ getSeed() {
89
+ return new Seed(this.getSeedBytes());
90
+ }
91
+
92
+ /**
93
+ * Copy of internal seed bytes.
94
+ * @returns {Uint8Array}
95
+ */
96
+ toBytes() {
97
+ return this.bytes.slice();
98
+ }
99
+
100
+ /**
101
+ * Build from components.
102
+ * @param {Descriptor} desc
103
+ * @param {Seed} seed
104
+ * @returns {ExtendedSeed}
105
+ */
106
+ static newExtendedSeed(desc, seed) {
107
+ const out = new Uint8Array(EXTENDED_SEED_SIZE);
108
+ out.set(desc.toBytes(), 0);
109
+ out.set(seed.toBytes(), DESCRIPTOR_SIZE);
110
+ return new ExtendedSeed(out);
111
+ }
112
+
113
+ /**
114
+ * Constructor: accepts hex string / Uint8Array / Buffer / number[].
115
+ * @param {string|Uint8Array|Buffer|number[]} input
116
+ * @returns {ExtendedSeed}
117
+ */
118
+ static from(input) {
119
+ return new ExtendedSeed(toFixedU8(input, EXTENDED_SEED_SIZE, 'ExtendedSeed'));
120
+ }
121
+ }
122
+
123
+ export { Seed, ExtendedSeed };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Wallet type enumeration.
3
+ * @module wallet/common/wallettype
4
+ */
5
+
6
+ /**
7
+ * @readonly
8
+ * @enum {number}
9
+ */
10
+ export const WalletType = Object.freeze({
11
+ SPHINCSPLUS_256S: 0,
12
+ ML_DSA_87: 1,
13
+ });
14
+
15
+ /**
16
+ * @param {number} t
17
+ * @return {boolean}
18
+ */
19
+ export function isValidWalletType(t) {
20
+ return t === WalletType.ML_DSA_87;
21
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Auto-select wallet implementation based on the ExtendedSeed descriptor.
3
+ * @module wallet/factory
4
+ */
5
+
6
+ import { isHexLike } from '../utils/bytes.js';
7
+ import { ExtendedSeed } from './common/seed.js';
8
+ import { WalletType } from './common/wallettype.js';
9
+ import { Wallet as MLDSA87 } from './ml_dsa_87/wallet.js';
10
+
11
+ /**
12
+ * Construct a wallet from an ExtendedSeed by auto-selecting the correct implementation.
13
+ *
14
+ * @param {ExtendedSeed|Uint8Array|string} extendedSeed - ExtendedSeed instance, 51 bytes or hex string.
15
+ * @returns {MLDSA87} Wallet instance
16
+ * @throws {Error} If wallet type is unsupported
17
+ */
18
+ function newWalletFromExtendedSeed(extendedSeed) {
19
+ let ext;
20
+ if (extendedSeed instanceof Uint8Array || isHexLike(extendedSeed)) {
21
+ ext = ExtendedSeed.from(extendedSeed);
22
+ } else if (extendedSeed instanceof ExtendedSeed) {
23
+ ext = extendedSeed;
24
+ } else {
25
+ throw new Error('Unsupported extendedSeed input');
26
+ }
27
+
28
+ const desc = ext.getDescriptor();
29
+ switch (desc.type()) {
30
+ case WalletType.ML_DSA_87:
31
+ return MLDSA87.newWalletFromExtendedSeed(ext);
32
+ // case WalletType.SPHINCSPLUS_256S:
33
+ // Not yet implemented - reserved for future use
34
+ default:
35
+ throw new Error(`Unsupported wallet type: ${desc.type()}`);
36
+ }
37
+ }
38
+
39
+ export { newWalletFromExtendedSeed };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Minimal mnemonic adapters.
3
+ * @module wallet/misc/mnemonic
4
+ */
5
+
6
+ import { WordList } from '../../qrl/wordlist.js';
7
+
8
+ const WORD_LOOKUP = WordList.reduce((acc, word, i) => {
9
+ acc[word] = i;
10
+ return acc;
11
+ }, Object.create(null));
12
+
13
+ /**
14
+ * Encode bytes to a spaced hex mnemonic string.
15
+ * @param {Uint8Array} input
16
+ * @returns {string}
17
+ */
18
+ function binToMnemonic(input) {
19
+ if (input.length % 3 !== 0) {
20
+ throw new Error('byte count needs to be a multiple of 3');
21
+ }
22
+
23
+ const words = [];
24
+ for (let nibble = 0; nibble < input.length * 2; nibble += 3) {
25
+ const p = nibble >> 1;
26
+ const b1 = input[p];
27
+ const b2 = p + 1 < input.length ? input[p + 1] : 0;
28
+ const idx = nibble % 2 === 0 ? (b1 << 4) + (b2 >> 4) : ((b1 & 0x0f) << 8) + b2;
29
+
30
+ if (idx >= WordList.length) {
31
+ throw new Error('mnemonic index out of range');
32
+ }
33
+ words.push(WordList[idx]);
34
+ }
35
+
36
+ return words.join(' ');
37
+ }
38
+
39
+ /**
40
+ * Decode spaced hex mnemonic to bytes.
41
+ * @param {string} mnemonic
42
+ * @returns {Uint8Array}
43
+ */
44
+ function mnemonicToBin(mnemonic) {
45
+ const mnemonicWords = mnemonic.trim().toLowerCase().split(/\s+/);
46
+ if (mnemonicWords.length % 2 !== 0) throw new Error('word count must be even');
47
+
48
+ const result = new Uint8Array((mnemonicWords.length * 15) / 10);
49
+ let current = 0;
50
+ let buffering = 0;
51
+ let resultIndex = 0;
52
+
53
+ for (let i = 0; i < mnemonicWords.length; i += 1) {
54
+ const w = mnemonicWords[i];
55
+ const value = WORD_LOOKUP[w];
56
+ if (value === undefined) throw new Error('invalid word in mnemonic');
57
+
58
+ buffering += 3;
59
+ current = (current << 12) + value;
60
+ for (; buffering > 2; ) {
61
+ const shift = 4 * (buffering - 2);
62
+ const mask = (1 << shift) - 1;
63
+ const tmp = current >> shift;
64
+ buffering -= 2;
65
+ current &= mask;
66
+ result[resultIndex++] = tmp;
67
+ }
68
+ }
69
+
70
+ if (buffering > 0) {
71
+ result[resultIndex++] = current & 0xff;
72
+ }
73
+
74
+ return result;
75
+ }
76
+
77
+ export { mnemonicToBin, binToMnemonic };