XdrJsonHelper
in package
Primitive SEP-51 (XDR-JSON) helper methods.
This class provides low-level encoding and decoding utilities for the SEP-0051 standard mapping between Stellar XDR structures and their JSON representations. Higher-level toJson/fromJson methods on individual XDR classes delegate to these primitives for the byte-level and numeric operations defined in the spec.
All methods are stateless and operate only on their arguments.
Tags
Table of Contents
Methods
- bytesToHex() : string
- SEP-51 Opaque type: encode raw bytes as a lowercase hex string.
- canonicalJson() : string
- Canonical JSON normalisation for structural JSON comparison.
- escapeString() : string
- SEP-51 String type: escape bytes per the SEP-51 String escape ladder.
- hexToBytes() : string
- Inverse of bytesToHex: decode a lowercase hex string back to raw bytes.
- int128PartsToString() : string
- Assemble a signed 128-bit integer from hi/lo limbs and return as a base-10 string.
- int256PartsToString() : string
- Assemble a signed 256-bit integer from four limbs and return as a base-10 string.
- int64ToString() : string
- 64-bit signed integer to base-10 string (SEP-51 Hyper Integer encoding).
- ksortRecursive() : mixed
- Recursively sort object property names for canonical JSON normalisation.
- safePreview() : string
- Return a safe, bounded preview of user-supplied input for use in exception messages.
- stringToInt128Parts() : array{hi: string, lo: string}
- Decompose a signed 128-bit integer string into hi/lo limbs.
- stringToInt256Parts() : array{hiHi: string, hiLo: string, loHi: string, loLo: string}
- Decompose a signed 256-bit integer string into four limbs.
- stringToInt64() : int
- Parse a base-10 integer string (or a PHP int) to a 64-bit signed int.
- stringToUint128Parts() : array{hi: string, lo: string}
- Decompose an unsigned 128-bit integer string into hi/lo uint64 parts.
- stringToUint256Parts() : array{hiHi: string, hiLo: string, loHi: string, loLo: string}
- Decompose an unsigned 256-bit integer string into four uint64 limbs.
- stringToUint64() : int
- Parse a base-10 unsigned-integer string (or a PHP int) to a uint64 value.
- uint128PartsToString() : string
- Assemble an unsigned 128-bit integer from hi/lo uint64 parts and return as a base-10 string.
- uint256PartsToString() : string
- Assemble an unsigned 256-bit integer from four uint64 limbs and return as a base-10 string.
- uint64ToString() : string
- 64-bit unsigned integer to base-10 string (SEP-51 Unsigned Hyper Integer encoding).
- unescapeString() : string
- Inverse of escapeString: decode a SEP-51 escaped-ASCII string back to raw bytes.
- wrapUnsignedToSignedInt() : int
- Wrap an unsigned uint64 base-10 string into the matching PHP signed-int representation used by the Parts struct fields.
Methods
bytesToHex()
SEP-51 Opaque type: encode raw bytes as a lowercase hex string.
public
static bytesToHex(string $bytes) : string
Per SEP-0051 the Opaque types (both fixed-length and variable-length) are represented as hexadecimal strings. An empty byte sequence encodes as an empty string "" (not "0" and not "00").
Parameters
- $bytes : string
-
Raw binary input (any length, including zero).
Return values
string —Lowercase hex string (even length; two hex chars per byte).
canonicalJson()
Canonical JSON normalisation for structural JSON comparison.
public
static canonicalJson(string $json) : string
The algorithm:
- Decode with assoc=false (stdClass mode) so the empty-object / empty-array distinction is preserved: json_decode('}') -> stdClass, json_decode('[]') -> [].
- Recurse via ksortRecursive: sort object property names lexicographically; preserve list order for indexed arrays.
- Re-encode with JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE so that forward slashes and non-ASCII codepoints are not double-escaped.
The result is a deterministic, whitespace-free JSON string. Two JSON values that are semantically equal (same structure, same key order after sorting, same values) will produce byte-identical canonical forms.
Note: this method does not impose an input-size limit. Callers are responsible for bounding $json length before invoking this method; an HTTP layer or file reader should apply a size limit appropriate to its threat model.
Parameters
- $json : string
-
Any valid JSON string.
Tags
Return values
string —Canonical (sorted-keys, compact) JSON string.
escapeString()
SEP-51 String type: escape bytes per the SEP-51 String escape ladder.
public
static escapeString(string $bytes) : string
Escaping rules (applied byte-by-byte on the raw binary input):
- 0x00 (NUL) -> \0
- 0x09 (TAB) -> \t
- 0x0A (LF) -> \n
- 0x0D (CR) -> \r
- 0x5C (BS) -> \
- 0x20..0x7E (printable ASCII, excluding backslash) -> verbatim
- all other bytes (0x01..0x08, 0x0B, 0x0C, 0x0E..0x1F, 0x7F, 0x80..0xFF) -> \xNN (lowercase hex, exactly two digits)
The input is treated as a raw byte string (not UTF-8); strlen() and ord() are used throughout — never mb_strlen() or mb_substr().
When this output is subsequently stored inside a JSON string literal the JSON encoder will escape each backslash a second time, producing the double-escaped form the spec specifies (e.g. "\xc3" in JSON text).
Parameters
- $bytes : string
-
Raw binary input (any byte sequence).
Return values
string —Escaped ASCII output (only printable ASCII bytes).
hexToBytes()
Inverse of bytesToHex: decode a lowercase hex string back to raw bytes.
public
static hexToBytes(string $hex) : string
Strict mode is enforced: only lowercase hexadecimal characters [0-9a-f] are accepted. Uppercase hex is rejected; SEP-0051 §Opaque specifies lowercase hex output, and the decoder mirrors that constraint so that round-trips remain canonical.
Parameters
- $hex : string
-
Lowercase hex string (even length; zero length allowed).
Tags
Return values
string —Raw binary bytes.
int128PartsToString()
Assemble a signed 128-bit integer from hi/lo limbs and return as a base-10 string.
public
static int128PartsToString(string $hi, string $lo) : string
Per XDR Int128Parts (verified against XdrInt128Parts.php encode path): hi is signed int64; lo is unsigned uint64.
result = hi_signed * 2^64 + lo_unsigned
lo_unsigned is obtained by treating the PHP int stored in $lo as an unsigned 64-bit value (i.e. adding 2^64 if negative). GMP is used throughout to handle the full range.
Parameters
- $hi : string
-
Signed int64 as base-10 string (range [-2^63, 2^63-1]).
- $lo : string
-
Unsigned uint64 as base-10 string. May be a negative base-10 string when the uint64 value exceeds PHP_INT_MAX and is stored as a signed PHP int (the negative form is reinterpreted as the unsigned bit pattern via two's-complement).
Tags
Return values
string —Base-10 decimal string of the assembled 128-bit signed integer.
int256PartsToString()
Assemble a signed 256-bit integer from four limbs and return as a base-10 string.
public
static int256PartsToString(string $hiHi, string $hiLo, string $loHi, string $loLo) : string
Per XDR Int256Parts (verified against XdrInt256Parts.php encode path):
- hiHi: signed int64 (most significant)
- hiLo: unsigned uint64
- loHi: unsigned uint64
- loLo: unsigned uint64 (least significant)
All limb strings may be supplied in either unsigned form ("18446744073709551615") or in PHP's signed two's-complement wrap form ("-1") — the internal uint64HalfToGmp helper handles both representations for lossless assembly.
Parameters
- $hiHi : string
-
Signed int64 as base-10 string (range [-2^63, 2^63-1]).
- $hiLo : string
-
Unsigned uint64 as base-10 string. May be a negative base-10 string when the uint64 value exceeds PHP_INT_MAX and is stored as a signed PHP int (the negative form is reinterpreted as the unsigned bit pattern via two's-complement).
- $loHi : string
-
Unsigned uint64 as base-10 string (same note as hiLo).
- $loLo : string
-
Unsigned uint64 as base-10 string (same note as hiLo).
Tags
Return values
string —Base-10 decimal string of the assembled 256-bit signed integer.
int64ToString()
64-bit signed integer to base-10 string (SEP-51 Hyper Integer encoding).
public
static int64ToString(int $value) : string
Parameters
- $value : int
-
A PHP int (64-bit signed on 64-bit systems).
Return values
string —Base-10 decimal string representation.
ksortRecursive()
Recursively sort object property names for canonical JSON normalisation.
public
static ksortRecursive(mixed $value[, int $depth = 512 ]) : mixed
Walk rules:
- stdClass (object): cast to array, ksort by string key, recurse into values, cast back to object. This preserves the } vs [] distinction in re-encoding.
- Indexed array (list-shape): preserve element order; recurse into elements.
- Associative array: ksort and recurse (defensive; should not occur in stdClass-mode decode but covered for callers using this method directly on mixed structures).
- Scalar / null: return as-is.
The $depth parameter bounds recursion depth, matching json_decode's own default of 512. Callers constructing deeply-nested PHP arrays by hand and passing them directly here (rather than via canonicalJson) should pass an appropriate depth bound. canonicalJson starts the recursion at depth=512 which aligns with json_decode's own limit.
Parameters
- $value : mixed
-
Any decoded JSON value.
- $depth : int = 512
-
Remaining recursion depth. Throws when it reaches zero.
Tags
Return values
mixed —The same structure with all object property keys sorted recursively.
safePreview()
Return a safe, bounded preview of user-supplied input for use in exception messages.
public
static safePreview(string $s[, int $max = 80 ]) : string
Embedding unbounded user input in exception messages can cause log-amplification denial-of-service when the message is logged. This helper caps the preview at $max bytes and appends "..." when the string is truncated, keeping messages informative without being exploitable.
Parameters
- $s : string
-
The user-supplied string.
- $max : int = 80
-
Maximum characters before truncation (must be > 3).
Return values
string —A safe, bounded preview.
stringToInt128Parts()
Decompose a signed 128-bit integer string into hi/lo limbs.
public
static stringToInt128Parts(string $value) : array{hi: string, lo: string}
Per XDR Int128Parts (verified against XdrInt128Parts.php decode path): hi is signed int64; lo is unsigned uint64.
Returns an associative array with:
- 'hi': signed int64 base-10 string (range [-2^63, 2^63-1])
- 'lo': unsigned uint64 base-10 string (range [0, 2^64-1])
Parameters
- $value : string
-
Base-10 decimal string of a signed 128-bit integer.
Tags
Return values
array{hi: string, lo: string}stringToInt256Parts()
Decompose a signed 256-bit integer string into four limbs.
public
static stringToInt256Parts(string $value) : array{hiHi: string, hiLo: string, loHi: string, loLo: string}
Per XDR Int256Parts (verified against XdrInt256Parts.php decode path): only hiHi is signed int64; hiLo, loHi, loLo are unsigned uint64. Returns an associative array with:
- 'hiHi': signed int64 base-10 string (range [-2^63, 2^63-1])
- 'hiLo': unsigned uint64 base-10 string (range [0, 2^64-1])
- 'loHi': unsigned uint64 base-10 string (range [0, 2^64-1])
- 'loLo': unsigned uint64 base-10 string (range [0, 2^64-1])
Parameters
- $value : string
-
Base-10 decimal string of a signed 256-bit integer.
Tags
Return values
array{hiHi: string, hiLo: string, loHi: string, loLo: string}stringToInt64()
Parse a base-10 integer string (or a PHP int) to a 64-bit signed int.
public
static stringToInt64(int|string $value) : int
Accepts int|string for compatibility with JSON producers that emit numbers for 64-bit integers in addition to the spec-required strings.
Strict validation rules:
- Empty string is rejected.
- Leading/trailing whitespace is rejected.
- Scientific notation ("1e10") is rejected.
- Decimal points ("1.0") are rejected.
- Hex notation ("0x10") is rejected.
- Leading "+" is rejected.
- Only an optional leading "-" followed by one or more decimal digits is accepted.
- Leading zeros are accepted (SEP-0051 does not forbid them in 64-bit string-encoded integers).
- The resulting value must be in [-2^63, 2^63-1].
- intval() is explicitly NOT used because intval("abc") silently returns 0.
Parameters
- $value : int|string
-
The value to parse.
Tags
Return values
int —The parsed 64-bit signed integer.
stringToUint128Parts()
Decompose an unsigned 128-bit integer string into hi/lo uint64 parts.
public
static stringToUint128Parts(string $value) : array{hi: string, lo: string}
Returns an associative array with keys 'hi' and 'lo', both as base-10 strings representing unsigned uint64 values in [0, 2^64-1].
Parameters
- $value : string
-
Base-10 decimal string of an unsigned 128-bit integer.
Tags
Return values
array{hi: string, lo: string}stringToUint256Parts()
Decompose an unsigned 256-bit integer string into four uint64 limbs.
public
static stringToUint256Parts(string $value) : array{hiHi: string, hiLo: string, loHi: string, loLo: string}
Returns an associative array with keys 'hiHi', 'hiLo', 'loHi', 'loLo', all as base-10 strings representing unsigned uint64 values in [0, 2^64-1].
Parameters
- $value : string
-
Base-10 decimal string of an unsigned 256-bit integer.
Tags
Return values
array{hiHi: string, hiLo: string, loHi: string, loLo: string}stringToUint64()
Parse a base-10 unsigned-integer string (or a PHP int) to a uint64 value.
public
static stringToUint64(int|string $value) : int
Accepts int|string for compatibility with JSON producers that emit numbers for 64-bit integers in addition to the spec-required strings.
For uint64 values above PHP_INT_MAX (i.e. 9223372036854775808..18446744073709551615) a string must be provided; those values cannot be expressed as a PHP native int. The method uses GMP for range validation against [0, 2^64-1] and returns the value as a PHP int, reinterpreting the upper-half as a negative signed int (two's complement) — this matches how the XDR parts structs store uint64 limbs.
Strict validation: same rules as stringToInt64 except no leading minus is allowed.
Parameters
- $value : int|string
-
The value to parse.
Tags
Return values
int —The uint64 value stored as a PHP int (may be negative for values > PHP_INT_MAX).
uint128PartsToString()
Assemble an unsigned 128-bit integer from hi/lo uint64 parts and return as a base-10 string.
public
static uint128PartsToString(string $hi, string $lo) : string
For unsigned 128-bit (UInt128Parts) both the hi and lo limbs are unsigned uint64 values stored as PHP signed ints (two's-complement). GMP is used to reconstruct the full value.
Parameters
- $hi : string
-
Unsigned uint64 as base-10 string. May be a negative base-10 string when the uint64 value exceeds PHP_INT_MAX and is stored as a signed PHP int (the negative form is reinterpreted as the unsigned bit pattern via two's-complement).
- $lo : string
-
Unsigned uint64 as base-10 string. May be a negative base-10 string when the uint64 value exceeds PHP_INT_MAX and is stored as a signed PHP int (the negative form is reinterpreted as the unsigned bit pattern via two's-complement).
Tags
Return values
string —Base-10 decimal string of the assembled 128-bit unsigned integer.
uint256PartsToString()
Assemble an unsigned 256-bit integer from four uint64 limbs and return as a base-10 string.
public
static uint256PartsToString(string $hiHi, string $hiLo, string $loHi, string $loLo) : string
Per XDR UInt256Parts all four limbs are unsigned uint64 values.
Parameters
- $hiHi : string
-
Unsigned uint64 as base-10 string (most significant limb). May be a negative base-10 string when the uint64 value exceeds PHP_INT_MAX and is stored as a signed PHP int (the negative form is reinterpreted as the unsigned bit pattern via two's-complement).
- $hiLo : string
-
Unsigned uint64 as base-10 string (same note as hiHi).
- $loHi : string
-
Unsigned uint64 as base-10 string (same note as hiHi).
- $loLo : string
-
Unsigned uint64 as base-10 string (least significant limb; same note as hiHi).
Tags
Return values
string —Base-10 decimal string of the assembled 256-bit unsigned integer.
uint64ToString()
64-bit unsigned integer to base-10 string (SEP-51 Unsigned Hyper Integer encoding).
public
static uint64ToString(int $value) : string
PHP's int is a 64-bit signed integer on 64-bit systems, so the valid range for the $value parameter is [0, PHP_INT_MAX] (i.e. [0, 2^63-1]). Values in the upper uint64 range (2^63..2^64-1) cannot be represented as a PHP int without overflow; callers holding such values must use stringToUint64() with a string argument instead of this method.
Parameters
- $value : int
-
A PHP int in [0, PHP_INT_MAX].
Tags
Return values
string —Base-10 decimal string.
unescapeString()
Inverse of escapeString: decode a SEP-51 escaped-ASCII string back to raw bytes.
public
static unescapeString(string $escaped) : string
Recognised escape sequences:
- \0 -> 0x00
- \t -> 0x09
- \n -> 0x0A
- \r -> 0x0D
- \ -> 0x5C
- \xNN (exactly two lowercase hex digits) -> byte 0xNN
Any other character following a backslash is rejected. Unescaped printable ASCII characters (0x20..0x7E) are returned as their byte value. The input is validated strictly so that malformed sequences do not silently produce wrong output.
Parameters
- $escaped : string
-
The escaped-ASCII string (output of escapeString).
Tags
Return values
string —Raw binary bytes.
wrapUnsignedToSignedInt()
Wrap an unsigned uint64 base-10 string into the matching PHP signed-int representation used by the Parts struct fields.
public
static wrapUnsignedToSignedInt(string $decimal) : int
The 128-bit / 256-bit Parts wrappers store every limb as a PHP signed int (XDR's hyper / unsigned hyper map onto the same 64-bit native slot on 64-bit systems). Values in [2^63, 2^64-1] therefore land in PHP as negative integers via two's-complement subtraction. This helper performs that conversion once, with full input validation, so the generated parts-decoder bodies do not each carry their own inline closure.
Accepted input: a non-empty string of decimal digits representing an unsigned integer in [0, 2^64-1]. Out-of-range values, leading signs, empty strings, and non-digit characters are rejected.
Parameters
- $decimal : string
-
Unsigned base-10 string in [0, 2^64-1].
Tags
Return values
int —PHP signed int (may be negative for values above PHP_INT_MAX).