From: Chris Duncan Date: Wed, 1 Jul 2026 06:45:41 +0000 (-0700) Subject: Extract blake ckd to vault to leave hash function more pure. Clean up blake input... X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=3af23a3f9cd2bf6ee43254ecac545f21af3cd51e;p=libnemo.git Extract blake ckd to vault to leave hash function more pure. Clean up blake input validation. --- diff --git a/src/lib/crypto/blake2b.ts b/src/lib/crypto/blake2b.ts index 2b97046..4dc3153 100644 --- a/src/lib/crypto/blake2b.ts +++ b/src/lib/crypto/blake2b.ts @@ -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} Private key for the account - */ - static ckd (seed: ArrayBuffer, index: number): Promise { - 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 diff --git a/src/lib/vault/vault-worker.ts b/src/lib/vault/vault-worker.ts index 4c53ed8..6b12f86 100644 --- a/src/lib/vault/vault-worker.ts +++ b/src/lib/vault/vault-worker.ts @@ -398,7 +398,23 @@ function _ckd (index: number): Promise { 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} 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) } } }