From e470d8a70bfb8d5917bacbafd3168445bca4e333 Mon Sep 17 00:00:00 2001 From: RichardAHBot Date: Sun, 13 Sep 2026 21:35:31 +1000 Subject: [PATCH] fix: tighten jsontx_exact integer boundary at 2^53 jsontx_exact: change the double boundary check from exclusive to inclusive (d > max -> d >= max, d < -max -> d <= -max). Doubles cannot uniquely represent odd integers at or above 2^53, so values like 2^53+1 silently round to 2^53, corrupting the canonical form. The safe ceiling for round-trip-exact integers is 2^53 - 1. tests: add coverage for the 2^53 boundary (positive and negative sides, and the silent rounding case). --- include/xrpl/protocol/JSONTxSignatures.h | 3 +- src/test/protocol/JSONTxSignatures_test.cpp | 36 +++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/include/xrpl/protocol/JSONTxSignatures.h b/include/xrpl/protocol/JSONTxSignatures.h index c787023b09..abe7a70c46 100644 --- a/include/xrpl/protocol/JSONTxSignatures.h +++ b/include/xrpl/protocol/JSONTxSignatures.h @@ -247,8 +247,7 @@ jsontx_exact(Json::Value const& v, std::int64_t& out) return false; } double const d = v.asDouble(); - if (!std::isfinite(d) || d != std::trunc(d) || d < -jsontx_exact_max || - d > jsontx_exact_max) + if (!std::isfinite(d) || d != std::trunc(d) || d <= -jsontx_exact_max || d >= jsontx_exact_max) return false; out = static_cast(d); return true; diff --git a/src/test/protocol/JSONTxSignatures_test.cpp b/src/test/protocol/JSONTxSignatures_test.cpp index d6d7282bb6..5ad3d78b87 100644 --- a/src/test/protocol/JSONTxSignatures_test.cpp +++ b/src/test/protocol/JSONTxSignatures_test.cpp @@ -635,6 +635,42 @@ public: BEAST_EXPECT(r2.second.size() >= r1.second.size()); }); + + // ---- 2^53 boundary: values at and above 2^53 must be rejected ---- + // Doubles cannot uniquely represent odd integers >= 2^53, so the + // safe boundary for exact integer round-trips is 2^53 - 1. + section("exact_at_2exp53_boundary", + [&] { + // 2^53 - 1 should be accepted (max safe value) + Json::Value ok; + ok = 9007199254740991; + std::int64_t out = 0; + BEAST_EXPECT(jsontx_exact(ok, out)); + BEAST_EXPECT(out == 9007199254740991); + + // 2^53 should be rejected (not uniquely representable) + Json::Value bad; + bad = 9007199254740992; + BEAST_EXPECT(!jsontx_exact(bad, out)); + + // 2^53 + 1 rounds down to 2^53 in double representation + // and should also be rejected + Json::Value bad2; + bad2 = 9007199254740993; + BEAST_EXPECT(!jsontx_exact(bad2, out)); + + // Negative boundary: -2^53 should be rejected + Json::Value neg_bad; + neg_bad = -9007199254740992; + BEAST_EXPECT(!jsontx_exact(neg_bad, out)); + + // -2^53 + 1 should be accepted + Json::Value neg_ok; + neg_ok = -9007199254740991; + BEAST_EXPECT(jsontx_exact(neg_ok, out)); + BEAST_EXPECT(out == -9007199254740991); + }); + } };