diff --git a/src/primitives/Nonce/Nonce.test.ts b/src/primitives/Nonce/Nonce.test.ts index 1548cdad2..2029fec31 100644 --- a/src/primitives/Nonce/Nonce.test.ts +++ b/src/primitives/Nonce/Nonce.test.ts @@ -29,7 +29,8 @@ describe("Nonce", () => { }); it("creates large nonce", () => { - const large = 2n ** 200n; + // Max uint64 value + const large = 18446744073709551615n; const nonce = Nonce.from(large); expect(Nonce.toBigInt(nonce)).toBe(large); }); diff --git a/src/primitives/Nonce/from.js b/src/primitives/Nonce/from.js index c921320d3..38eb296c8 100644 --- a/src/primitives/Nonce/from.js +++ b/src/primitives/Nonce/from.js @@ -1,10 +1,20 @@ -import * as Uint from "../Uint/index.js"; +import { + IntegerOverflowError, + IntegerUnderflowError, + InvalidFormatError, +} from "../errors/index.js"; + +/** Maximum uint64 value (2^64 - 1) */ +const MAX_UINT64 = 18446744073709551615n; /** * Create Nonce from number, bigint, or hex string * * @param {bigint | number | string} value - Value to convert * @returns {import('./NonceType.js').NonceType} Nonce + * @throws {InvalidFormatError} If value is not a valid integer + * @throws {IntegerUnderflowError} If value is negative + * @throws {IntegerOverflowError} If value exceeds uint64 max (2^64-1) * * @example * ```typescript @@ -14,7 +24,54 @@ import * as Uint from "../Uint/index.js"; * ``` */ export function from(value) { - return /** @type {import('./NonceType.js').NonceType} */ ( - /** @type {unknown} */ (Uint.from(value)) - ); + /** @type {bigint} */ + let bigintValue; + + if (typeof value === "string") { + try { + bigintValue = BigInt(value); + } catch { + throw new InvalidFormatError(`Invalid Nonce string: ${value}`, { + code: "NONCE_INVALID_STRING", + value, + expected: "decimal or 0x-prefixed hex string", + docsPath: "/primitives/nonce#error-handling", + }); + } + } else if (typeof value === "number") { + if (!Number.isInteger(value)) { + throw new InvalidFormatError(`Nonce must be an integer: ${value}`, { + code: "NONCE_NOT_INTEGER", + value, + expected: "integer value", + docsPath: "/primitives/nonce#error-handling", + }); + } + bigintValue = BigInt(value); + } else { + bigintValue = value; + } + + if (bigintValue < 0n) { + throw new IntegerUnderflowError(`Nonce cannot be negative: ${bigintValue}`, { + value: bigintValue, + min: 0n, + type: "nonce", + docsPath: "/primitives/nonce#error-handling", + }); + } + + if (bigintValue > MAX_UINT64) { + throw new IntegerOverflowError( + `Nonce exceeds maximum uint64 (2^64-1): ${bigintValue}`, + { + value: bigintValue, + max: MAX_UINT64, + type: "nonce", + docsPath: "/primitives/nonce#error-handling", + }, + ); + } + + return /** @type {import('./NonceType.js').NonceType} */ (bigintValue); } diff --git a/src/primitives/Nonce/from.test.ts b/src/primitives/Nonce/from.test.ts index 523ff185d..63818573d 100644 --- a/src/primitives/Nonce/from.test.ts +++ b/src/primitives/Nonce/from.test.ts @@ -1,5 +1,10 @@ import { describe, expect, it } from "vitest"; import { from } from "./from.js"; +import { + IntegerOverflowError, + IntegerUnderflowError, + InvalidFormatError, +} from "../errors/index.js"; describe("Nonce.from", () => { it("creates nonce from number", () => { @@ -24,4 +29,48 @@ describe("Nonce.from", () => { const nonce = from(0); expect(nonce).toBe(0n); }); + + it("handles max uint64 nonce", () => { + const maxUint64 = 18446744073709551615n; + const nonce = from(maxUint64); + expect(nonce).toBe(maxUint64); + }); + + describe("validation", () => { + it("rejects negative number", () => { + expect(() => from(-1)).toThrow(IntegerUnderflowError); + expect(() => from(-1)).toThrow("Nonce cannot be negative"); + }); + + it("rejects negative bigint", () => { + expect(() => from(-1n)).toThrow(IntegerUnderflowError); + expect(() => from(-1n)).toThrow("Nonce cannot be negative"); + }); + + it("rejects negative hex string", () => { + // "-0x1" is not valid BigInt syntax, throws InvalidFormat + expect(() => from("-0x1")).toThrow(InvalidFormatError); + // "-1" is valid BigInt syntax, throws Underflow + expect(() => from("-1")).toThrow(IntegerUnderflowError); + }); + + it("rejects large negative values", () => { + expect(() => from(-9999999999n)).toThrow(IntegerUnderflowError); + }); + + it("rejects value exceeding uint64 max", () => { + const overMax = 18446744073709551616n; // 2^64 + expect(() => from(overMax)).toThrow(IntegerOverflowError); + expect(() => from(overMax)).toThrow("exceeds maximum uint64"); + }); + + it("rejects non-integer number", () => { + expect(() => from(1.5)).toThrow(InvalidFormatError); + expect(() => from(1.5)).toThrow("must be an integer"); + }); + + it("rejects invalid string", () => { + expect(() => from("not-a-number")).toThrow(InvalidFormatError); + }); + }); }); diff --git a/src/primitives/Nonce/toBigInt.test.ts b/src/primitives/Nonce/toBigInt.test.ts index 4263bb1b4..3c120a440 100644 --- a/src/primitives/Nonce/toBigInt.test.ts +++ b/src/primitives/Nonce/toBigInt.test.ts @@ -50,7 +50,8 @@ describe("Nonce.toBigInt", () => { }); it("preserves large values", () => { - const original = 2n ** 200n; + // Max uint64 value + const original = 18446744073709551615n; const nonce = Nonce.from(original); const result = Nonce.toBigInt(nonce); expect(result).toBe(original); diff --git a/src/primitives/Nonce/toNumber.test.ts b/src/primitives/Nonce/toNumber.test.ts index 2b6313ed3..1fa7cec9d 100644 --- a/src/primitives/Nonce/toNumber.test.ts +++ b/src/primitives/Nonce/toNumber.test.ts @@ -60,7 +60,8 @@ describe("Nonce.toNumber", () => { }); it("throws on very large value", () => { - const nonce = Nonce.from(2n ** 100n); + // Use a value larger than MAX_SAFE_INTEGER but within uint64 range + const nonce = Nonce.from(BigInt(Number.MAX_SAFE_INTEGER) + 1000n); expect(() => Nonce.toNumber(nonce)).toThrow(); }); });