We can not sensibly parse both hex and base64, assume all input is hex

This commit is contained in:
2026-03-10 17:48:51 +01:00
parent 7a2522cf32
commit a30448c130
4 changed files with 45 additions and 61 deletions

View File

@@ -1,13 +1,12 @@
import { equalBytes } from "@noble/ciphers/utils.js";
import { bytesToHex, hexToBytes } from "@noble/hashes/utils.js";
import { bytesToHex, hexToBytes as nobleHexToBytes } from "@noble/hashes/utils.js";
export {
bytesToHex,
hexToBytes,
equalBytes
};
export const base64ToBytes = (base64: string): Uint8Array => {
export const base64ToBytes = (base64: string, size?: number): Uint8Array => {
// Normalize URL-safe base64 to standard base64
let normalized = base64.replace(/-/g, '+').replace(/_/g, '/');
// Add padding if missing
@@ -19,32 +18,23 @@ export const base64ToBytes = (base64: string): Uint8Array => {
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
if (size !== undefined && bytes.length !== size) {
throw new Error(`Invalid base64 length: expected ${size} bytes, got ${bytes.length}`);
}
return bytes;
}
export const encodedStringToBytes = (str: string, size: number): Uint8Array => {
const hexRegex = /^[0-9a-fA-F]+$/;
// Accept both standard and URL-safe base64, with or without padding
const b64Regex = /^(?:[A-Za-z0-9+\/_-]{4})*(?:[A-Za-z0-9+\/_-]{2}(?:==)?|[A-Za-z0-9+\/_-]{3}=?)?$/;
// Note: encodedStringToBytes removed — prefer explicit parsers.
// Use `hexToBytes` for hex inputs and `base64ToBytes` for base64 inputs.
if (hexRegex.test(str) && str.length === size * 2) {
return hexToBytes(str);
} else if (b64Regex.test(str)) {
const bytes = base64ToBytes(str);
if (bytes.length === size) {
return bytes;
}
} else if (str.length === size) {
// Raw format: treat as bytes (latin1)
const bytes = new Uint8Array(size);
for (let i = 0; i < size; i++) {
bytes[i] = str.charCodeAt(i) & 0xFF;
}
return bytes;
// Wrapper around @noble/hashes hexToBytes that optionally validates size.
export const hexToBytes = (hex: string, size?: number): Uint8Array => {
const bytes = nobleHexToBytes(hex);
if (size !== undefined && bytes.length !== size) {
throw new Error(`Invalid hex length: expected ${size} bytes, got ${bytes.length}`);
}
throw new Error(`Invalid input: expected hex, base64 (standard or URL-safe), or raw string of size ${size}`);
}
return bytes;
};
export class BufferReader {
private buffer: Uint8Array;
@@ -56,6 +46,7 @@ export class BufferReader {
}
public readByte(): number {
if (!this.hasMore()) throw new Error('read past end');
return this.buffer[this.offset++];
}
@@ -63,6 +54,7 @@ export class BufferReader {
if (length === undefined) {
length = this.buffer.length - this.offset;
}
if (this.remainingBytes() < length) throw new Error('read past end');
const bytes = this.buffer.slice(this.offset, this.offset + length);
this.offset += length;
return bytes;
@@ -77,31 +69,36 @@ export class BufferReader {
}
public peekByte(): number {
if (!this.hasMore()) throw new Error('read past end');
return this.buffer[this.offset];
}
public readUint16LE(): number {
if (this.remainingBytes() < 2) throw new Error('read past end');
const value = this.buffer[this.offset] | (this.buffer[this.offset + 1] << 8);
this.offset += 2;
return value;
}
public readUint32LE(): number {
const value = this.buffer[this.offset] | (this.buffer[this.offset + 1] << 8) | (this.buffer[this.offset + 2] << 16) | (this.buffer[this.offset + 3] << 24);
if (this.remainingBytes() < 4) throw new Error('read past end');
const value = (this.buffer[this.offset] | (this.buffer[this.offset + 1] << 8) | (this.buffer[this.offset + 2] << 16) | (this.buffer[this.offset + 3] << 24)) >>> 0;
this.offset += 4;
return value;
}
public readInt16LE(): number {
if (this.remainingBytes() < 2) throw new Error('read past end');
const value = this.buffer[this.offset] | (this.buffer[this.offset + 1] << 8);
this.offset += 2;
return value < 0x8000 ? value : value - 0x10000;
}
public readInt32LE(): number {
const value = this.buffer[this.offset] | (this.buffer[this.offset + 1] << 8) | (this.buffer[this.offset + 2] << 16) | (this.buffer[this.offset + 3] << 24);
if (this.remainingBytes() < 4) throw new Error('read past end');
const u = (this.buffer[this.offset] | (this.buffer[this.offset + 1] << 8) | (this.buffer[this.offset + 2] << 16) | (this.buffer[this.offset + 3] << 24)) >>> 0;
this.offset += 4;
return value < 0x80000000 ? value : value - 0x100000000;
return u < 0x80000000 ? u : u - 0x100000000;
}
public readTimestamp(): Date {