Skip to content

fix(binarycodec): harden MPT Amount — bounds, sign-bit, 0X prefix, TryFrom guard - #339

Open
e-desouza wants to merge 2 commits into
mainfrom
fix/issue-259-262-mpt-amount
Open

fix(binarycodec): harden MPT Amount — bounds, sign-bit, 0X prefix, TryFrom guard#339
e-desouza wants to merge 2 commits into
mainfrom
fix/issue-259-262-mpt-amount

Conversation

@e-desouza

Copy link
Copy Markdown
Collaborator

Why

Amount in the binary codec had several gaps when processing MPT wire data:

  • The decoder silently produced a negative-prefixed string for wire data with a cleared positive flag bit, rather than rejecting the malformed payload. XLS-33 defines MPT amounts as unsigned; a cleared positive bit is invalid.
  • is_native, is_mpt, and is_positive panicked on empty buffers — any caller could trigger a process crash by constructing a zero-length Amount.
  • _deserialize_native_amount sliced 8 bytes without a length guard; a short buffer panicked inside Serialize.
  • The MPT decode path in Serialize accessed bytes[1..9] and bytes[9..33] without a prior len < 33 check.
  • TryFrom<serde_json::Value> for MPT amounts indexed obj["value"] via Map::index, which panics on a missing key. An object carrying only mpt_issuance_id (no "value") crashed the process on any public encode() call.
  • Uppercase "0X" hex prefix was silently accepted; xrpl.js rejects it.

Fixes #259, #262.

What changed

  • is_native, is_mpt, is_positive: guard with !self.0.is_empty() before byte 0 access.
  • _deserialize_native_amount: changed return to XRPLCoreResult<String>; added len < 8 early return.
  • MPT Serialize branch: added len < 33 check; returns Err instead of producing a negative string when positive bit is clear.
  • _serialize_mpt_amount: explicit Err on "0X" uppercase prefix (matches xrpl.js); uses strip_prefix("0x") only.
  • TryFrom<serde_json::Value> MPT branch: replaced obj["value"] / obj["mpt_issuance_id"] indexing with .get(key).and_then(|v| v.as_str()).ok_or(...).
  • Restored ICA fallthrough comment with XRPL spec note.

How to validate

cargo test --release
cargo test --release --no-default-features --features embassy-rt,core,utils,wallet,models,helpers,websocket,json-rpc

New tests: test_mpt_negative_sign_bit_rejected_on_serialize, test_mpt_short_buffer_serialize_error, test_amount_bool_methods_empty_buffer, test_mpt_reject_uppercase_hex_prefix, test_native_short_buffer_serialize_error.

@codecov

codecov Bot commented Jul 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.74%. Comparing base (c83df4f) to head (04854a8).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #339      +/-   ##
==========================================
+ Coverage   86.71%   86.74%   +0.03%     
==========================================
  Files         252      252              
  Lines       32630    32683      +53     
==========================================
+ Hits        28296    28352      +56     
+ Misses       4334     4331       -3     
Flag Coverage Δ
integration 71.32% <ø> (ø)
unit 87.36% <100.00%> (+0.03%) ⬆️

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 89.05% <100.00%> (+2.58%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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 binary codec’s Amount handling for MPT and native XRP wire data to avoid panics on malformed buffers, enforce XLS-33’s “MPT amounts are unsigned” rule, and align MPT hex parsing with xrpl.js behavior.

Changes:

  • Added length guards and error returns to prevent out-of-bounds reads during native/MPT serialization and in is_* helpers.
  • Enforced MPT positivity by rejecting wire payloads with the positive bit (0x40) cleared, instead of emitting negative-prefixed strings.
  • Tightened MPT value parsing to reject uppercase "0X" prefixes and added regression tests for the new hardening behavior.

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 Adds bounds checks, converts native decode to fallible, rejects invalid MPT sign bit and "0X" prefix, and adds new tests.
src/core/binarycodec/test/tx_encode_decode_tests.rs Updates MPT negative-sign-bit test to expect serialization rejection instead of a negative string.

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

Comment on lines 404 to +407
let is_positive = leading & 0x40 != 0;
let sign = if is_positive { "" } else { "-" };
if !is_positive {
return Err(S::Error::custom("MPT amount has negative sign bit set"));
}
Comment on lines +281 to +285
/// Returns true if the positive-sign bit (0x40) in byte 0 is set.
/// Returns false on an empty or structurally invalid buffer. Callers that need to
/// distinguish "not positive" from "invalid/empty" should check `!self.0.is_empty()` first.
pub fn is_positive(&self) -> bool {
self.0[0] & 0x40 > 0
!self.0.is_empty() && self.0[0] & 0x40 > 0
Comment on lines +469 to +473
if obj.contains_key("mpt_issuance_id") {
// Primary MPT discriminator — presence of mpt_issuance_id marks this as MPT.
if obj.contains_key("currency") || obj.contains_key("issuer") {
return Err(XRPLCoreException::SerdeJsonError(
XRPLSerdeJsonError::UnexpectedValueType {
- Correct misleading error message: "negative sign bit set" → "positive-sign
  bit (0x40) is not set", which accurately describes the failure condition
- Tighten is_positive() rustdoc: remove unsupported "structurally invalid"
  claim; only an empty buffer is guarded against
- Enforce exact-key validation for MPT in TryFrom<serde_json::Value>: require
  exactly {"mpt_issuance_id","value"} (len == 2 + both keys present), matching
  xrpl.js isAmountObjectMPT and the strict shape in src/models/amount/mod.rs
@e-desouza
e-desouza force-pushed the fix/issue-259-262-mpt-amount branch from 04854a8 to 5fc169a Compare July 15, 2026 16:45
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.

MPT amount encoder hardcodes positive sign bit; decoder permits negative

2 participants