Skip to content

[WIP] Confidential transfers - #233

Draft
JordiParraCrespo wants to merge 79 commits into
mainfrom
confidential-transfers
Draft

[WIP] Confidential transfers#233
JordiParraCrespo wants to merge 79 commits into
mainfrom
confidential-transfers

Conversation

@JordiParraCrespo

Copy link
Copy Markdown
Collaborator

Confidential transfers

Description

This pull request introduces comprehensive support for XLS-96 confidential transfer features in the codebase, particularly for Multi-Party Token (MPT) ledger entries and transactions. The changes add confidential transfer fields, flags, transaction types, and validation helpers to enable confidential amounts, encrypted balances, and related cryptographic operations for tokens. The update also extends test coverage to include these new features.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Refactoring

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code where needed
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective
  • New and existing unit tests pass locally with my changes

Notes (optional)

Pre-merge checklist - XLS-96 Confidential MPT


Must fix

  • BlindingFactor type and nth changed in rippled: Our definitions.json has BlindingFactor as Blob, nth 45, isVLEncoded: true. Rippled now defines it as UINT256, nth 39, isVLEncoded: false (commit a43cf94ff7). This is a serialization-breaking change - the field will encode/decode incorrectly.
  • AmountCommitment nth changed: Our definitions.json has nth 46. Rippled now has nth 45. Serialization will break.
  • BalanceCommitment nth changed: Our definitions.json has nth 47. Rippled now has nth 46. Serialization will break.
  • HolderEncryptionKey size validation: confidential_mpt_convert.go:106 validates 64 bytes (uncompressed). Rippled enforces 33 bytes (compressed, 0x02/0x03 prefix via isValidCompressedECPoint). Every valid key is rejected and every invalid key is accepted.

Updated field comparison - definitions.json vs rippled

The following table shows all fields that differ between our definitions.json and the latest rippled sfields.macro:

Field Our type Our nth Rippled type Rippled nth Status
BlindingFactor Blob 45 UINT256 39 WRONG type + nth
AmountCommitment Blob 46 VL (Blob) 45 WRONG nth
BalanceCommitment Blob 47 VL (Blob) 46 WRONG nth

Should fix ?

  • Clawback ZKProof length: Currently IsValidHexBlob (any non-empty hex). Rippled requires exactly 98 bytes (196 hex) — ecEqualityProofLength.
  • ConvertBack ZKProof length: Currently IsValidHexBlob. Rippled requires exactly 883 bytes (1766 hex) — ecPedersenProofLength + ecSingleBulletproofLength.
  • Send ZKProof length: Currently IsValidHexBlob. Rippled requires 1503 bytes (3006 hex) without auditor, 1601 bytes (3202 hex) with auditor.
  • Compressed key prefix check: IsValidCompressedEncryptionKey should verify 02/03 prefix to match rippled's isValidCompressedECPoint. Same for IsValidCommitment (Pedersen commitments are compressed EC points).
  • Delete dead code: UncompressedPointLen and IsValidUncompressedEncryptionKey become unused after the HolderEncryptionKey fix.
  • BlindingFactor validation: After updating the type to UINT256 in definitions.json, the IsValidBlindingFactor function (64 hex = 32 bytes) is still correct for transaction-level validation since UINT256 is always 32 bytes. But the Go struct field type in the transaction structs should be reviewed - it may need to change from string to a fixed-size type if the binary codec handles UINT256 differently from Blob.

Missing server-side validations

  • Issuer cannot Convert/ConvertBack/Send: Rippled checks that the transaction sender is not the issuer of the MPTokenIssuanceID for Convert (ConfidentialMPTConvert.cpp:19), ConvertBack (ConfidentialMPTConvertBack.cpp:22), and Send (ConfidentialMPTSend.cpp:25). Clawback has the inverse check (only issuer can submit). The Go code does not validate any of these. Implementing it client-side requires parsing the issuer account out of the MPTokenIssuanceID hex encoding. Server rejects with temMALFORMED. Spec references: section 8.3.1 item 2, section 10.4.1 item 2, section 7 ("self-conversion only").

Spec divergences

  • Spec says HolderEncryptionKey is 64 bytes (XLS-96 section 7.3.1, item 4). Rippled enforces 33 bytes compressed. We follow rippled. To be reported upstream.
  • Spec omits exact ZKProof sizes for Clawback, ConvertBack, and Send. Only says "length is incorrect". Exact byte lengths sourced from rippled's Protocol.h. To be reported upstream.
  • Spec uses BlindingFactor as Blob (nth 45). Rippled changed it to UINT256 (nth 39). Spec not yet updated.

…l/feat/confidential-transfers-new-transactions
…l/feat/confidential-transfers-new-transactions
kpitapeersyst and others added 26 commits March 31, 2026 10:52
feat(confidential): add ElGamal encrypt/decrypt with hex-string API
feat(confidential): add ZK proof, commitment, and context-hash CGo bindings with hex-string API
chore: update vendored mpt-crypto to 0.3.0-rc1
feat(confidential): update vendored mpt-crypto to 0.3.0-rc1
…ransfers

# Conflicts:
#	CHANGELOG.md
#	Makefile
#	README.md
#	xrpl/hash/ledger.go
#	xrpl/transaction/validations_xrpl_objects.go
Comment on lines +123 to +148
run: |
VERSION="${{ needs.check.outputs.version }}"
BRANCH="chore/update-mpt-crypto-${VERSION}"

git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b "$BRANCH"
git add -f confidential/deps/
git commit -m "chore(confidential): update vendored mpt-crypto to $VERSION"
git push -u origin "$BRANCH"

gh pr create \
--base "${{ github.ref_name }}" \
--title "Update vendored mpt-crypto to $VERSION" \
--body "$(cat <<EOF
Automated update of vendored mpt-crypto static libraries.

**${{ needs.check.outputs.current }} → $VERSION**

Platforms built:
- linux-amd64
- linux-arm64
- darwin-arm64
- darwin-amd64
EOF
)"

@semgrep-companion-app semgrep-companion-app Bot Jul 23, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Using variable interpolation ${{...}} with github context data in a run: step could allow an attacker to inject their own code into the runner. This would allow them to steal secrets and code. github context data can have arbitrary user input and should be treated as untrusted. Instead, use an intermediate environment variable with env: to store the data and use the environment variable in the run: script. Be sure to use double-quotes the environment variable, like this: "$ENVVAR".

🧹 Fixed in commit a386a3a 🧹

Comment on lines +31 to +56
run: |
CURRENT=$(cat confidential/deps/VERSION 2>/dev/null || echo "")
echo "current=$CURRENT" >> "$GITHUB_OUTPUT"

if [ -n "${{ inputs.version }}" ]; then
VERSION="${{ inputs.version }}"
else
VERSION=$(conan list 'mpt-crypto/*' -r xrplf 2>/dev/null \
| grep 'mpt-crypto/' \
| sed 's/.*mpt-crypto\///' \
| sort -V \
| tail -1)
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"

if [ "$CURRENT" = "$VERSION" ]; then
echo "needed=false" >> "$GITHUB_OUTPUT"
echo "Already at mpt-crypto/$VERSION — skipping build."
elif git ls-remote --exit-code --heads origin "chore/update-mpt-crypto-${VERSION}" >/dev/null 2>&1; then
echo "needed=false" >> "$GITHUB_OUTPUT"
echo "::warning::Branch 'chore/update-mpt-crypto-${VERSION}' already exists. Delete it before re-running."
else
echo "needed=true" >> "$GITHUB_OUTPUT"
echo "Update needed: $CURRENT -> $VERSION"
fi

@semgrep-companion-app semgrep-companion-app Bot Jul 23, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Using variable interpolation ${{...}} with github context data in a run: step could allow an attacker to inject their own code into the runner. This would allow them to steal secrets and code. github context data can have arbitrary user input and should be treated as untrusted. Instead, use an intermediate environment variable with env: to store the data and use the environment variable in the run: script. Be sure to use double-quotes the environment variable, like this: "$ENVVAR".

🎉 Fixed in commit a386a3a 🎉

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.

2 participants