Skip to content

fix(binarycodec): reject 0X prefix, harden MPT sign bit, add bounds checks#310

Open
e-desouza wants to merge 2 commits into
mainfrom
fix/mpt-amount-codec-hardening
Open

fix(binarycodec): reject 0X prefix, harden MPT sign bit, add bounds checks#310
e-desouza wants to merge 2 commits into
mainfrom
fix/mpt-amount-codec-hardening

Conversation

@e-desouza

Copy link
Copy Markdown
Collaborator

Summary

Three correctness issues in the MPT amount codec are addressed together, as they all live in the same file and are closely related.

Issue #258 - Drop uppercase 0X hex prefix

_serialize_mpt_amount previously accepted both 0x and 0X as valid hex prefixes. xrpl.js only accepts lowercase 0x, so allowing 0X created a silent interoperability gap. The check is now value.strip_prefix("0x") only. A test confirms that "0X1F" now returns an error and falls through to decimal parsing, which also fails.

Issue #259 - MPT encoder hardcodes positive bit; decoder must reject negative wire values

The encoder correctly sets result[0] = 0x60 (MPT flag 0x20 and positive flag 0x40) and rejects negative inputs at encode time. The gap was on the decode side: the Serialize impl previously read the positive bit from the leading byte and emitted a "-" prefix if it was clear, meaning a malformed wire payload would round-trip as a negative string instead of surfacing an error. The fix adds a guard in the MPT serialization branch, returning Err when the positive bit is not set. The existing test_mpt_amount_negative_sign_bit test is updated to assert the new error behavior rather than the former silent negative-value output.

Issue #262 - Bounds checks before indexing

is_native, is_mpt, and is_positive all indexed self.0[0] or self.0[1] unconditionally, panicking on an empty or single-byte buffer. The MPT serialization path also indexed bytes[1..9] and bytes[9..33] without checking length. Fixed as follows.

  • is_native and is_mpt use !self.0.is_empty() before accessing self.0[0].
  • is_positive checks self.0.len() >= 2 before accessing self.0[1].
  • The MPT branch in Serialize returns Err("MPT amount buffer too short") when bytes.len() < 33.

Tests confirm that Amount::new(Some(&[])) returns false for all three bool methods, and that serializing a 5-byte buffer with a valid MPT leading byte returns an error.

Test plan

  • test_mpt_reject_uppercase_hex_prefix: verifies "0X1F" is rejected at encode time.
  • test_mpt_negative_sign_bit_rejected_on_serialize: verifies a 33-byte buffer with 0x20 leading byte (positive bit clear) fails serialization.
  • test_amount_bool_methods_empty_buffer: verifies is_native, is_mpt, is_positive all return false on an empty buffer.
  • test_mpt_short_buffer_serialize_error: verifies a 5-byte MPT buffer fails serialization.
  • Existing test_mpt_amount_negative_sign_bit updated to expect an error instead of "-100".
  • All 722 unit tests and 63 doc tests pass with cargo test --release.
  • Zero clippy warnings under -D warnings with full feature set.

@codecov-commenter

codecov-commenter commented May 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (main@c80d932). Learn more about missing BASE report.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main     #310   +/-   ##
=======================================
  Coverage        ?   84.29%           
=======================================
  Files           ?      200           
  Lines           ?    20794           
  Branches        ?        0           
=======================================
  Hits            ?    17528           
  Misses          ?     3266           
  Partials        ?        0           
Flag Coverage Δ
unit 84.29% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/core/binarycodec/types/amount.rs 88.68% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread src/core/binarycodec/types/amount.rs Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the Amount binary codec’s MPT handling to align behavior with other XRPL implementations and to avoid panics on malformed buffers during (de)serialization.

Changes:

  • Restricts MPT hex parsing to lowercase 0x prefix (rejects 0X) to match xrpl.js canonicalization.
  • Rejects MPT payloads with the positive bit cleared during JSON serialization (treats as malformed wire data).
  • Adds bounds checks to avoid indexing short buffers in MPT serialization and in Amount classification helpers.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/core/binarycodec/types/amount.rs Tightens MPT parsing/serialization rules and adds length checks; adds targeted regression tests.
src/core/binarycodec/test/tx_encode_decode_tests.rs Updates existing MPT sign-bit test to expect serialization failure for malformed payloads.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 261 to 269
/// Returns True if this amount is a native XRP amount.
pub fn is_native(&self) -> bool {
self.0[0] & 0x80 == 0 && self.0[0] & 0x20 == 0
!self.0.is_empty() && self.0[0] & 0x80 == 0 && self.0[0] & 0x20 == 0
}

/// Returns True if this amount is an MPT amount.
pub fn is_mpt(&self) -> bool {
self.0[0] & 0x80 == 0 && self.0[0] & 0x20 != 0
!self.0.is_empty() && self.0[0] & 0x80 == 0 && self.0[0] & 0x20 != 0
}
let sign = if is_positive { "" } else { "-" };
// MPT amounts are unsigned; a cleared positive bit indicates a malformed payload.
if !is_positive {
return Err(S::Error::custom("MPT amount has negative sign bit set"));
Comment on lines +618 to +619
let mpt_id =
"A000000000000000000000000000000000000000000000000000000000000000"[..48].to_string();
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.

3 participants