From: Chris Duncan Date: Fri, 8 Aug 2025 03:26:00 +0000 (-0700) Subject: Replace remaining usage of Entropy with plain crypto calls. X-Git-Tag: v0.10.5~43^2~31 X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=63060e6b3f61cf8b8f27fd5fe5f9f0d51da55e34;p=libnemo.git Replace remaining usage of Entropy with plain crypto calls. --- diff --git a/README.md b/README.md index 126fa52..8b574c7 100644 --- a/README.md +++ b/README.md @@ -291,11 +291,11 @@ import { Tools } from 'libnemo' const address = 'nano_1pu7p5n3ghq1i1p4rhmek41f5add1uh34xpb94nkbxe8g4a6x1p69emk8y1d' const privateKey = '3BE4FC2EF3F3B7374E6FC4FB6E7BB153F8A2998B3B3DAB50853EABE128024143' -const randomData = new Entropy().hex +const randomData = crypto.getRandomValues(new Uint8Array(32)) -const signature = await Tools.sign(privateKey, randomData) +const signature = await Tools.sign(privateKey, ...randomData) const publicKey = new Account(address).publicKey -const isValid = await Tools.verify(publicKey, signature, randomData) +const isValid = await Tools.verify(publicKey, signature, ...randomData) ``` #### Validate a Nano account address diff --git a/src/lib/bip39.ts b/src/lib/bip39.ts index a4002fd..e2a5485 100644 --- a/src/lib/bip39.ts +++ b/src/lib/bip39.ts @@ -3,7 +3,6 @@ import { BIP39_ITERATIONS } from './constants' import { bytes, utf8 } from './convert' -import { Entropy } from './entropy' /** * Represents a mnemonic phrase that identifies a wallet as defined by BIP-39. @@ -37,16 +36,28 @@ export class Bip39 { * the limit of 128-256 bits defined in BIP-39. Typically, wallets use the * maximum entropy allowed. * - * @param {(string|ArrayBuffer|Uint8Array)} entropy - Cryptographically secure random value + * @param {(ArrayBuffer|Uint8Array)} entropy - Cryptographically secure random value * @returns {string} Mnemonic phrase created using the BIP-39 wordlist */ - static async fromEntropy (entropy: string | ArrayBuffer | Uint8Array): Promise { - const e = new Entropy(entropy) - const phraseLength = 0.75 * e.byteLength - const checksum = await this.#checksum(e.bytes) + static async fromEntropy (entropy: ArrayBuffer | Uint8Array): Promise { + if (entropy instanceof ArrayBuffer) { + entropy = new Uint8Array(entropy) + } + if (!(entropy instanceof Uint8Array)) { + throw new TypeError('Invalid entropy') + } + if (entropy.byteLength < 16 || entropy.byteLength > 32) { + throw new RangeError(`Entropy must be 16-32 bytes`) + } + if (entropy.byteLength % 4 !== 0) { + throw new RangeError(`Entropy must be a multiple of 4 bytes`) + } + + const phraseLength = 0.75 * entropy.byteLength + const checksum = await this.#checksum(entropy) + const bigEntropy = entropy.reduce((a, b) => a = (a << 8n) | BigInt(b), 0n) - let concatenation: bigint = (e.bigint << BigInt(e.byteLength) / 4n) | checksum - e.destroy() + let concatenation: bigint = (bigEntropy << BigInt(entropy.byteLength) / 4n) | checksum const words: string[] = [] for (let i = 0; i < phraseLength; i++) { const wordBits = concatenation & 2047n diff --git a/src/lib/safe.ts b/src/lib/safe.ts index e1f7c43..55c8b29 100644 --- a/src/lib/safe.ts +++ b/src/lib/safe.ts @@ -9,7 +9,6 @@ import { Bip44 } from './bip44' import { Blake2b } from './blake2b' import { default as Constants, BIP44_COIN_NANO } from './constants' import { default as Convert, bytes, hex, utf8 } from './convert' -import { Entropy } from './entropy' import { NanoNaCl } from './nano-nacl' import { NamedData } from '#types' @@ -20,7 +19,7 @@ export class Safe { static #locked: boolean = true static #type?: 'BIP-44' | 'BLAKE2b' static #seed?: ArrayBuffer - static #mnemonic?: Bip39 + static #mnemonic?: string static #parentPort?: any static { NODE: this.#parentPort = parentPort @@ -111,13 +110,13 @@ export class Safe { */ static async create (type?: 'BIP-44' | 'BLAKE2b', key?: CryptoKey, keySalt?: ArrayBuffer, mnemonicSalt?: string): Promise> { try { - const entropy = new Entropy() - const { phrase: mnemonicPhrase } = await Bip39.fromEntropy(entropy.bytes) + const entropy = crypto.getRandomValues(new Uint8Array(32)) + const { phrase: mnemonicPhrase } = await Bip39.fromEntropy(entropy) const record = await this.import(type, key, keySalt, mnemonicPhrase, mnemonicSalt) - if (this.#seed == null || this.#mnemonic?.phrase == null) { + if (this.#seed == null || this.#mnemonic == null) { throw new Error('Failed to generate seed and mnemonic') } - return { ...record, seed: this.#seed.slice(), mnemonic: utf8.toBuffer(this.#mnemonic.phrase) } + return { ...record, seed: this.#seed.slice(), mnemonic: utf8.toBuffer(this.#mnemonic) } } catch (err) { throw new Error('Failed to create wallet', { cause: err }) } @@ -190,13 +189,14 @@ export class Safe { if (secret instanceof ArrayBuffer) { this.#seed = secret if (type === 'BLAKE2b') { - this.#mnemonic = await Bip39.fromEntropy(new Uint8Array(secret)) + this.#mnemonic = (await Bip39.fromEntropy(new Uint8Array(secret))).phrase } } else { - this.#mnemonic = await Bip39.fromPhrase(secret) + const mnemonic = await Bip39.fromPhrase(secret) + this.#mnemonic = mnemonic.phrase this.#seed = type === 'BIP-44' - ? (await this.#mnemonic.toBip39Seed(mnemonicSalt ?? '')).buffer - : (await this.#mnemonic.toBlake2bSeed()).buffer + ? (await mnemonic.toBip39Seed(mnemonicSalt ?? '')).buffer + : (await mnemonic.toBlake2bSeed()).buffer } const { iv, encrypted } = await this.#encryptWallet(key) return { iv, salt: keySalt, encrypted } @@ -261,7 +261,7 @@ export class Safe { throw new TypeError('Invalid seed') } this.#seed = seed - if (mnemonic != null) this.#mnemonic = await Bip39.fromPhrase(mnemonic) + if (mnemonic != null) this.#mnemonic = (await Bip39.fromPhrase(mnemonic)).phrase this.#locked = false return { isUnlocked: !this.#locked } } catch (err) { @@ -326,7 +326,7 @@ export class Safe { } } if (mnemonicPhrase != null) { - if (mnemonicPhrase === this.#mnemonic?.phrase) { + if (mnemonicPhrase === this.#mnemonic) { isVerified = true } } @@ -388,8 +388,8 @@ export class Safe { const data: NamedData = { seed: bytes.toHex(new Uint8Array(this.#seed)) } - if (this.#mnemonic?.phrase != null) data.mnemonic = this.#mnemonic.phrase - const iv = new Entropy().buffer + if (this.#mnemonic != null) data.mnemonic = this.#mnemonic + const iv = crypto.getRandomValues(new Uint8Array(32)).buffer const encoded = utf8.toBytes(JSON.stringify(data)) const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, encoded) return { iv, encrypted } @@ -436,7 +436,7 @@ export class Safe { } const iv: ArrayBuffer = action === 'unlock' && messageData.iv instanceof ArrayBuffer ? messageData.iv - : new Entropy().buffer + : crypto.getRandomValues(new Uint8Array(32)).buffer // Salt for decryption key to unlock if (action === 'unlock' && !(messageData.keySalt instanceof ArrayBuffer)) { @@ -444,7 +444,7 @@ export class Safe { } const keySalt: ArrayBuffer = action === 'unlock' && messageData.keySalt instanceof ArrayBuffer ? messageData.keySalt - : new Entropy().buffer + : crypto.getRandomValues(new Uint8Array(32)).buffer // CryptoKey from password, decryption key if unlocking else encryption key const key = password instanceof ArrayBuffer @@ -533,7 +533,6 @@ export default ` const Bip39 = ${Bip39} const Bip44 = ${Bip44} const Blake2b = ${Blake2b} - const Entropy = ${Entropy} const NanoNaCl = ${NanoNaCl} const Safe = ${Safe} `