Add SDK utility for off-chain operations#12
Conversation
mathbunnyru
left a comment
There was a problem hiding this comment.
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.
mrtcnk
left a comment
There was a problem hiding this comment.
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(); | ||
|
|
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.hheader defining the utility API with functions for keypair generation, encryption/decryption, proof generation, and context hash computation - Implemented
mpt_utility.cppwith cryptographic primitives including ElGamal encryption, Pedersen commitments, and zero-knowledge proof generation - Added comprehensive test suite
test_mpt_utility.cppcovering 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> | |||
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
896b08a to
5fc6304
Compare
be84d3c to
65eaa4c
Compare
3db6c4b to
9024327
Compare
| * @brief Represents a recipient in a Confidential Send transaction. | ||
| * Contains a pubkey and an encrypted amount. | ||
| */ | ||
| struct mpt_confidential_recipient |
There was a problem hiding this comment.
@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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
All the off-chain operations should go through this layer.
For example:
The interface can be found in:
include/utility/mpt_utility.hThe implementation details:
src/utility/mpt_utility.cppFor 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.cppIt 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: