@theqrl/wallet.js 0.1.2 → 0.2.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 +195 -1
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/wallet.js +4856 -0
- package/dist/mjs/package.json +1 -0
- package/dist/mjs/wallet.js +4841 -0
- package/package.json +22 -8
- package/src/index.js +33 -13
- package/src/qrl/wordlist.js +11 -5
- package/src/utils/bytes.js +59 -0
- package/src/wallet/common/address.js +94 -0
- package/src/wallet/common/constants.js +16 -0
- package/src/wallet/common/descriptor.js +70 -0
- package/src/wallet/common/seed.js +123 -0
- package/src/wallet/common/wallettype.js +21 -0
- package/src/wallet/factory.js +39 -0
- package/src/wallet/misc/mnemonic.js +77 -0
- package/src/wallet/ml_dsa_87/crypto.js +90 -0
- package/src/wallet/ml_dsa_87/descriptor.js +18 -0
- package/src/wallet/ml_dsa_87/wallet.js +158 -0
- package/types/index.d.ts +13 -0
- package/types/index.d.ts.map +1 -0
- package/types/qrl/wordlist.d.ts +11 -0
- package/types/qrl/wordlist.d.ts.map +1 -0
- package/types/utils/bytes.d.ts +27 -0
- package/types/utils/bytes.d.ts.map +1 -0
- package/types/wallet/common/address.d.ts +17 -0
- package/types/wallet/common/address.d.ts.map +1 -0
- package/types/wallet/common/constants.d.ts +13 -0
- package/types/wallet/common/constants.d.ts.map +1 -0
- package/types/wallet/common/descriptor.d.ts +32 -0
- package/types/wallet/common/descriptor.d.ts.map +1 -0
- package/types/wallet/common/seed.d.ts +67 -0
- package/types/wallet/common/seed.d.ts.map +1 -0
- package/types/wallet/common/wallettype.d.ts +19 -0
- package/types/wallet/common/wallettype.d.ts.map +1 -0
- package/types/wallet/factory.d.ts +9 -0
- package/types/wallet/factory.d.ts.map +1 -0
- package/types/wallet/misc/mnemonic.d.ts +13 -0
- package/types/wallet/misc/mnemonic.d.ts.map +1 -0
- package/types/wallet/ml_dsa_87/crypto.d.ts +24 -0
- package/types/wallet/ml_dsa_87/crypto.d.ts.map +1 -0
- package/types/wallet/ml_dsa_87/descriptor.d.ts +8 -0
- package/types/wallet/ml_dsa_87/descriptor.d.ts.map +1 -0
- package/types/wallet/ml_dsa_87/wallet.d.ts +74 -0
- package/types/wallet/ml_dsa_87/wallet.d.ts.map +1 -0
- package/src/dilithium.js +0 -158
- package/src/utils/mnemonic.js +0 -93
package/package.json
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theqrl/wallet.js",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "",
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
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'",
|
|
@@ -14,29 +25,32 @@
|
|
|
14
25
|
"author": "QRL contributors <info@theqrl.org> (https://theqrl.org)",
|
|
15
26
|
"license": "MIT",
|
|
16
27
|
"dependencies": {
|
|
17
|
-
"@
|
|
18
|
-
"
|
|
19
|
-
"
|
|
28
|
+
"@noble/hashes": "^1.8.0",
|
|
29
|
+
"@theqrl/mldsa87": "^0.2.0",
|
|
30
|
+
"randombytes": "^2.1.0"
|
|
20
31
|
},
|
|
21
32
|
"directories": {
|
|
22
33
|
"lib": "src",
|
|
23
34
|
"test": "test"
|
|
24
35
|
},
|
|
25
36
|
"files": [
|
|
26
|
-
"
|
|
37
|
+
"dist",
|
|
38
|
+
"src",
|
|
39
|
+
"types"
|
|
27
40
|
],
|
|
28
41
|
"devDependencies": {
|
|
29
42
|
"@types/node": "^20.14.12",
|
|
30
43
|
"c8": "^7.13.0",
|
|
31
44
|
"chai": "^4.3.7",
|
|
32
|
-
"codecov": "^3.8.3",
|
|
33
45
|
"eslint": "^8.37.0",
|
|
34
46
|
"eslint-config-airbnb": "^19.0.4",
|
|
35
47
|
"eslint-config-prettier": "^8.8.0",
|
|
36
48
|
"eslint-plugin-import": "^2.27.5",
|
|
37
49
|
"eslint-plugin-prettier": "^4.2.1",
|
|
50
|
+
"fast-check": "^4.5.3",
|
|
38
51
|
"mocha": "^10.2.0",
|
|
39
52
|
"prettier": "^2.8.7",
|
|
53
|
+
"rollup": "^4.55.1",
|
|
40
54
|
"typescript": "^5.5.4"
|
|
41
55
|
}
|
|
42
56
|
}
|
package/src/index.js
CHANGED
|
@@ -1,15 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Package entry: re-export modules at the root for convenience.
|
|
3
|
+
* @module index
|
|
4
|
+
*/
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
};
|
package/src/qrl/wordlist.js
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
|
|
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 };
|