Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
1dc905c
Rust FFI C-bindings using bindgen; Download all platform binaries fro…
ckeshava Apr 24, 2026
2eba122
WIP 2/3: Safe mpt-crypto wrapper: This crate allows users to access t…
ckeshava Apr 25, 2026
39aca93
WIP: This commit includes all the transaction models pertaining to Co…
ckeshava Apr 25, 2026
10dab56
Merge branch 'main' into cmpt
ckeshava Jun 1, 2026
8ac286f
fix linter errors
ckeshava Jun 1, 2026
1e25b8d
minor: fix cargo clippy errors
ckeshava Jun 1, 2026
acee836
Merge branch 'main' into cmpt
ckeshava Jun 1, 2026
9fef818
feat: Do not auto-remove docker container on an ephemeral CI machine
ckeshava Jun 1, 2026
2a20f36
feat: Wire up confidential_mpt (non-default feature) in the Github CI…
ckeshava Jun 2, 2026
3821cff
minor: re-export mpt_crypto library; Ensures that all users of xrpl-r…
ckeshava Jun 2, 2026
5b8e2a2
tests: framework to test the cMPTConvert transaction.
ckeshava Jun 2, 2026
1dbcec3
feat: autofill the 10× fee for ConfidentialMPT transactions.
ckeshava Jun 2, 2026
a7b0de0
tests: cMPT Convert + MergeInbox happy-path integration tests.
ckeshava Jun 2, 2026
f51df57
tests: Integration tests for all the five transactions in Confidentia…
ckeshava Jun 2, 2026
c048ada
minor: fix linter errors
ckeshava Jun 2, 2026
ad96ce9
unit test: Additional unit tests for conf_mpt_convert transaction
ckeshava Jun 3, 2026
ba947d2
tests: MergeInbox protocol-level failure cases (XLS-0096 §9.2.1.2)
ckeshava Jun 3, 2026
49600f8
add missing utility functions
pdp2121 Jun 8, 2026
954106c
Merge branch 'main' into cmpt
ckeshava Jun 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .ci-config/xrpld.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ TokenEscrow
SingleAssetVault
LendingProtocol
PermissionDelegationV1_1
ConfidentialTransfer

# This section can be used to simulate various FeeSettings scenarios for rippled node in standalone mode
[voting]
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/integration_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ jobs:
run: |
docker run \
--detach \
--rm \
--publish 5005:5005 \
--publish 6006:6006 \
--volume "${{ github.workspace }}/.ci-config/":"/etc/xrpld/" \
Expand Down Expand Up @@ -59,7 +58,7 @@ jobs:
- name: Run integration tests under coverage
run: |
cargo llvm-cov --no-report --release \
--features std,json-rpc,helpers,cli,websocket,integration \
--features std,json-rpc,helpers,cli,websocket,integration,confidential-mpt \
--test integration_test \
--test cli_integration \
--test funding \
Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/unit_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ jobs:
env:
RUST_BACKTRACE: 1

# confidential-mpt is not a default feature (it links the mpt-crypto
# native library via FFI), so the cMPT transaction-model unit tests and
# the mpt-crypto safe-wrapper tests are not covered by the step above.
# Run them explicitly. Kept separate from the coverage step so the
# unexercised FFI crate does not affect the unit coverage threshold.
- name: Test confidential-mpt (models + mpt-crypto wrapper)
run: |
cargo test --release --features confidential-mpt --lib
cargo test --release -p mpt-crypto
env:
RUST_BACKTRACE: 1

- name: Test for no_std
run: cargo test --release --no-default-features --features
embassy-rt,core,utils,wallet,models,helpers,websocket,json-rpc
Expand Down
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[workspace]
members = ["internal/mpt-crypto-sys", "internal/mpt-crypto"]

[package]
name = "xrpl-rust"
version = "1.1.0"
Expand Down Expand Up @@ -26,6 +29,9 @@ crate-type = ["lib"]

[dependencies]
xrpl-rust-macros = { version = "0.1.0", path = "xrpl-rust-macros" }
# Optional: enabled by the `confidential-mpt` feature. The mpt-crypto crate
# is std-only (FFI to the C library), so the feature also pulls in `std`.
mpt-crypto = { version = "0.1.0", path = "internal/mpt-crypto", optional = true }

lazy_static = "1.4.0"
sha2 = { version = "0.10.2", default-features = false }
Expand Down Expand Up @@ -139,6 +145,10 @@ websocket = [
core = ["utils"]
utils = []
cli = ["dep:clap", "dep:bip39"]
# Confidential MPT (XLS-0096) support. Requires `std` because mpt-crypto links
# the native libmpt-crypto library via FFI. Adds the 5 ConfidentialMPT
# transaction models and the safe-wrapper crypto crate to the public surface.
confidential-mpt = ["std", "models", "dep:mpt-crypto"]
integration = [] # Enable integration tests (requires network access)
std = [
"rand/std",
Expand Down
7 changes: 7 additions & 0 deletions internal/mpt-crypto-sys/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Vendored native binaries are not committed — they're large (~40 MB across
# all platforms), and `build.rs` fetches them from the upstream GitHub
# release on first build, verified by SHA-256.
#
# Developers who want offline builds can run `scripts/fetch_upstream.sh`
# locally to populate this directory; their local copy is gitignored.
vendor/lib/
27 changes: 27 additions & 0 deletions internal/mpt-crypto-sys/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[package]
name = "mpt-crypto-sys"
version = "0.1.0"
edition = "2024"
description = "Low-level FFI bindings to the mpt-crypto C library for XLS-0096 Confidential MPTs."
license = "ISC"
repository = "https://github.com/XRPLF/xrpl-rust"
publish = false
links = "mpt-crypto"

# If this crate is ever published, the committed binaries would exceed
# crates.io's size limit. Exclude them — consumers would have to use the
# build.rs download path or set MPT_CRYPTO_LIB_DIR.
exclude = ["vendor/lib/**"]

[lib]
name = "mpt_crypto_sys"

# --- build-time dependencies: HTTP, gzip, tar, checksum ---
# These compile once per target machine on the first build that has to
# fetch the upstream bundle. They are not part of the consumer's final
# binary; they exist only to run build.rs.
[build-dependencies]
ureq = { version = "2.10", default-features = false, features = ["tls"] }
flate2 = "1.0"
tar = "0.4"
sha2 = "0.10"
223 changes: 223 additions & 0 deletions internal/mpt-crypto-sys/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
# mpt-crypto-sys

Low-level FFI bindings to the [mpt-crypto](https://github.com/XRPLF/mpt-crypto)
C library, which implements the cryptographic primitives for
[XLS-0096 Confidential Multi-Purpose Tokens](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0096-confidential-mpt)
on the XRP Ledger.

This is an **internal crate**. The public API lives in the sibling
`mpt-crypto` crate (safe Rust wrappers, idiomatic types, secret zeroization).

## How the native library is resolved

`build.rs` finds `libmpt-crypto.{so,dylib,dll}` in this priority order:

1. **`MPT_CRYPTO_LIB_DIR` environment variable.** For air-gapped builds,
enterprise setups, or pointing at a locally-built copy. Must contain
the platform-appropriate library file.
2. **`vendor/lib/<rust-target-triple>/`** — populated locally by running
`scripts/fetch_upstream.sh`. **Not committed to git** (binaries are
~40 MB across all platforms). Useful for offline builds or iterating
against a custom mpt-crypto build.
3. **Downloaded from the upstream GitHub release** (the default for fresh
clones). Runs once per clean build, cached in `OUT_DIR`, verified by the
hardcoded `BUNDLE_SHA256` constant against tampering or upstream
retagging. Supported platforms:
- `aarch64-apple-darwin`
- `x86_64-apple-darwin`
- `aarch64-unknown-linux-gnu`
- `x86_64-unknown-linux-gnu`
- `s390x-unknown-linux-gnu`
- `x86_64-pc-windows-msvc`

The linker is pointed at whichever directory wins. `build.rs` also copies
the shared library to `target/<profile>/` so tests, examples, and built
binaries find it at runtime via embedded rpath (`@loader_path` on macOS,
`$ORIGIN` on Linux) or adjacency (Windows).

### First-build network requirement

A fresh `cargo build` after `git clone` downloads roughly 20 MB from
`github.com/XRPLF/mpt-crypto/releases/`. Subsequent builds reuse the
cached extract in `OUT_DIR`. To skip the download (e.g., on an air-gapped
build server), either run `scripts/fetch_upstream.sh` once on a machine
with network access and copy the populated `vendor/lib/` over, or set
`MPT_CRYPTO_LIB_DIR` to a directory already containing the right binary.

## Where each file comes from

This crate mixes hand-written code, auto-generated code, and vendored upstream
content. Provenance:

| Path | Source | Refreshed by |
|---|---|---|
| `src/bindings.rs` | Auto-generated by [bindgen](https://rust-lang.github.io/rust-bindgen/) from the C headers in `vendor/include/`. The first ~5 lines are a script-added banner; everything below is bindgen output verbatim. | `scripts/regenerate_bindings.sh` (requires `bindgen-cli` + `libclang`) |
| `vendor/include/secp256k1_mpt.h` | Upstream mpt-crypto release tarball: `https://github.com/XRPLF/mpt-crypto/releases/download/<TAG>/mpt-crypto-natives-<TAG>.tar.gz` | `scripts/fetch_upstream.sh <tag>` |
| `vendor/include/utility/mpt_utility.h` | Same upstream release tarball | `scripts/fetch_upstream.sh <tag>` |
| `vendor/include/secp256k1.h` | [bitcoin-core/secp256k1](https://github.com/bitcoin-core/secp256k1) `include/secp256k1.h` at commit [`ea174fe`](https://github.com/bitcoin-core/secp256k1/blob/ea174fe045e1832548cd3b7090958afe9573ad2b/include/secp256k1.h) (file SHA-256 `e1717d89…d2209`). Vendored separately because the mpt-crypto release bundle doesn't include it but `secp256k1_mpt.h` `#include`s it. | One-time vendoring; refresh manually (see below) if mpt-crypto starts using newer secp256k1 APIs |
| `vendor/lib/<target>/libmpt-crypto.*` | Same upstream release tarball, per-platform binaries. **Gitignored.** | `build.rs` downloads on-demand at build time, OR `scripts/fetch_upstream.sh <tag>` |
| `build.rs`, `Cargo.toml`, `src/lib.rs`, `tests/smoke.rs`, `scripts/*.sh`, `README.md`, `.gitignore` | Hand-written | Edit directly |

### About `src/bindings.rs` specifically

It is **machine-generated**. Do not edit it by hand — your changes will be
overwritten by the next run of `scripts/regenerate_bindings.sh`. The script
creates a temporary "shim" header in `vendor/include/_shim_all.h` that
`#include`s both public headers, runs bindgen against it with an allowlist
filtering down to the `mpt-crypto` and `secp256k1` symbol families, and
writes the result here.

The committed file is what every downstream `cargo build` compiles against.
Regenerating it requires `libclang` and `bindgen-cli`, but **only the
maintainer regenerating** needs those tools — downstream consumers compile
the committed Rust source directly.

To inspect what's available in the FFI surface without reading the file:

```bash
# All exported functions
grep "pub fn " src/bindings.rs | awk '{print $3}' | sed 's/(.*//' | sort

# All compile-time constants
grep "^pub const " src/bindings.rs | awk '{print $3}' | sed 's/:$//'
```

## Bumping the mpt-crypto version

Upstream releases a new tag (e.g. `0.4.0`):

```bash
cd internal/mpt-crypto-sys

# 1. Refresh vendored headers + per-platform binaries.
scripts/fetch_upstream.sh 0.4.0

# 2. Update constants in build.rs:
# MPT_CRYPTO_VERSION = "0.4.0"
# BUNDLE_SHA256 = "<what the script printed>"

# 3. Refresh the Rust FFI declarations.
scripts/regenerate_bindings.sh

# 4. Commit headers, bindings, and build.rs (NOT vendor/lib/, gitignored).
git add vendor/include/ src/bindings.rs build.rs
git commit -m "mpt-crypto-sys: bump to 0.4.0"
```

### Refreshing `vendor/include/secp256k1.h` separately

The bitcoin-core `secp256k1.h` header isn't bundled with mpt-crypto releases,
so it's tracked independently. To refresh — typically only needed if mpt-crypto
starts depending on a newer secp256k1 API:

```bash
# Pin to a specific commit or release tag for reproducibility.
COMMIT=ea174fe045e1832548cd3b7090958afe9573ad2b # or e.g. v0.7.1
curl -sSL \
"https://raw.githubusercontent.com/bitcoin-core/secp256k1/${COMMIT}/include/secp256k1.h" \
-o vendor/include/secp256k1.h

# Sanity-check the header parses against the rest of vendor/include/
scripts/regenerate_bindings.sh
```

After that, update the `secp256k1.h` row in this README's provenance table
with the new commit SHA and file SHA-256.

## Verifying the build linked correctly

Two commands cover day-to-day "is it working?":

```bash
# 1. Functional check — runs three smoke tests that exercise FFI end-to-end.
cargo test -p mpt-crypto-sys

# 2. Linkage check — confirms the test binary references the shared lib via
# rpath (relocatable), not an absolute path.
otool -L target/debug/deps/smoke-* 2>/dev/null | grep -A1 mpt-crypto # macOS
ldd target/debug/deps/smoke-* 2>/dev/null | grep mpt-crypto # Linux
```

If the test command passes and the linkage check shows
`@rpath/libmpt-crypto.dylib` (macOS) or just `libmpt-crypto.so => …` (Linux),
the FFI chain is healthy.

### Deeper inspection (when something is wrong)

If a build succeeds but the test binary fails at runtime with "Library not
loaded" (macOS) or "error while loading shared libraries" (Linux):

```bash
# Was the dylib copied to target/<profile>/ by build.rs?
ls -lh target/debug/libmpt-crypto.*

# What rpath entries did the linker bake in?
otool -l target/debug/deps/smoke-* | grep -A2 LC_RPATH # macOS
readelf -d target/debug/deps/smoke-* | grep -E 'RPATH|RUNPATH' # Linux

# What is dyld actually trying? Re-run the test binary directly so its
# diagnostic stderr is not swallowed by cargo:
TEST_BIN=$(ls -t target/debug/deps/smoke-* | grep -v '\.d$' | head -1)
"$TEST_BIN"
```

The diagnostic message lists exactly which directories were searched —
that's usually enough to identify a wrong-arch dylib, a missing copy, or a
malformed rpath.

### Negative test — proving rpath is load-bearing

To confirm the rpath mechanism is actually doing the work (and the dylib
isn't being found by accident via `DYLD_LIBRARY_PATH` or similar):

```bash
TEST_BIN=$(ls -t target/debug/deps/smoke-* | grep -v '\.d$' | head -1)

# Hide the dylib; the binary should fail to load.
mv target/debug/libmpt-crypto.dylib /tmp/.libmpt-crypto.dylib.bak
"$TEST_BIN" # expect: dyld: Library not loaded: @rpath/libmpt-crypto.dylib

# Restore; the binary should run again.
mv /tmp/.libmpt-crypto.dylib.bak target/debug/libmpt-crypto.dylib
"$TEST_BIN" # expect: 3 passed; 0 failed
```

### Platform reference

| macOS | Linux |
|---|---|
| `otool -L <bin>` | `ldd <bin>` |
| `otool -l <bin> \| grep LC_RPATH` | `readelf -d <bin> \| grep -E 'RPATH\|RUNPATH'` |
| `@rpath/libmpt-crypto.dylib` | `libmpt-crypto.so` |
| `@loader_path`, `@executable_path` | `$ORIGIN` |
| `dyld[…]: Library not loaded:` | `error while loading shared libraries:` |

## Downstream build dependencies

Because `src/bindings.rs` is committed, downstream consumers of this crate
**do not** need `libclang` or `bindgen-cli` installed. Only maintainers
regenerating bindings need those tools.

If the native library is fetched at build time (priority 3 above), the
consumer needs network access during the first `cargo build` after a clean
checkout. Subsequent builds use the cached copy in `OUT_DIR`.

## Why `publish = false`

This is an internal binding crate; it has no value as a standalone library
on crates.io. Consumers should use the parent `xrpl-rust` workspace via a
path or git dependency, which routes through the safe `mpt-crypto` wrapper
crate.

## Offline or enterprise build

If the build machine can't reach github.com (or the download path is
otherwise undesired), point the build at a pre-fetched library directory:

```bash
export MPT_CRYPTO_LIB_DIR=/path/to/dir/containing/libmpt-crypto.so
cargo build
```

The directory must contain `libmpt-crypto.{so,dylib}` (Unix) or
`mpt-crypto.dll` (Windows) matching the target triple.
Loading
Loading