From 35e4fad557f579048706cb044954e52cad143518 Mon Sep 17 00:00:00 2001 From: Shawn Xie <35279399+shawnxie999@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:57:18 -0400 Subject: [PATCH] Add ciphertext check (#5930) --- include/xrpl/protocol/ConfidentialTransfer.h | 8 ++++++ include/xrpl/protocol/TER.h | 1 + src/libxrpl/protocol/ConfidentialTransfer.cpp | 18 ++++++++----- src/libxrpl/protocol/TER.cpp | 1 + src/test/app/ConfidentialTransfer_test.cpp | 27 +++++++++++++++++++ .../app/tx/detail/ConfidentialConvert.cpp | 4 +++ 6 files changed, 53 insertions(+), 6 deletions(-) diff --git a/include/xrpl/protocol/ConfidentialTransfer.h b/include/xrpl/protocol/ConfidentialTransfer.h index b31bd3f5ea..5283a08733 100644 --- a/include/xrpl/protocol/ConfidentialTransfer.h +++ b/include/xrpl/protocol/ConfidentialTransfer.h @@ -130,6 +130,14 @@ serializeEcPair( secp256k1_pubkey const& in2, Buffer& buffer); +/** + * @brief Verifies that a buffer contains two valid, parsable EC public keys. + * @param buffer The input buffer containing two concatenated components. + * @return true if both components can be parsed successfully, false otherwise. + */ +bool +isValidCiphertext(Slice const& buffer); + TER homomorphicAdd(Slice const& a, Slice const& b, Buffer& out); diff --git a/include/xrpl/protocol/TER.h b/include/xrpl/protocol/TER.h index abf5f26aa4..1f245a1b43 100644 --- a/include/xrpl/protocol/TER.h +++ b/include/xrpl/protocol/TER.h @@ -141,6 +141,7 @@ enum TEMcodes : TERUnderlyingType { temARRAY_TOO_LARGE, temBAD_TRANSFER_FEE, temINVALID_INNER_BATCH, + temBAD_CIPHERTEXT, }; //------------------------------------------------------------------------------ diff --git a/src/libxrpl/protocol/ConfidentialTransfer.cpp b/src/libxrpl/protocol/ConfidentialTransfer.cpp index 802d6077f8..bdb84e7ac8 100644 --- a/src/libxrpl/protocol/ConfidentialTransfer.cpp +++ b/src/libxrpl/protocol/ConfidentialTransfer.cpp @@ -371,6 +371,18 @@ serializeEcPair( return res1 && res2; } +bool +isValidCiphertext(Slice const& buffer) +{ + // Local/temporary variables to pass to makeEcPair. + // Their contents will be discarded when the function returns. + secp256k1_pubkey key1; + secp256k1_pubkey key2; + + // Call makeEcPair and return its result. + return makeEcPair(buffer, key1, key2); +} + TER homomorphicAdd(Slice const& a, Slice const& b, Buffer& out) { @@ -516,12 +528,6 @@ encryptCanonicalZeroAmount( // Allocate ciphertext placeholders secp256k1_pubkey c1, c2; - - // Prepare a random blinding factor - unsigned char blinding_factor[32]; - if (RAND_bytes(blinding_factor, 32) != 1) - Throw("Failed to generate random number"); - secp256k1_pubkey pubKey; std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength); diff --git a/src/libxrpl/protocol/TER.cpp b/src/libxrpl/protocol/TER.cpp index 9f2fe4fb85..738f2a26f6 100644 --- a/src/libxrpl/protocol/TER.cpp +++ b/src/libxrpl/protocol/TER.cpp @@ -221,6 +221,7 @@ transResults() MAKE_ERROR(temARRAY_TOO_LARGE, "Malformed: Array is too large."), MAKE_ERROR(temBAD_TRANSFER_FEE, "Malformed: Transfer fee is outside valid range."), MAKE_ERROR(temINVALID_INNER_BATCH, "Malformed: Invalid inner batch transaction."), + MAKE_ERROR(temBAD_CIPHERTEXT, "Malformed: Invalid ciphertext."), MAKE_ERROR(terRETRY, "Retry transaction."), MAKE_ERROR(terFUNDS_SPENT, "DEPRECATED."), diff --git a/src/test/app/ConfidentialTransfer_test.cpp b/src/test/app/ConfidentialTransfer_test.cpp index d9cee0f216..e9a3f56257 100644 --- a/src/test/app/ConfidentialTransfer_test.cpp +++ b/src/test/app/ConfidentialTransfer_test.cpp @@ -153,6 +153,33 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .holderPubKey = mptAlice.getPubKey(bob), .err = temMALFORMED}); + // A 66-byte array of random unsigned char values + unsigned char badCiphertext[ecGamalEncryptedTotalLength] = { + 0x3E, 0x9A, 0x0F, 0x7C, 0x51, 0xD8, 0x22, 0x8B, 0x6E, 0x14, 0xC9, + 0xF5, 0x4D, 0x6A, 0x03, 0x81, 0x77, 0x2B, 0xEE, 0x9F, 0x10, 0xC2, + 0x57, 0x3D, 0x88, 0x65, 0x0C, 0xAB, 0xF1, 0x4E, 0x19, 0x96, 0x2A, + 0x73, 0xDC, 0x44, 0xB8, 0x5F, 0x01, 0xEA, 0x87, 0x36, 0x60, 0xCE, + 0x92, 0x25, 0x7D, 0x5B, 0xC0, 0x1E, 0x48, 0xF9, 0x84, 0x33, 0x67, + 0xAD, 0x0B, 0xE3, 0x91, 0x50, 0xDA, 0x2F, 0x75, 0xC6, 0xBD, 0x42}; + + mptAlice.convert( + {.account = bob, + .amt = 1, + .proof = "123", + .holderPubKey = mptAlice.getPubKey(bob), + .holderEncryptedAmt = + Buffer{badCiphertext, ecGamalEncryptedTotalLength}, + .err = temBAD_CIPHERTEXT}); + + mptAlice.convert( + {.account = bob, + .amt = 1, + .proof = "123", + .holderPubKey = mptAlice.getPubKey(bob), + .issuerEncryptedAmt = + Buffer{badCiphertext, ecGamalEncryptedTotalLength}, + .err = temBAD_CIPHERTEXT}); + // todo: change to to check proof size // mptAlice.convert( // {.account = bob, diff --git a/src/xrpld/app/tx/detail/ConfidentialConvert.cpp b/src/xrpld/app/tx/detail/ConfidentialConvert.cpp index 18df8df596..9cc233ce84 100644 --- a/src/xrpld/app/tx/detail/ConfidentialConvert.cpp +++ b/src/xrpld/app/tx/detail/ConfidentialConvert.cpp @@ -47,6 +47,10 @@ ConfidentialConvert::preflight(PreflightContext const& ctx) if (ctx.tx[sfMPTAmount] > maxMPTokenAmount) return temMALFORMED; + if (!isValidCiphertext(ctx.tx[sfHolderEncryptedAmount]) || + !isValidCiphertext(ctx.tx[sfIssuerEncryptedAmount])) + return temBAD_CIPHERTEXT; + // if (ctx.tx[sfZKProof].length() != ecEqualityProofLength) // return temMALFORMED;