Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/shiny-ants-say.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ckb-ccc/core": patch
---

`hexFrom` passthru normalized hex and `numToHex` enforce hex normalization
34 changes: 32 additions & 2 deletions packages/core/src/hex/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,33 @@ export type Hex = `0x${string}`;
export type HexLike = BytesLike;

/**
* Converts a HexLike value to a Hex string.
* @public
* Determines whether a given value is a properly formatted hexadecimal string (ccc.Hex).
*
* A valid hexadecimal string:
* - Has at least two characters.
* - Starts with "0x".
* - Has an even length.
* - Contains only characters representing digits (0-9) or lowercase letters (a-f) after the "0x" prefix.
*
* @param v - The value to validate as a hexadecimal (ccc.Hex) string.
* @returns True if the string is a valid hex string, false otherwise.
*/
export function isHex(v: unknown): v is Hex {
if (!(typeof v === "string" && v.length % 2 === 0 && v.startsWith("0x"))) {
return false;
}

for (let i = 2; i < v.length; i++) {
const c = v.charAt(i);
if (!(("0" <= c && c <= "9") || ("a" <= c && c <= "f"))) {
return false;
}
}
return true;
}

/**
* Returns the hexadecimal representation of the given value.
*
* @param hex - The value to convert, which can be a string, Uint8Array, ArrayBuffer, or number array.
* @returns A Hex string representing the value.
Expand All @@ -26,5 +51,10 @@ export type HexLike = BytesLike;
* ```
*/
export function hexFrom(hex: HexLike): Hex {
// Passthru an already normalized hex. V8 optimization: maintain existing hidden string fields.
if (isHex(hex)) {
return hex;
}

return `0x${bytesTo(bytesFrom(hex), "hex")}`;
}
18 changes: 15 additions & 3 deletions packages/core/src/num/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Bytes, BytesLike, bytesConcat, bytesFrom } from "../bytes/index.js";
import { Zero } from "../fixedPoint/index.js";
import { Hex, HexLike, hexFrom } from "../hex/index.js";

/**
Expand Down Expand Up @@ -90,19 +91,30 @@ export function numFrom(val: NumLike): Num {
}

/**
* Converts a NumLike value to a hexadecimal string.
* Convert a NumLike value into a canonical Hex, so prefixed with `0x` and
* containing an even number of lowercase hex digits (full-byte representation).
*
* @public
*
* @param val - The value to convert, which can be a string, number, bigint, or HexLike.
* @returns A Hex string representing the numeric value.
* @returns A Hex string representing the provided value, prefixed with `0x` and
* containing an even number of lowercase hex digits.
*
* @throws {Error} If the normalized numeric value is negative.
*
* @example
* ```typescript
* const hex = numToHex(12345); // Outputs "0x3039"
* ```
*/
export function numToHex(val: NumLike): Hex {
return `0x${numFrom(val).toString(16)}`;
const v = numFrom(val);
if (v < Zero) {
throw new Error("value must be non-negative");
}
const h = v.toString(16);
// ensure even length (full bytes)
return h.length % 2 === 0 ? `0x${h}` : `0x0${h}`;
}

/**
Expand Down