]> git.codecow.com Git - libnemo.git/commitdiff
Extract blake ckd to vault to leave hash function more pure. Clean up blake input...
authorChris Duncan <chris@codecow.com>
Wed, 1 Jul 2026 06:45:41 +0000 (23:45 -0700)
committerChris Duncan <chris@codecow.com>
Wed, 1 Jul 2026 06:45:41 +0000 (23:45 -0700)
src/lib/crypto/blake2b.ts
src/lib/vault/vault-worker.ts

index 2b970467daaf418396e713442532d20dc9a6c6ef..4dc3153f0c85248f3c63eea324ec1e5ab77c7ad2 100644 (file)
@@ -46,28 +46,6 @@ function isBytes (a: unknown): a is Bytes {
  * Original source commit: https://github.com/emilbayes/blake2b/blob/1f63e02e3f226642959506cdaa67c8819ff145cd/index.js
  */
 export class Blake2b {
-       /**
-        * Derives account private keys from a wallet seed using the BLAKE2b hashing
-        * algorithm.
-        *
-        * Separately, account public keys are derived from the private key using the
-        * Ed25519 key algorithm, and account addresses are derived from the public key
-        * as described in the Nano documentation.
-        * https://docs.nano.org/integration-guides/the-basics/
-        *
-        * @param {ArrayBuffer} seed - 32-byte secret seed of the wallet
-        * @param {number} index - 4-byte index of account to derive
-        * @returns {Promise<ArrayBuffer>} Private key for the account
-        */
-       static ckd (seed: ArrayBuffer, index: number): Promise<ArrayBuffer> {
-               const b = new ArrayBuffer(4)
-               new DataView(b).setUint32(0, index, false)
-               const s = new Uint8Array(seed)
-               const i = new Uint8Array(b)
-               const sk = new this(32).update(s).update(i).digest()
-               return Promise.resolve(sk.buffer)
-       }
-
        #G (r: number, i: number, a: number, b: number, c: number, d: number): void {
                this.#v[a] += this.#v[b] + this.#m[SIGMA[r][i << 1]]
                this.#v[d] ^= this.#v[a]
@@ -95,11 +73,8 @@ export class Blake2b {
        }
 
        #blake2bInit (length: number, key?: Bytes, salt?: Bytes, personal?: Bytes): void {
-               // output length in bytes
-               this.#outlen = length
-
                // state, 'param block'
-               this.#parameter_block[0] = length
+               this.#parameter_block[0] = this.#outlen = length
                this.#parameter_block[1] = key?.length ?? 0
                this.#parameter_block[2] = 1 // fanout
                this.#parameter_block[3] = 1 // depth
@@ -166,7 +141,7 @@ export class Blake2b {
         * Update the BLAKE2b streaming hash with additional input. When the 128-byte
         * input buffer is full, compress and start refilling.
         */
-       #blake2bUpdate (input: Uint8Array): void {
+       #blake2bUpdate (input: Bytes): void {
                for (let i = 0; i < input.length; i++) {
                        if (this.#c === this.#b.byteLength) { // is buffer full?
                                this.#t += BigInt(this.#b.byteLength) // increment total byte counter
@@ -180,7 +155,7 @@ export class Blake2b {
        /**
         * Completes a BLAKE2b streaming hash.
         *
-        * @param {Uint8Array} out - Buffer to store the final output
+        * @param {Bytes} out - Buffer to store the final output
         */
        #blake2bFinal (out: Bytes): void {
                this.#t += BigInt(this.#c) // add final message block size to total bytes
@@ -214,10 +189,10 @@ export class Blake2b {
         * 32-47: salt
         * 48-64: personal
         */
-       #parameter_block: Uint8Array = new Uint8Array(64)
+       #parameter_block: Bytes = new Uint8Array(64)
        #parameter_view: DataView = new DataView(this.#parameter_block.buffer)
        /** Byte buffer which is compressed when full */
-       #b: Uint8Array = new Uint8Array(128)
+       #b: Bytes = new Uint8Array(128)
        /** Hash chain value */
        #h: BigUint64Array = new BigUint64Array(8)
        /** Total input byte count (BLAKE2b supports 2¹²⁸-1) */
@@ -241,38 +216,17 @@ export class Blake2b {
         */
        constructor (length: number, key?: Bytes, salt?: Bytes, personal?: Bytes)
        constructor (length: unknown, key?: unknown, salt?: unknown, personal?: unknown) {
-               if (length == null) {
-                       throw new TypeError(`length is required`)
-               }
-               if (typeof length !== 'number') {
-                       throw new TypeError(`length must be number`)
+               if (typeof length !== 'number' || length < OUTBYTES_MIN || length > OUTBYTES_MAX) {
+                       throw new TypeError(`length is required and must be a number between ${OUTBYTES_MIN}-${OUTBYTES_MAX}`)
                }
-               if (length < OUTBYTES_MIN || length > OUTBYTES_MAX) {
-                       throw new RangeError(`length must be ${OUTBYTES_MIN}-${OUTBYTES_MAX} bytes`)
+               if (key !== undefined && (!isBytes(key) || key.length < KEYBYTES_MIN || key.length > KEYBYTES_MAX)) {
+                       throw new RangeError(`key must be ${KEYBYTES_MIN}-${KEYBYTES_MAX} bytes`)
                }
-               if (key !== undefined) {
-                       if (!isBytes(key)) {
-                               throw new TypeError(`key must be Uint8Array or Buffer`)
-                       }
-                       if (key.length < KEYBYTES_MIN || key.length > KEYBYTES_MAX) {
-                               throw new RangeError(`key must be ${KEYBYTES_MIN}-${KEYBYTES_MAX} bytes`)
-                       }
-               }
-               if (salt !== undefined) {
-                       if (!isBytes(salt)) {
-                               throw new TypeError(`salt must be Uint8Array or Buffer`)
-                       }
-                       if (salt.length !== SALTBYTES) {
-                               throw new RangeError(`salt must be ${SALTBYTES} bytes`)
-                       }
+               if (salt !== undefined && (!isBytes(salt) || salt.length !== SALTBYTES)) {
+                       throw new RangeError(`salt must be ${SALTBYTES} bytes`)
                }
-               if (personal !== undefined) {
-                       if (!isBytes(personal)) {
-                               throw new TypeError(`personal must be Uint8Array or Buffer`)
-                       }
-                       if (personal.length !== PERSONALBYTES) {
-                               throw new RangeError(`personal must be ${PERSONALBYTES} bytes`)
-                       }
+               if (personal !== undefined && (!isBytes(personal) || personal.length !== PERSONALBYTES)) {
+                       throw new RangeError(`personal must be ${PERSONALBYTES} bytes`)
                }
                this.#blake2bInit(length, key, salt, personal)
        }
@@ -280,15 +234,13 @@ export class Blake2b {
        /**
         * Adds bytes to the context of the streaming hash.
         *
-        * @param {(ArrayBuffer|Uint8Array)} input - Bytes to hash
+        * @param {(ArrayBuffer|Bytes)} input - Bytes to hash
         * @returns {Blake2b}
         */
-       update (input: ArrayBuffer | Uint8Array): Blake2b {
-               if (input instanceof ArrayBuffer) {
-                       input = new Uint8Array(input)
-               }
-               if (!(input instanceof Uint8Array)) {
-                       throw new TypeError('Input must be ArrayBuffer or Uint8Array')
+       update (input: ArrayBuffer | Bytes): Blake2b {
+               input = new Uint8Array(input)
+               if (!isBytes(input)) {
+                       throw new TypeError('input must be bytes')
                }
                this.#blake2bUpdate(input)
                return this
index 4c53ed8ac1c9489781f27f7563f921997be853a1..6b12f862c72cdeed6aae93030c04258fc02508b0 100644 (file)
@@ -398,7 +398,23 @@ function _ckd (index: number): Promise<ArrayBuffer> {
                        return Bip44('Bitcoin seed', _seed, 0x100, index, 0, 0)
                }
                default: {
-                       return Blake2b.ckd(_seed, index)
+                       /**
+                        * Derives account private keys from a wallet seed using the BLAKE2b hashing
+                        * algorithm.
+                        *
+                        * Separately, account public keys are derived from the private key using the
+                        * Ed25519 key algorithm, and account addresses are derived from the public key
+                        * as described in the Nano documentation.
+                        * https://docs.nano.org/integration-guides/the-basics/
+                        *
+                        * @param {ArrayBuffer} seed - 32-byte secret seed of the wallet
+                        * @param {number} index - 4-byte index of account to derive
+                        * @returns {Promise<ArrayBuffer>} Private key for the account
+                       */
+                       const i = new ArrayBuffer(4)
+                       new DataView(i).setUint32(0, index, false)
+                       const sk = new Blake2b(32).update(_seed).update(i).digest()
+                       return Promise.resolve(sk.buffer)
                }
        }
 }