Initial Commit
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
# Hex encoding
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Convert between `Uint8Array` byte buffers and lowercase hexadecimal strings. Hex is the canonical human-readable representation of cryptographic material (digests, public keys, nonces) because every byte maps to exactly two characters with no ambiguity or padding. This module re-exports `bytesToHex` and `hexToBytes` from `@suaveplan/bytes` under the crypto-friendly names `toHex` / `fromHex`, ensuring every `@suaveplan/crypto-*` package produces byte-for-byte identical output and rejects malformed input with a stable error code.
|
||||
|
||||
## Features
|
||||
|
||||
- `toHex(bytes)` — lowercase, no `0x` prefix; delegates to `@suaveplan/bytes`.
|
||||
- `fromHex(hex)` — accepts both upper and lower case; rejects odd length or non-hex characters.
|
||||
- Throws `BytesError(BYTES.ENCODE.INVALID_HEX)` on malformed input (from `@suaveplan/bytes`).
|
||||
- Zero implementation code in this module — pure delegation ensures a single source of truth.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { fromHex, toHex } from "@suaveplan/crypto-utils/encoding";
|
||||
|
||||
const digest = new Uint8Array([0xde, 0xad, 0xbe, 0xef]);
|
||||
const s = toHex(digest); // "deadbeef"
|
||||
const back = fromHex(s); // Uint8Array([0xde, 0xad, 0xbe, 0xef])
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { fromHex, toHex } from "@suaveplan/crypto-utils/encoding";
|
||||
import { BytesError } from "@suaveplan/bytes";
|
||||
|
||||
function parseDigest(input: string): Uint8Array {
|
||||
try {
|
||||
return fromHex(input.trim().replace(/^0x/, ""));
|
||||
} catch (err) {
|
||||
if (err instanceof BytesError) {
|
||||
throw new Error(`digest must be hex-encoded: ${input.slice(0, 32)}`);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
const digest = parseDigest("0xDEADBEEF");
|
||||
console.log(toHex(digest));
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `toHex(bytes: Uint8Array): string` — Returns a lowercase hex string whose length is `2 * bytes.length`. Never throws. Alias for `bytesToHex` from `@suaveplan/bytes`.
|
||||
- `fromHex(hex: string): Uint8Array` — Parses upper or lower case. Throws `BytesError` with code `BYTES.ENCODE.INVALID_HEX` on odd-length or non-hex input. Alias for `hexToBytes` from `@suaveplan/bytes`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
This module is a thin delegation layer: `toHex` and `fromHex` are direct `const` aliases for `bytesToHex` and `hexToBytes` from `@suaveplan/bytes`. No encoding logic lives here — this removes duplication between the two packages and ensures all `@suaveplan/crypto-*` consumers share exactly the same implementation. Errors thrown on invalid input originate from `@suaveplan/bytes` as `BytesError` instances; callers should catch `BytesError` rather than the old `CryptoUtilsError`. The public function names (`toHex` / `fromHex`) are preserved so no downstream caller requires import changes.
|
||||
@@ -0,0 +1,38 @@
|
||||
import { BytesError } from "@suaveplan/bytes";
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import { fromHex, toHex } from "./hex.js";
|
||||
|
||||
describe("hex", () => {
|
||||
it("round-trips the empty string", { timeout: 5000 }, () => {
|
||||
expect(toHex(new Uint8Array())).toBe("");
|
||||
expect(fromHex("")).toEqual(new Uint8Array());
|
||||
});
|
||||
|
||||
it("encodes known vectors", { timeout: 5000 }, () => {
|
||||
// RFC 4648 test vectors
|
||||
expect(toHex(new Uint8Array([0x66]))).toBe("66"); // 'f'
|
||||
expect(toHex(new Uint8Array([0x66, 0x6f, 0x6f]))).toBe("666f6f"); // 'foo'
|
||||
expect(toHex(new Uint8Array([0x00, 0xff, 0x10]))).toBe("00ff10");
|
||||
});
|
||||
|
||||
it("decodes known vectors", { timeout: 5000 }, () => {
|
||||
expect(fromHex("666f6f")).toEqual(new Uint8Array([0x66, 0x6f, 0x6f]));
|
||||
expect(fromHex("FF10")).toEqual(new Uint8Array([0xff, 0x10]));
|
||||
});
|
||||
|
||||
it("round-trips random bytes", { timeout: 5000 }, () => {
|
||||
for (let n = 0; n < 200; n += 13) {
|
||||
const bytes = new Uint8Array(n);
|
||||
crypto.getRandomValues(bytes);
|
||||
expect(fromHex(toHex(bytes))).toEqual(bytes);
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects odd-length input", { timeout: 5000 }, () => {
|
||||
expect(() => fromHex("abc")).toThrow(BytesError);
|
||||
});
|
||||
|
||||
it("rejects non-hex chars", { timeout: 5000 }, () => {
|
||||
expect(() => fromHex("zz")).toThrow(BytesError);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import { bytesToHex, hexToBytes } from "@suaveplan/bytes";
|
||||
|
||||
/** Convert a Uint8Array to lowercase hex string. Delegates to @suaveplan/bytes. */
|
||||
export const toHex = bytesToHex;
|
||||
|
||||
/** Convert a hex string to Uint8Array. Delegates to @suaveplan/bytes. */
|
||||
export const fromHex = hexToBytes;
|
||||
@@ -0,0 +1 @@
|
||||
export { fromHex, toHex } from "./hex";
|
||||
Reference in New Issue
Block a user