Skip to content

Add SDK utility for off-chain operations#12

Open
yinyiqian1 wants to merge 19 commits intoXRPLF:mainfrom
yinyiqian1:mpt-utility
Open

Add SDK utility for off-chain operations#12
yinyiqian1 wants to merge 19 commits intoXRPLF:mainfrom
yinyiqian1:mpt-utility

Conversation

@yinyiqian1
Copy link

@yinyiqian1 yinyiqian1 commented Feb 17, 2026

All the off-chain operations should go through this layer.
For example:

  • Key generation
  • Blinding factor generation
  • Encryption
  • Decryption
  • Context hash generation for each tx
  • Proof generation for each tx

The interface can be found in:

  • include/utility/mpt_utility.h

The implementation details:

  • src/utility/mpt_utility.cpp

For details on how to use these functions, the test cases contain the demos on how to generate proof for each transaction:

  • tests/test_mpt_utility.cpp

It acts as the producer of the required off-chain values when submitting requests to rippled, providing a single source of truth for all off-chain operations so everyone can consistently prepare their requests.

This can be consumed by:

  • Rippled's test framework
  • QA framework
  • Libs
  • Other users

@yinyiqian1 yinyiqian1 changed the title Support an utility layer that generates proof for all the tx Support an utility layer that do all the off-chain operations Feb 17, 2026
Copy link
Collaborator

@mathbunnyru mathbunnyru left a comment

Choose a reason for hiding this comment

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

It seems that mpt_utility.h is a C++ only header, but some things are implemented in C, some in C++.
I would prefer sticking with one and not mixing them.

Copy link
Contributor

@mrtcnk mrtcnk left a comment

Choose a reason for hiding this comment

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

Left a few comments mostly as reference points. Nothing blocking from my side. Happy to iterate further in follow-up PRs as the utility layer evolves.


secp256k1_context*
mpt_secp256k1_context();

Copy link
Contributor

Choose a reason for hiding this comment

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

Small note: can we ensure the TransactionContextID construction here matches the spec
H(TxType ∥ Account ∥ MPTokenIssuanceID ∥ SequenceOrTicket ∥ TxSpecific)
where:

  • Send / ConvertBack: TxSpecific = Receiver ∥ CBSVersion
  • Convert: TxSpecific = Account ∥ 0

Copy link
Author

@yinyiqian1 yinyiqian1 Feb 23, 2026

Choose a reason for hiding this comment

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

The context hash is adapting to the rippled's implementation hash. We'll change on both sides if anything requires modification.

The current hashing is like:
We have a common hash matches TxType ∥ Account ∥ MPTokenIssuanceID ∥ SequenceOrTicket, see the function mpt_add_common_zkp_fields.

Convert: TxSpecific = amount. (see mpt_get_convert_context_hash). because we are trying to add all the inputs into the hash function. (account already included in the common hash)
ConvertBack : TxSpecific = amount | CBSVersion. (account already included in the common hash)
Send: TxSpecific = receiver | CBSVersion.
Clawback: TxSpecific = amount | holder

The difference with the doc:

  • introduced for convert/convertback/clawback.
  • some TxSpecific fields omit the Account field, since it is already included in the common hash section.

What we will add according to what you suggested in the doc:

  • We need to pass in the ticket number if it is a ticket, although the interface won't change.
  • We'll add version 0 for convert: TxSpecific = amount | 0

Please suggest if this looks good to you or any other modifications needed?

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the detailed breakdown! The common hash structure aligns well with the spec. From the spec side, I’d prefer that we keep the TransactionContextID definition. The cryptographic intent there is that TxSpecific binds the transaction context and state freshness (identity||version), rather than public payload inputs like the plaintext amount. For example, in ConfidentialMPTConvert, the spec intentionally defines TxSpecific := Account||0 to maintain a uniform semantic structure across transaction types, where 0 explicitly indicates that no CBSVersion is involved. The amount is already bound inside the Fiat–Shamir transcript (mG) during challenge computation, so it does not need to be part of TransactionContextID.

To keep the transcript definition consistent with the spec, my preference for the semantic shape of TxSpecific would be:

ConfidentialMPTSend: receiver||CBSVersion
ConfidentialMPTConvertBack: receiver (=Account)||CBSVersion
ConfidentialMPTConvert: account||0
ConfidentialMPTClawback: holder||0

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request introduces a utility layer for off-chain cryptographic operations related to Multi-Purpose Token (MPT) confidential transactions. The layer provides a unified API for encryption, proof generation, and context hash computation, serving as the single source of truth for these operations across rippled's test framework, QA framework, and client libraries.

Changes:

  • Added mpt_utility.h header defining the utility API with functions for keypair generation, encryption/decryption, proof generation, and context hash computation
  • Implemented mpt_utility.cpp with cryptographic primitives including ElGamal encryption, Pedersen commitments, and zero-knowledge proof generation
  • Added comprehensive test suite test_mpt_utility.cpp covering encryption, convert, send, convert-back, and clawback operations

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 20 comments.

Show a summary per file
File Description
include/utility/mpt_utility.h Header file defining the public API for MPT off-chain operations with constants, types, and function declarations
src/utility/mpt_utility.cpp Implementation of cryptographic operations including encryption, proof generation, and context hashing with platform-specific endianness handling
tests/test_mpt_utility.cpp Test suite verifying encryption roundtrips and proof generation/verification for all confidential transaction types
CMakeLists.txt Build configuration update to include the new utility source file
tests/CMakeLists.txt Test configuration update to add the new utility test with required dependencies

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

@@ -0,0 +1,642 @@
#include <arpa/inet.h>
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The include of arpa/inet.h is not portable to Windows. While Windows is handled with the _WIN32 defines for byte order operations below, the header will fail to compile on Windows. Consider making this include conditional with #ifndef _WIN32 or using winsock2.h for Windows instead.

Copilot uses AI. Check for mistakes.
Copy link

Choose a reason for hiding this comment

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

Hi @yinyiqian1 , can this issue be fixed? I had to create a patch on our end to handle Windows. Probably something like this should work:

#ifdef _WIN32
    #include <winsock2.h>
#else
    #include <arpa/inet.h>
#endif

if (!ctx)
return -1;

std::vector<secp256k1_pubkey> r(n_recipients);
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

Missing validation: The function does not validate that n_recipients is greater than zero. If n_recipients is 0, the code will access pk[0].data at lines 580 and 587, which is undefined behavior. Add a check to ensure n_recipients > 0 at the beginning of the function.

Copilot uses AI. Check for mistakes.
@yinyiqian1 yinyiqian1 changed the title Support an utility layer that do all the off-chain operations Add SDK utility for off-chain operations Feb 26, 2026
* @brief Represents a recipient in a Confidential Send transaction.
* Contains a pubkey and an encrypted amount.
*/
struct mpt_confidential_recipient
Copy link

@Patel-Raj11 Patel-Raj11 Mar 2, 2026

Choose a reason for hiding this comment

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

@yinyiqian1 Hi, Would it make sense to call this struct as mpt_confidential_participant or something else that does not imply that it is a recipient of the confidential amount? As other then the receiver all other are the participants of the send transaction?

if (secp256k1_ec_pubkey_parse(ctx, &pk[i], rec.pubkey, kMPT_PUBKEY_SIZE) != 1)
return -1;

sr.insert(sr.end(), tx_blinding_factor, tx_blinding_factor + kMPT_BLINDING_FACTOR_SIZE);

Choose a reason for hiding this comment

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

I think if each participant/recipient has different blinding factor, the proof generation and verification both works. Since there is no BlindingFactor field for ConfidentialMPTSend transaction type, different blinding factor's can be used by each participant to encrypt the same amount.
Could you confirm this on your end once and if it's true then should we append multiple blinding factors here?

Copy link
Author

@yinyiqian1 yinyiqian1 Mar 2, 2026

Choose a reason for hiding this comment

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

No. We should use the same blinding factor for encrypting the participant/recipient. The blinding factor is generated by the sender off-chain. It is a value that zkproof needs to verify, it should not be part of the request.
If we use different blinding factors for the ElGamal ciphertexts, the Equality Proof inside the ZK-Proof would fail.

We can use another set of blinding factor for pedersen commitment. But for the ElGamal ciphertexts, we must use the same blinding factor for issuer,auditor and sender.

Btw, in ConfidentialMPTConvert, it is a public amount, we can put the blinding factor in the request. We are not trying to prove the knowledge of the randomness in that case. We are proving the knowledge of private key in that case, using schnorr proof.

Key difference:

  • convert: prove the knowledge of identity (private key)
  • send (equality proof part): prove the knowledge of randomness (blinding factor) and the actual value we are encrypting.

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.

6 participants