From 923bd426ae5d61a741f1594a7052d4b5190a53c6 Mon Sep 17 00:00:00 2001 From: Jackie Lu Date: Tue, 9 Dec 2025 16:07:27 -0800 Subject: [PATCH 1/6] fixed encoding for large values --- CHANGELOG.md | 8 +++++ docs/reference/primitives.md | 61 +++++++++++++++++++++++++----------- src/primitives/AESGCM.ts | 44 ++++++++++++++++++++------ 3 files changed, 85 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6acac84..5fea1cc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. The format ## Table of Contents - [Unreleased](#unreleased) +- [1.9.26 - 2025-12-09](#1926---2025-12-09) - [1.9.25 - 2025-12-09](#1925---2025-12-09) - [1.9.24 - 2025-12-09](#1924---2025-12-09) - [1.9.23 - 2025-12-08](#1923---2025-12-08) @@ -198,6 +199,13 @@ All notable changes to this project will be documented in this file. The format --- +## [1.9.26] - 2025-12-09 + +### Fixed +- Clarified and corrected byte-order helper behavior when converting numeric values to byte arrays. + +--- + ## [1.9.25] - 2025-12-09 ### Added diff --git a/docs/reference/primitives.md b/docs/reference/primitives.md index ac7e4d3b..9e4e9807 100644 --- a/docs/reference/primitives.md +++ b/docs/reference/primitives.md @@ -5299,24 +5299,24 @@ Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions]( | | | | | --- | --- | --- | -| [BI_EIGHT](#variable-bi_eight) | [biModSqrt](#variable-bimodsqrt) | [multiply](#variable-multiply) | -| [BI_FOUR](#variable-bi_four) | [biModSub](#variable-bimodsub) | [rightShift](#variable-rightshift) | -| [BI_ONE](#variable-bi_one) | [checkBit](#variable-checkbit) | [ripemd160](#variable-ripemd160) | -| [BI_THREE](#variable-bi_three) | [encode](#variable-encode) | [scalarMultiplyWNAF](#variable-scalarmultiplywnaf) | -| [BI_TWO](#variable-bi_two) | [exclusiveOR](#variable-exclusiveor) | [sha1](#variable-sha1) | -| [BI_ZERO](#variable-bi_zero) | [fromBase58](#variable-frombase58) | [sha256](#variable-sha256) | -| [GX_BIGINT](#variable-gx_bigint) | [fromBase58Check](#variable-frombase58check) | [sha256hmac](#variable-sha256hmac) | -| [GY_BIGINT](#variable-gy_bigint) | [getBytes](#variable-getbytes) | [sha512](#variable-sha512) | -| [MASK_256](#variable-mask_256) | [hash160](#variable-hash160) | [sha512hmac](#variable-sha512hmac) | -| [N_BIGINT](#variable-n_bigint) | [hash256](#variable-hash256) | [sign](#variable-sign) | -| [P_BIGINT](#variable-p_bigint) | [incrementLeastSignificantThirtyTwoBits](#variable-incrementleastsignificantthirtytwobits) | [toArray](#variable-toarray) | -| [P_PLUS1_DIV4](#variable-p_plus1_div4) | [jpAdd](#variable-jpadd) | [toBase58](#variable-tobase58) | -| [biMod](#variable-bimod) | [jpDouble](#variable-jpdouble) | [toBase58Check](#variable-tobase58check) | -| [biModAdd](#variable-bimodadd) | [jpNeg](#variable-jpneg) | [toHex](#variable-tohex) | -| [biModInv](#variable-bimodinv) | [minimallyEncode](#variable-minimallyencode) | [toUTF8](#variable-toutf8) | -| [biModMul](#variable-bimodmul) | [modInvN](#variable-modinvn) | [verify](#variable-verify) | -| [biModPow](#variable-bimodpow) | [modMulN](#variable-modmuln) | [zero2](#variable-zero2) | -| [biModSqr](#variable-bimodsqr) | [modN](#variable-modn) | | +| [BI_EIGHT](#variable-bi_eight) | [biModSqrt](#variable-bimodsqrt) | [modN](#variable-modn) | +| [BI_FOUR](#variable-bi_four) | [biModSub](#variable-bimodsub) | [multiply](#variable-multiply) | +| [BI_ONE](#variable-bi_one) | [checkBit](#variable-checkbit) | [rightShift](#variable-rightshift) | +| [BI_THREE](#variable-bi_three) | [encode](#variable-encode) | [ripemd160](#variable-ripemd160) | +| [BI_TWO](#variable-bi_two) | [exclusiveOR](#variable-exclusiveor) | [scalarMultiplyWNAF](#variable-scalarmultiplywnaf) | +| [BI_ZERO](#variable-bi_zero) | [fromBase58](#variable-frombase58) | [sha1](#variable-sha1) | +| [GX_BIGINT](#variable-gx_bigint) | [fromBase58Check](#variable-frombase58check) | [sha256](#variable-sha256) | +| [GY_BIGINT](#variable-gy_bigint) | [getBytes](#variable-getbytes) | [sha256hmac](#variable-sha256hmac) | +| [MASK_256](#variable-mask_256) | [getBytes64](#variable-getbytes64) | [sha512](#variable-sha512) | +| [N_BIGINT](#variable-n_bigint) | [hash160](#variable-hash160) | [sha512hmac](#variable-sha512hmac) | +| [P_BIGINT](#variable-p_bigint) | [hash256](#variable-hash256) | [sign](#variable-sign) | +| [P_PLUS1_DIV4](#variable-p_plus1_div4) | [incrementLeastSignificantThirtyTwoBits](#variable-incrementleastsignificantthirtytwobits) | [toArray](#variable-toarray) | +| [biMod](#variable-bimod) | [jpAdd](#variable-jpadd) | [toBase58](#variable-tobase58) | +| [biModAdd](#variable-bimodadd) | [jpDouble](#variable-jpdouble) | [toBase58Check](#variable-tobase58check) | +| [biModInv](#variable-bimodinv) | [jpNeg](#variable-jpneg) | [toHex](#variable-tohex) | +| [biModMul](#variable-bimodmul) | [minimallyEncode](#variable-minimallyencode) | [toUTF8](#variable-toutf8) | +| [biModPow](#variable-bimodpow) | [modInvN](#variable-modinvn) | [verify](#variable-verify) | +| [biModSqr](#variable-bimodsqr) | [modMulN](#variable-modmuln) | [zero2](#variable-zero2) | Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables) @@ -5673,6 +5673,31 @@ getBytes = function (numericValue: number): number[] { Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables) +--- +### Variable: getBytes64 + +```ts +getBytes64 = function (numericValue: number): number[] { + if (numericValue < 0 || numericValue > Number.MAX_SAFE_INTEGER) { + throw new Error("getBytes64: value out of range"); + } + const hi = Math.floor(numericValue / 4294967296); + const lo = numericValue >>> 0; + return [ + (hi >>> 24) & 255, + (hi >>> 16) & 255, + (hi >>> 8) & 255, + hi & 255, + (lo >>> 24) & 255, + (lo >>> 16) & 255, + (lo >>> 8) & 255, + lo & 255 + ]; +} +``` + +Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables) + --- ### Variable: hash160 diff --git a/src/primitives/AESGCM.ts b/src/primitives/AESGCM.ts index 18098d38..3e78a17f 100644 --- a/src/primitives/AESGCM.ts +++ b/src/primitives/AESGCM.ts @@ -202,6 +202,26 @@ export const getBytes = function (numericValue: number): number[] { ] } +export const getBytes64 = function (numericValue: number): number[] { + if (numericValue < 0 || numericValue > Number.MAX_SAFE_INTEGER) { + throw new Error('getBytes64: value out of range') + } + + const hi = Math.floor(numericValue / 0x100000000) + const lo = numericValue >>> 0 + + return [ + (hi >>> 24) & 0xFF, + (hi >>> 16) & 0xFF, + (hi >>> 8) & 0xFF, + hi & 0xFF, + (lo >>> 24) & 0xFF, + (lo >>> 16) & 0xFF, + (lo >>> 8) & 0xFF, + lo & 0xFF + ] +} + const createZeroBlock = function (length: number): number[] { return new Array(length).fill(0) } @@ -398,8 +418,10 @@ export function AESGCM ( preCounterBlock = preCounterBlock.concat(createZeroBlock(8)) - preCounterBlock = ghash(preCounterBlock.concat(createZeroBlock(4)) - .concat(getBytes(initializationVector.length * 8)), hashSubKey) + preCounterBlock = ghash( + preCounterBlock.concat(getBytes64(initializationVector.length * 8)), + hashSubKey + ) } const cipherText = gctr(plainText, incrementLeastSignificantThirtyTwoBits(preCounterBlock), key) @@ -413,9 +435,9 @@ export function AESGCM ( plainTag = plainTag.concat(createZeroBlock(16 - (cipherText.length % 16))) } - plainTag = plainTag.concat(createZeroBlock(4)) - .concat(getBytes(0)) - .concat(createZeroBlock(4)).concat(getBytes(cipherText.length * 8)) + plainTag = plainTag + .concat(getBytes64(0)) + .concat(getBytes64(cipherText.length * 8)) return { result: cipherText, @@ -457,7 +479,10 @@ export function AESGCMDecrypt ( preCounterBlock = preCounterBlock.concat(createZeroBlock(8)) - preCounterBlock = ghash(preCounterBlock.concat(createZeroBlock(4)).concat(getBytes(initializationVector.length * 8)), hashSubKey) + preCounterBlock = ghash( + preCounterBlock.concat(getBytes64(initializationVector.length * 8)), + hashSubKey + ) } // Decrypt to obtain the plain text @@ -472,10 +497,9 @@ export function AESGCMDecrypt ( compareTag = compareTag.concat(createZeroBlock(16 - (cipherText.length % 16))) } - compareTag = compareTag.concat(createZeroBlock(4)) - .concat(getBytes(0)) - .concat(createZeroBlock(4)) - .concat(getBytes(cipherText.length * 8)) + compareTag = compareTag + .concat(getBytes64(0)) + .concat(getBytes64(cipherText.length * 8)) // Generate the authentication tag const calculatedTag = gctr(ghash(compareTag, hashSubKey), preCounterBlock, key) From 8eada0eeb04ee0de5225b580b4919fc04ebf06bf Mon Sep 17 00:00:00 2001 From: Jackie Lu Date: Tue, 9 Dec 2025 16:11:14 -0800 Subject: [PATCH 2/6] changelog --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fea1cc0..f5863248 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -201,8 +201,11 @@ All notable changes to this project will be documented in this file. The format ## [1.9.26] - 2025-12-09 +### Added +- Add getBytes64 helper for 64-bit length fields. + ### Fixed -- Clarified and corrected byte-order helper behavior when converting numeric values to byte arrays. +- Use 64-bit length encoding for GHASH inputs. --- From ebfaaef9c8d268070fb8cf1dedbeab71c640020e Mon Sep 17 00:00:00 2001 From: Jackie Lu Date: Tue, 9 Dec 2025 16:11:26 -0800 Subject: [PATCH 3/6] 1.9.26 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 459ae56e..1d95cb55 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bsv/sdk", - "version": "1.9.25", + "version": "1.9.26", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bsv/sdk", - "version": "1.9.25", + "version": "1.9.26", "license": "SEE LICENSE IN LICENSE.txt", "devDependencies": { "@eslint/js": "^9.39.1", diff --git a/package.json b/package.json index 9e52d969..e3409d7d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bsv/sdk", - "version": "1.9.25", + "version": "1.9.26", "type": "module", "description": "BSV Blockchain Software Development Kit", "main": "dist/cjs/mod.js", From 180c597d53f63f185df0e4cc98bc4318159dac4d Mon Sep 17 00:00:00 2001 From: Jackie Lu Date: Thu, 11 Dec 2025 16:16:10 -0800 Subject: [PATCH 4/6] aes num to uint8 --- src/primitives/AESGCM.ts | 249 ++++++++++------- src/primitives/SymmetricKey.ts | 47 ++-- src/primitives/__tests/AESGCM.test.ts | 369 ++++++++++++++++++-------- src/primitives/hex.ts | 2 - 4 files changed, 442 insertions(+), 225 deletions(-) diff --git a/src/primitives/AESGCM.ts b/src/primitives/AESGCM.ts index 3e78a17f..cd3252ce 100644 --- a/src/primitives/AESGCM.ts +++ b/src/primitives/AESGCM.ts @@ -222,33 +222,53 @@ export const getBytes64 = function (numericValue: number): number[] { ] } -const createZeroBlock = function (length: number): number[] { - return new Array(length).fill(0) +type Bytes = Uint8Array + +const createZeroBlock = function (length: number): Bytes { + // Uint8Array is already zero-filled + return new Uint8Array(length) } -const R = [0xe1].concat(createZeroBlock(15)) +// R = 0xe1 || 15 zero bytes +const R: Bytes = (() => { + const r = new Uint8Array(16) + r[0] = 0xe1 + return r +})() + +const concatBytes = (...arrays: Bytes[]): Bytes => { + let total = 0 + for (const a of arrays) total += a.length + + const out = new Uint8Array(total) + let offset = 0 + for (const a of arrays) { + out.set(a, offset) + offset += a.length + } + return out +} -export const exclusiveOR = function (block0: number[], block1: number[]): number[] { +export const exclusiveOR = function (block0: Bytes, block1: Bytes): Bytes { const len = block0.length - const result = new Array(len) + const result = new Uint8Array(len) for (let i = 0; i < len; i++) { - result[i] = block0[i] ^ block1[i] + result[i] = block0[i] ^ (block1[i] ?? 0) } return result } -const xorInto = function (target: number[], block: number[]): void { +const xorInto = function (target: Bytes, block: Bytes): void { for (let i = 0; i < target.length; i++) { - target[i] ^= block[i] + target[i] ^= block[i] ?? 0 } } -export const rightShift = function (block: number[]): number[] { - let i: number +export const rightShift = function (block: Bytes): Bytes { let carry = 0 let oldCarry = 0 - for (i = 0; i < block.length; i++) { + for (let i = 0; i < block.length; i++) { oldCarry = carry carry = block[i] & 0x01 block[i] = block[i] >> 1 @@ -261,7 +281,7 @@ export const rightShift = function (block: number[]): number[] { return block } -export const multiply = function (block0: number[], block1: number[]): number[] { +export const multiply = function (block0: Bytes, block1: Bytes): Bytes { const v = block1.slice() const z = createZeroBlock(16) @@ -284,16 +304,14 @@ export const multiply = function (block0: number[], block1: number[]): number[] } export const incrementLeastSignificantThirtyTwoBits = function ( - block: number[] -): number[] { - let i + block: Bytes +): Bytes { const result = block.slice() - for (i = 15; i !== 11; i--) { - result[i] = result[i] + 1 - if (result[i] === 256) { - result[i] = 0 - } else { + for (let i = 15; i !== 11; i--) { + result[i] = (result[i] + 1) & 0xff // wrap explicitly + + if (result[i] !== 0) { break } } @@ -301,11 +319,12 @@ export const incrementLeastSignificantThirtyTwoBits = function ( return result } -export function ghash (input: number[], hashSubKey: number[]): number[] { +export function ghash (input: Bytes, hashSubKey: Bytes): Bytes { let result = createZeroBlock(16) + const block = new Uint8Array(16) for (let i = 0; i < input.length; i += 16) { - const block = result.slice() + block.set(result) for (let j = 0; j < 16; j++) { block[j] ^= input[i + j] ?? 0 } @@ -316,14 +335,14 @@ export function ghash (input: number[], hashSubKey: number[]): number[] { } function gctr ( - input: number[], - initialCounterBlock: number[], - key: number[] -): number[] { - if (input.length === 0) return [] - - const output = new Array(input.length) - let counterBlock = initialCounterBlock + input: Bytes, + initialCounterBlock: Bytes, + key: Bytes +): Bytes { + if (input.length === 0) return new Uint8Array(0) + + const output = new Uint8Array(input.length) + let counterBlock = initialCounterBlock.slice() let pos = 0 const n = Math.ceil(input.length / 16) @@ -343,6 +362,42 @@ function gctr ( return output } +function buildAuthInput (cipherText: Bytes): Bytes { + const aadLenBits = 0 + const ctLenBits = cipherText.length * 8 + + const padLen = + cipherText.length === 0 + ? 16 + : (cipherText.length % 16 === 0 ? 0 : 16 - (cipherText.length % 16)) + + const total = + 16 + + cipherText.length + + padLen + + 16 + + const out = new Uint8Array(total) + let offset = 0 + + offset += 16 + + out.set(cipherText, offset) + offset += cipherText.length + + offset += padLen + + const aadLen = getBytes64(aadLenBits) + out.set(aadLen, offset) + offset += 8 + + const ctLen = getBytes64(ctLenBits) + out.set(ctLen, offset) + offset += 8 + + return out +} + /** * SECURITY NOTE – NON-STANDARD AES-GCM PADDING * @@ -391,10 +446,10 @@ function gctr ( * undecryptable by newer versions of the library. */ export function AESGCM ( - plainText: number[], - initializationVector: number[], - key: number[] -): { result: number[], authenticationTag: number[] } { + plainText: Bytes, + initializationVector: Bytes, + key: Bytes +): { result: Bytes, authenticationTag: Bytes } { if (initializationVector.length === 0) { throw new Error('Initialization vector must not be empty') } @@ -403,54 +458,54 @@ export function AESGCM ( throw new Error('Key must not be empty') } - let preCounterBlock - let plainTag: number[] = [] - const hashSubKey = AES(createZeroBlock(16), key) - preCounterBlock = [...initializationVector] + const hashSubKey = new Uint8Array(AES(createZeroBlock(16), key)) + + let preCounterBlock: Bytes + if (initializationVector.length === 12) { - preCounterBlock = preCounterBlock.concat(createZeroBlock(3)).concat([0x01]) + preCounterBlock = concatBytes(initializationVector, createZeroBlock(3), new Uint8Array([0x01])) } else { - if (initializationVector.length % 16 !== 0) { - preCounterBlock = preCounterBlock.concat( - createZeroBlock(16 - (initializationVector.length % 16)) + let ivPadded = initializationVector + if (ivPadded.length % 16 !== 0) { + ivPadded = concatBytes( + ivPadded, + createZeroBlock(16 - (ivPadded.length % 16)) ) } - preCounterBlock = preCounterBlock.concat(createZeroBlock(8)) - - preCounterBlock = ghash( - preCounterBlock.concat(getBytes64(initializationVector.length * 8)), - hashSubKey + const lenBlock = getBytes64(initializationVector.length * 8) + const s = concatBytes( + ivPadded, + createZeroBlock(8), + new Uint8Array(lenBlock) ) - } - const cipherText = gctr(plainText, incrementLeastSignificantThirtyTwoBits(preCounterBlock), key) + preCounterBlock = ghash(s, hashSubKey) + } - plainTag = plainTag.concat(createZeroBlock(16)) - plainTag = plainTag.concat(cipherText) + const cipherText = gctr( + plainText, + incrementLeastSignificantThirtyTwoBits(preCounterBlock), + key + ) - if (cipherText.length === 0) { - plainTag = plainTag.concat(createZeroBlock(16)) - } else if (cipherText.length % 16 !== 0) { - plainTag = plainTag.concat(createZeroBlock(16 - (cipherText.length % 16))) - } + const authInput = buildAuthInput(cipherText) - plainTag = plainTag - .concat(getBytes64(0)) - .concat(getBytes64(cipherText.length * 8)) + const s = ghash(authInput, hashSubKey) + const authenticationTag = gctr(s, preCounterBlock, key) return { result: cipherText, - authenticationTag: gctr(ghash(plainTag, hashSubKey), preCounterBlock, key) + authenticationTag } } export function AESGCMDecrypt ( - cipherText: number[], - initializationVector: number[], - authenticationTag: number[], - key: number[] -): number[] | null { + cipherText: Bytes, + initializationVector: Bytes, + authenticationTag: Bytes, + key: Bytes +): Bytes | null { if (cipherText.length === 0) { throw new Error('Cipher text must not be empty') } @@ -463,49 +518,57 @@ export function AESGCMDecrypt ( throw new Error('Key must not be empty') } - let preCounterBlock - let compareTag: number[] = [] - // Generate the hash subkey - const hashSubKey = AES(createZeroBlock(16), key) + const hashSubKey = new Uint8Array(AES(createZeroBlock(16), key)) + + let preCounterBlock: Bytes - preCounterBlock = [...initializationVector] if (initializationVector.length === 12) { - preCounterBlock = preCounterBlock.concat(createZeroBlock(3)).concat([0x01]) + preCounterBlock = concatBytes( + initializationVector, + createZeroBlock(3), + new Uint8Array([0x01]) + ) } else { - if (initializationVector.length % 16 !== 0) { - preCounterBlock = preCounterBlock.concat(createZeroBlock(16 - (initializationVector.length % 16))) + let ivPadded = initializationVector + if (ivPadded.length % 16 !== 0) { + ivPadded = concatBytes( + ivPadded, + createZeroBlock(16 - (ivPadded.length % 16)) + ) } - preCounterBlock = preCounterBlock.concat(createZeroBlock(8)) - - preCounterBlock = ghash( - preCounterBlock.concat(getBytes64(initializationVector.length * 8)), - hashSubKey + const lenBlock = getBytes64(initializationVector.length * 8) + const s = concatBytes( + ivPadded, + createZeroBlock(8), + new Uint8Array(lenBlock) ) + + preCounterBlock = ghash(s, hashSubKey) } // Decrypt to obtain the plain text - const plainText = gctr(cipherText, incrementLeastSignificantThirtyTwoBits(preCounterBlock), key) + const plainText = gctr( + cipherText, + incrementLeastSignificantThirtyTwoBits(preCounterBlock), + key + ) - compareTag = compareTag.concat(createZeroBlock(16)) - compareTag = compareTag.concat(cipherText) + const authInput = buildAuthInput(cipherText) + const s = ghash(authInput, hashSubKey) + const calculatedTag = gctr(s, preCounterBlock, key) - if (cipherText.length === 0) { - compareTag = compareTag.concat(createZeroBlock(16)) - } else if (cipherText.length % 16 !== 0) { - compareTag = compareTag.concat(createZeroBlock(16 - (cipherText.length % 16))) + if (calculatedTag.length !== authenticationTag.length) { + return null } - compareTag = compareTag - .concat(getBytes64(0)) - .concat(getBytes64(cipherText.length * 8)) - - // Generate the authentication tag - const calculatedTag = gctr(ghash(compareTag, hashSubKey), preCounterBlock, key) + let diff = 0 + for (let i = 0; i < calculatedTag.length; i++) { + diff |= calculatedTag[i] ^ authenticationTag[i] + } - // If the calculated tag does not match the provided tag, return null - the decryption failed. - if (calculatedTag.join() !== authenticationTag.join()) { + if (diff !== 0) { return null } diff --git a/src/primitives/SymmetricKey.ts b/src/primitives/SymmetricKey.ts index e5365748..364572f1 100644 --- a/src/primitives/SymmetricKey.ts +++ b/src/primitives/SymmetricKey.ts @@ -41,19 +41,27 @@ export default class SymmetricKey extends BigNumber { * const encryptedMessage = key.encrypt('plainText', 'utf8'); */ encrypt (msg: number[] | string, enc?: 'hex'): string | number[] { - const iv = Random(32) - msg = toArray(msg, enc) - const keyBytes = this.toArray('be', 32) - const { result, authenticationTag } = AESGCM(msg, iv, keyBytes) + const iv = new Uint8Array(Random(32)) + const msgBytes = new Uint8Array(toArray(msg, enc)) + const keyBytes = new Uint8Array(this.toArray('be', 32)) + + const { result, authenticationTag } = AESGCM( + msgBytes, + iv, + keyBytes + ) + const totalLength = iv.length + result.length + authenticationTag.length - const combined = new Array(totalLength) + const combined = new Uint8Array(totalLength) let offset = 0 - for (const chunk of [iv, result, authenticationTag]) { - for (let i = 0; i < chunk.length; i++) { - combined[offset++] = chunk[i] - } - } - return encode(combined, enc) + + combined.set(iv, offset) + offset += iv.length + combined.set(result, offset) + offset += result.length + combined.set(authenticationTag, offset) + + return encode(Array.from(combined), enc) } /** @@ -73,29 +81,30 @@ export default class SymmetricKey extends BigNumber { * @throws {Error} Will throw an error if the decryption fails, likely due to message tampering or incorrect decryption key. */ decrypt (msg: number[] | string, enc?: 'hex' | 'utf8'): string | number[] { - msg = toArray(msg, enc) + const msgBytes = new Uint8Array(toArray(msg, enc)) const ivLength = 32 const tagLength = 16 - if (msg.length < ivLength + tagLength) { + if (msgBytes.length < ivLength + tagLength) { throw new Error('Ciphertext too short') } - const iv = msg.slice(0, ivLength) - const tagStart = msg.length - tagLength - const ciphertext = msg.slice(ivLength, tagStart) - const messageTag = msg.slice(tagStart) + const iv = msgBytes.slice(0, ivLength) + const tagStart = msgBytes.length - tagLength + const ciphertext = msgBytes.slice(ivLength, tagStart) + const messageTag = msgBytes.slice(tagStart) + const keyBytes = new Uint8Array(this.toArray('be', 32)) const result = AESGCMDecrypt( ciphertext, iv, messageTag, - this.toArray('be', 32) + keyBytes ) if (result === null) { throw new Error('Decryption failed!') } - return encode(result, enc) + return encode(Array.from(result), enc) } } diff --git a/src/primitives/__tests/AESGCM.test.ts b/src/primitives/__tests/AESGCM.test.ts index f5645c43..d3965d72 100644 --- a/src/primitives/__tests/AESGCM.test.ts +++ b/src/primitives/__tests/AESGCM.test.ts @@ -68,258 +68,335 @@ describe('AES', () => { describe('ghash', () => { it('should ghash', () => { - expect(toArray('f38cbb1ad69223dcc3457ae5b6b0f885', 'hex')).toEqual( - ghash( - toArray( - '000000000000000000000000000000000388dace60b6a392f328c2b971b2fe780000000000000000000000' + - '0000000080', - 'hex' - ), - toArray('66e94bd4ef8a2c3b884cfa59ca342b2e', 'hex') + const input = new Uint8Array( + toArray( + '000000000000000000000000000000000388dace60b6a392f328c2b971b2fe780000000000000000000000' + + '0000000080', + 'hex' ) ) + const h = new Uint8Array( + toArray('66e94bd4ef8a2c3b884cfa59ca342b2e', 'hex') + ) + const out = ghash(input, h) + + expect(toArray('f38cbb1ad69223dcc3457ae5b6b0f885', 'hex')).toEqual( + Array.from(out) + ) }) }) describe('AESGCM', () => { it('should encrypt: Test Case 1', () => { - const output = AESGCM( - [], - toArray('000000000000000000000000', 'hex'), + const plainText = new Uint8Array(0) + const iv = new Uint8Array( + toArray('000000000000000000000000', 'hex') + ) + const key = new Uint8Array( toArray('00000000000000000000000000000000', 'hex') ) - expect([]).toEqual(output.result) + const output = AESGCM(plainText, iv, key) + + expect([]).toEqual(Array.from(output.result)) expect(toArray('58e2fccefa7e3061367f1d57a4e7455a', 'hex')).toEqual( - output.authenticationTag + Array.from(output.authenticationTag) ) }) it('should encrypt: Test Case 2', () => { - const output = AESGCM( - toArray('00000000000000000000000000000000', 'hex'), - toArray('000000000000000000000000', 'hex'), + const plainText = new Uint8Array( toArray('00000000000000000000000000000000', 'hex') ) + const iv = new Uint8Array( + toArray('000000000000000000000000', 'hex') + ) + const key = new Uint8Array( + toArray('00000000000000000000000000000000', 'hex') + ) + + const output = AESGCM(plainText, iv, key) expect(toArray('0388dace60b6a392f328c2b971b2fe78', 'hex')).toEqual( - output.result + Array.from(output.result) ) expect(toArray('ab6e47d42cec13bdf53a67b21257bddf', 'hex')).toEqual( - output.authenticationTag + Array.from(output.authenticationTag) ) }) it('should encrypt: Test Case 3', () => { - const output = AESGCM( + const plainText = new Uint8Array( toArray( 'd9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956' + '809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255', 'hex' - ), - toArray('cafebabefacedbaddecaf888', 'hex'), + ) + ) + const iv = new Uint8Array( + toArray('cafebabefacedbaddecaf888', 'hex') + ) + const key = new Uint8Array( toArray('feffe9928665731c6d6a8f9467308308', 'hex') ) + const output = AESGCM(plainText, iv, key) + expect( toArray( '42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8' + 'f6a5aac84aa051ba30b396a0aac973d58e091473f5985', 'hex' ) - ).toEqual(output.result) + ).toEqual(Array.from(output.result)) expect(toArray('4d5c2af327cd64a62cf35abd2ba6fab4', 'hex')).toEqual( - output.authenticationTag + Array.from(output.authenticationTag) ) }) it('should encrypt: Test Case 7', () => { - const output = AESGCM( - [], - toArray('000000000000000000000000', 'hex'), + const plainText = new Uint8Array(0) + const iv = new Uint8Array( + toArray('000000000000000000000000', 'hex') + ) + const key = new Uint8Array( toArray('000000000000000000000000000000000000000000000000', 'hex') ) - expect([]).toEqual(output.result) + const output = AESGCM(plainText, iv, key) + + expect([]).toEqual(Array.from(output.result)) expect(toArray('cd33b28ac773f74ba00ed1f312572435', 'hex')).toEqual( - output.authenticationTag + Array.from(output.authenticationTag) ) }) it('should encrypt: Test Case 8', () => { - const output = AESGCM( - toArray('00000000000000000000000000000000', 'hex'), - toArray('000000000000000000000000', 'hex'), + const plainText = new Uint8Array( + toArray('00000000000000000000000000000000', 'hex') + ) + const iv = new Uint8Array( + toArray('000000000000000000000000', 'hex') + ) + const key = new Uint8Array( toArray('000000000000000000000000000000000000000000000000', 'hex') ) + const output = AESGCM(plainText, iv, key) + expect(toArray('98e7247c07f0fe411c267e4384b0f600', 'hex')).toEqual( - output.result + Array.from(output.result) ) expect(toArray('2ff58d80033927ab8ef4d4587514f0fb', 'hex')).toEqual( - output.authenticationTag + Array.from(output.authenticationTag) ) }) it('should encrypt: Test Case 9', () => { - const output = AESGCM( + const plainText = new Uint8Array( toArray( 'd9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956' + '809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255', 'hex' - ), - toArray('cafebabefacedbaddecaf888', 'hex'), + ) + ) + const iv = new Uint8Array( + toArray('cafebabefacedbaddecaf888', 'hex') + ) + const key = new Uint8Array( toArray('feffe9928665731c6d6a8f9467308308feffe9928665731c', 'hex') ) + const output = AESGCM(plainText, iv, key) + expect( toArray( '3980ca0b3c00e841eb06fac4872a2757859e1ceaa6efd984628593b40ca1e19c7d773d00c144c525ac6' + '19d18c84a3f4718e2448b2fe324d9ccda2710acade256', 'hex' ) - ).toEqual(output.result) + ).toEqual(Array.from(output.result)) expect(toArray('9924a7c8587336bfb118024db8674a14', 'hex')).toEqual( - output.authenticationTag + Array.from(output.authenticationTag) ) }) it('should encrypt: Test Case 13', () => { - const output = AESGCM( - [], - toArray('000000000000000000000000', 'hex'), + const plainText = new Uint8Array(0) + const iv = new Uint8Array( + toArray('000000000000000000000000', 'hex') + ) + const key = new Uint8Array( toArray( '0000000000000000000000000000000000000000000000000000000000000000', 'hex' ) ) - expect([]).toEqual(output.result) + const output = AESGCM(plainText, iv, key) + + expect([]).toEqual(Array.from(output.result)) expect(toArray('530f8afbc74536b9a963b4f1c4cb738b', 'hex')).toEqual( - output.authenticationTag + Array.from(output.authenticationTag) ) }) it('should encrypt: Test Case 14', () => { - const output = AESGCM( - toArray('00000000000000000000000000000000', 'hex'), - toArray('000000000000000000000000', 'hex'), + const plainText = new Uint8Array( + toArray('00000000000000000000000000000000', 'hex') + ) + const iv = new Uint8Array( + toArray('000000000000000000000000', 'hex') + ) + const key = new Uint8Array( toArray( '0000000000000000000000000000000000000000000000000000000000000000', 'hex' ) ) + const output = AESGCM(plainText, iv, key) + expect(toArray('cea7403d4d606b6e074ec5d3baf39d18', 'hex')).toEqual( - output.result + Array.from(output.result) ) expect(toArray('d0d1c8a799996bf0265b98b5d48ab919', 'hex')).toEqual( - output.authenticationTag + Array.from(output.authenticationTag) ) }) it('should encrypt: Test Case 15', () => { - const output = AESGCM( + const plainText = new Uint8Array( toArray( 'd9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956' + '809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255', 'hex' - ), - toArray('cafebabefacedbaddecaf888', 'hex'), + ) + ) + const iv = new Uint8Array( + toArray('cafebabefacedbaddecaf888', 'hex') + ) + const key = new Uint8Array( toArray( 'feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308', 'hex' ) ) + const output = AESGCM(plainText, iv, key) + expect( toArray( '522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b' + '08b1056828838c5f61e6393ba7a0abcc9f662898015ad', 'hex' ) - ).toEqual(output.result) + ).toEqual(Array.from(output.result)) expect(toArray('b094dac5d93471bdec1a502270e3cc6c', 'hex')).toEqual( - output.authenticationTag + Array.from(output.authenticationTag) ) }) }) describe('exclusiveOR', () => { it('should exclusiveOR', () => { + const out1 = exclusiveOR( + new Uint8Array([ + 0xf0, 0xf8, 0x7f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00 + ]), + new Uint8Array([0x0f, 0x0f, 0x00, 0xf0]) + ) + expect([ 0xff, 0xf7, 0x7f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - ]).toEqual( - exclusiveOR( - [ - 0xf0, 0xf8, 0x7f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00 - ], - [0x0f, 0x0f, 0x00, 0xf0] - ) - ) + ]).toEqual(Array.from(out1)) - expect([0xff, 0xf7, 0x7f, 0x0f]).toEqual( - exclusiveOR([0xf0, 0xf8, 0x7f, 0xff], [0x0f, 0x0f, 0x00, 0xf0]) + const out2 = exclusiveOR( + new Uint8Array([0xf0, 0xf8, 0x7f, 0xff]), + new Uint8Array([0x0f, 0x0f, 0x00, 0xf0]) ) + + expect([0xff, 0xf7, 0x7f, 0x0f]).toEqual(Array.from(out2)) }) }) describe('rightShift', () => { it('should rightShift', () => { + const input = new Uint8Array( + toArray('7b5b54657374566563746f725d53475d', 'hex') + ) + const out = rightShift(input) + expect(toArray('3dadaa32b9ba2b32b1ba37b92ea9a3ae', 'hex')).toEqual( - rightShift(toArray('7b5b54657374566563746f725d53475d', 'hex')) + Array.from(out) ) }) }) describe('multiply', () => { it('should multiply', () => { + const a = new Uint8Array( + toArray('952b2a56a5604ac0b32b6656a05b40b6', 'hex') + ) + const b = new Uint8Array( + toArray('dfa6bf4ded81db03ffcaff95f830f061', 'hex') + ) + const out = multiply(a, b) + expect(toArray('da53eb0ad2c55bb64fc4802cc3feda60', 'hex')).toEqual( - multiply( - toArray('952b2a56a5604ac0b32b6656a05b40b6', 'hex'), - toArray('dfa6bf4ded81db03ffcaff95f830f061', 'hex') - ) + Array.from(out) ) }) it('should commutatively multiply', () => { - expect( - multiply( - toArray('48692853686179295b477565726f6e5d', 'hex'), - toArray('7b5b54657374566563746f725d53475d', 'hex') - ) - ).toEqual( - multiply( - toArray('7b5b54657374566563746f725d53475d', 'hex'), - toArray('48692853686179295b477565726f6e5d', 'hex') - ) + const x = new Uint8Array( + toArray('48692853686179295b477565726f6e5d', 'hex') + ) + const y = new Uint8Array( + toArray('7b5b54657374566563746f725d53475d', 'hex') ) + + const out1 = multiply(x, y) + const out2 = multiply(y, x) + + expect(Array.from(out1)).toEqual(Array.from(out2)) }) }) describe('incrementLeastSignificantThirtyTwoBits', () => { it('should incrementLeastSignificantThirtyTwoBits', () => { + const in1 = new Uint8Array( + toArray('00000000000000000000000000000000', 'hex') + ) + const out1 = incrementLeastSignificantThirtyTwoBits(in1) expect(toArray('00000000000000000000000000000001', 'hex')).toEqual( - incrementLeastSignificantThirtyTwoBits( - toArray('00000000000000000000000000000000', 'hex') - ) + Array.from(out1) ) + + const in2 = new Uint8Array( + toArray('000000000000000000000000000000ff', 'hex') + ) + const out2 = incrementLeastSignificantThirtyTwoBits(in2) expect(toArray('00000000000000000000000000000100', 'hex')).toEqual( - incrementLeastSignificantThirtyTwoBits( - toArray('000000000000000000000000000000ff', 'hex') - ) + Array.from(out2) ) + + const in3 = new Uint8Array( + toArray('00000000000000000000000000ffffff', 'hex') + ) + const out3 = incrementLeastSignificantThirtyTwoBits(in3) expect(toArray('00000000000000000000000001000000', 'hex')).toEqual( - incrementLeastSignificantThirtyTwoBits( - toArray('00000000000000000000000000ffffff', 'hex') - ) + Array.from(out3) + ) + + const in4 = new Uint8Array( + toArray('000000000000000000000000ffffffff', 'hex') ) + const out4 = incrementLeastSignificantThirtyTwoBits(in4) expect(toArray('00000000000000000000000000000000', 'hex')).toEqual( - incrementLeastSignificantThirtyTwoBits( - toArray('000000000000000000000000ffffffff', 'hex') - ) + Array.from(out4) ) }) }) @@ -329,7 +406,9 @@ describe('checkBit', () => { let i let j let k = 0 - let block = toArray('7b5b54657374566563746f725d53475d', 'hex') + let block = new Uint8Array( + toArray('7b5b54657374566563746f725d53475d', 'hex') + ) as any const expected = [ 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, @@ -342,12 +421,12 @@ describe('checkBit', () => { for (i = 0; i < 16; i++) { for (j = 7; j !== -1; j--) { - expect(expected[k++]).toEqual(checkBit(block, i, j)) + expect(expected[k++]).toEqual(checkBit(Array.from(block), i, j)) } } for (i = 0; i < 128; i++) { - expect(expectedLSB[i]).toEqual(checkBit(block, 15, 0)) + expect(expectedLSB[i]).toEqual(checkBit(Array.from(block), 15, 0)) block = rightShift(block) } }) @@ -400,55 +479,123 @@ describe('getBytes', () => { }) describe('AESGCM IV validation', () => { - const key = new Array(16).fill(0x01) - const plaintext = [1, 2, 3, 4] + const key = new Uint8Array(new Array(16).fill(0x01)) + const plaintext = new Uint8Array([1, 2, 3, 4]) it('AESGCM throws when IV is empty', () => { expect(() => { - AESGCM(plaintext, [], key) + AESGCM(plaintext, new Uint8Array(), key) }).toThrow(new Error('Initialization vector must not be empty')) }) it('AESGCMDecrypt throws when IV is empty', () => { - const iv = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] - const { result: ciphertext, authenticationTag } = AESGCM(plaintext, iv, key) + const iv = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) + const { result: ciphertext, authenticationTag } = AESGCM( + plaintext, + iv, + key + ) // Now call decrypt but with an empty IV – this should be rejected expect(() => { - AESGCMDecrypt(ciphertext, [], authenticationTag, key) + AESGCMDecrypt(ciphertext, new Uint8Array(), authenticationTag, key) }).toThrow(new Error('Initialization vector must not be empty')) }) it('AESGCM throws when key is empty', () => { - const iv = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + const iv = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) expect(() => { - AESGCM(plaintext, iv, []) + AESGCM(plaintext, iv, new Uint8Array()) }).toThrow(new Error('Key must not be empty')) }) it('AESGCMDecrypt throws when key is empty', () => { - const iv = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] - const { result: ciphertext, authenticationTag } = AESGCM(plaintext, iv, key) + const iv = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) + const { result: ciphertext, authenticationTag } = AESGCM( + plaintext, + iv, + key + ) expect(() => { - AESGCMDecrypt(ciphertext, iv, authenticationTag, []) + AESGCMDecrypt( + ciphertext, + iv, + authenticationTag, + new Uint8Array() + ) }).toThrow(new Error('Key must not be empty')) }) it('AESGCMDecrypt throws when cipher text is empty', () => { - const iv = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + const iv = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) expect(() => { - AESGCMDecrypt([], iv, [], key) + AESGCMDecrypt(new Uint8Array(), iv, new Uint8Array(), key) }).toThrow(new Error('Cipher text must not be empty')) }) it('AESGCM still work with a valid IV', () => { - const iv = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] - const { result: ciphertext, authenticationTag } = AESGCM(plaintext, iv, key) - const decrypted = AESGCMDecrypt(ciphertext, iv, authenticationTag, key) + const iv = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) + const { result: ciphertext, authenticationTag } = AESGCM( + plaintext, + iv, + key + ) + const decrypted = AESGCMDecrypt( + ciphertext, + iv, + authenticationTag, + key + ) as Uint8Array + + expect(Array.from(decrypted)).toEqual(Array.from(plaintext)) + }) +}) + - expect(decrypted).toEqual(plaintext) +function expectUint8ArrayEqual(a: Uint8Array, b: Uint8Array) { + expect(a.length).toBe(b.length) + + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) { + throw new Error(`mismatch at index ${i}: ${a[i]} !== ${b[i]}`) + } + } +} + +describe('AESGCM large input (non-mocked)', () => { + // NOTE: This test is intentionally skipped by default because it allocates + // ~500MB+ and will be very slow / memory-heavy. + // Un-skip locally when you want to manually verify behavior for lengths + // larger than 2^32 bits. + it.skip('handles ciphertext longer than 2^32 bits', () => { + const key = new Uint8Array(new Array(16).fill(0x01)) + const iv = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) + + // 2^32 bits = 2^29 bytes. Go just beyond that boundary. + const bigSizeBytes = (1 << 26) + 16 // 2^29 + 16 bytes (> 2^32 bits) + + // Use a typed array instead of a giant sparse JS array. + const plaintext = new Uint8Array(bigSizeBytes) // already zero-initialized + + const { result: ciphertext, authenticationTag } = AESGCM( + plaintext, + iv, + key + ) + + const decrypted = AESGCMDecrypt( + ciphertext, + iv, + authenticationTag, + key + ) as Uint8Array | null + + expect(decrypted).not.toBeNull() + const decryptedBytes = decrypted as Uint8Array + expect(decryptedBytes.length).toBe(bigSizeBytes) + expectUint8ArrayEqual(decryptedBytes, plaintext) }) }) diff --git a/src/primitives/hex.ts b/src/primitives/hex.ts index 655c7723..d1385689 100644 --- a/src/primitives/hex.ts +++ b/src/primitives/hex.ts @@ -5,7 +5,6 @@ const PURE_HEX_REGEX = /^[0-9a-fA-F]*$/ export function assertValidHex (msg: string): void { if (typeof msg !== 'string') { - console.error('assertValidHex FAIL (non-string):', msg) throw new Error('Invalid hex string') } @@ -13,7 +12,6 @@ export function assertValidHex (msg: string): void { if (msg.length === 0) return if (!PURE_HEX_REGEX.test(msg)) { - console.error('assertValidHex FAIL (bad hex):', msg) throw new Error('Invalid hex string') } } From 4062a02c63e447a93bd91d56a70f89e7e0e87097 Mon Sep 17 00:00:00 2001 From: Jackie Lu Date: Thu, 11 Dec 2025 16:31:50 -0800 Subject: [PATCH 5/6] 1.9.28 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 708c91fe..72a53fab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bsv/sdk", - "version": "1.9.27", + "version": "1.9.28", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bsv/sdk", - "version": "1.9.27", + "version": "1.9.28", "license": "SEE LICENSE IN LICENSE.txt", "devDependencies": { "@eslint/js": "^9.39.1", diff --git a/package.json b/package.json index 241e7f17..2a11df72 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bsv/sdk", - "version": "1.9.27", + "version": "1.9.28", "type": "module", "description": "BSV Blockchain Software Development Kit", "main": "dist/cjs/mod.js", From 89d369d04d139a8e0d6af9bec24adcd5e1a1fe7b Mon Sep 17 00:00:00 2001 From: Jackie Lu Date: Thu, 11 Dec 2025 16:46:21 -0800 Subject: [PATCH 6/6] sonarqube issues --- src/primitives/AESGCM.ts | 15 +++++++++------ src/primitives/hex.ts | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/primitives/AESGCM.ts b/src/primitives/AESGCM.ts index cd3252ce..22eaf4da 100644 --- a/src/primitives/AESGCM.ts +++ b/src/primitives/AESGCM.ts @@ -308,7 +308,7 @@ export const incrementLeastSignificantThirtyTwoBits = function ( ): Bytes { const result = block.slice() - for (let i = 15; i !== 11; i--) { + for (let i = 15; i > 11; i--) { result[i] = (result[i] + 1) & 0xff // wrap explicitly if (result[i] !== 0) { @@ -366,10 +366,14 @@ function buildAuthInput (cipherText: Bytes): Bytes { const aadLenBits = 0 const ctLenBits = cipherText.length * 8 - const padLen = - cipherText.length === 0 - ? 16 - : (cipherText.length % 16 === 0 ? 0 : 16 - (cipherText.length % 16)) + let padLen: number + if (cipherText.length === 0) { + padLen = 16 + } else if (cipherText.length % 16 === 0) { + padLen = 0 + } else { + padLen = 16 - (cipherText.length % 16) + } const total = 16 + @@ -393,7 +397,6 @@ function buildAuthInput (cipherText: Bytes): Bytes { const ctLen = getBytes64(ctLenBits) out.set(ctLen, offset) - offset += 8 return out } diff --git a/src/primitives/hex.ts b/src/primitives/hex.ts index d1385689..bc0b0fa1 100644 --- a/src/primitives/hex.ts +++ b/src/primitives/hex.ts @@ -5,7 +5,7 @@ const PURE_HEX_REGEX = /^[0-9a-fA-F]*$/ export function assertValidHex (msg: string): void { if (typeof msg !== 'string') { - throw new Error('Invalid hex string') + throw new TypeError('Invalid hex string') } // allow empty