Initial Commit
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
# Base58 encoding (Bitcoin alphabet)
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Encodes and decodes byte buffers using the Bitcoin Base58 alphabet, which omits the four visually ambiguous characters `0`, `O`, `I`, and `l`. Base58 is the encoding of choice for address formats where a human will copy the string by eye (Bitcoin addresses, Solana public keys, some Ethereum vanity formats) and where the absence of punctuation lets the value travel through URLs, QR codes, and plain text without escaping. Leading zero bytes are preserved as the `'1'` prefix character, matching the behaviour of Bitcoin Base58Check address payloads.
|
||||
|
||||
## Features
|
||||
|
||||
- Bitcoin alphabet: `123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz`.
|
||||
- `BigInt`-based conversion — no floating point, no length limit beyond JS heap size.
|
||||
- Preserves leading-zero bytes as `'1'` prefix characters.
|
||||
- Stable error code `CRYPTO.UTILS.ENCODING.INVALID_INPUT` on decode failure.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { fromBase58, toBase58 } from "@suaveplan/crypto-utils/encoding";
|
||||
|
||||
const addr = toBase58(new Uint8Array([0x00, 0xeb, 0x15, 0x23])); // "1NS..."-style prefix
|
||||
const back = fromBase58(addr);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { fromBase58, toBase58 } from "@suaveplan/crypto-utils/encoding";
|
||||
import { CryptoUtilsError } from "@suaveplan/crypto-utils/errors";
|
||||
|
||||
function verifyAddressPayload(address: string, expectedLeadingZeros: number): Uint8Array {
|
||||
let bytes: Uint8Array;
|
||||
try {
|
||||
bytes = fromBase58(address);
|
||||
} catch (err) {
|
||||
if (err instanceof CryptoUtilsError) {
|
||||
throw new Error(`Not a Base58 address: ${address}`);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
let zeros = 0;
|
||||
while (zeros < bytes.length && bytes[zeros] === 0) zeros++;
|
||||
if (zeros !== expectedLeadingZeros) {
|
||||
throw new Error(`Expected ${expectedLeadingZeros} leading zero bytes, got ${zeros}`);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
verifyAddressPayload("1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L", 1);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `toBase58(bytes: Uint8Array): string` — Encodes into the Bitcoin alphabet. Empty input produces the empty string. Leading zero bytes become leading `'1'` characters, one per zero byte.
|
||||
- `fromBase58(s: string): Uint8Array` — Decodes into bytes. Leading `'1'` characters become leading zero bytes. Throws `CryptoUtilsError(CRYPTO.UTILS.ENCODING.INVALID_INPUT)` on any out-of-alphabet character, with `context.input` truncated to the first 32 characters of the offending string.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Conversion goes through `BigInt` so arbitrary-length buffers are supported without losing precision. The encoder counts leading zero bytes up front, then converts the remaining bytes to a `BigInt`, repeatedly divides by 58 to produce characters from least significant to most, and finally prepends one `'1'` per leading zero byte. The decoder is the reverse; it pre-counts `'1'` prefix characters, then accumulates `n = n * 58 + value` into a `BigInt`, and finally emits bytes by repeated `n >>= 8`. Base58 is intentionally slower than Base64 — its purpose is human-readability, not throughput. Use Base64/Base64Url for wire formats.
|
||||
@@ -0,0 +1,74 @@
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import { CryptoUtilsError } from "../../errors/crypto-utils-error/crypto-utils-error.js";
|
||||
import { fromBase58, toBase58 } from "./base58.js";
|
||||
|
||||
function ascii(s: string): Uint8Array {
|
||||
return new Uint8Array([...s].map((c) => c.charCodeAt(0)));
|
||||
}
|
||||
|
||||
// From the Bitcoin Base58 reference test vectors (github.com/bitcoin/bitcoin)
|
||||
// and libbase58.
|
||||
const VECTORS: Array<{ hex: string; b58: string }> = [
|
||||
{ hex: "", b58: "" },
|
||||
{ hex: "61", b58: "2g" }, // 'a'
|
||||
{ hex: "626262", b58: "a3gV" }, // 'bbb'
|
||||
{ hex: "636363", b58: "aPEr" }, // 'ccc'
|
||||
{
|
||||
hex: "73696d706c792061206c6f6e6720737472696e67",
|
||||
b58: "2cFupjhnEsSn59qHXstmK2ffpLv2",
|
||||
},
|
||||
{
|
||||
hex: "00eb15231dfceb60925886b67d065299925915aeb172c06647",
|
||||
b58: "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L",
|
||||
},
|
||||
{ hex: "516b6fcd0f", b58: "ABnLTmg" },
|
||||
{ hex: "bf4f89001e670274dd", b58: "3SEo3LWLoPntC" },
|
||||
{ hex: "572e4794", b58: "3EFU7m" },
|
||||
{ hex: "ecac89cad93923c02321", b58: "EJDM8drfXA6uyA" },
|
||||
{ hex: "10c8511e", b58: "Rt5zm" },
|
||||
{ hex: "00000000000000000000", b58: "1111111111" },
|
||||
];
|
||||
|
||||
function hexToBytes(h: string): Uint8Array {
|
||||
if (h.length === 0) return new Uint8Array();
|
||||
const out = new Uint8Array(h.length / 2);
|
||||
for (let i = 0; i < out.length; i++) {
|
||||
out[i] = Number.parseInt(h.slice(i * 2, i * 2 + 2), 16);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
describe("base58", () => {
|
||||
it("bitcoin reference vectors round-trip", { timeout: 5000 }, () => {
|
||||
for (const v of VECTORS) {
|
||||
const bytes = hexToBytes(v.hex);
|
||||
expect(toBase58(bytes)).toBe(v.b58);
|
||||
expect(fromBase58(v.b58)).toEqual(bytes);
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves leading zero bytes as '1' prefix", { timeout: 5000 }, () => {
|
||||
const bytes = new Uint8Array([0, 0, 0, 1]);
|
||||
const encoded = toBase58(bytes);
|
||||
expect(encoded.startsWith("111")).toBe(true);
|
||||
expect(fromBase58(encoded)).toEqual(bytes);
|
||||
});
|
||||
|
||||
it("round-trips random bytes", { timeout: 5000 }, () => {
|
||||
for (let n = 0; n < 80; n += 5) {
|
||||
const bytes = new Uint8Array(n);
|
||||
crypto.getRandomValues(bytes);
|
||||
expect(fromBase58(toBase58(bytes))).toEqual(bytes);
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects out-of-alphabet characters", { timeout: 5000 }, () => {
|
||||
expect(() => fromBase58("0OIl")).toThrow(CryptoUtilsError);
|
||||
expect(() => fromBase58("!")).toThrow(CryptoUtilsError);
|
||||
expect(() => fromBase58("héllo")).toThrow(CryptoUtilsError);
|
||||
});
|
||||
|
||||
it("handles encoding of ascii 'a'", { timeout: 5000 }, () => {
|
||||
expect(toBase58(ascii("a"))).toBe("2g");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import { CRYPTO_UTILS_CODES } from "../../errors/codes/codes.js";
|
||||
import { CryptoUtilsError } from "../../errors/crypto-utils-error/crypto-utils-error.js";
|
||||
|
||||
// Bitcoin alphabet (Base58Check): no 0, O, I, l to avoid visual ambiguity.
|
||||
const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
||||
const BASE = 58n;
|
||||
|
||||
const DECODE_TABLE = new Int8Array(128).fill(-1);
|
||||
for (let i = 0; i < ALPHABET.length; i++) {
|
||||
DECODE_TABLE[ALPHABET.charCodeAt(i)] = i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode `bytes` with the Bitcoin Base58 alphabet. Leading `0x00` bytes are
|
||||
* preserved as leading `'1'` characters (one per zero byte), matching the
|
||||
* Bitcoin Base58Check convention for address payloads.
|
||||
*/
|
||||
export function toBase58(bytes: Uint8Array): string {
|
||||
if (bytes.length === 0) return "";
|
||||
let zeros = 0;
|
||||
while (zeros < bytes.length && bytes[zeros] === 0) zeros++;
|
||||
|
||||
let n = 0n;
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
n = (n << 8n) | BigInt(bytes[i] as number);
|
||||
}
|
||||
|
||||
let out = "";
|
||||
while (n > 0n) {
|
||||
const r = Number(n % BASE);
|
||||
out = ALPHABET[r] + out;
|
||||
n = n / BASE;
|
||||
}
|
||||
return "1".repeat(zeros) + out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a Base58 string into bytes.
|
||||
*
|
||||
* @throws CryptoUtilsError(CRYPTO.UTILS.ENCODING.INVALID_INPUT) on non-alphabet
|
||||
* characters.
|
||||
*/
|
||||
export function fromBase58(s: string): Uint8Array {
|
||||
if (s.length === 0) return new Uint8Array();
|
||||
|
||||
let zeros = 0;
|
||||
while (zeros < s.length && s[zeros] === "1") zeros++;
|
||||
|
||||
let n = 0n;
|
||||
for (let i = zeros; i < s.length; i++) {
|
||||
const cc = s.charCodeAt(i);
|
||||
const v = cc < 128 ? DECODE_TABLE[cc] : -1;
|
||||
if (v === -1 || v === undefined) {
|
||||
throw new CryptoUtilsError(
|
||||
CRYPTO_UTILS_CODES["ENCODING.INVALID_INPUT"],
|
||||
"error.crypto.utils.encoding.invalid-input",
|
||||
{ context: { encoding: "base58", input: s.slice(0, 32) } }
|
||||
);
|
||||
}
|
||||
n = n * BASE + BigInt(v);
|
||||
}
|
||||
|
||||
const bytes: number[] = [];
|
||||
while (n > 0n) {
|
||||
bytes.unshift(Number(n & 0xffn));
|
||||
n >>= 8n;
|
||||
}
|
||||
const out = new Uint8Array(zeros + bytes.length);
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
out[zeros + i] = bytes[i] as number;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { fromBase58, toBase58 } from "./base58";
|
||||
@@ -0,0 +1,56 @@
|
||||
# Base64 encoding
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Standard Base64 (RFC 4648 §4) and URL-safe Base64 (§5) encoding and decoding for byte buffers. Standard Base64 is the default for JSON, JWT headers, and binary payloads in MIME. URL-safe Base64 substitutes `-` and `_` for `+` and `/` so the result can appear in URLs and file paths without percent-escaping. Standard Base64 (`toBase64` / `fromBase64`) is delegated entirely to `@suaveplan/bytes`; the URL-safe variants are implemented here because they use a crypto-specific `+→- / →_` substitution pattern not present in the bytes package.
|
||||
|
||||
## Features
|
||||
|
||||
- `toBase64` / `fromBase64`: RFC 4648 §4 with `=` padding — delegated to `@suaveplan/bytes`.
|
||||
- `toBase64Url` / `fromBase64Url`: RFC 4648 §5 without padding — implemented natively by post-processing the standard base64 output.
|
||||
- URL-safe decoder tolerates optional trailing `=` padding.
|
||||
- Throws `BytesError` (from `@suaveplan/bytes`) on malformed standard base64; same error type propagates through the URL-safe decoder.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { fromBase64, toBase64 } from "@suaveplan/crypto-utils/encoding";
|
||||
|
||||
const s = toBase64(new TextEncoder().encode("foobar")); // "Zm9vYmFy"
|
||||
const bytes = fromBase64(s);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { fromBase64Url, toBase64Url } from "@suaveplan/crypto-utils/encoding";
|
||||
import { BytesError } from "@suaveplan/bytes";
|
||||
|
||||
function parseJwtSegment(segment: string): Uint8Array {
|
||||
try {
|
||||
return fromBase64Url(segment);
|
||||
} catch (err) {
|
||||
if (err instanceof BytesError) {
|
||||
throw new Error(`JWT segment not valid base64url: ${segment.slice(0, 8)}…`);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
const encoded = toBase64Url(new Uint8Array([0xfb, 0xff, 0xbf]));
|
||||
parseJwtSegment(encoded);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `toBase64(bytes: Uint8Array): string` — RFC 4648 §4 with `=` padding. Never throws. Alias for `bytesToBase64` from `@suaveplan/bytes`.
|
||||
- `fromBase64(s: string): Uint8Array` — RFC 4648 §4. Throws `BytesError(BYTES.ENCODE.INVALID_BASE64)` on invalid characters or bad length.
|
||||
- `toBase64Url(bytes: Uint8Array): string` — RFC 4648 §5 without padding. Calls `toBase64` then substitutes `+→-`, `/→_`, strips `=`.
|
||||
- `fromBase64Url(s: string): Uint8Array` — RFC 4648 §5. Restores `+` and `/`, adds padding, then delegates to `fromBase64`. Tolerates optional trailing `=` padding. Throws `BytesError` on malformed input.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Standard base64 (`toBase64` / `fromBase64`) are direct `const` aliases for `bytesToBase64` / `base64ToBytes` from `@suaveplan/bytes`, eliminating duplicate encoding logic. URL-safe base64 is implemented as a post-processing step: `toBase64Url` calls `toBase64` then performs three `String.replace` calls to swap characters and strip padding. `fromBase64Url` reverses the substitution, computes missing padding (aligning to a multiple of 4), then delegates to `fromBase64`. This approach ensures the URL-safe round-trip shares exactly the same underlying byte semantics as the standard variant. Error type for invalid input is `BytesError` (from `@suaveplan/bytes`) rather than the legacy `CryptoUtilsError`.
|
||||
@@ -0,0 +1,65 @@
|
||||
import { BytesError } from "@suaveplan/bytes";
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import { fromBase64, fromBase64Url, toBase64, toBase64Url } from "./base64.js";
|
||||
|
||||
// RFC 4648 §10 test vectors
|
||||
const RFC4648 = [
|
||||
{ bytes: "", b64: "" },
|
||||
{ bytes: "f", b64: "Zg==" },
|
||||
{ bytes: "fo", b64: "Zm8=" },
|
||||
{ bytes: "foo", b64: "Zm9v" },
|
||||
{ bytes: "foob", b64: "Zm9vYg==" },
|
||||
{ bytes: "fooba", b64: "Zm9vYmE=" },
|
||||
{ bytes: "foobar", b64: "Zm9vYmFy" },
|
||||
] as const;
|
||||
|
||||
function ascii(s: string): Uint8Array {
|
||||
return new Uint8Array([...s].map((c) => c.charCodeAt(0)));
|
||||
}
|
||||
|
||||
describe("base64", () => {
|
||||
it("RFC 4648 §10 vectors round-trip", { timeout: 5000 }, () => {
|
||||
for (const v of RFC4648) {
|
||||
expect(toBase64(ascii(v.bytes))).toBe(v.b64);
|
||||
expect(fromBase64(v.b64)).toEqual(ascii(v.bytes));
|
||||
}
|
||||
});
|
||||
|
||||
it("base64url drops padding", { timeout: 5000 }, () => {
|
||||
expect(toBase64Url(ascii("fo"))).toBe("Zm8");
|
||||
expect(fromBase64Url("Zm8")).toEqual(ascii("fo"));
|
||||
// With explicit trailing padding — decoder tolerates it
|
||||
expect(fromBase64Url("Zm8=")).toEqual(ascii("fo"));
|
||||
});
|
||||
|
||||
it("base64url uses - and _", { timeout: 5000 }, () => {
|
||||
const bytes = new Uint8Array([0xfb, 0xff, 0xbf]);
|
||||
const std = toBase64(bytes);
|
||||
const url = toBase64Url(bytes);
|
||||
expect(std).toContain("+");
|
||||
expect(url).not.toContain("+");
|
||||
expect(url).not.toContain("/");
|
||||
expect(fromBase64Url(url)).toEqual(bytes);
|
||||
});
|
||||
|
||||
it("round-trips random bytes", { timeout: 5000 }, () => {
|
||||
for (let n = 0; n < 200; n += 7) {
|
||||
const bytes = new Uint8Array(n);
|
||||
crypto.getRandomValues(bytes);
|
||||
expect(fromBase64(toBase64(bytes))).toEqual(bytes);
|
||||
expect(fromBase64Url(toBase64Url(bytes))).toEqual(bytes);
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects single-char remainders", { timeout: 5000 }, () => {
|
||||
expect(() => fromBase64("A")).toThrow(BytesError);
|
||||
expect(() => fromBase64Url("A")).toThrow(BytesError);
|
||||
});
|
||||
|
||||
it("rejects invalid characters", { timeout: 5000 }, () => {
|
||||
expect(() => fromBase64("!!!!")).toThrow(BytesError);
|
||||
expect(() => fromBase64Url("@@@@")).toThrow(BytesError);
|
||||
// Non-ASCII
|
||||
expect(() => fromBase64("éééé")).toThrow(BytesError);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { base64ToBytes, bytesToBase64 } from "@suaveplan/bytes";
|
||||
|
||||
/** Encode `bytes` as standard Base64 (RFC 4648 §4, `=` padding). Delegates to @suaveplan/bytes. */
|
||||
export const toBase64 = bytesToBase64;
|
||||
|
||||
/** Decode a standard Base64 string. Delegates to @suaveplan/bytes. */
|
||||
export const fromBase64 = base64ToBytes;
|
||||
|
||||
/** Encode `bytes` as URL-safe Base64 (RFC 4648 §5, no padding). */
|
||||
export function toBase64Url(bytes: Uint8Array): string {
|
||||
return toBase64(bytes)
|
||||
.replace(/\+/g, "-")
|
||||
.replace(/\//g, "_")
|
||||
.replace(/=/g, "");
|
||||
}
|
||||
|
||||
/** Decode a URL-safe Base64 string. Padding optional. */
|
||||
export function fromBase64Url(b64url: string): Uint8Array {
|
||||
const padded = b64url.replace(/-/g, "+").replace(/_/g, "/");
|
||||
const pad = padded.length % 4;
|
||||
return fromBase64(pad ? padded + "=".repeat(4 - pad) : padded);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { fromBase64, fromBase64Url, toBase64, toBase64Url } from "./base64";
|
||||
@@ -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";
|
||||
@@ -0,0 +1,8 @@
|
||||
export { fromBase58, toBase58 } from "./base58/base58.js";
|
||||
export {
|
||||
fromBase64,
|
||||
fromBase64Url,
|
||||
toBase64,
|
||||
toBase64Url,
|
||||
} from "./base64/base64.js";
|
||||
export { fromHex, toHex } from "./hex/hex.js";
|
||||
Reference in New Issue
Block a user