diff --git a/README.md b/README.md index 8d3b4c9..f2829d4 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ const wallet = newWalletFromExtendedSeed('0x01000000...'); // 51-byte hex | Method | Returns | Description | |--------|---------|-------------| | `getAddressStr()` | `string` | Address with Q prefix (e.g., `Qabc123...`) | -| `getAddress()` | `Uint8Array` | Raw 20-byte address | +| `getAddress()` | `Uint8Array` | Raw 48-byte address | | `getMnemonic()` | `string` | 34-word mnemonic phrase | | `getPK()` | `Uint8Array` | Public key (2,592 bytes) | | `getSK()` | `Uint8Array` | Secret key (4,896 bytes) | @@ -116,7 +116,7 @@ const wallet = newWalletFromExtendedSeed('0x01000000...'); // 51-byte hex ### Address Utilities -**Address Format:** `Q` prefix + 40 lowercase hex characters (41 chars total). +**Address Format:** `Q` prefix + 96 lowercase hex characters (97 chars total). - Output is always lowercase; input parsing is case-insensitive - No checksum encoding (unlike EIP-55) — `isValidAddress()` checks format only, not correctness. A single mistyped character will produce a valid but unrelated address. Applications should implement their own checksum or confirmation UX to guard against transcription errors. See [SECURITY.md](SECURITY.md#address-security) for recommendations. diff --git a/SECURITY.md b/SECURITY.md index 681ef7f..2a2db9a 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -43,10 +43,12 @@ Seed (48 bytes, random) ### Address Derivation ``` -Address = SHAKE-256(Descriptor || PublicKey, 20 bytes) +Address = SHAKE-256(Descriptor || PublicKey, 48 bytes) ``` -Addresses are 20 bytes, displayed with a `Q` prefix in hexadecimal (41 characters total). +Addresses are 48 bytes (384 bits), displayed with a `Q` prefix in hexadecimal (97 characters total). + +The 48-byte (384-bit) address provides **NIST Category 5** post-quantum collision resistance. A shorter address would reduce collision resistance below the security level of the underlying signature schemes (ML-DSA-87 and SPHINCS+-256s both target NIST Level 5). The 48-byte size ensures the address does not become the weakest link in the security chain. --- @@ -88,10 +90,10 @@ Addresses are 20 bytes, displayed with a `Q` prefix in hexadecimal (41 character ### No Built-in Checksum -**Important:** QRL addresses do not include a checksum (unlike EIP-55 mixed-case encoding in Ethereum). `isValidAddress()` only checks the structural format — `Q` prefix followed by 40 hex characters — it cannot detect a mistyped or truncated address. +**Important:** QRL addresses do not include a checksum (unlike EIP-55 mixed-case encoding in Ethereum). `isValidAddress()` only checks the structural format — `Q` prefix followed by 96 hex characters — it cannot detect a mistyped or truncated address. **Implications:** -- Any 20-byte hex value with a `Q` prefix passes validation +- Any 48-byte hex value with a `Q` prefix passes validation - A single character error produces a valid but unrelated address - Funds sent to a mistyped address are unrecoverable @@ -170,7 +172,7 @@ This is by design for FIPS 204 compliance and go-qrllib cross-implementation com | `new Descriptor(bytes)` | Exactly 3 bytes, valid wallet type | | `wallet.sign(message)` | message is Uint8Array | | `MLDSA87.verify(sig, msg, pk)` | All inputs are Uint8Array of correct lengths | -| `stringToAddress(str)` | Starts with Q, 40 hex characters | +| `stringToAddress(str)` | Starts with Q, 96 hex characters | ### Error Handling diff --git a/dist/cjs/wallet.js b/dist/cjs/wallet.js index 1893dc4..9523c99 100644 --- a/dist/cjs/wallet.js +++ b/dist/cjs/wallet.js @@ -9,7 +9,7 @@ const DESCRIPTOR_SIZE = 3; /** @type {number} Address length in bytes */ -const ADDRESS_SIZE = 20; +const ADDRESS_SIZE = 48; /** @type {number} Seed length in bytes */ const SEED_SIZE = 48; @@ -1978,8 +1978,8 @@ function cryptoSignVerify(sig, m, pk, ctx) { * @module wallet/common/address * * Address Format: - * - String form: "Q" prefix followed by 40 lowercase hex characters (41 chars total) - * - Byte form: 20-byte SHAKE-256 hash of (descriptor || public key) + * - String form: "Q" prefix followed by 96 lowercase hex characters (97 chars total) + * - Byte form: 48-byte SHAKE-256 hash of (descriptor || public key) * - Output is always lowercase hex; input parsing is case-insensitive for both * the "Q"/"q" prefix and hex characters * - Unlike EIP-55, no checksum encoding is used in the address itself @@ -2002,8 +2002,8 @@ function addressToString(addrBytes) { /** * Convert address string to bytes. - * @param {string} addrStr - Address string starting with 'Q' followed by 40 hex characters. - * @returns {Uint8Array} 20-byte address. + * @param {string} addrStr - Address string starting with 'Q' followed by 96 hex characters. + * @returns {Uint8Array} 48-byte address. * @throws {Error} If address format is invalid. */ function stringToAddress(addrStr) { @@ -2030,7 +2030,7 @@ function stringToAddress(addrStr) { /** * Check if a string is a valid QRL address format (structure only). - * QRL addresses contain no checksum — any well-formed Q + 40 hex string passes. + * QRL addresses contain no checksum — any well-formed Q + 96 hex string passes. * Applications should add their own confirmation or checksum layer. * @param {string} addrStr - Address string to validate. * @returns {boolean} True if valid address format. @@ -2048,7 +2048,7 @@ function isValidAddress(addrStr) { * Derive an address from a public key and descriptor. * @param {Uint8Array} pk * @param {Descriptor} descriptor - * @returns {Uint8Array} 20-byte address. + * @returns {Uint8Array} 48-byte address. * @throws {Error} If pk length mismatch. */ function getAddressFromPKAndDescriptor(pk, descriptor) { @@ -2342,7 +2342,7 @@ function isUint8(input) { function isHexLike(input) { if (typeof input !== 'string') return false; const s = input.trim().replace(/^0x/i, ''); - return /^[0-9a-fA-F\s:_-]*$/.test(s); + return /[0-9a-fA-F]/.test(s) && /^[0-9a-fA-F\s:_-]+$/.test(s); } /** @@ -2524,17 +2524,15 @@ class ExtendedSeed { /** * Layout: [3 bytes descriptor] || [48 bytes seed]. * @param {Uint8Array} bytes Exactly 51 bytes. - * @param {{ skipValidation?: boolean }} [options] - * @throws {Error} If size mismatch. + * @throws {Error} If size mismatch or invalid wallet type. */ - constructor(bytes, options = {}) { + constructor(bytes) { if (!bytes || bytes.length !== EXTENDED_SEED_SIZE) { throw new Error(`ExtendedSeed must be ${EXTENDED_SEED_SIZE} bytes`); } - const { skipValidation = false } = options; /** @private @type {Uint8Array} */ this.bytes = Uint8Array.from(bytes); - if (!skipValidation && !isValidWalletType(this.bytes[0])) { + if (!isValidWalletType(this.bytes[0])) { throw new Error('Invalid wallet type in descriptor'); } } @@ -2607,17 +2605,6 @@ class ExtendedSeed { zeroize() { this.bytes.fill(0); } - - /** - * Internal helper: construct without wallet type validation. - * @param {string|Uint8Array|Buffer|number[]} input - * @returns {ExtendedSeed} - */ - static fromUnchecked(input) { - return new ExtendedSeed(toFixedU8(input, EXTENDED_SEED_SIZE, 'ExtendedSeed'), { - skipValidation: true, - }); - } } /** @@ -2672,7 +2659,7 @@ function randomBytes(size) { } { let acc = 0; - for (let i = 0; i < 16; i++) acc |= out[i]; + for (let i = 0; i < size; i++) acc |= out[i]; if (acc === 0) throw new Error('getRandomValues returned all zeros'); } return out; @@ -6977,6 +6964,8 @@ class Wallet { this.pk = pk; this.sk = sk; this.extendedSeed = ExtendedSeed.newExtendedSeed(descriptor, seed); + /** @private */ + this._zeroized = false; } /** @@ -7047,28 +7036,37 @@ class Wallet { return new Descriptor(this.descriptor.toBytes()); } + /** + * @private + * @throws {Error} If the wallet has been zeroized. + */ + _requireLive() { + if (this._zeroized) { + throw new Error('Wallet has been zeroized'); + } + } + /** @returns {ExtendedSeed} */ getExtendedSeed() { - const bytes = this.extendedSeed.toBytes(); - try { - return ExtendedSeed.from(bytes); - } catch { - return ExtendedSeed.fromUnchecked(bytes); - } + this._requireLive(); + return ExtendedSeed.from(this.extendedSeed.toBytes()); } /** @returns {Seed} */ getSeed() { + this._requireLive(); return new Seed(this.seed.toBytes()); } /** @returns {string} hex(ExtendedSeed) */ getHexExtendedSeed() { - return `0x${bytesToHex(this.extendedSeed.toBytes())}`; + this._requireLive(); + return `0x${bytesToHex(this.getExtendedSeed().toBytes())}`; } /** @returns {string} */ getMnemonic() { + this._requireLive(); return binToMnemonic(this.getExtendedSeed().toBytes()); } @@ -7085,6 +7083,7 @@ class Wallet { * returned by this method. */ getSK() { + this._requireLive(); return this.sk.slice(); } @@ -7094,6 +7093,7 @@ class Wallet { * @returns {Uint8Array} Signature bytes. */ sign(message) { + this._requireLive(); return sign(this.sk, message); } @@ -7119,13 +7119,17 @@ class Wallet { zeroize() { if (this.sk) { this.sk.fill(0); + this.sk = null; } if (this.seed) { this.seed.zeroize(); + this.seed = null; } if (this.extendedSeed) { this.extendedSeed.zeroize(); + this.extendedSeed = null; } + this._zeroized = true; } } diff --git a/dist/mjs/wallet.js b/dist/mjs/wallet.js index 6335c97..9cbd8eb 100644 --- a/dist/mjs/wallet.js +++ b/dist/mjs/wallet.js @@ -12,7 +12,7 @@ import { hexToBytes, bytesToHex } from '@noble/hashes/utils.js'; const DESCRIPTOR_SIZE = 3; /** @type {number} Address length in bytes */ -const ADDRESS_SIZE = 20; +const ADDRESS_SIZE = 48; /** @type {number} Seed length in bytes */ const SEED_SIZE = 48; @@ -25,8 +25,8 @@ const EXTENDED_SEED_SIZE = DESCRIPTOR_SIZE + SEED_SIZE; * @module wallet/common/address * * Address Format: - * - String form: "Q" prefix followed by 40 lowercase hex characters (41 chars total) - * - Byte form: 20-byte SHAKE-256 hash of (descriptor || public key) + * - String form: "Q" prefix followed by 96 lowercase hex characters (97 chars total) + * - Byte form: 48-byte SHAKE-256 hash of (descriptor || public key) * - Output is always lowercase hex; input parsing is case-insensitive for both * the "Q"/"q" prefix and hex characters * - Unlike EIP-55, no checksum encoding is used in the address itself @@ -49,8 +49,8 @@ function addressToString(addrBytes) { /** * Convert address string to bytes. - * @param {string} addrStr - Address string starting with 'Q' followed by 40 hex characters. - * @returns {Uint8Array} 20-byte address. + * @param {string} addrStr - Address string starting with 'Q' followed by 96 hex characters. + * @returns {Uint8Array} 48-byte address. * @throws {Error} If address format is invalid. */ function stringToAddress(addrStr) { @@ -77,7 +77,7 @@ function stringToAddress(addrStr) { /** * Check if a string is a valid QRL address format (structure only). - * QRL addresses contain no checksum — any well-formed Q + 40 hex string passes. + * QRL addresses contain no checksum — any well-formed Q + 96 hex string passes. * Applications should add their own confirmation or checksum layer. * @param {string} addrStr - Address string to validate. * @returns {boolean} True if valid address format. @@ -95,7 +95,7 @@ function isValidAddress(addrStr) { * Derive an address from a public key and descriptor. * @param {Uint8Array} pk * @param {Descriptor} descriptor - * @returns {Uint8Array} 20-byte address. + * @returns {Uint8Array} 48-byte address. * @throws {Error} If pk length mismatch. */ function getAddressFromPKAndDescriptor(pk, descriptor) { @@ -140,7 +140,7 @@ function isUint8(input) { function isHexLike(input) { if (typeof input !== 'string') return false; const s = input.trim().replace(/^0x/i, ''); - return /^[0-9a-fA-F\s:_-]*$/.test(s); + return /[0-9a-fA-F]/.test(s) && /^[0-9a-fA-F\s:_-]+$/.test(s); } /** @@ -322,17 +322,15 @@ class ExtendedSeed { /** * Layout: [3 bytes descriptor] || [48 bytes seed]. * @param {Uint8Array} bytes Exactly 51 bytes. - * @param {{ skipValidation?: boolean }} [options] - * @throws {Error} If size mismatch. + * @throws {Error} If size mismatch or invalid wallet type. */ - constructor(bytes, options = {}) { + constructor(bytes) { if (!bytes || bytes.length !== EXTENDED_SEED_SIZE) { throw new Error(`ExtendedSeed must be ${EXTENDED_SEED_SIZE} bytes`); } - const { skipValidation = false } = options; /** @private @type {Uint8Array} */ this.bytes = Uint8Array.from(bytes); - if (!skipValidation && !isValidWalletType(this.bytes[0])) { + if (!isValidWalletType(this.bytes[0])) { throw new Error('Invalid wallet type in descriptor'); } } @@ -405,17 +403,6 @@ class ExtendedSeed { zeroize() { this.bytes.fill(0); } - - /** - * Internal helper: construct without wallet type validation. - * @param {string|Uint8Array|Buffer|number[]} input - * @returns {ExtendedSeed} - */ - static fromUnchecked(input) { - return new ExtendedSeed(toFixedU8(input, EXTENDED_SEED_SIZE, 'ExtendedSeed'), { - skipValidation: true, - }); - } } /** @@ -470,7 +457,7 @@ function randomBytes(size) { } { let acc = 0; - for (let i = 0; i < 16; i++) acc |= out[i]; + for (let i = 0; i < size; i++) acc |= out[i]; if (acc === 0) throw new Error('getRandomValues returned all zeros'); } return out; @@ -4775,6 +4762,8 @@ class Wallet { this.pk = pk; this.sk = sk; this.extendedSeed = ExtendedSeed.newExtendedSeed(descriptor, seed); + /** @private */ + this._zeroized = false; } /** @@ -4845,28 +4834,37 @@ class Wallet { return new Descriptor(this.descriptor.toBytes()); } + /** + * @private + * @throws {Error} If the wallet has been zeroized. + */ + _requireLive() { + if (this._zeroized) { + throw new Error('Wallet has been zeroized'); + } + } + /** @returns {ExtendedSeed} */ getExtendedSeed() { - const bytes = this.extendedSeed.toBytes(); - try { - return ExtendedSeed.from(bytes); - } catch { - return ExtendedSeed.fromUnchecked(bytes); - } + this._requireLive(); + return ExtendedSeed.from(this.extendedSeed.toBytes()); } /** @returns {Seed} */ getSeed() { + this._requireLive(); return new Seed(this.seed.toBytes()); } /** @returns {string} hex(ExtendedSeed) */ getHexExtendedSeed() { - return `0x${bytesToHex(this.extendedSeed.toBytes())}`; + this._requireLive(); + return `0x${bytesToHex(this.getExtendedSeed().toBytes())}`; } /** @returns {string} */ getMnemonic() { + this._requireLive(); return binToMnemonic(this.getExtendedSeed().toBytes()); } @@ -4883,6 +4881,7 @@ class Wallet { * returned by this method. */ getSK() { + this._requireLive(); return this.sk.slice(); } @@ -4892,6 +4891,7 @@ class Wallet { * @returns {Uint8Array} Signature bytes. */ sign(message) { + this._requireLive(); return sign(this.sk, message); } @@ -4917,13 +4917,17 @@ class Wallet { zeroize() { if (this.sk) { this.sk.fill(0); + this.sk = null; } if (this.seed) { this.seed.zeroize(); + this.seed = null; } if (this.extendedSeed) { this.extendedSeed.zeroize(); + this.extendedSeed = null; } + this._zeroized = true; } } diff --git a/package.json b/package.json index 076c609..2707daa 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ } }, "scripts": { - "build": "rollup -c && ./fixup", + "build": "rollup -c && ./fixup && tsc", "prepublishOnly": "npm run build", "test": "mocha", "test:browser": "playwright test", @@ -25,6 +25,9 @@ }, "author": "QRL contributors (https://theqrl.org)", "license": "MIT", + "engines": { + "node": ">=20.19" + }, "repository": { "type": "git", "url": "https://github.com/theQRL/wallet.js" diff --git a/src/utils/bytes.js b/src/utils/bytes.js index 5ee3f90..2b6fb79 100644 --- a/src/utils/bytes.js +++ b/src/utils/bytes.js @@ -21,7 +21,7 @@ export function isUint8(input) { export function isHexLike(input) { if (typeof input !== 'string') return false; const s = input.trim().replace(/^0x/i, ''); - return /^[0-9a-fA-F\s:_-]*$/.test(s); + return /[0-9a-fA-F]/.test(s) && /^[0-9a-fA-F\s:_-]+$/.test(s); } /** diff --git a/src/utils/random.js b/src/utils/random.js index 0467a43..0e6ceff 100644 --- a/src/utils/random.js +++ b/src/utils/random.js @@ -40,7 +40,7 @@ export function randomBytes(size) { } if (size >= 16) { let acc = 0; - for (let i = 0; i < 16; i++) acc |= out[i]; + for (let i = 0; i < size; i++) acc |= out[i]; if (acc === 0) throw new Error('getRandomValues returned all zeros'); } return out; diff --git a/src/wallet/common/address.js b/src/wallet/common/address.js index 0c4ccbd..d7e2b90 100644 --- a/src/wallet/common/address.js +++ b/src/wallet/common/address.js @@ -3,8 +3,8 @@ * @module wallet/common/address * * Address Format: - * - String form: "Q" prefix followed by 40 lowercase hex characters (41 chars total) - * - Byte form: 20-byte SHAKE-256 hash of (descriptor || public key) + * - String form: "Q" prefix followed by 96 lowercase hex characters (97 chars total) + * - Byte form: 48-byte SHAKE-256 hash of (descriptor || public key) * - Output is always lowercase hex; input parsing is case-insensitive for both * the "Q"/"q" prefix and hex characters * - Unlike EIP-55, no checksum encoding is used in the address itself @@ -31,8 +31,8 @@ function addressToString(addrBytes) { /** * Convert address string to bytes. - * @param {string} addrStr - Address string starting with 'Q' followed by 40 hex characters. - * @returns {Uint8Array} 20-byte address. + * @param {string} addrStr - Address string starting with 'Q' followed by 96 hex characters. + * @returns {Uint8Array} 48-byte address. * @throws {Error} If address format is invalid. */ function stringToAddress(addrStr) { @@ -59,7 +59,7 @@ function stringToAddress(addrStr) { /** * Check if a string is a valid QRL address format (structure only). - * QRL addresses contain no checksum — any well-formed Q + 40 hex string passes. + * QRL addresses contain no checksum — any well-formed Q + 96 hex string passes. * Applications should add their own confirmation or checksum layer. * @param {string} addrStr - Address string to validate. * @returns {boolean} True if valid address format. @@ -77,7 +77,7 @@ function isValidAddress(addrStr) { * Derive an address from a public key and descriptor. * @param {Uint8Array} pk * @param {Descriptor} descriptor - * @returns {Uint8Array} 20-byte address. + * @returns {Uint8Array} 48-byte address. * @throws {Error} If pk length mismatch. */ function getAddressFromPKAndDescriptor(pk, descriptor) { diff --git a/src/wallet/common/constants.js b/src/wallet/common/constants.js index 3e1f455..b833737 100644 --- a/src/wallet/common/constants.js +++ b/src/wallet/common/constants.js @@ -7,7 +7,7 @@ export const DESCRIPTOR_SIZE = 3; /** @type {number} Address length in bytes */ -export const ADDRESS_SIZE = 20; +export const ADDRESS_SIZE = 48; /** @type {number} Seed length in bytes */ export const SEED_SIZE = 48; diff --git a/src/wallet/common/seed.js b/src/wallet/common/seed.js index f2df465..099d7be 100644 --- a/src/wallet/common/seed.js +++ b/src/wallet/common/seed.js @@ -55,17 +55,15 @@ class ExtendedSeed { /** * Layout: [3 bytes descriptor] || [48 bytes seed]. * @param {Uint8Array} bytes Exactly 51 bytes. - * @param {{ skipValidation?: boolean }} [options] - * @throws {Error} If size mismatch. + * @throws {Error} If size mismatch or invalid wallet type. */ - constructor(bytes, options = {}) { + constructor(bytes) { if (!bytes || bytes.length !== EXTENDED_SEED_SIZE) { throw new Error(`ExtendedSeed must be ${EXTENDED_SEED_SIZE} bytes`); } - const { skipValidation = false } = options; /** @private @type {Uint8Array} */ this.bytes = Uint8Array.from(bytes); - if (!skipValidation && !isValidWalletType(this.bytes[0])) { + if (!isValidWalletType(this.bytes[0])) { throw new Error('Invalid wallet type in descriptor'); } } @@ -138,17 +136,6 @@ class ExtendedSeed { zeroize() { this.bytes.fill(0); } - - /** - * Internal helper: construct without wallet type validation. - * @param {string|Uint8Array|Buffer|number[]} input - * @returns {ExtendedSeed} - */ - static fromUnchecked(input) { - return new ExtendedSeed(toFixedU8(input, EXTENDED_SEED_SIZE, 'ExtendedSeed'), { - skipValidation: true, - }); - } } export { Seed, ExtendedSeed }; diff --git a/src/wallet/ml_dsa_87/wallet.js b/src/wallet/ml_dsa_87/wallet.js index 1a02c24..801a037 100644 --- a/src/wallet/ml_dsa_87/wallet.js +++ b/src/wallet/ml_dsa_87/wallet.js @@ -23,6 +23,8 @@ class Wallet { this.pk = pk; this.sk = sk; this.extendedSeed = ExtendedSeed.newExtendedSeed(descriptor, seed); + /** @private */ + this._zeroized = false; } /** @@ -93,28 +95,37 @@ class Wallet { return new Descriptor(this.descriptor.toBytes()); } + /** + * @private + * @throws {Error} If the wallet has been zeroized. + */ + _requireLive() { + if (this._zeroized) { + throw new Error('Wallet has been zeroized'); + } + } + /** @returns {ExtendedSeed} */ getExtendedSeed() { - const bytes = this.extendedSeed.toBytes(); - try { - return ExtendedSeed.from(bytes); - } catch { - return ExtendedSeed.fromUnchecked(bytes); - } + this._requireLive(); + return ExtendedSeed.from(this.extendedSeed.toBytes()); } /** @returns {Seed} */ getSeed() { + this._requireLive(); return new Seed(this.seed.toBytes()); } /** @returns {string} hex(ExtendedSeed) */ getHexExtendedSeed() { - return `0x${bytesToHex(this.extendedSeed.toBytes())}`; + this._requireLive(); + return `0x${bytesToHex(this.getExtendedSeed().toBytes())}`; } /** @returns {string} */ getMnemonic() { + this._requireLive(); return binToMnemonic(this.getExtendedSeed().toBytes()); } @@ -131,6 +142,7 @@ class Wallet { * returned by this method. */ getSK() { + this._requireLive(); return this.sk.slice(); } @@ -140,6 +152,7 @@ class Wallet { * @returns {Uint8Array} Signature bytes. */ sign(message) { + this._requireLive(); return sign(this.sk, message); } @@ -165,13 +178,17 @@ class Wallet { zeroize() { if (this.sk) { this.sk.fill(0); + this.sk = null; } if (this.seed) { this.seed.zeroize(); + this.seed = null; } if (this.extendedSeed) { this.extendedSeed.zeroize(); + this.extendedSeed = null; } + this._zeroized = true; } } diff --git a/test/fixtures/ml_dsa_87.fixtures.js b/test/fixtures/ml_dsa_87.fixtures.js index 394234a..9f71e59 100644 --- a/test/fixtures/ml_dsa_87.fixtures.js +++ b/test/fixtures/ml_dsa_87.fixtures.js @@ -6,7 +6,7 @@ export const walletTestCases = [ '010000f29f58aff0b00de2844f7e20bd9eeaacc379150043beeb328335817512b29fbb7184da84a092f842b2a06d72a24a5d28', wantMnemonic: 'absorb aback veto waiter rail aroma aunt chess fiend than sahara unwary punk dawn belong agent sane reefy loyal from judas clean paste rho madam poor pay convoy duty circa hybrid circus exempt splash', - wantAddress: 'Qd5812f6cf4a0f645aa620cd57319a0ed649dd8f5', + wantAddress: 'Qd5812f6cf4a0f645aa620cd57319a0ed649dd8f5519a9dde7770ae5b0e49e547985f35eb972a2a07041561aa39c65a39', wantPK: 'd2d7a04f06b477907a62a7c5ba0b8d5bc0755354d7dcfed5c4268f324aad7afa3763f3538a0bb6fb4e03106bcf9282b8f06a14d10d2fb325b2dc4aaa729bb9f96b6f93fd26390bcd4fae8ab22f76c9e7f8d2eedd5f4d07061cfde53ee02a9125e0f49336357ee96c0b1494befd49e4771977cdce1a22b445213d8a0af9c6c69c110f6e37ed4589387fbf693f7ccabe46512c2fd5845399790c545da71cc36fbf37f84f810e76d9f6d2e545ecbaea2e182616ec6394229b3c5d2b476f4c31c45c19e76bfce9b101bc9360e400920b75280915e2d2fd3ecabb458c66bd414efb3cbaa82ec03e9f4bfd723d16da088dd0bcd6b62e1cead240eb630b594d1793be1a0dce8e6832e3bdf7767a94f32a096a245a46e8580d137ecc4c12dbd2f3ed7b0fed2ffd9d28e391b959a869401d30e84250ac4d1ca476488430a5a44770fa043c9118701553e8c84bf2c70c0b9ca3e7a4836a4881e754b2a59fc0a35af7f8bd6b8d4abb4d5582e8ec8be9f7ae3a11e32718d07dfc2b518e1075cf36087c6167dc344f91e240f8c02335c8df200c2bad5010f50a1501a32786035c2877ddfd4f74f01fe93d07ed2c4405ac198ca50c2fb2f61e0552a48aa4c177d2ee76b02629764146d98b58af4751d140fc40ca94240f85b43e7bb45a1d9f9728398437c56b9023c2d248011dc6ecb8b9201496212593204722ed2d8054e9f1de6e13c9509b4984a928b003088430001abd0257cde9352fd80cbad728b8645ca7e3d722d5a2ba211a5bebf7adba676a06d4edc7284e522b19412d31a1de6c2e09e0147759cb3554a68bec189783a9ff6fff194a5325db68e1d4903415d4ffb19f18ddfbfa51568b77d55461b4443ea10202d7bc4121dd391915c09654110239b73a4b209f31d86fe50fad3602b8fe9cc6e6fbdb441ce43a6d56ccc11e39e1353c987e5af5f34b0439d3146fca9c8067e4b6ba53abb23ae634f25ded6509fb76293a5fdb1d32671cb87df5981ff2970f816e2011eaaa6ec810a9bcc670d959b62d79df9ae3e8643237d41600df6a3be8c0c8e3ef95e1ce0cdacf0fb2e24e44e3b279ac7e97c433143714c269923acc644634da6abfef9f3a19bbce029f5e49a63e645fbcf08771829e6996faa4f551142a3a2b6bd647b498105b537a9e829351824b873f0acc568e26e554c987ed21ee302e00a3b6177d1779101b0f2945782cc95df387d14425de4180b401d9fe3516697162b119f0d181c03d1449199ec40f03545ce67997715a749085bac6fffa005ba6187130e831af6e41cf2ff2be59cbdf0b3076e6aaa41eb8da4a08920f68b024f41816e35e7e492ecc0b964fbc11a6761e463da7a5943447b44ea8d1d491cd2fa1ab5d0d9f2a3f3719ed3781a961a85dfc94a4539926af967003cc1ae18b4401e448a4da38f91adec2aca6abf445921008d7bdf3e0da818afc64f13078852eeecf8f291abfd5a992d2a82284dc36ca9f3201de67087ddead3f239fb2c0afa240fc451db64d159c0dee372a5f4f2374e22edce0137521400e4831f8164d90991f3eacd106154f24061c6c1a757ff47aeb21421a93626680c61d808c359c14e4c4be92b0fe0a24590c5a406e9e2b6730c458510129df62aa281339b1d117fe38d62f530042b16eda0a3389ea8bb1d617bf0e3e75d03d2cced188184ad89ffb044a91e7bf18b9fc95a48cdb2c9863224df03a9cdd269c2bfae5f312de03f0bde5d6f47c270a679a49af372d96ec84999195ec584cb8a19793f764f314f117d470091b7ff1ac8bc0a1d53b575e6bee6cf76b8eb56a9ac73b8f925657986418a21db2cdb0f5f3b604884ac53ed278f49976859ebf3ab51b4a7d1084a145757f0853579b7bd9b4c0233308edae2848f6c3b3fa8268fcf8e4649c587a89fbc492f2ab9fe2aa8ba62db6e1ad64315d9d974169926f7a45bdb518c4f58f24b4c6b9a45bf78725a1ff218648300383320f53af620c548b91b3f05ee705b3892bd7788c28151483fb20d2bc042f4691a9476524ead501114931b561792f34003be31d75a246ebd1b32214c9b307cc5c01b20f4f53a8b3c1d41b5c62b0754e7efc11e4f0f0d602c09ed00b4aa4c2f5bd07b9adfbd67748f6a1c144f088513b7f963d8be5c9ce5faa0a8bbbdd6406885119d2606de058af4bb721f6d3894487effd64b2b1eaf7edb37572a5b0715e5e1ba0967c1b591d5647a9ce581748ae1c7e86ecb9787918d232a37b4d6f03bd4462d96b839c171581f1c3e7ad689775290bae38e3f18c1c260fb61b23790af68fcebcc94dcdd3191521b1c87b3b7f2f503d8024b9bb8498a470b1dc9ca56eb1708803a408b6d850a3090eee75aba9b407caeb63250ab6e796778b04466f055fa1272df81e21dfff99a780b8737aada488de893fd6f5943ddd5481f6538ca8c1d7853384919cd719d67dd68adbbe5b539c111e7efe95e01cc83fef2a1458d81aeb0c3cd5e4fa3338f10ebe34dded123f612e99cbe4bd2798702c11e65a5e5d615c3e6b6ffec22ae3b8594ee1aa674d44db73a035e7a2214e71e0173f4381794e00fdc14550300ce1fb1fd0a2af6fd57e593d03bfeadf63c4fff0b3f14e9af9d716d4ccb012d248686881f324df220a671957945ba5d8e4411513fb98913608b86a3d17e39125d9fc6e4004d03a03157ca6e4ab885d39edfceba4cd0be32cd94f7a45903d1291ebcabd20a6bd9291228e8b9ca473db08ffe50cd8066323767239d1c780756f12e68d3db383fd9e02407b9c4cfd3d15c1809eab0d3f67cb00d4e994634164c6c0a26337ae6e39c2067b48662d3b35c49e359c0b4bdeba2d167d4e649634e2d2e6413bd3fdf68abba0cae31abaea0596b664d573497ef038843deed7580aff064b900a67646a0eb090dba30ff748445780e88188679e4f1249d8a56f795e31b7ad08cebc9277492a5d774c596ede41dfe67dd7c4a56c1f3651927259d830a4a46aa9b6accbcb58713466f0f14e28867f97df163ea8420a55077b521e9953c2f52049d21530a54b70b992b4b07003829f92ea1a0a91ac3cc186309ba3011aba87272eeebd4e578992a57a9644240752556067fdfb95ead63e29faa2d581851938c76b38fb19f7337e642bedec496eb2fbda65891f9181986bc8317f5f6e2829839fe05c2b44c675a94ccd3296628bc4bb728dd788b6f939da7af1a5e161650931a8e16bbf5035f0769f018c4075d1f107512d9bca93f98c716b93080cc61194dbb9e245132c711fef64442b6d75c3329a9c46b249b1dcecfc8e93bdfa7ce439f66afadbd5fc7b54c86e4783d2c6c397a6b3c482ccffceaffe22ed6c47c27d972a4529cf58c5b18ba50aaee96be9f5c0412ee9bd7e0a5bc0848a519762591cbaa1aaeeaaa06e07424bc7d21f8a12046a7d13cb08ca106984c456a82d9c2d91e188e30d8b6cf62546ff6e46eaed60d1b4af9ae45780524f5bc74e44708f2b995169559153d0d8127841923240543682fe8274b95519615ce375e51bf06dff7f3a6343b6f40c69d6f2b989c8848419c4cff4aa09734c7070ce50d3fc9248208cacb0be1325c226fbb906a32d742eb81be7e92da9b9ac9fe572ea4713524899d70c3ef4d60fa87aa9783e58b043a35e96e436b547a55e91699a21cd76e1afe459bf2c9ac60bf58dd25c11c3', wantSK: @@ -21,7 +21,7 @@ export const walletTestCases = [ '010000a7b1a3005d9e110009c48d45deb43f0a0e31846ed2c5aaefb6d4238040ad4c08794ffe65585c13eb6948c2faf6db90c2', wantMnemonic: 'absorb aback polish bony able such ban abound shaggy stale tangle eat april thin luxury type sheet quota rex dune liver armful fair mars fill token frown scrub try negate senile racket surf aspect', - wantAddress: 'Qbe95a82d87a6cb9c7ff4c64e0c15bb1dff20b1d7', + wantAddress: 'Qbe95a82d87a6cb9c7ff4c64e0c15bb1dff20b1d77e6b571b28ad4736f2a2a3e5857e8c225d6d61399b15beef3b196936', wantPK: '6c5c0256b7e67cb7e89b75395082c19cf08a31bdb0cddaa0dbf9099951f1eaba6b78effac950c20e2f10a07e53303e56ca0228685effffd57eb45677935753560cc2301fcf56c73a80684609704b96a50bb25921492b1fd20ee2dc796d4a9f0c6d1b21875c28081e722470236636ab762f8f7b3451f1c5d1865cd6bc75f9e063a93921f4c263413956931d4e4b951bf0c0915f130f32e9334efdb6df5e761a981e0be8708c07d4948643f85862adecad1008ddd0fd166e7933230d8683944959d99f22b61c59542e90caa92c43a1dabda2259029fa798d2aa4226af07ebf1de625603d173833064141bafeb367f2a5df2c177f229dacc119c5a94649a897a01289d4e0d4ace80868153c96fcdb03b4f3c8aa8fdd6b8ab74366322454e400fd2301e30912f6dccaae2647bba0f4b2e893c50f24ac81fb2b9f2fd6ccb8f4e6e1438054dc0284650ba99320654140da53bc57b927bdea089557661056290d826c547483839f8ab77467d959ea0a4bdcbd955b96e27e1d775ce35550f5dfcd8c4347609b75f8fc9746ceecd2da06f36803988a9bf902ccb27c1602333cdb9ff2e4409a7993fc5b54d5d57d1de56564e0885f1d71ee7144df744b0960f753d973d832cc1b7b761282a366abc3a42866b26b8c84518fa05d6a152f017e9bcb466575a470db29f5d7451ff3ae88ff706e9b71e2da68b4eafb82cda58b7ca208685e4c4caeab1ba4a79622e85c08826effe2224f2f16bde95b6a7cccde9b469e9b991e4ea8fc3f1eb54a8fa719af71d321d7b893e3751173f9ef2c48200695f1e29b17597dd5450dd43b3a04042c38f2c602c88ffa4e3e96f1611a43ff6c6734b63f685a9818c9354b900c2d2474afae0728e69e0ba93a9d8c782011fb51e7c6728fb940fef69701a9b32e8527c0a47f8a70f896f5700219215e78cd776ef5079fb6ed9caf20f93c50e20a6e3e7bff8c9b7bbd800e1e59139a3ab3aa5d4018dcbc7095b0d017b77185cc5400693992b1ce633b8d0406b3ba97573108cff5a897b06992bd6385ab5f3537f20d607788d4f0f956103506a0337295915fbe02851b9fcf32e8013a9b433c3250f072571b148436c898e6e78bed671c37fc467491254f68debe627b114c5cffeb1935312fe5abeb4ed22771a35f4aae9b64399908d4be447566e4bddb5ebf2b3837a9b99a7dc59cadba535a77814a43000464cb35a2998688fb2ad2eb66ed367807c9d92e9d776ad2895fdaf12d37cd8c61ff46d027d9adfc0316ec5f3b52f80af2614b18f1d7bdbbda4dc35ba5363d960399042efc1a1fd41f860ed1422ee84e73dc12f1f2eb895d59660c11b1edb72509e5b2212716b055fe52e5a58c3e9b5d22c6d9dc4ca2a1f205d65bc4f9f24f44c72cf42d9b8b91c65858332bb24324b47877aafa523f880cd4724ff990cd2b079105071df7ee90692f43ce23e71a8bc0745984e652b65e6013385e81d44563491f546b0064e9cfb7643da4393cf6b3a7afd4fec232016700c65a8531a88e213a9ab5546255528fc0ac9992c46c1f2ac3192adc32423dbe052dbe33e46aa6d369717c74da5a8eea8e8e281b1f37c8352d2879e4ca9b8e3e0ea3454744ba8bec5434cd3ade1c783085a3dfeaeb90703cca8d0b9fcd9ea063b2ae88ded6b84f9a1498252483a5c7583756631da66bf81ec480478204731baff7f2f2e8cd6ee8a2a76d1dbaed7a8254eb002cc18fd65b57f7982176242e546fda668458378e52eb25ccf149ca097381a878643a34c811d4391474d5d09f5d4317815fc02095cda18372d72ed60d35b70c6554bb91dd8ea4a948ffbeb1bbb130b1109cf4d1702070bb8c9572674fc1df859d9aac67a541af321aa518b1d0de130621141f6be4ffc9226afff782cd2ebb9b1f87162cb32c7df2ee2931a55dff603dda6f81bc1858ab48044c1e6c45c3740c8386d8c357ec0d9dc0ff9ddd7f38ebab0e9f6e6d0f23c944a059109d332e35d2b786fde3df1975b761cef5d52df5f837315c1218d9003ea916f3dd98f926eec8781dd92e726752095e2388cac1a3c8b41da90af4942f8e734105d3b8cba0f310dd38267d2d53e0aa00780efcebbc267ff9bebc980918bdd1b4f42655d0671de9b762ea98ec55fd5d07bb102aeec85903f6653f47ee4951213170bbdcf35a276e0fc4a10df4077b14e9c326890fc502dd5422fc601ad76d45e2ac2f9cdcf1802b4fd2f54af989b0c4087feb032dda3fac5b292af1fbe53013acf162d0a7cfd908d7be37d78904161a04e6f67a63350936ba9d0a94b9400777a3cf2e5c78fc9ea4d0b2de1bd80bed2f0b2201d78a45f37099f912da6698f6bc1eb5a98bf11bd7f3a06fe95a7211a52410913d5ba04d1f80ae24fc499d380980b324ea617def1fd7b90e15df83cd0233f9cbf603aeb41f8f2f3d796596edbca55d385e2620542ecf4fddcee771a69642e7c7bbd2b20acf2774f3e9b0e9adae01ddb5925350b9977ea53071b0db3306dd6dd7c667225d540595013398205381501203bb05bf7c44738936014e1ad5214e2f26059d11f986bdc234f9da8860548e1ede1ca0bf87a010b503f2346610137bc7df2295bb5de4d38c5dc73a7738294232cae9effb82e5a7f012f7b15e223925fb9a1b998545efc28b76a2d9361d545d603cbcc019f70ff073d16c5e2727f123d95eb42339ebac494ce7f28555aea065e6161486fde260a02024f15af6592892934e9fab09025c0ac606e0491e965b58d1dcf60058059e4726728e037a698cf74bf2f2f0f4f9bec51cbc035f32eb3e617cb892af5406e6f3ee74e177a1bb07d1d3b5f80cba842d6d2e7eeb5b2ad7bedb0d9f5e6255033b1bc187064b19b72a2eb926961be1441426200ce27893e30cc217e0a8a9c33fdc22e7c94b49ef094443fca1e528035ec04c83f187686d22862ffcd9edf223d2d1d86da65a597f9f1b784395541bb5d960c1bbc34d0c15404182b3746f875b06bd5f4d41ccb58ed03de6df566cb85595b21ce3bd7062a515bc365c925965e8800263731618f70258a731e41eb45a7d8e19cf18ee8ff0eabb0dd7c89e0f6aacce42dfeee14704d53105f81b627fdb6399c23d330d8ed169e4a76c3d4158016afafdade3b725bfbfcdd8457eef2a8f8566908da236e3a0b35486e0cc979bdcef27329d354624f346a308470d813b28d204b7fab4613243b37ccf92b347390d1da0bf902cd4803a9aa54fd46fc85ff63cae54ce45785aff19cf6b1cf7ada4f3fdaeafc8989e7c9221efc0d5048c6b873bb9951729f92aa097393726a1e904460eb6344a7f4c8d680c0bafdd2f80ac559d82b4970153b87fb7724ac9c92a8dd35c8ebaff1f3d99202d8456d3ed4fa8728f1d4901c883e7cb26dbf804871e4170b6e274c1514d5a64b3c8f4bc70ee41bd0bbc44c1ae016e9bcdf6e71158d37623cf40a64cdf9fb4c230ca8680cd74d49de200acb00ea38117dd8cb246d421b29c355de0cee7b0de003e3c55821a067d8b0b5f8b01a4e5634e48131578a89870e059fb4794d45ba0713179a545a2ffa43d92f178dcf7c6f05c80aca3a6b16419f8d9f6d118caaa5a59535b3211b8ab651de3444ff1205c38df1f7d6b6bf96e04a7d2141a248e0b39619f3da001c437ea752d9c1605d4c11950ffeef401dd3608f368b4e365e9', wantSK: @@ -36,7 +36,7 @@ export const walletTestCases = [ '0100006e4d5bc880b7206ca70852e252172e7218437a14a117983dacdf06c380f48cef4b7f46cd9769ff09a153db94c6ff7dd3', wantMnemonic: 'absorb aback ignite steam silky arson burned slab andrew collar flee ivan irish lusory last excess blade lunar pupil valet serial awhile mink vogue limp hung nought pat apex bench surf falter zenith symbol', - wantAddress: 'Q31f654037d4d7bce04e9522e4d346ab47a90686e', + wantAddress: 'Q31f654037d4d7bce04e9522e4d346ab47a90686ef20a6c19916e68d3c77950f54babb7725ad48a3201c0acb74271e790', wantPK: '5609a41897a694b83ed80af63cc2f713ee5bd8bd312986ad7da5b3e567794ebeec7c72b8bf61de048a7e03656624c89f44df74e9feaa3efec4b044a8434771f109a55c24f9ba08dc4f63bf9a10fe7c2d1298883cdecf8774f690fc510b2b1b94a74f685bc540c0be629bc4b81900c10d4692779a35443026220241cc6f99be749d854b747d5c1c67fd195c070c5991f88ccf56a12ad2269a370b9041710d363cede515e9c20c3af8f27df0d3fe6506125661816d42887ce30e094e90198f77e92e87ca9b39f16e50885f83c833739057f8acce5f4edb2a51dee1805b1101a5b6fec6020d0f53493c063ccf5f3ee4e20547be264224f120ba5a18943317b13b6cc2adeb1d7e9d2f1c2a6dcfb82cb1555f85c8c0076d82fbe99468ac9bed5b9490356ae524804c4f4fe069dc33237c1fc12f73df3bcb7574a887fa1256abd32a91daaa7f44aa148d2f79c80e2f2749f85d7036d25d90cbba1179669a46e76d243c23b035c4ac546bdd5be2cd927f304c8a34b0b5bc64ec902200d074ffb1947dda2cb0214fba21d81755918c4cf58b2f8c8c239ffb625d8644545999cc6047a9b1783e6f098b3f17a47871bee269c00e1059bf67649d7a3046fe4c231a89ae402ebe2ee29b30730be96ac8f2821b4f304bf0ed3590a387ab6a465fd3a7c90b7a9993248e25ebc6ef4426affd2a6be96d8cc4d965818223170a19c51cfd125b26536b5bffe362eecc4f936bd0913647a072d48e04b19a4b0bb50bbf067ebdda64691547b01e0c90d0193e9f669380e11f45ac4ccb4491cd089a54a5457ca961900d3c0b24722f41f53d055f384912c86a1e1207c44e626d148896fb73e15a569143aef67df7245780909790c10696e5cfdcb50e720bf16663ce8276db5ec2efc0744b9d0d11f38dc12f03367ab824083f771b24cf72e5d65955dd4dfd15e728b438ffc6b4ba5aff222b78a5e294ea595371c2b9c5015c8aa74aad40bc2b9ff54268e3f0e4e0b1ea4d39d6fb5cbff8041d0f992bfc213cceab3fc15a639ee9270aaaed2b4bbe3794f70f95ffa9d7497f162301b4769a891f2a7f15c7d7230365e7e866f58b27cb7526e235f79647dc46d0e430d9fdeef8dd1a837fa94dce9e0c352fdff62f7f70ea52d83921f704c2f83c5ebb23d09379851503c7d55230269ab121906f743d184091edcc943bc286fe4578ba3d87ba8c2ffb2faa04f2390ad80017768c6523e78b3c76da244c5a95662dfa52292c43e3181807b6f0527d5940958fbefe88e2d2100fa88351d41c6b5ee66e83ab6affb298a86dafc87be90547d02011e4529607f9d6748538ee330a89c4bc700c09ff13618ee669277b749bc071879398e939e0416cce9d47509d59fae29ab92849ea71921b21475178839613bedf9f667a3b1cc1a0f81c0d0b1370d1b68767a8a0096c8ae1416aa3b7c82fe4a4140869ca134c360faf276b84aaa9ff1d14703fbd7e444f45f18b176da08bb756420593a94928c7acccff19679d7efd9c985d2eb2db9b5f996c7cc07d5e1d45c0d90bad06d8c502e8672e35cd289ec5c10c45ba814b546f4812baf12ed501946ee49786433923b6dff1d985e4d0a137cf03e012055b1f9fcd9f3f64625d9a58a67b63c49d79c86b0cdef46ba8c7c73307df622ce758bb6cffda58a42b80505753c3542ee75b566df64518093c061cdc2e374e5ad9064b12599ea47e4cd7bb973cf4a2cb2f1e846515c978a442c717f178eeb3c25a5eccf026c273ec1e98c0bef2b8bd51656f70fd1d28ad2b88b457e26e714e97ea1fdf6ca11f62d19b784021001a288e0d37604f902b3ea2891a658e3292a97ba1735370e9f0e6787262f8f520f03619a26e5dfc38b28db33cb23dfe629c1b5369688acb302eba2900caaf83baf80e4f0cfd6837082089bd7300585701c615dfc18caefad514fddbd749daf8e86b9b1785d164c66b87765b2162f02f2cbbf9ab812cf908c6e3e062e861a618c43192d0f95b88057d6e14635eff5d97bec59f9148df8f5fb0fbc824cafe5139438b3a6fd1cd164e60b8eb3095c7fe388eb26001253e1c4c5eb31ebd21deb455a4db3bef458a3234667b66263c750a6d76ca87920266fc58f157ad2b7aa522a2810422dd54d358b88ba9ddc397b162e811e60ba0b9c84eaf2b3e77aa8d51b52bbaf78470ed070e1d798d54c615311d5f781246cb3d5ce90b23ff366787e04233b59ca0b2f6214baf54ef9b9787f0991284f2cb6ec2a9e5575babc451b89562c736716f3498ebd7aebc0ba3759adf69bab566e2afd867302fdc430111e2766a8720326459a5add87c8b812583fc263d7cee6a306b64f765cbf68ef52364a0f6499ce2b54b773dd81b998b85a7d0ad4f520770e81474efad8e0965e58a8cab134dcb14bc43743cd5eafc52a7a8efffff0a57bee1871728215d9dfa99eb12580485a178a44d0a7168bb7c0e34655410ceaca0cff84f5cecbc4e1a6872a33f05db63bf82913d3f173c20d2cb51072bf39f7398dfd296dd68633ecbd123b40ce3326c9d631118e3977deed84bc47d3f88199a0858fe962edceb42d54907e5fe61c60ceace423786e9687ccd6c0a189820b2b01b53afc646ed9d1134d0ba69486e8407efc8c3dc25f0923e578ca449d7891b16f52431d4638f05d55b1bab8412dd0aaf6b3ff5739f81687da4b75070e8c8a4d98f8ecbd834c6eb503a2873ea3d6bf61b44d8a88cde2f04da69718bc5026e8a67f1707692455966781c6ca733b8382b153fd6d962fac6095b2bc39a7952821eff65a2a7a385ef601c4c2c7eb59060c7377dfd1c99fb40b00c34accd00f43eeff88418af56461edda89a761fccefc84e021dcf9b4e51b97a4b985a9c3534a772a31ee5f46be32e9555fc20482088ef5261af113f7ae3b8e5b4642cf779e5e34e09d2edc130fd1c886301e02024a2fcf0c7b181c4d993c4c0978be9aea3cfd414e4f6c498e7e747ab01560f02c202dfd214071b14a12e9a60e392237bd611d02fa0e7cbbd826eb7abaf61dc66ad1ea62b16fd8b718d3c21fd2fd4e4c1947f5699de4c6b774e9f05a66feac004891ad000b9e959adfeb686d78393bf893383d2a8b3915f5ff4780053f03ced760daf5529d34aa53917aa2e31c9a5ed7ae39de62b424717b0daa67355a7fd2d52b748dadfe60eebb14bf7344bdc85265385d387ea428190c4749408f641bbf42d578061f0935dcaa3f7bfbadcabf5edac4b3a92d7a786890ee34b9b9dbefe4e43d11a609e3838acef9723a152f44f1964516a27fb0606bf2b64f1c89d5c8ecdfdb8a448b16c1b64ffdd7c1754d61a53731c28a7508059007dba007267dfd932c71c4547f22d0b877b0fea9e341ac39bf6e0c8abc9422194a1613371929d79ce09ccf502c865f9ebf2ee8ce0f12bb9d6e8cb1b1b274501dd69b2df075bae8e6ba60fa524a51406645917d9643342a9205d830d695ec5a9cf6fbc4f751e0c6a5f69a5e254d7fc840366ef4e55a4006ca60824a182ff25ec82aa597cbd24b11b6dcfed2d57a39d2f9d0a5035e25e1abcbb6a9c3df54897fa1e922736dc3b8e2a505c11e086e666789269ca93061aa4b8937dfa26b54249e22a19bf03e1c79bed204c21af026bf60d735b7c0be94c08b2da65ebcb15d79b08040460c95fb9e66c3806b22da486799840', wantSK: @@ -51,7 +51,7 @@ export const walletTestCases = [ '01000087a3b8db3576a190186bb9bba24cdccf3eaa6ecd62eb9cb03379514daaa6109d9d8ac730c41739feecffbfd9ceaf2684', wantMnemonic: 'absorb aback marsh deter superb frenzy pennon ache horror origin pest soak solve troop impose steppe tulle sleek crisis newark faulty primal bald suburb merry ivy severe jaw yolk soothe scan owing rabbit helium', - wantAddress: 'Qafae844fa3be904799ccdb74e6f8b55d92f350df', + wantAddress: 'Qafae844fa3be904799ccdb74e6f8b55d92f350df0b48605d1eaf4ffd63170d6c74a8db5f9f58309bec4cd18d500a8c68', wantPK: '12b3c82861ff56bdbc1936eaef4841111e9e3ef7670d53eb91562106593858dbde697ccf48f44ff0b2e0dc9ff38bde3ec5de6094dc8df882289d27cfa03b6c51d13ccaf47c7daf3c2312cd6beee1406309a66dfdbe724139369c740b0d80cf38d329feb03889bd4ef897fd52ab182df8dcda1dee38ee2d4f6f690f8762556c4d59fda34ec5a9344a316f76632f1eb7808915fc7021d5e6275daee4583ccd3bef91fce84a2fd1c0cd124652984a9ed24e494450c5078c5d6cdbbfab238ab0a1a319e541846cbf83dc2b6a8c470c695e07d00b908ace47fb322cfa168f3c7a3f970eddcaa6c6703a7ec4164cf26f640dc95ad9e1cadd7c20b9d79a4357e173415de0b4d5ef5d7647a8f4730fb81e0674a0b0c7a6e0da2ece02242503a1d714efd1052a93394be735e2cdd28278fa7d8d7aad597efdf05ce079c1aa9968e877a0326990f0504f89af8ec55754770ebf586ede2335744a1f183d344c10cb1aa89ec229d7d4ceed3051d170313e6e9cea115e5873bd506e3016f98bc6d08402186a2dd62a842685bc1490699342dd01ced49efc1c5c9f90e6944628ca4f409461f4acc8f26c30c6e6104c3fb75c527815ccda77f12f6a0a8c89eeb00dd154b108b528d50cb0b586aff11732ce20fdad03b78c1ed286e4794921e0a77a046c2c09f993738c7f67f5f2d83f43f7f3fb20c7c20bd1ce8cd05376dbeef72862b47443cc6f35aa053ad7243cc713f4562600245c8d5da607e754101d7c2caab8cef20a2ac932ff20dcc6d56b29b2a909fefe866569fc1073dc28bf637d192e8d0f69b2d482c5845293e0f4cb677fbb10837beded9356025ac110080cf11862963dc736bccf21bf3a3116524b3ee631a012e88f8a6652375d3d59a0af62a1566f9eeb52822e5eff7e9eff2ab1c9571711c968cc53266eb1f4af004a7094a0e137b69d55650970b9b4473ca4582d113ffb54e285101b4c061d232beccecef9b592347e8600bb57eb941b555af23c4aba77db1a9414604056b07dc990d76fbc9860fed011e69cae64e5af93ef81eddac76f96e65005448b4d8cff95fa761be2272da09bb4c808f83eebe2bab3267913b73f0077e8529648a0046b2662bd57a7fc121b81ecba68421fe961189b114bfdb230be4af5effe99ceb0542ff1f846b0ccf6adb91071d47d03ad7f2cdbb1defae77f82c3426b638cae2e02d4b15b5432172494d97e63102dbd5c1d991769ad1e68aaecd6ca70164f014a1a783b7dd95babb3265ddf17f5ce5765873387e037833990ea5b378717feeaadc12f5ee0ddb2d4f9cdea722242a88801898c482b16bccf7ae2a2fcbd7b092b233c0a5c5aa5f85f1d14a0aafe23ea7084132ef0f63b9eeffc488a797eb07e2b8e7a10794be9be6e47a119dece7c825d60fb597128e097324ec526a3c92b4e8ed4132a32bcab7f41a574846ce9d059a053106ba2d8a1bf0a7f042ddabad45738388741938ea6a72690466bae81a7854f6163a7713a2c137411f3d2145e00f0c09fabcf939a1a640a0e1483128bb73a4a9cab108785c451987a2c087fdfa764e3969e341485580c67ec614a4c21a7e40459314e70d079f25a6298be58118f8952fd088aa19876a61f711872a5e61cb7a5ca556066866fe172fb4bc2ea489ee628494305433ef89b81a0cd4502cd55976f3caff56da318ccef6e8225678aca9ff349e63f286d9fd18afb488fdbdb5f6abedbf0b1dd91d028dbd9823eb70f2fd2aad52eb292e7e6bb3a02e6b7076648197b8720421efa2acaa764e3da00fa8a046231ef4d39403939cec8435b2acf1c64190da678391babb78e315c07d88113bd4774dd320aa73b73e079543398ab1a36ab485120e89ed2aaa7789972b8c23ca549d5a0653f085700d18edee6fc332aaf334cd8eacfb9e65ccb0e6638105b9a74e429783f39ca7bad42e4db6443ad36195eeea333f3d93821f3436b20d641b3d00cbfa33841161b94a298cf5479e9e59a5243019178df117995a4a09efd6c749aedb2852d3521bba26e49a4acabe17bcde091f0d5a87001c20e167741667e24e2a9e344975b75c4406458c2096efc50ee0562942b05f2f517dcb17a366be0aa2fd22c308b0ae7f2e987e320780aa2e180075aa1e0de516301ee0f677ec12613a17575f4be98125b9d483358f011afea6aa6283378b8664661d8a74792de7f2aecdcd3a3404cf48aca08f5fe22fc64be70a4d7356a5630a10473bd8658fc5df56942d15f49e8829a21124e83c22384757e88577fe1e2e3d22f75a61d8f680073ddbf3aa2f2a629349853403baee3ce4654d8cac2c6f091f9b616f08032dc0d700e5957b12c1798a40980fe49d0a1ce26db38dc0df1f90afbe31784c24a30171f4a935bd0d186c4a1dc6bd3598ac0ee2b68af6c4274b0419346b76e1e3522848c2b362ae543eb5bca4ca4db18cf93984e85f1af35af9f68e36f8ae3ec0c4fe8ad905f7475e9ecf46a00c73f8d4493f15047e173bb12222b052f8b6977973759ce75c988cda836eeade49a174a0f0779ff7943490ba13ec3a1f46ec51cb064c988d2d0cc69f0639ff53a0ba02afd91bff79b013f3d113363debb245953c6ebaef3333083cfea8a65e1782f932124422942f691e5ac24e6cb7aa9726ade4e609220cdf291370dba5e912f17a0298e5fd23a9f058844eaa7e0b346c29c1fe462ddc185a8b61f14023af2cfc0fb56be043a8bf74b675c32d97ad34477d5fd77f91cb4203991f24d3e940787d813a79c5fb0bf71d00aae34455f51bf69e63dc60973b0d9519f7e88e85bc6ad40728cdf11e2244eb731d0a30a8400cf5ba30bb0810f3a5a95c923040d68a8138a788d8b757b7fb49e4cd317796651be4dd9fddbdd8a9e0d4e57c648ba5b2d85c65137a05d8c4e49a47afaf75b00dd9f3113bd385d4f224d758184af44a2e37bd78aa8be0f5a3bd98e612f4fe0d87b0ac2677ec8b31a88036041289452f69d8592859ff410eb44cc745ce42150a63a8f138eea7cf94c90c5cc435ee684d4d889d5eaf0d22cca8f0f08dfbc7e6e01a8beff5eef6da32f33b994695dda43310f932b8d40ab9d70c32bcad535e6e031360b50b434b8307c7796717fae28703a68bf1a7f1ba0fdaf34b077042e95b2e9d3f13d450ba77a54f175c384f6043c75e4ce5575ca0dbdc45a19dcb79072aedb4c7355a46cfc819d18fdd7435595ae6deab60bc5dbe95730240c22088069ba088c175e8ed8788f09aac5f037df7f3bdc74ab97ce8283fc066f9ec06dca4fcf7a74a8f5138aae6147f41b41d75b6da41f95ba32c433d87982a860ea4d6da08c2c88583b5376bd72f739d4efaebf787540ad1025075d8edb3de50fba2242bfa58c3dbff9d80e79ce26935cfb3c9bf3b5808290e79c844388ba189b04c88e94f785e63b649d2e2b021514470fcfda3a5e86f82f8e61c05808c4c29ce7a73cbc793b2f8361da9c28f32c185afbd43086c9bc51ad8e27d559ea5565cf14f2c13daf799ccb428603b701117deab7ff15ef38a05c3efb4bf6ddfbfc1c635999de4a102512a2c2d4f08bc852554afa18d5cdf731c7eb4cd943dd0eca6003f4447d3ff33ce8f650d9f37020ee1638dba9a0c69ca0463024cff6475410f01c4b2a2401ee39faad7f14268104ad6d624c9e274ded97f0c1b2d1221215b8f9', wantSK: @@ -66,7 +66,7 @@ export const walletTestCases = [ '0100004a73c8340b8933a6c496be614846ef3aec367b48b9ed1c97ffe6f4dc9454233b6032c7fcc4fc3138db0dbf307eda7382', wantMnemonic: 'absorb aback exile differ crown risky croft hover normal title eric income depth sequel layman mickey tying sirius zone indian swift eighty canary resume create lip shanks sense decree rash saturn amiss suitor debate', - wantAddress: 'Q09a4e13536ec5ac05a1080522898bae3210d473a', + wantAddress: 'Q09a4e13536ec5ac05a1080522898bae3210d473a0a9e9a900bdc98361d1a9e8c2cc0652344bd35b0590b537a527cc68f', wantPK: '8631211d67da488ce09902d556589479c01a828933b4cf0473b54cdc5c66b75d2d224564a80f53bc9a66a6013b158a47db37e9fe77903c231a97421d8ca97dcdc54c49dad5d990eb5689567155c3f217f9daa21d58c7f5c0f6281d027d113c7330c12dcde7d3238f265221cd039798a26370415a2bd309c3c0e5bb69922ac2b1fcd0348904787a52da2a8c9bb6662b5fafa69e7df50dff9a7f0dab0c254901a83a3483fd032e56bc98d925793e19698ac53769f0c31c0fb1947822444519756d5347d399a07bd928aeb327a52aaf31dfb0c35144d8c05d14784889429021f5e4509959ed7e25f2daf57d31722fece07045add4abea46c4c1416390ac78127d233b26d0e9253629022e5e29e6c343216493555dbc6e29dff3ff7eeb47e628a0f66a5e6fddb3adfdd52d9607a03576874e4e27fdcb973f662f354f33bb996fd81d1db9a923575e602941a71694e2e2acf12cba6238e0438741260fd0f9c752d47c01c1df326effcd78231a847dbf10390985e36b0feb0cf2def7d92a8307f61b86263501d21d14cd1649cd682ef19aa57a132335c6b450be436d4ab5452145d6105b1b1cb8a5cda1ca64126eafb7a534076ef1501d6844bfba7e08daeaa79824b9d773324ce2e6d5cebfdaaf2cbc615c4f26e8591cd243a61337621d875f01f91909f101cb2563cb313ffe0c264e4e8433051594141a43a405095d3b5cb63b060e22cff66dc96604191b006aba79bb0d65447173a8df8aec9b548ed2cdffe3b29644053f4edd7cf29f006de6ad32ae696ba7c86bffa5c91352ae193aa8855c1dc498efd7571e7a1eb0f90a1f217d6120a1bf4e8458dbd338efede72397cdbe02cea227b00a0b3660198837602c849071630ab87fb58d1c7733ad0a47684ef9c92e5fc6a1ef7beee0b8ea3ace37c61d28bc52fee68c5d4b4c867198750bc78edb3c5c63f4d39f7965d4252b75610e41e55e5a30432b61ccf8c5f3879b72dfe46aaa4c3d3cc85bb478e9a0fd5065571fc8e3c6f01552e6b33477ae555c06565f82c70b85a1354de803dd4228ccbc6b5f257b4da241b5974cb7439a230e3ac278151f2afad973e1cfdf3fa7dddc3194a2bafed9a2a8527707b43ef5834490e45920cb63d56790024225655daea8da7f628f52be5ff4d740bf206ad31ee6a5560e9620c17341143358b346f98e3a731f608577bf8607adf9a265202699e0dc75aaa459f0bbdb958c9a9a72e77662852308baa78db7283ec682f8c0c850e8264269e5a7a26190bc5dc6113abb05e4f0ca280ba44b7d044531c49286c10d4583b619e2bbdfaa433abc9f921b86fc43b6d20fe1c47b8094a1abd2fa45b2ce6733f24824ad029587c89fa4bafb048729c940c27447478e25df124d254dab05ff6745ac7363a08426477efe968db6aa28900075ba9d5acd8280a0c2714c05cd87a0c8f740ea50b40ee941c375c82e4f6f6957f98eaead017fa2e9fc60ac6a4168deed6bdd8552af3d8e615665ca30cc181a0c1dce8d44d47d34a11394c0e050671ba3d0306694e65b6c6fb60c756372e22a237076ed8f3a56d3ef6838c3bf41e659fd1b8487ad10955ffea9cccd9b5faadc1660c4186525cf1b64274b22777a24fb6010b284354858a6d41a00b127c4835cc3e9d6a639ff5d109b340ed6a67b3ec0dd54ae4a09d4020c62cf7e72741f4edcf61396a1bf4cd2ecf654d06213cc6bf0de7eea532ac4a9038327ec809d3ce2778971aeb01769b6c1a96e5cccd0d190e1bb1317790f7a8ab70370ebc18287d6bb8752f2e3b0fc6de1925fd0fb74e6bdfe140b67bb24fa972ebb916dd03f69865978f35583891d2f938b2e035f378db2fb785e77358eeabd25d952e22792fea0269666f5f470c1d1963400ff7517d8b833d9306f9015d01b3308c83cd9fa376e14a1312e804e131777b9bbb8483f612ca539f1c92529f2907d2be61041e7b74735f89e5c2e1a2c13f5fbc8c4a1b1ca0c190ea06df1a153868bc3e97aa6421228ef8f019bfc57bcc1cdb4d835f649fa1dc216d77e6bf49c130bf189e6c109b1a44d38e6996dee99f0c1e6dfc9e62363eaa8acb5574eb3cbc40bfb09b8789e3938719ff9e2b114c82a179e56f6031ca9e4f51fa3dd08bba5cc90c6d5e978623a10c2d8023ce2432e9d5ed0d4ebd588fd87fcfbb3325a33c21d0974b3b1cd8fe3d67f1631e5c2615ba8a37afc17fba07445adcc9231b574447e67e6047b52c077499faf3cea14b0fefae5917e9c32c5deca8b192b5c314b72df82bfadfe6ceb7028071a05a927c1bcdcd0a50cb6711bc1872d7bc42110165b4c12039997299bf419fc588725ce2abe0603e9a4b25805b2f73d6022f4c0c5f957901c3cd171008539710d7fcd8821950815f6e228f75eeb64207971b03c7bacd9398c1087f68a2add87836a8892094cb5fbdc25b6c465da9317a6187a81a9afe039c968074d1fd0838bd4ef7ebb1fcfb8d0d05353e24c2d1a6682c6ca8db73176d5c499b8743d1725b95399b1cd090a629912704b9162a3e0e92e862d75c91d1a0e553fe565a25d0757775fcf7d8496555d6a5da56171fa96be03a9b8cd2d068194095cf63e81d556501262947b52d97e5f7eeb6afd8fd413f20d229853c30662d0b581c631ac8d9cad323cf2acc53d4d175ab819065f2edb7546bfe2836162274d16b7d8007806a51d47c0308a80ef092148e703bff50316efd9b9d7eccb8bac0446d082994a5bf4d547ecd9fbf18c377d9bf349db4fdc89fd83b873c314ed0959c769c4ee4d150e2481dc82b150bb4741a9b9df405b60e4e02f9605b3669936251a10d8b2120ad063eb0b894d3271b38557f876bc307b84175557e4e63facffa00486a6ed85c53591dca74fb7b455efb4c1d177102f2fd8249813e3690f90a4b79edb67cbe7ab22a57e47f25db7118400fed847fd8390564f1b032813deae5a7f22bbd99bc711cf497d306cb021522b947741f2398e183a11b120ead960f0709d52037717ecc4431a84849c72f17a765a16bc063aba996efef215c819a43edc3a41e9928d3ea7062ac36917ed2492024cd0c1831bbe3a75d219657a12f15851b10439ab1c67c685b39614b56ff010d4c6b995995053e53cb39e3d85aa53c150d9fea93d70f20b8a45e9466386900b652c68f7cbdbbce92c08c66b425ab1f465adf4655bf6d52d78b498775fdc6af44eae0f8a166ddfca6e23f1318ab9bc35871df2588133ef25afd32763ea8a71581d01c20fdfe63213fb85864a8ac524e73d5ba55e83b0d34ae687087c46554e840256b094b290dfcb1dd70f53fe694cdf30d330d2ce75a52d42d2d9de68a1e9a17a6d0e5ea9e2d908a14a0500443f618c7258be487f4bc541dce218f0c27de9b63544e992a7443c5146cd13e48c4843bb73e3577ec6d6787d082fd2fe1371c206424bb07a0b33bed4fdcb00c64ccc1fb9bee9e81b16f90a77ddb732b9b6e530e02c442eec57c248f108e71afc42838e897ad48b042fda629b6d04b752fafd3cd36f6d890126432b3a171f5412c1edade220c43676701a2b1cdef0bd3c19d7aa411e1d4547269d59e938d937407a1e8092ad10f23bc4f5e1d44449fb774ddd40167837f1c8639f32b4e2d99411f3742c7093fa879c8e954135e41b6b3b88402d6ee942c5b76d08bec97d195c398f88', wantSK: diff --git a/test/unit/address.mocha.js b/test/unit/address.mocha.js index 93a54bf..fb7e920 100644 --- a/test/unit/address.mocha.js +++ b/test/unit/address.mocha.js @@ -17,7 +17,7 @@ describe('wallet/common/address', () => { }); it('addressToString throws on wrong length', () => { - expect(() => addressToString(Uint8Array.from([1, 2]))).to.throw('address must be 20 bytes'); + expect(() => addressToString(Uint8Array.from([1, 2]))).to.throw('address must be 48 bytes'); }); it('getAddressFromPKAndDescriptor rejects wrong pk length for ML-DSA-87', () => { diff --git a/test/unit/cross-implementation.mocha.js b/test/unit/cross-implementation.mocha.js index ef0b3df..887c4fd 100644 --- a/test/unit/cross-implementation.mocha.js +++ b/test/unit/cross-implementation.mocha.js @@ -26,7 +26,7 @@ describe('Cross-Implementation Verification', () => { describe('Address Derivation Algorithm', () => { /** * Address derivation in wallet.js: - * address = SHAKE256(descriptor || public_key, 20 bytes) + * address = SHAKE256(descriptor || public_key, 48 bytes) * * This must match go-qrllib's implementation. */ diff --git a/test/unit/dist-bundle.mocha.js b/test/unit/dist-bundle.mocha.js index 66278dd..cb92065 100644 --- a/test/unit/dist-bundle.mocha.js +++ b/test/unit/dist-bundle.mocha.js @@ -34,7 +34,7 @@ describe('dist bundle smoke tests', () => { const w = MLDSA87.newWallet(); console.log(w.getAddressStr()); `); - expect(stdout.trim()).to.match(/^Q[0-9a-f]{40}$/); + expect(stdout.trim()).to.match(/^Q[0-9a-f]{96}$/); }); it('sign and verify round-trip', async () => { @@ -71,7 +71,7 @@ describe('dist bundle smoke tests', () => { `, { cjs: true } ); - expect(stdout.trim()).to.match(/^Q[0-9a-f]{40}$/); + expect(stdout.trim()).to.match(/^Q[0-9a-f]{96}$/); }); it('sign and verify round-trip', async () => { diff --git a/test/unit/edge-cases.mocha.js b/test/unit/edge-cases.mocha.js index 5b3f5ed..a1fbac9 100644 --- a/test/unit/edge-cases.mocha.js +++ b/test/unit/edge-cases.mocha.js @@ -194,20 +194,20 @@ describe('Edge Cases', () => { describe('Address Validation Edge Cases', () => { it('stringToAddress accepts lowercase q prefix', () => { - const addr = 'q0000000000000000000000000000000000000000'; + const addr = 'q' + '0'.repeat(96); const bytes = stringToAddress(addr); expect(bytes).to.be.instanceOf(Uint8Array); - expect(bytes.length).to.equal(20); + expect(bytes.length).to.equal(48); }); it('stringToAddress accepts uppercase Q prefix', () => { - const addr = 'Q0000000000000000000000000000000000000000'; + const addr = 'Q' + '0'.repeat(96); const bytes = stringToAddress(addr); expect(bytes).to.be.instanceOf(Uint8Array); }); it('stringToAddress trims whitespace', () => { - const addr = ' Q0000000000000000000000000000000000000000 '; + const addr = ' Q' + '0'.repeat(96) + ' '; const bytes = stringToAddress(addr); expect(bytes).to.be.instanceOf(Uint8Array); }); @@ -219,47 +219,41 @@ describe('Edge Cases', () => { }); it('stringToAddress rejects missing Q prefix', () => { - expect(() => stringToAddress('0000000000000000000000000000000000000000')).to.throw('address must start with Q'); + expect(() => stringToAddress('0'.repeat(96))).to.throw('address must start with Q'); }); it('stringToAddress rejects wrong length', () => { - expect(() => stringToAddress('Q000000')).to.throw('address must be Q + 40 hex characters'); - expect(() => stringToAddress('Q00000000000000000000000000000000000000000000')).to.throw( - 'address must be Q + 40 hex characters' - ); + expect(() => stringToAddress('Q000000')).to.throw('address must be Q + 96 hex characters'); + expect(() => stringToAddress('Q' + '0'.repeat(97))).to.throw('address must be Q + 96 hex characters'); }); it('stringToAddress rejects invalid hex characters', () => { - expect(() => stringToAddress('Qzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz')).to.throw( - 'address contains invalid characters' - ); - expect(() => stringToAddress('Q000000000000000000000000000000000000000g')).to.throw( - 'address contains invalid characters' - ); + expect(() => stringToAddress('Q' + 'z'.repeat(96))).to.throw('address contains invalid characters'); + expect(() => stringToAddress('Q' + '0'.repeat(95) + 'g')).to.throw('address contains invalid characters'); }); it('isValidAddress returns true for valid address', () => { - expect(isValidAddress('Q0000000000000000000000000000000000000000')).to.equal(true); - expect(isValidAddress('Qffffffffffffffffffffffffffffffffffffffff')).to.equal(true); + expect(isValidAddress('Q' + '0'.repeat(96))).to.equal(true); + expect(isValidAddress('Q' + 'f'.repeat(96))).to.equal(true); }); it('isValidAddress returns false for invalid address', () => { expect(isValidAddress('')).to.equal(false); expect(isValidAddress('Q')).to.equal(false); expect(isValidAddress('Q000')).to.equal(false); - expect(isValidAddress('0000000000000000000000000000000000000000')).to.equal(false); + expect(isValidAddress('0'.repeat(96))).to.equal(false); expect(isValidAddress(null)).to.equal(false); expect(isValidAddress(123)).to.equal(false); }); it('addressToString handles all-zero address', () => { - const zeros = new Uint8Array(20).fill(0); - expect(addressToString(zeros)).to.equal('Q0000000000000000000000000000000000000000'); + const zeros = new Uint8Array(48).fill(0); + expect(addressToString(zeros)).to.equal('Q' + '0'.repeat(96)); }); it('addressToString handles all-ff address', () => { - const ffs = new Uint8Array(20).fill(0xff); - expect(addressToString(ffs)).to.equal('Qffffffffffffffffffffffffffffffffffffffff'); + const ffs = new Uint8Array(48).fill(0xff); + expect(addressToString(ffs)).to.equal('Q' + 'f'.repeat(96)); }); it('roundtrip: addressToString -> stringToAddress', () => { @@ -291,31 +285,76 @@ describe('Edge Cases', () => { }); describe('Wallet Zeroization', () => { - it('zeroize() clears secret key', () => { + it('zeroize() nulls secret key reference', () => { const w = MLDSA87.newWallet(); const skBefore = new Uint8Array(w.getSK()); - expect(skBefore.some((b) => b !== 0)).to.equal(true); // SK should have non-zero bytes + expect(skBefore.some((b) => b !== 0)).to.equal(true); w.zeroize(); + expect(w.sk).to.equal(null); + }); - const skAfter = w.getSK(); - expect(skAfter.every((b) => b === 0)).to.equal(true); // SK should be all zeros + it('zeroize() nulls seed reference', () => { + const w = MLDSA87.newWallet(); + w.zeroize(); + expect(w.seed).to.equal(null); }); - it('zeroize() clears seed bytes', () => { + it('zeroize() nulls extendedSeed reference', () => { const w = MLDSA87.newWallet(); w.zeroize(); + expect(w.extendedSeed).to.equal(null); + }); - const seedBytes = w.getSeed().toBytes(); - expect(seedBytes.every((b) => b === 0)).to.equal(true); + it('zeroize() prevents sign()', () => { + const w = MLDSA87.newWallet(); + w.zeroize(); + expect(() => w.sign(new Uint8Array([1, 2, 3]))).to.throw('Wallet has been zeroized'); }); - it('zeroize() clears extended seed bytes', () => { + it('zeroize() prevents getSK()', () => { const w = MLDSA87.newWallet(); w.zeroize(); + expect(() => w.getSK()).to.throw('Wallet has been zeroized'); + }); + + it('zeroize() prevents getSeed()', () => { + const w = MLDSA87.newWallet(); + w.zeroize(); + expect(() => w.getSeed()).to.throw('Wallet has been zeroized'); + }); + + it('zeroize() prevents getExtendedSeed()', () => { + const w = MLDSA87.newWallet(); + w.zeroize(); + expect(() => w.getExtendedSeed()).to.throw('Wallet has been zeroized'); + }); - const extBytes = w.getExtendedSeed().toBytes(); - expect(extBytes.every((b) => b === 0)).to.equal(true); + it('zeroize() prevents getMnemonic()', () => { + const w = MLDSA87.newWallet(); + w.zeroize(); + expect(() => w.getMnemonic()).to.throw('Wallet has been zeroized'); + }); + + it('zeroize() prevents getHexExtendedSeed()', () => { + const w = MLDSA87.newWallet(); + w.zeroize(); + expect(() => w.getHexExtendedSeed()).to.throw('Wallet has been zeroized'); + }); + + it('zeroize() still allows getPK() and getAddress()', () => { + const w = MLDSA87.newWallet(); + const pkBefore = w.getPK(); + const addrBefore = w.getAddressStr(); + w.zeroize(); + expect(w.getPK()).to.deep.equal(pkBefore); + expect(w.getAddressStr()).to.equal(addrBefore); + }); + + it('zeroize() is idempotent', () => { + const w = MLDSA87.newWallet(); + w.zeroize(); + expect(() => w.zeroize()).to.not.throw(); }); }); diff --git a/test/unit/fuzz.mocha.js b/test/unit/fuzz.mocha.js index 204d9bd..54eb260 100644 --- a/test/unit/fuzz.mocha.js +++ b/test/unit/fuzz.mocha.js @@ -82,7 +82,7 @@ describe('Fuzz Tests (Property-Based)', function propertyBasedTests() { describe('Address Properties', () => { it('addressToString -> stringToAddress roundtrip preserves bytes', () => { fc.assert( - fc.property(fc.uint8Array({ minLength: 20, maxLength: 20 }), (addrBytes) => { + fc.property(fc.uint8Array({ minLength: 48, maxLength: 48 }), (addrBytes) => { const str = addressToString(addrBytes); const recovered = stringToAddress(str); return recovered.every((b, i) => b === addrBytes[i]); @@ -93,7 +93,7 @@ describe('Fuzz Tests (Property-Based)', function propertyBasedTests() { it('valid address strings are always recognized as valid', () => { fc.assert( - fc.property(fc.uint8Array({ minLength: 20, maxLength: 20 }), (addrBytes) => { + fc.property(fc.uint8Array({ minLength: 48, maxLength: 48 }), (addrBytes) => { const str = addressToString(addrBytes); return isValidAddress(str) === true; }), @@ -119,7 +119,7 @@ describe('Fuzz Tests (Property-Based)', function propertyBasedTests() { fc .array(fc.integer({ min: 0, max: 15 }), { minLength: 1, maxLength: 100 }) .map((arr) => arr.map((n) => n.toString(16)).join('')) - .filter((s) => s.length !== 40), + .filter((s) => s.length !== 96), (hex) => { try { stringToAddress(`Q${hex}`); diff --git a/test/unit/utils.bytes.mocha.js b/test/unit/utils.bytes.mocha.js index 51fe881..9dc3c13 100644 --- a/test/unit/utils.bytes.mocha.js +++ b/test/unit/utils.bytes.mocha.js @@ -29,6 +29,15 @@ describe('utils/bytes', () => { expect(isHexLike(1234)).to.equal(false); expect(isHexLike(null)).to.equal(false); }); + + it('rejects strings with no hex digits', () => { + expect(isHexLike('')).to.equal(false); + expect(isHexLike('0x')).to.equal(false); + expect(isHexLike(' ')).to.equal(false); + expect(isHexLike('---')).to.equal(false); + expect(isHexLike(':::')).to.equal(false); + expect(isHexLike(':_: ')).to.equal(false); + }); }); describe('cleanHex', () => { diff --git a/types/index.d.ts b/types/index.d.ts index 18bb059..607c8e2 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,13 +1,16 @@ -import { Seed } from "./wallet/common/seed.js"; -import { SEED_SIZE } from "./wallet/common/constants.js"; -import { ExtendedSeed } from "./wallet/common/seed.js"; -import { EXTENDED_SEED_SIZE } from "./wallet/common/constants.js"; -import { Descriptor } from "./wallet/common/descriptor.js"; -import { DESCRIPTOR_SIZE } from "./wallet/common/constants.js"; -import { newMLDSA87Descriptor } from "./wallet/ml_dsa_87/descriptor.js"; -import { getAddressFromPKAndDescriptor } from "./wallet/common/address.js"; -import { WalletType } from "./wallet/common/wallettype.js"; -import { newWalletFromExtendedSeed } from "./wallet/factory.js"; -import { Wallet as MLDSA87 } from "./wallet/ml_dsa_87/wallet.js"; -export { Seed, SEED_SIZE, ExtendedSeed, EXTENDED_SEED_SIZE, Descriptor, DESCRIPTOR_SIZE, newMLDSA87Descriptor, getAddressFromPKAndDescriptor, WalletType, newWalletFromExtendedSeed, MLDSA87 }; +import { Seed } from './wallet/common/seed.js'; +import { SEED_SIZE } from './wallet/common/constants.js'; +import { ExtendedSeed } from './wallet/common/seed.js'; +import { EXTENDED_SEED_SIZE } from './wallet/common/constants.js'; +import { Descriptor } from './wallet/common/descriptor.js'; +import { DESCRIPTOR_SIZE } from './wallet/common/constants.js'; +import { newMLDSA87Descriptor } from './wallet/ml_dsa_87/descriptor.js'; +import { getAddressFromPKAndDescriptor } from './wallet/common/address.js'; +import { addressToString } from './wallet/common/address.js'; +import { stringToAddress } from './wallet/common/address.js'; +import { isValidAddress } from './wallet/common/address.js'; +import { WalletType } from './wallet/common/wallettype.js'; +import { newWalletFromExtendedSeed } from './wallet/factory.js'; +import { Wallet as MLDSA87 } from './wallet/ml_dsa_87/wallet.js'; +export { Seed, SEED_SIZE, ExtendedSeed, EXTENDED_SEED_SIZE, Descriptor, DESCRIPTOR_SIZE, newMLDSA87Descriptor, getAddressFromPKAndDescriptor, addressToString, stringToAddress, isValidAddress, WalletType, newWalletFromExtendedSeed, MLDSA87 }; //# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/types/index.d.ts.map b/types/index.d.ts.map index 6e08bd5..0931bb5 100644 --- a/types/index.d.ts.map +++ b/types/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":""} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"qBAYmC,yBAAyB;0BAPG,8BAA8B;6BAO1D,yBAAyB;mCAPG,8BAA8B;2BASlE,+BAA+B;gCATK,8BAA8B;qCAQxD,kCAAkC;8CAFhE,4BAA4B;gCAA5B,4BAA4B;gCAA5B,4BAA4B;+BAA5B,4BAA4B;2BAMR,+BAA+B;0CAFhB,qBAAqB;kCAC7B,8BAA8B"} \ No newline at end of file diff --git a/types/utils/random.d.ts b/types/utils/random.d.ts new file mode 100644 index 0000000..5f8cf54 --- /dev/null +++ b/types/utils/random.d.ts @@ -0,0 +1,13 @@ +/** + * Generate cryptographically secure random bytes. + * + * Uses Web Crypto API (getRandomValues) exclusively. + * Throws if Web Crypto API is unavailable. + * + * @param {number} size - Number of random bytes to generate + * @returns {Uint8Array} Random bytes + * @throws {RangeError} If size is invalid or too large + * @throws {Error} If no secure random source is available or RNG output is suspect + */ +export function randomBytes(size: number): Uint8Array; +//# sourceMappingURL=random.d.ts.map \ No newline at end of file diff --git a/types/utils/random.d.ts.map b/types/utils/random.d.ts.map new file mode 100644 index 0000000..99f810c --- /dev/null +++ b/types/utils/random.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"random.d.ts","sourceRoot":"","sources":["../../src/utils/random.js"],"names":[],"mappings":"AAcA;;;;;;;;;;GAUG;AACH,kCALW,MAAM,GACJ,UAAU,CA4BtB"} \ No newline at end of file diff --git a/types/wallet/common/address.d.ts b/types/wallet/common/address.d.ts index f3a5e32..f2791f5 100644 --- a/types/wallet/common/address.d.ts +++ b/types/wallet/common/address.d.ts @@ -1,4 +1,4 @@ -export type Descriptor = any; +export type Descriptor = import("./descriptor.js").Descriptor; /** * Convert address bytes to string form. * @param {Uint8Array} addrBytes @@ -6,12 +6,27 @@ export type Descriptor = any; * @throws {Error} If length mismatch. */ export function addressToString(addrBytes: Uint8Array): string; +/** + * Convert address string to bytes. + * @param {string} addrStr - Address string starting with 'Q' followed by 96 hex characters. + * @returns {Uint8Array} 48-byte address. + * @throws {Error} If address format is invalid. + */ +export function stringToAddress(addrStr: string): Uint8Array; +/** + * Check if a string is a valid QRL address format (structure only). + * QRL addresses contain no checksum — any well-formed Q + 96 hex string passes. + * Applications should add their own confirmation or checksum layer. + * @param {string} addrStr - Address string to validate. + * @returns {boolean} True if valid address format. + */ +export function isValidAddress(addrStr: string): boolean; /** * Derive an address from a public key and descriptor. * @param {Uint8Array} pk * @param {Descriptor} descriptor - * @returns {Uint8Array} 20-byte address. + * @returns {Uint8Array} 48-byte address. * @throws {Error} If pk length mismatch. */ -export function getAddressFromPKAndDescriptor(pk: Uint8Array, descriptor: any): Uint8Array; +export function getAddressFromPKAndDescriptor(pk: Uint8Array, descriptor: Descriptor): Uint8Array; //# sourceMappingURL=address.d.ts.map \ No newline at end of file diff --git a/types/wallet/common/address.d.ts.map b/types/wallet/common/address.d.ts.map index 4b4ad7a..720de12 100644 --- a/types/wallet/common/address.d.ts.map +++ b/types/wallet/common/address.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"address.d.ts","sourceRoot":"","sources":["../../../src/wallet/common/address.js"],"names":[],"mappings":";AAUA;;;;;GAKG;AACH,2CAJW,UAAU,GACR,MAAM,CASlB;AAED;;;;;;GAMG;AACH,kDALW,UAAU,oBAER,UAAU,CAsBtB"} \ No newline at end of file +{"version":3,"file":"address.d.ts","sourceRoot":"","sources":["../../../src/wallet/common/address.js"],"names":[],"mappings":"yBAYc,OAAO,iBAAiB,EAAE,UAAU;AAKlD;;;;;GAKG;AACH,2CAJW,UAAU,GACR,MAAM,CASlB;AAED;;;;;GAKG;AACH,yCAJW,MAAM,GACJ,UAAU,CAuBtB;AAED;;;;;;GAMG;AACH,wCAHW,MAAM,GACJ,OAAO,CASnB;AAED;;;;;;GAMG;AACH,kDALW,UAAU,cACV,UAAU,GACR,UAAU,CAqBtB"} \ No newline at end of file diff --git a/types/wallet/common/constants.d.ts.map b/types/wallet/common/constants.d.ts.map index 425462e..46e3df4 100644 --- a/types/wallet/common/constants.d.ts.map +++ b/types/wallet/common/constants.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/wallet/common/constants.js"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,4DAA4D;AAC5D,8BADW,MAAM,CACS;AAE1B,6CAA6C;AAC7C,2BADW,MAAM,CACO;AAExB,0CAA0C;AAC1C,wBADW,MAAM,CACI;AAErB,mDAAmD;AACnD,iCADW,MAAM,CACsC"} \ No newline at end of file +{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/wallet/common/constants.js"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,4DAA4D;AAC5D,8BADW,MAAM,CACgB;AAEjC,6CAA6C;AAC7C,2BADW,MAAM,CACc;AAE/B,0CAA0C;AAC1C,wBADW,MAAM,CACW;AAE5B,mDAAmD;AACnD,iCADW,MAAM,CAC6C"} \ No newline at end of file diff --git a/types/wallet/common/descriptor.d.ts.map b/types/wallet/common/descriptor.d.ts.map index 48807be..2328aba 100644 --- a/types/wallet/common/descriptor.d.ts.map +++ b/types/wallet/common/descriptor.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"descriptor.d.ts","sourceRoot":"","sources":["../../../src/wallet/common/descriptor.js"],"names":[],"mappings":"AAWA;IA+BE;;;;OAIG;IACH,mBAHW,MAAM,GAAC,UAAU,GAAC,MAAM,GAAC,MAAM,EAAE,GAC/B,UAAU,CAItB;IArCD;;;OAGG;IACH,mBAHW,UAAU,GAAC,MAAM,EAAE,EAY7B;IALC,kCAAkC;IAClC,cAAmC;IAMrC;;OAEG;IACH,QAFa,MAAM,CAIlB;IAED;;;OAGG;IACH,WAFa,UAAU,CAItB;CAUF;AAED;;;;;GAKG;AACH,+CAJW,MAAM,aACN,CAAC,MAAM,EAAE,MAAM,CAAC,GACd,UAAU,CAWtB"} \ No newline at end of file +{"version":3,"file":"descriptor.d.ts","sourceRoot":"","sources":["../../../src/wallet/common/descriptor.js"],"names":[],"mappings":"AAWA;IA+BE;;;;OAIG;IACH,mBAHW,MAAM,GAAC,UAAU,GAAC,MAAM,GAAC,MAAM,EAAE,GAC/B,UAAU,CAItB;IArCD;;;OAGG;IACH,mBAHW,UAAU,GAAC,MAAM,EAAE,EAY7B;IALC,kCAAkC;IAClC,cAAmC;IAMrC;;OAEG;IACH,QAFa,MAAM,CAIlB;IAED;;;OAGG;IACH,WAFa,UAAU,CAItB;CAUF;AAED;;;;;GAKG;AACH,+CAJW,MAAM,aACN,CAAC,MAAM,EAAE,MAAM,CAAC,GACd,UAAU,CAgBtB"} \ No newline at end of file diff --git a/types/wallet/common/seed.d.ts b/types/wallet/common/seed.d.ts index e9bce53..9fe3dee 100644 --- a/types/wallet/common/seed.d.ts +++ b/types/wallet/common/seed.d.ts @@ -10,7 +10,7 @@ export class Seed { * @throws {Error} If size mismatch. */ constructor(bytes: Uint8Array); - bytes: Uint8Array; + bytes: Uint8Array; /** @returns {Uint8Array} */ hashSHA256(): Uint8Array; /** @@ -18,6 +18,10 @@ export class Seed { * @returns {Uint8Array} */ toBytes(): Uint8Array; + /** + * Best-effort zeroize internal seed bytes. + */ + zeroize(): void; } export class ExtendedSeed { /** @@ -36,7 +40,7 @@ export class ExtendedSeed { /** * Layout: [3 bytes descriptor] || [48 bytes seed]. * @param {Uint8Array} bytes Exactly 51 bytes. - * @throws {Error} If size mismatch. + * @throws {Error} If size mismatch or invalid wallet type. */ constructor(bytes: Uint8Array); /** @private @type {Uint8Array} */ @@ -62,6 +66,10 @@ export class ExtendedSeed { * @returns {Uint8Array} */ toBytes(): Uint8Array; + /** + * Best-effort zeroize internal extended seed bytes. + */ + zeroize(): void; } -import { Descriptor } from "./descriptor.js"; +import { Descriptor } from './descriptor.js'; //# sourceMappingURL=seed.d.ts.map \ No newline at end of file diff --git a/types/wallet/common/seed.d.ts.map b/types/wallet/common/seed.d.ts.map index 782940c..05f3cba 100644 --- a/types/wallet/common/seed.d.ts.map +++ b/types/wallet/common/seed.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"seed.d.ts","sourceRoot":"","sources":["../../../src/wallet/common/seed.js"],"names":[],"mappings":"AAWA;IAyBE;;;;OAIG;IACH,mBAHW,MAAM,GAAC,UAAU,GAAC,MAAM,GAAC,MAAM,EAAE,GAC/B,IAAI,CAIhB;IA/BD;;;OAGG;IACH,mBAHW,UAAU,EAQpB;IADC,kBAAmC;IAGrC,4BAA4B;IAC5B,cADc,UAAU,CAGvB;IAED;;;OAGG;IACH,WAFa,UAAU,CAItB;CAUF;AAED;IAqDE;;;;;OAKG;IACH,6BAJW,UAAU,QACV,IAAI,GACF,YAAY,CAOxB;IAED;;;;OAIG;IACH,mBAHW,MAAM,GAAC,UAAU,GAAC,MAAM,GAAC,MAAM,EAAE,GAC/B,YAAY,CAIxB;IAxED;;;;OAIG;IACH,mBAHW,UAAU,EAYpB;IALC,kCAAkC;IAClC,cAAmC;IAMrC;;OAEG;IACH,iBAFa,UAAU,CAItB;IAED;;OAEG;IACH,sBAFa,UAAU,CAItB;IAED;;OAEG;IACH,gBAFa,UAAU,CAItB;IAED;;OAEG;IACH,WAFa,IAAI,CAIhB;IAED;;;OAGG;IACH,WAFa,UAAU,CAItB;CAuBF"} \ No newline at end of file +{"version":3,"file":"seed.d.ts","sourceRoot":"","sources":["../../../src/wallet/common/seed.js"],"names":[],"mappings":"AAWA;IAgCE;;;;OAIG;IACH,mBAHW,MAAM,GAAC,UAAU,GAAC,MAAM,GAAC,MAAM,EAAE,GAC/B,IAAI,CAIhB;IAtCD;;;OAGG;IACH,mBAHW,UAAU,EAQpB;IADC,+BAAmC;IAGrC,4BAA4B;IAC5B,cADc,UAAU,CAGvB;IAED;;;OAGG;IACH,WAFa,UAAU,CAItB;IAED;;OAEG;IACH,gBAEC;CAUF;AAED;IAqDE;;;;;OAKG;IACH,6BAJW,UAAU,QACV,IAAI,GACF,YAAY,CAWxB;IAED;;;;OAIG;IACH,mBAHW,MAAM,GAAC,UAAU,GAAC,MAAM,GAAC,MAAM,EAAE,GAC/B,YAAY,CAIxB;IA5ED;;;;OAIG;IACH,mBAHW,UAAU,EAYpB;IALC,kCAAkC;IAClC,cAAmC;IAMrC;;OAEG;IACH,iBAFa,UAAU,CAItB;IAED;;OAEG;IACH,sBAFa,UAAU,CAItB;IAED;;OAEG;IACH,gBAFa,UAAU,CAItB;IAED;;OAEG;IACH,WAFa,IAAI,CAIhB;IAED;;;OAGG;IACH,WAFa,UAAU,CAItB;IA4BD;;OAEG;IACH,gBAEC;CACF;2BAlI0B,iBAAiB"} \ No newline at end of file diff --git a/types/wallet/common/wallettype.d.ts b/types/wallet/common/wallettype.d.ts index b1318a4..471a07b 100644 --- a/types/wallet/common/wallettype.d.ts +++ b/types/wallet/common/wallettype.d.ts @@ -1,3 +1,8 @@ +/** + * @param {number} t + * @return {boolean} + */ +export function isValidWalletType(t: number): boolean; export type WalletType = number; /** * Wallet type enumeration. @@ -11,9 +16,4 @@ export const WalletType: Readonly<{ SPHINCSPLUS_256S: 0; ML_DSA_87: 1; }>; -/** - * @param {number} t - * @return {boolean} - */ -export function isValidWalletType(t: number): boolean; //# sourceMappingURL=wallettype.d.ts.map \ No newline at end of file diff --git a/types/wallet/common/wallettype.d.ts.map b/types/wallet/common/wallettype.d.ts.map index 8e4a401..df2f440 100644 --- a/types/wallet/common/wallettype.d.ts.map +++ b/types/wallet/common/wallettype.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"wallettype.d.ts","sourceRoot":"","sources":["../../../src/wallet/common/wallettype.js"],"names":[],"mappings":"yBAOU,MAAM;AAPhB;;;GAGG;AAEH;;;GAGG;AACH;;;GAGG;AAEH;;;GAGG;AACH,qCAHW,MAAM,GACL,OAAO,CAIlB"} \ No newline at end of file +{"version":3,"file":"wallettype.d.ts","sourceRoot":"","sources":["../../../src/wallet/common/wallettype.js"],"names":[],"mappings":"AAcA;;;GAGG;AACH,qCAHW,MAAM,GACL,OAAO,CAIlB;yBAbS,MAAM;AAPhB;;;GAGG;AAEH;;;GAGG;AACH;;;GAGG"} \ No newline at end of file diff --git a/types/wallet/factory.d.ts b/types/wallet/factory.d.ts index 770b7d9..3399ce6 100644 --- a/types/wallet/factory.d.ts +++ b/types/wallet/factory.d.ts @@ -2,8 +2,10 @@ * Construct a wallet from an ExtendedSeed by auto-selecting the correct implementation. * * @param {ExtendedSeed|Uint8Array|string} extendedSeed - ExtendedSeed instance, 51 bytes or hex string. - * @returns {any} Wallet instance (only ML-DSA-87 for now) + * @returns {MLDSA87} Wallet instance + * @throws {Error} If wallet type is unsupported */ -export function newWalletFromExtendedSeed(extendedSeed: ExtendedSeed | Uint8Array | string): any; -import { ExtendedSeed } from "./common/seed.js"; +export function newWalletFromExtendedSeed(extendedSeed: ExtendedSeed | Uint8Array | string): MLDSA87; +import { ExtendedSeed } from './common/seed.js'; +import { Wallet as MLDSA87 } from './ml_dsa_87/wallet.js'; //# sourceMappingURL=factory.d.ts.map \ No newline at end of file diff --git a/types/wallet/factory.d.ts.map b/types/wallet/factory.d.ts.map index 63af26f..cc8ed6f 100644 --- a/types/wallet/factory.d.ts.map +++ b/types/wallet/factory.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/wallet/factory.js"],"names":[],"mappings":"AASA;;;;;GAKG;AACH,wDAHW,YAAY,GAAC,UAAU,GAAC,MAAM,GAC5B,GAAG,CAiBf"} \ No newline at end of file +{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/wallet/factory.js"],"names":[],"mappings":"AAUA;;;;;;GAMG;AACH,wDAJW,YAAY,GAAC,UAAU,GAAC,MAAM,GAC5B,OAAO,CAuBnB;6BA/B4B,kBAAkB;kCAEb,uBAAuB"} \ No newline at end of file diff --git a/types/wallet/misc/mnemonic.d.ts b/types/wallet/misc/mnemonic.d.ts index ac737b6..b6b3100 100644 --- a/types/wallet/misc/mnemonic.d.ts +++ b/types/wallet/misc/mnemonic.d.ts @@ -2,6 +2,9 @@ * Decode spaced hex mnemonic to bytes. * @param {string} mnemonic * @returns {Uint8Array} + * + * Note: Mnemonic words are normalized to lowercase for user convenience. + * This is by design to reduce errors from capitalization differences. */ export function mnemonicToBin(mnemonic: string): Uint8Array; /** diff --git a/types/wallet/misc/mnemonic.d.ts.map b/types/wallet/misc/mnemonic.d.ts.map index a1d79fc..92a261a 100644 --- a/types/wallet/misc/mnemonic.d.ts.map +++ b/types/wallet/misc/mnemonic.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"mnemonic.d.ts","sourceRoot":"","sources":["../../../src/wallet/misc/mnemonic.js"],"names":[],"mappings":"AAsCA;;;;GAIG;AACH,wCAHW,MAAM,GACJ,UAAU,CAiCtB;AA9DD;;;;GAIG;AACH,qCAHW,UAAU,GACR,MAAM,CAqBlB"} \ No newline at end of file +{"version":3,"file":"mnemonic.d.ts","sourceRoot":"","sources":["../../../src/wallet/misc/mnemonic.js"],"names":[],"mappings":"AAoCA;;;;;;;GAOG;AACH,wCANW,MAAM,GACJ,UAAU,CAqCtB;AAhED;;;;GAIG;AACH,qCAHW,UAAU,GACR,MAAM,CAmBlB"} \ No newline at end of file diff --git a/types/wallet/ml_dsa_87/crypto.d.ts b/types/wallet/ml_dsa_87/crypto.d.ts index b05cb5c..c91a9a5 100644 --- a/types/wallet/ml_dsa_87/crypto.d.ts +++ b/types/wallet/ml_dsa_87/crypto.d.ts @@ -1,24 +1,33 @@ /** * Generate a keypair. + * + * Note: ML-DSA-87 (FIPS 204) requires a 32-byte seed for key generation. + * QRL uses a 48-byte seed for mnemonic compatibility across wallet types. + * SHA-256 hashing reduces the 48-byte seed to the required 32 bytes per spec. + * This matches go-qrllib behavior for cross-implementation compatibility. + * + * @param {Seed} seed - 48-byte QRL seed (hashed to 32 bytes internally) * @returns {{ pk: Uint8Array, sk: Uint8Array }} */ -export function keygen(seed: any): { +export function keygen(seed: Seed): { pk: Uint8Array; sk: Uint8Array; }; /** * Sign a message. - * @param {Uint8Array} sk - * @param {Uint8Array} message + * @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes bytes) + * @param {Uint8Array} message - Message to sign * @returns {Uint8Array} signature + * @throws {Error} If sk or message is invalid */ export function sign(sk: Uint8Array, message: Uint8Array): Uint8Array; /** * Verify a signature. - * @param {Uint8Array} signature - * @param {Uint8Array} message - * @param {Uint8Array} pk + * @param {Uint8Array} signature - Signature to verify (must be CryptoBytes bytes) + * @param {Uint8Array} message - Original message + * @param {Uint8Array} pk - Public key (must be CryptoPublicKeyBytes bytes) * @returns {boolean} + * @throws {Error} If signature, message, or pk is invalid */ export function verify(signature: Uint8Array, message: Uint8Array, pk: Uint8Array): boolean; //# sourceMappingURL=crypto.d.ts.map \ No newline at end of file diff --git a/types/wallet/ml_dsa_87/crypto.d.ts.map b/types/wallet/ml_dsa_87/crypto.d.ts.map index b24b43d..45c3e70 100644 --- a/types/wallet/ml_dsa_87/crypto.d.ts.map +++ b/types/wallet/ml_dsa_87/crypto.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../../src/wallet/ml_dsa_87/crypto.js"],"names":[],"mappings":"AAaA;;;GAGG;AACH;QAFmB,UAAU;QAAM,UAAU;EAQ5C;AAED;;;;;GAKG;AACH,yBAJW,UAAU,WACV,UAAU,GACR,UAAU,CAOtB;AAED;;;;;;GAMG;AACH,kCALW,UAAU,WACV,UAAU,MACV,UAAU,GACR,OAAO,CAOnB"} \ No newline at end of file +{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../../src/wallet/ml_dsa_87/crypto.js"],"names":[],"mappings":"AAeA;;;;;;;;;;GAUG;AACH,6BAHW,IAAI,GACF;IAAE,EAAE,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,UAAU,CAAA;CAAE,CAa9C;AAWD;;;;;;GAMG;AACH,yBALW,UAAU,WACV,UAAU,GACR,UAAU,CAiBtB;AAED;;;;;;;GAOG;AACH,kCANW,UAAU,WACV,UAAU,MACV,UAAU,GACR,OAAO,CAwBnB"} \ No newline at end of file diff --git a/types/wallet/ml_dsa_87/descriptor.d.ts b/types/wallet/ml_dsa_87/descriptor.d.ts index 8c8479f..feb3b92 100644 --- a/types/wallet/ml_dsa_87/descriptor.d.ts +++ b/types/wallet/ml_dsa_87/descriptor.d.ts @@ -4,5 +4,5 @@ * @returns {Descriptor} */ export function newMLDSA87Descriptor(metadata?: [number, number]): Descriptor; -import { Descriptor } from "../common/descriptor.js"; +import { Descriptor } from '../common/descriptor.js'; //# sourceMappingURL=descriptor.d.ts.map \ No newline at end of file diff --git a/types/wallet/ml_dsa_87/descriptor.d.ts.map b/types/wallet/ml_dsa_87/descriptor.d.ts.map index 7ece410..a9ade6f 100644 --- a/types/wallet/ml_dsa_87/descriptor.d.ts.map +++ b/types/wallet/ml_dsa_87/descriptor.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"descriptor.d.ts","sourceRoot":"","sources":["../../../src/wallet/ml_dsa_87/descriptor.js"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,gDAHW,CAAC,MAAM,EAAE,MAAM,CAAC,GACd,UAAU,CAItB"} \ No newline at end of file +{"version":3,"file":"descriptor.d.ts","sourceRoot":"","sources":["../../../src/wallet/ml_dsa_87/descriptor.js"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,gDAHW,CAAC,MAAM,EAAE,MAAM,CAAC,GACd,UAAU,CAItB;2BAV8C,yBAAyB"} \ No newline at end of file diff --git a/types/wallet/ml_dsa_87/wallet.d.ts b/types/wallet/ml_dsa_87/wallet.d.ts index d44fa8d..cb626be 100644 --- a/types/wallet/ml_dsa_87/wallet.d.ts +++ b/types/wallet/ml_dsa_87/wallet.d.ts @@ -1,4 +1,4 @@ -export type Descriptor = import('../common/descriptor.js').Descriptor; +export type Descriptor = import("../common/descriptor.js").Descriptor; export class Wallet { /** * Create a new random wallet(non-deterministic). @@ -39,17 +39,24 @@ export class Wallet { pk: Uint8Array; sk: Uint8Array; }); - descriptor: import("../common/descriptor.js").Descriptor; + descriptor: Descriptor; seed: Seed; - pk: Uint8Array; - sk: Uint8Array; + pk: Uint8Array; + sk: Uint8Array; extendedSeed: ExtendedSeed; + /** @private */ + private _zeroized; /** @returns {Uint8Array} */ getAddress(): Uint8Array; /** @returns {string} */ getAddressStr(): string; /** @returns {Descriptor} */ getDescriptor(): Descriptor; + /** + * @private + * @throws {Error} If the wallet has been zeroized. + */ + private _requireLive; /** @returns {ExtendedSeed} */ getExtendedSeed(): ExtendedSeed; /** @returns {Seed} */ @@ -60,7 +67,13 @@ export class Wallet { getMnemonic(): string; /** @returns {Uint8Array} */ getPK(): Uint8Array; - /** @returns {Uint8Array} */ + /** + * Returns a copy of the secret key. + * @returns {Uint8Array} + * @warning Caller is responsible for zeroing the returned buffer when done + * (e.g. `sk.fill(0)`). The Wallet's `zeroize()` method cannot reach copies + * returned by this method. + */ getSK(): Uint8Array; /** * Sign a message. @@ -68,7 +81,17 @@ export class Wallet { * @returns {Uint8Array} Signature bytes. */ sign(message: Uint8Array): Uint8Array; + /** + * Securely zeroize sensitive key material. + * Call this when the wallet is no longer needed to minimize + * the window where secrets exist in memory. + * + * Note: JavaScript garbage collection may retain copies; + * this provides best-effort zeroization. + */ + zeroize(): void; } -import { Seed } from "../common/seed.js"; -import { ExtendedSeed } from "../common/seed.js"; +import { Descriptor } from '../common/descriptor.js'; +import { Seed } from '../common/seed.js'; +import { ExtendedSeed } from '../common/seed.js'; //# sourceMappingURL=wallet.d.ts.map \ No newline at end of file diff --git a/types/wallet/ml_dsa_87/wallet.d.ts.map b/types/wallet/ml_dsa_87/wallet.d.ts.map index 575f777..39701fa 100644 --- a/types/wallet/ml_dsa_87/wallet.d.ts.map +++ b/types/wallet/ml_dsa_87/wallet.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"wallet.d.ts","sourceRoot":"","sources":["../../../src/wallet/ml_dsa_87/wallet.js"],"names":[],"mappings":"yBAKc,OAAO,yBAAyB,EAAE,UAAU;AAS1D;IAYE;;;;OAIG;IACH,4BAHW,CAAC,MAAM,EAAE,MAAM,CAAC,GACd,MAAM,CAQlB;IAED;;;;OAIG;IACH,+BAJW,IAAI,aACJ,CAAC,MAAM,EAAE,MAAM,CAAC,GACd,MAAM,CAMlB;IAED;;;OAGG;IACH,+CAHW,YAAY,GACV,MAAM,CAOlB;IAED;;;OAGG;IACH,uCAHW,MAAM,GACJ,MAAM,CAMlB;IAwDD;;;;;;OAMG;IACH,yBALW,UAAU,WACV,UAAU,MACV,UAAU,GACR,OAAO,CAInB;IAvHD;;OAEG;IACH,0CAFW;QAAC,UAAU,EAAE,UAAU,CAAC;QAAC,IAAI,EAAE,IAAI,CAAC;QAAC,EAAE,EAAE,UAAU,CAAC;QAAC,EAAE,EAAE,UAAU,CAAA;KAAC,EAQ9E;IALC,yDAA4B;IAC5B,WAAgB;IAChB,eAAY;IACZ,eAAY;IACZ,2BAAkE;IAgDpE,4BAA4B;IAC5B,cADc,UAAU,CAGvB;IAED,wBAAwB;IACxB,iBADc,MAAM,CAGnB;IAED,4BAA4B;IAC5B,iBADc,UAAU,CAGvB;IAED,8BAA8B;IAC9B,mBADc,YAAY,CAGzB;IAED,sBAAsB;IACtB,WADc,IAAI,CAGjB;IAED,0CAA0C;IAC1C,sBADc,MAAM,CAGnB;IAED,wBAAwB;IACxB,eADc,MAAM,CAGnB;IAED,4BAA4B;IAC5B,SADc,UAAU,CAGvB;IAED,4BAA4B;IAC5B,SADc,UAAU,CAGvB;IAED;;;;OAIG;IACH,cAHW,UAAU,GACR,UAAU,CAItB;CAYF"} \ No newline at end of file +{"version":3,"file":"wallet.d.ts","sourceRoot":"","sources":["../../../src/wallet/ml_dsa_87/wallet.js"],"names":[],"mappings":"yBAKc,OAAO,yBAAyB,EAAE,UAAU;AAU1D;IAcE;;;;OAIG;IACH,4BAHW,CAAC,MAAM,EAAE,MAAM,CAAC,GACd,MAAM,CAYlB;IAED;;;;OAIG;IACH,+BAJW,IAAI,aACJ,CAAC,MAAM,EAAE,MAAM,CAAC,GACd,MAAM,CAMlB;IAED;;;OAGG;IACH,+CAHW,YAAY,GACV,MAAM,CAOlB;IAED;;;OAGG;IACH,uCAHW,MAAM,GACJ,MAAM,CAUlB;IA8ED;;;;;;OAMG;IACH,yBALW,UAAU,WACV,UAAU,MACV,UAAU,GACR,OAAO,CAInB;IAvJD;;OAEG;IACH,0CAFW;QAAC,UAAU,EAAE,UAAU,CAAC;QAAC,IAAI,EAAE,IAAI,CAAC;QAAC,EAAE,EAAE,UAAU,CAAC;QAAC,EAAE,EAAE,UAAU,CAAA;KAAC,EAU9E;IAPC,uBAA4B;IAC5B,WAAgB;IAChB,gCAAY;IACZ,gCAAY;IACZ,2BAAkE;IAClE,eAAe;IACf,kBAAsB;IAwDxB,4BAA4B;IAC5B,cADc,UAAU,CAGvB;IAED,wBAAwB;IACxB,iBADc,MAAM,CAGnB;IAED,4BAA4B;IAC5B,iBADc,UAAU,CAGvB;IAED;;;OAGG;IACH,qBAIC;IAED,8BAA8B;IAC9B,mBADc,YAAY,CAIzB;IAED,sBAAsB;IACtB,WADc,IAAI,CAIjB;IAED,0CAA0C;IAC1C,sBADc,MAAM,CAInB;IAED,wBAAwB;IACxB,eADc,MAAM,CAInB;IAED,4BAA4B;IAC5B,SADc,UAAU,CAGvB;IAED;;;;;;OAMG;IACH,SALa,UAAU,CAQtB;IAED;;;;OAIG;IACH,cAHW,UAAU,GACR,UAAU,CAKtB;IAaD;;;;;;;OAOG;IACH,gBAcC;CACF;2BAtL0B,yBAAyB;qBACjB,mBAAmB;6BAAnB,mBAAmB"} \ No newline at end of file