]> git.codecow.com Git - libnemo.git/commitdiff
Refactor bytes to decimal integer.
authorChris Duncan <chris@codecow.com>
Thu, 2 Jul 2026 18:37:15 +0000 (11:37 -0700)
committerChris Duncan <chris@codecow.com>
Thu, 2 Jul 2026 18:37:15 +0000 (11:37 -0700)
src/lib/convert/bytes.ts
src/lib/crypto/bip39.ts
src/lib/ledger/account.ts
src/lib/ledger/cache.ts
src/lib/ledger/close.ts
src/lib/ledger/open.ts
src/lib/ledger/sign.ts
src/lib/ledger/version.ts

index 9c406c54db5915518925354964ac40d987211b8c..033a81894a6be31267c7d4f7984b48bf76c350fd 100644 (file)
@@ -71,25 +71,6 @@ export const bytes = Object.freeze({
                return output
        },
 
-       /**
-        * Sum an array of bytes to a decimal integer. If the result is larger than
-        * Number.MAX_SAFE_INTEGER, it will be returned as a bigint.
-        *
-        * @param {Bytes} bytes - Byte array to convert
-        * @returns {bigint|number} Decimal sum of the literal byte values
-        */
-       toDec (bytes: Bytes): bigint | number {
-               let int = 0n
-               for (let i = bytes.byteLength; i > 0; i--) {
-                       int += BigInt(bytes[i - 1]) << (BigInt(bytes.byteLength - i) << 3n)
-               }
-               if (int > 9007199254740991n) {
-                       return int
-               } else {
-                       return Number(int)
-               }
-       },
-
        /**
         * Convert a Uint8Array of bytes to a hexadecimal string.
         *
@@ -105,6 +86,21 @@ export const bytes = Object.freeze({
                return hex
        },
 
+       /**
+        * Sum an array of bytes to a big integer.
+        *
+        * @param {Bytes} bytes - Byte array to convert
+        * @returns {bigint} Sum of the byte values
+        */
+       toInt (bytes: Bytes): bigint {
+               let int = 0n
+               for (let i = 0; i < bytes.byteLength; i++) {
+                       const offset = BigInt(bytes.byteLength - 1 - i) << 3n
+                       int |= BigInt(bytes[i]) << offset
+               }
+               return int
+       },
+
        /**
         * Convert a Uint8Array of bytes to a UTF-8 text string.
         *
index c1dcd1e40dd0d75fb8063806acbc60bd76084ba7..a00c755d1e497f5514369f49ca918218472841a6 100644 (file)
@@ -1,7 +1,8 @@
 //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>\r
 //! SPDX-License-Identifier: GPL-3.0-or-later\r
 \r
-import { wordlist } from "./wordlist"\r
+import { bytes } from '../convert'\r
+import { wordlist } from './wordlist'\r
 /**\r
 * Represents a mnemonic phrase that identifies a wallet as defined by BIP-39.\r
 */\r
@@ -15,7 +16,7 @@ export class Bip39 {
         * @param {Bytes} 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 checksum (entropy: Bytes): Promise<bigint> {\r
+       static checksum (entropy: ArrayBuffer | Bytes): Promise<bigint> {\r
                const checksumBitLength = (entropy.byteLength / 4) | 0\r
                return crypto.subtle.digest('SHA-256', entropy)\r
                        .then(hash => {\r
@@ -39,7 +40,7 @@ export class Bip39 {
                if (entropy instanceof ArrayBuffer) {\r
                        entropy = new Uint8Array(entropy)\r
                }\r
-               if (!(entropy instanceof Uint8Array)) {\r
+               if (!bytes.is(entropy)) {\r
                        throw new TypeError('Invalid entropy')\r
                }\r
                if (entropy.byteLength < 16 || entropy.byteLength > 32) {\r
@@ -52,7 +53,7 @@ export class Bip39 {
                const phraseLength = 0.75 * entropy.byteLength\r
                return this.checksum(entropy)\r
                        .then(checksum => {\r
-                               const bigEntropy = entropy.reduce((a, b) => a = (a << 8n) | BigInt(b), 0n)\r
+                               const bigEntropy = bytes.toInt(entropy)\r
 \r
                                let concatenation: bigint = (bigEntropy << BigInt(entropy.byteLength) / 4n) | checksum\r
                                const words: string[] = []\r
@@ -191,7 +192,7 @@ export class Bip39 {
                }\r
                if (this.#bip39Seed != null) {\r
                        const seed = format === 'hex'\r
-                               ? [...this.#bip39Seed].map(b => b.toString(16).padStart(2, '0')).join('').toUpperCase()\r
+                               ? bytes.toHex(this.#bip39Seed)\r
                                : this.#bip39Seed\r
                        return Promise.resolve(seed)\r
                } else {\r
@@ -248,7 +249,7 @@ export class Bip39 {
                        }\r
                }\r
                return format === 'hex'\r
-                       ? [...this.#blake2bSeed].map(b => b.toString(16).padStart(2, '0')).join('').toUpperCase()\r
+                       ? bytes.toHex(this.#blake2bSeed)\r
                        : this.#blake2bSeed\r
        }\r
 }\r
index ba155a61bf1eea0dc2590763da6d791f35df826a..0a651f196f90315ddf9ddfe137d5994247487766 100644 (file)
@@ -1,7 +1,7 @@
 //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
 //! SPDX-License-Identifier: GPL-3.0-or-later
 
-import { APDU_CODES, DERIVATION_PATH, LedgerAccountResponse, LedgerTransport, STATUS_CODES, LISTEN_TIMEOUT, OPEN_TIMEOUT } from '.'
+import { APDU_CODES, DERIVATION_PATH, LedgerAccountResponse, LedgerTransport, LISTEN_TIMEOUT, OPEN_TIMEOUT, STATUS_CODES } from '.'
 import { HARDENED_OFFSET } from '../constants'
 import { bytes, dec } from '../convert'
 
@@ -19,7 +19,7 @@ export async function _account (transport: LedgerTransport, index: number = 0, s
                        .catch((err: any) => dec.toBytes(err.statusCode))
                        .finally(async () => await t.close()) as Uint8Array
 
-               const statusCode = bytes.toDec(response.slice(-2)) as number
+               const statusCode = Number(bytes.toInt(response.slice(-2)))
                const status = STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'
                if (status !== 'OK') {
                        return { status, publicKey: null, address: null }
index 11b4d3f5700589d4dbfb0aae95229e6959795310..255f90918fea9998000696722098e3908e6bf7ef 100644 (file)
@@ -1,6 +1,7 @@
 //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
 //! SPDX-License-Identifier: GPL-3.0-or-later
 
+import { TransportStatusError } from '@ledgerhq/errors'
 import { APDU_CODES, LedgerResponse, LedgerTransport, LISTEN_TIMEOUT, OPEN_TIMEOUT, STATUS_CODES } from '.'
 import { Account } from '../account'
 import { Block } from '../block'
@@ -35,9 +36,9 @@ export async function _cache (transport: LedgerTransport, index: number = 0, blo
                const t = await transport.create(OPEN_TIMEOUT, LISTEN_TIMEOUT)
                const response = await t
                        .send(APDU_CODES.class, APDU_CODES.cacheBlock, APDU_CODES.paramUnused, APDU_CODES.paramUnused, data as Buffer)
-                       .then((res: Buffer) => bytes.toDec(bytes.assert(res)))
-                       .catch((err: any) => err.statusCode)
-                       .finally(async () => await t.close()) as number
+                       .then((res) => Number(bytes.toInt(bytes.assert(res))))
+                       .catch((err: TransportStatusError) => err.statusCode)
+                       .finally(async () => await t.close())
 
                const status = STATUS_CODES[response]
                if (status !== 'OK') {
index be819cce71f9df3f06e30f88cffec6e0d2f23682..662678316e814d94294307877ca1664d71ee19ae 100644 (file)
@@ -1,6 +1,7 @@
 //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
 //! SPDX-License-Identifier: GPL-3.0-or-later
 
+import { TransportStatusError } from '@ledgerhq/errors'
 import { APDU_CODES, LedgerResponse, LedgerTransport, LISTEN_TIMEOUT, OPEN_TIMEOUT, STATUS_CODES } from '.'
 import { bytes } from '../convert'
 
@@ -8,8 +9,8 @@ export async function _close (transport: LedgerTransport): Promise<LedgerRespons
        const t = await transport.create(OPEN_TIMEOUT, LISTEN_TIMEOUT)
        const response = await t
                .send(0xb0, 0xa7, APDU_CODES.paramUnused, APDU_CODES.paramUnused)
-               .then((res: Buffer) => bytes.toDec(bytes.assert(res)))
-               .catch((err: any) => err.statusCode)
-               .finally(async () => await t.close()) as number
+               .then((res: Buffer) => Number(bytes.toInt(bytes.assert(res))))
+               .catch((err: TransportStatusError) => err.statusCode)
+               .finally(async () => await t.close())
        return new Promise(r => setTimeout(r, 1000, { status: STATUS_CODES[response] }))
 }
index 52effa821e203e0f40507ac42d10a0b870e9ec71..f2f4c0d47499fbc7baec574c0fca43c599b90454 100644 (file)
@@ -1,6 +1,7 @@
 //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
 //! SPDX-License-Identifier: GPL-3.0-or-later
 
+import { TransportStatusError } from '@ledgerhq/errors'
 import { APDU_CODES, LedgerResponse, LedgerTransport, LISTEN_TIMEOUT, OPEN_TIMEOUT, STATUS_CODES } from '.'
 import { bytes } from '../convert'
 
@@ -9,8 +10,8 @@ export async function _open (transport: LedgerTransport): Promise<LedgerResponse
        const t = await transport.create(OPEN_TIMEOUT, LISTEN_TIMEOUT)
        const response = await t
                .send(0xe0, 0xd8, APDU_CODES.paramUnused, APDU_CODES.paramUnused, name as Buffer)
-               .then((res: Buffer) => bytes.toDec(bytes.assert(res)))
-               .catch((err: any) => err.statusCode)
-               .finally(async () => await t.close()) as number
+               .then((res: Buffer) => Number(bytes.toInt(bytes.assert(res))))
+               .catch((err: TransportStatusError) => err.statusCode)
+               .finally(async () => await t.close())
        return new Promise(r => setTimeout(r, 1000, { status: STATUS_CODES[response] }))
 }
index 8c8a553f344d761e1240cd842b9ce71afc8e4833..ccd4f3415b94b4544685eab97777cfa7791401c6 100644 (file)
@@ -78,7 +78,7 @@ async function req (transport: LedgerTransport, command: typeof APDU_CODES.signB
                .send(APDU_CODES.class, command, APDU_CODES.paramUnused, APDU_CODES.paramUnused, data)
                .catch((err: any) => dec.toBytes(err.statusCode))
                .finally(async () => await t.close()) as Uint8Array
-       const statusCode = bytes.toDec(response.slice(-2)) as number
+       const statusCode = Number(bytes.toInt(response.slice(-2)))
        const status = STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'
        if (status !== 'OK') {
                throw new Error('Signing with ledger failed', { cause: status })
index 0c6745c78e3a0a1a2b4e849290ad4ce73dae95d6..f618034089838751aba2c6e29237b58bf294df3e 100644 (file)
@@ -24,7 +24,7 @@ export async function _version (transport: LedgerTransport): Promise<LedgerVersi
                        .catch((err: any) => dec.toBytes(err.statusCode))
                        .finally(async () => await t.close()) as Uint8Array
 
-               const statusCode = bytes.toDec(response.slice(-2)) as number
+               const statusCode = Number(bytes.toInt(response.slice(-2)))
                const status = STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'
                if (status !== 'OK') {
                        return { status, name: null, version: null }