]> git.codecow.com Git - libnemo.git/commitdiff
Replace async-await with Promise chains.
authorChris Duncan <chris@zoso.dev>
Sat, 30 Aug 2025 04:17:38 +0000 (21:17 -0700)
committerChris Duncan <chris@zoso.dev>
Sat, 30 Aug 2025 04:17:38 +0000 (21:17 -0700)
src/lib/crypto/bip39.ts

index 7baf54a63340f553318ffc19ad4fd9477e0ce440..1fae699f3365a8b0262946e3b22bc6ffd13dc9d1 100644 (file)
@@ -17,11 +17,14 @@ export class Bip39 {
         * @param {Uint8Array<ArrayBuffer>} entropy - Cryptographically strong pseudorandom data of length N bits\r
         * @returns {Promise<bigint>} First N/32 bits of the hash as a bigint\r
         */\r
-       static async #checksum (entropy: Uint8Array<ArrayBuffer>): Promise<bigint> {\r
-               const sha256sum = new Uint8Array(await crypto.subtle.digest('SHA-256', entropy))[0]\r
-               const checksumBitLength = BigInt(entropy.byteLength) / 4n\r
-               const checksum = BigInt(sha256sum) >> (8n - checksumBitLength)\r
-               return checksum\r
+       static #checksum (entropy: Uint8Array<ArrayBuffer>): Promise<bigint> {\r
+               const checksumBitLength = (entropy.byteLength / 4) | 0\r
+               return crypto.subtle.digest('SHA-256', entropy)\r
+                       .then(hash => {\r
+                               const sha256sum = new Uint8Array(hash)[0]\r
+                               const checksum = sha256sum >> (8 - checksumBitLength)\r
+                               return BigInt(checksum)\r
+                       })\r
        }\r
 \r
        /**\r
@@ -39,7 +42,7 @@ export class Bip39 {
        * @param {(ArrayBuffer|Uint8Array<ArrayBuffer>)} entropy - Cryptographically secure random value\r
        * @returns {string} Mnemonic phrase created using the BIP-39 wordlist\r
        */\r
-       static async fromEntropy (entropy: ArrayBuffer | Uint8Array<ArrayBuffer>): Promise<Bip39> {\r
+       static fromEntropy (entropy: ArrayBuffer | Uint8Array<ArrayBuffer>): Promise<Bip39> {\r
                if (entropy instanceof ArrayBuffer) {\r
                        entropy = new Uint8Array(entropy)\r
                }\r
@@ -54,19 +57,21 @@ export class Bip39 {
                }\r
 \r
                const phraseLength = 0.75 * entropy.byteLength\r
-               const checksum = await this.#checksum(entropy)\r
-               const bigEntropy = entropy.reduce((a, b) => a = (a << 8n) | BigInt(b), 0n)\r
+               return this.#checksum(entropy)\r
+                       .then(checksum => {\r
+                               const bigEntropy = entropy.reduce((a, b) => a = (a << 8n) | BigInt(b), 0n)\r
 \r
-               let concatenation: bigint = (bigEntropy << BigInt(entropy.byteLength) / 4n) | checksum\r
-               const words: string[] = []\r
-               for (let i = 0; i < phraseLength; i++) {\r
-                       const wordBits = concatenation & 2047n\r
-                       const wordIndex = Number(wordBits)\r
-                       words.unshift(this.wordlist[wordIndex])\r
-                       concatenation >>= 11n\r
-               }\r
-               const sentence = words.join(' ')\r
-               return this.fromPhrase(sentence)\r
+                               let concatenation: bigint = (bigEntropy << BigInt(entropy.byteLength) / 4n) | checksum\r
+                               const words: string[] = []\r
+                               for (let i = 0; i < phraseLength; i++) {\r
+                                       const wordBits = concatenation & 2047n\r
+                                       const wordIndex = Number(wordBits)\r
+                                       words.unshift(this.wordlist[wordIndex])\r
+                                       concatenation >>= 11n\r
+                               }\r
+                               const sentence = words.join(' ')\r
+                               return this.fromPhrase(sentence)\r
+                       })\r
        }\r
 \r
        /**\r
@@ -78,16 +83,18 @@ export class Bip39 {
        * @param {string} phrase - String of 12, 15, 18, 21, or 24 words\r
        * @returns {string} Mnemonic phrase validated using the BIP-39 wordlist\r
        */\r
-       static async fromPhrase (phrase: string): Promise<Bip39> {\r
+       static fromPhrase (phrase: string): Promise<Bip39> {\r
                this.#isInternal = true\r
                const self = new this()\r
-               const isValid = await this.validate(phrase)\r
-               if (isValid) {\r
-                       self.#phrase = phrase.normalize('NFKD').toLowerCase().split(' ')\r
-                       return self\r
-               } else {\r
-                       throw new Error('Invalid mnemonic phrase.')\r
-               }\r
+               return this.validate(phrase)\r
+                       .then(isValid => {\r
+                               if (isValid) {\r
+                                       self.#phrase = phrase.normalize('NFKD').toLowerCase().split(' ')\r
+                                       return self\r
+                               } else {\r
+                                       throw new Error('Invalid mnemonic phrase.')\r
+                               }\r
+                       })\r
        }\r
 \r
        /**\r
@@ -96,29 +103,29 @@ export class Bip39 {
        * @param {string} mnemonic - Mnemonic phrase to validate\r
        * @returns {boolean} True if the mnemonic phrase is valid\r
        */\r
-       static async validate (mnemonic: string): Promise<boolean>\r
-       static async validate (mnemonic: unknown): Promise<boolean> {\r
+       static validate (mnemonic: string): Promise<boolean>\r
+       static validate (mnemonic: unknown): Promise<boolean> {\r
                if (typeof mnemonic !== 'string') {\r
-                       return false\r
+                       return Promise.resolve(false)\r
                }\r
                if (!/^(?:[a-z]{3,8} ){11,23}[a-z]{3,8}$/i.test(mnemonic)) {\r
-                       return false\r
+                       return Promise.resolve(false)\r
                }\r
                const words = mnemonic.normalize('NFKD').split(' ')\r
                if (words.length % 3 !== 0) {\r
-                       return false\r
+                       return Promise.resolve(false)\r
                }\r
                let bits = 0n, bitLength = 0n\r
                for (const word of words) {\r
                        const wordIndex = this.wordlist.indexOf(word)\r
                        if (wordIndex === -1) {\r
-                               return false\r
+                               return Promise.resolve(false)\r
                        }\r
                        bits = (bits << 11n) | BigInt(this.wordlist.indexOf(word))\r
                        bitLength += 11n\r
                }\r
                if (Number(bitLength) % 33 !== 0) {\r
-                       return false\r
+                       return Promise.resolve(false)\r
                }\r
                const checksumLength = bitLength / 33n\r
                const entropyLength = bitLength - checksumLength\r
@@ -131,7 +138,7 @@ export class Bip39 {
                        || entropyLength > 256n\r
                        || Number(entropyLength) % 32 !== 0\r
                ) {\r
-                       return false\r
+                       return Promise.resolve(false)\r
                }\r
                const bytes = new Uint8Array(Number(entropyLength) / 8)\r
                for (let i = 0; i < bytes.length; i++) {\r
@@ -139,11 +146,8 @@ export class Bip39 {
                        const byte = (entropyBits >> shift) & 255n\r
                        bytes[i] = Number(byte)\r
                }\r
-               const expectedChecksum = await this.#checksum(bytes)\r
-               if (expectedChecksum !== checksumBits) {\r
-                       return false\r
-               }\r
-               return true\r
+               return this.#checksum(bytes)\r
+                       .then(expectedChecksum => expectedChecksum === checksumBits)\r
        }\r
 \r
        #bip39Seed?: Uint8Array<ArrayBuffer>\r
@@ -153,7 +157,7 @@ export class Bip39 {
 \r
        private constructor () {\r
                if (!Bip39.#isInternal) {\r
-                       throw new Error(`Bip39 must be created with async methods 'fromPhrase()' or 'fromEntropy().`)\r
+                       throw new Error(`Bip39 must be created with factory methods 'fromPhrase()' or 'fromEntropy().`)\r
                }\r
                Bip39.#isInternal = false\r
        }\r
@@ -178,7 +182,7 @@ export class Bip39 {
        * @param {string} [passphrase=''] - Used as the PBKDF2 salt. Default: ""\r
        * @returns {Promise<Uint8Array<ArrayBuffer>>} Promise for seed as bytes\r
        */\r
-       async toBip39Seed (passphrase: string): Promise<Uint8Array<ArrayBuffer>>\r
+       toBip39Seed (passphrase: string): Promise<Uint8Array<ArrayBuffer>>\r
        /**\r
        * Converts the mnemonic phrase to a BIP-39 seed.\r
        *\r
@@ -188,45 +192,47 @@ export class Bip39 {
        * @param {string} [passphrase=''] - Used as the PBKDF2 salt. Default: ""\r
        * @returns {Promise<string>}  Promise for seed as hexadecimal string\r
        */\r
-       async toBip39Seed (passphrase: string, format: 'hex'): Promise<string>\r
-       async toBip39Seed (passphrase: unknown, format?: 'hex'): Promise<string | Uint8Array<ArrayBuffer>> {\r
+       toBip39Seed (passphrase: string, format?: 'hex'): Promise<string>\r
+       toBip39Seed (passphrase: unknown, format?: 'hex'): Promise<string | Uint8Array<ArrayBuffer>> {\r
                if (this.phrase == null) {\r
                        throw new Error('BIP-39 mnemonic phrase not found')\r
                }\r
-               if (this.#bip39Seed == null) {\r
-                       const salt = (passphrase == null || typeof passphrase !== 'string')\r
-                               ? ''\r
-                               : passphrase\r
+               if (this.#bip39Seed != null) {\r
+                       return Promise.resolve(format === 'hex' ? bytes.toHex(this.#bip39Seed) : this.#bip39Seed)\r
+               } else {\r
+                       const salt = (typeof passphrase === 'string') ? passphrase : ''\r
                        const keyData = utf8.toBytes(this.phrase)\r
-                       const phraseKey = await crypto.subtle.importKey('raw', keyData, 'PBKDF2', false, ['deriveBits', 'deriveKey'])\r
-                       const algorithm: Pbkdf2Params = {\r
-                               name: 'PBKDF2',\r
-                               hash: 'SHA-512',\r
-                               salt: utf8.toBytes(`mnemonic${salt.normalize('NFKD')}`),\r
-                               iterations: Bip39.#ITERATIONS\r
-                       }\r
-                       const seed = await crypto.subtle.deriveBits(algorithm, phraseKey, 512)\r
-                       this.#bip39Seed = new Uint8Array(seed)\r
+                       return crypto.subtle.importKey('raw', keyData, 'PBKDF2', false, ['deriveBits', 'deriveKey'])\r
+                               .then(phraseKey => {\r
+                                       const algorithm: Pbkdf2Params = {\r
+                                               name: 'PBKDF2',\r
+                                               hash: 'SHA-512',\r
+                                               salt: utf8.toBytes(`mnemonic${salt.normalize('NFKD')}`),\r
+                                               iterations: Bip39.#ITERATIONS\r
+                                       }\r
+                                       return crypto.subtle.deriveBits(algorithm, phraseKey, 512)\r
+                                               .then(seed => {\r
+                                                       this.#bip39Seed = new Uint8Array(seed)\r
+                                                       return this.toBip39Seed(salt, format)\r
+                                               })\r
+                               })\r
                }\r
-               return format === 'hex'\r
-                       ? bytes.toHex(this.#bip39Seed)\r
-                       : this.#bip39Seed\r
        }\r
 \r
        /**\r
        * Converts the mnemonic phrase to a BLAKE2b seed.\r
        *\r
-       * @returns {Promise<Uint8Array<ArrayBuffer>>} Promise for seed as bytes\r
+       * @returns {Uint8Array<ArrayBuffer>} Seed as bytes\r
        */\r
-       async toBlake2bSeed (): Promise<Uint8Array<ArrayBuffer>>\r
+       toBlake2bSeed (): Uint8Array<ArrayBuffer>\r
        /**\r
        * Converts the mnemonic phrase to a BLAKE2b seed.\r
        *\r
        * @param {string} format\r
-       * @returns {Promise<string>} Promise for seed as hexadecimal string\r
+       * @returns {string} Seed as hexadecimal string\r
        */\r
-       async toBlake2bSeed (format: 'hex'): Promise<string>\r
-       async toBlake2bSeed (format?: 'hex'): Promise<string | Uint8Array<ArrayBuffer>> {\r
+       toBlake2bSeed (format: 'hex'): string\r
+       toBlake2bSeed (format?: 'hex'): string | Uint8Array<ArrayBuffer> {\r
                if (this.#phrase?.length !== 24) {\r
                        throw new Error('BIP-39 mnemonic phrase must be 24 words to convert to BLAKE2b seed')\r
                }\r