Skip to content

fix(serializer): reject unsigned 64-bit overflow in BigVLQ encode and decode - #224

Open
Kshot3000 wants to merge 1 commit into
fleet-sdk:masterfrom
Kshot3000:fix/bigvlq-ulong-range
Open

Kshot3000 wants to merge 1 commit into
fleet-sdk:masterfrom
Kshot3000:fix/bigvlq-ulong-range

Conversation

@Kshot3000

Copy link
Copy Markdown

Problem

writeBigVLQ / readBigVLQ — the codec behind writeBigUInt / readBigUInt, i.e. every box value and token amount — placed no bound on the value at all:

  • writeBigVLQ(2n ** 64n) happily emits a 10-byte encoding, and writeBigVLQ(2n ** 100n) an even longer one. The reference putULong in scorex-util takes a 64-bit Long, so values above 2^64 − 1 cannot be represented there at all.
  • readBigVLQ decoded encodings of any length into arbitrarily large bigints. The reference getULong reads at most 10 bytes (shifts 0..63) and errors on longer encodings; a 10-byte encoding whose high bits exceed 2^64 − 1 doesn't decode there to the value Fleet returned either — it wraps into a signed Long bit pattern. So a box value or token amount parsed by Fleet could be a value the node computes differently, or rejects outright, while Fleet's own writer would happily produce those same invalid bytes again on re-serialization.

This is the same fail-open pattern as the readI256 gap (#221): Fleet accepted values its counterpart implementation cannot represent.

Fix

  • writeBigVLQ throws a RangeError for values above the new MAX_U64 constant in numRanges.ts, mirroring the existing range checks in the other writers.
  • readBigVLQ throws a RangeError as soon as the decoded value exceeds MAX_U64 (partial values only grow, so this is exact) or the encoding exceeds 10 bytes.
  • Changeset included (@fleet-sdk/serializer: patch).

Every in-range encoding is byte-identical to before. All internal callers are already inside the 64-bit domain: box values and token amounts are ULongs, and zigZag64.encode wraps its output with asUintN(64) — the maximum zigzag encoding (for MIN_I64, exactly 2^64 − 1) still round-trips, as the existing SLong tests in the full suite confirm.

One deliberate behavior change: an oversized encoding on the readI64 path previously flowed into zigZag64.decode's 64-bit truncation and returned a silently wrong Long. It now fails closed with a RangeError instead, which matches the reference reader erroring on those bytes.

Tests

Tests-first: the three new rejection tests fail on pristine master (encode 2^64, decode 2^64, decode an 11-byte encoding — none threw), while the new maximum-value boundary vectors (encode/decode of 18446744073709551615, bytes ff ff ff ff ff ff ff ff ff 01) already passed, pinning the behavior that must be preserved.

  • vlq.spec: new encode/decode boundary and rejection tests; the BigVLQ round-trip property is widened from Number.MAX_SAFE_INTEGER to the full unsigned 64-bit range.
  • Full monorepo suite: 1137/1137 tests pass (66 files). Biome check clean on the changed files.

Submitted by @Kshot9000 — Ergo ecosystem work. If this is useful, tips in ERG are welcome: 9fcM5RWnAjmP4vx5bnW6yohB6H9bLq8sJbaPLHtwZLtQPB32Pvy

@changeset-bot

changeset-bot Bot commented Oct 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 81cb0ee

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@fleet-sdk/serializer Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant