diff --git a/include/xrpl/protocol/ConfidentialTransfer.h b/include/xrpl/protocol/ConfidentialTransfer.h index 15c3266656..3feaf9fed6 100644 --- a/include/xrpl/protocol/ConfidentialTransfer.h +++ b/include/xrpl/protocol/ConfidentialTransfer.h @@ -47,6 +47,13 @@ getConvertContextHash( uint192 const& issuanceID, std::uint64_t amount); +uint256 +getConvertBackContextHash( + AccountID const& account, + std::uint32_t sequence, + uint192 const& issuanceID, + std::uint64_t amount, + std::uint32_t version); /** * @brief Generates a new secp256k1 key pair. */ @@ -286,6 +293,39 @@ getEqualityProofs(Slice const& zkp); NotTEC checkEncryptedAmountFormat(STObject const& object); +// Helper struct to bundle the ElGamal Public Key and the associated Ciphertext +struct EncryptedAmountInfo +{ + Slice const publicKey; + Slice const encryptedAmount; +}; + +/** + * Verifies equality proofs for Holder, Issuer, and optionally Auditor. + */ +TER +verifyEqualityProofs( + std::uint64_t amount, + std::vector const& zkps, + EncryptedAmountInfo const& holder, + EncryptedAmountInfo const& issuer, + std::optional const& auditor, + uint256 const& contextHash); + +// returns the number of entries +size_t inline getEqualityProofSize(bool const hasAuditor) +{ + // Be careful if we ever need to change the numbers below, it will be a + // breaking change! + return (hasAuditor ? 3 : 2); +} + +// returns the total byte length of all the equality proofs combined +size_t inline getEqualityProofLength(bool const hasAuditor) +{ + return getEqualityProofSize(hasAuditor) * ecEqualityProofLength; +} + } // namespace ripple #endif diff --git a/src/libxrpl/protocol/ConfidentialTransfer.cpp b/src/libxrpl/protocol/ConfidentialTransfer.cpp index a55c9e759e..a851d6f947 100644 --- a/src/libxrpl/protocol/ConfidentialTransfer.cpp +++ b/src/libxrpl/protocol/ConfidentialTransfer.cpp @@ -53,6 +53,23 @@ getConvertContextHash( return s.getSHA512Half(); } +uint256 +getConvertBackContextHash( + AccountID const& account, + std::uint32_t sequence, + uint192 const& issuanceID, + std::uint64_t amount, + std::uint32_t version) +{ + Serializer s; + addCommonZKPFields( + s, ttCONFIDENTIAL_CONVERT_BACK, account, sequence, issuanceID, amount); + + s.addInteger(version); + + return s.getSHA512Half(); +} + int secp256k1_elgamal_generate_keypair( secp256k1_context const* ctx, @@ -1081,4 +1098,51 @@ checkEncryptedAmountFormat(STObject const& object) return tesSUCCESS; } +TER +verifyEqualityProofs( + std::uint64_t amount, + std::vector const& zkps, + EncryptedAmountInfo const& holder, + EncryptedAmountInfo const& issuer, + std::optional const& auditor, + uint256 const& contextHash) +{ + // Sanity check: Ensure we have enough proofs + size_t const required = getEqualityProofSize(auditor.has_value()); + if (zkps.size() != required) + return tecINTERNAL; // LCOV_EXCL_LINE + + // 1. Verify Holder Proof (Index 0) + if (!isTesSuccess(verifyEqualityProof( + amount, + zkps[0], + holder.publicKey, + holder.encryptedAmount, + contextHash))) + return tecBAD_PROOF; + + // 2. Verify Issuer Proof (Index 1) + if (!isTesSuccess(verifyEqualityProof( + amount, + zkps[1], + issuer.publicKey, + issuer.encryptedAmount, + contextHash))) + return tecBAD_PROOF; + + // 3. Verify Auditor Proof (Index 2) - if applicable + if (auditor) + { + if (!isTesSuccess(verifyEqualityProof( + amount, + zkps[2], + auditor->publicKey, + auditor->encryptedAmount, + contextHash))) + return tecBAD_PROOF; + } + + return tesSUCCESS; +} + } // namespace ripple diff --git a/src/test/app/ConfidentialTransfer_test.cpp b/src/test/app/ConfidentialTransfer_test.cpp index 92b2f41aa6..4ac2bdaeea 100644 --- a/src/test/app/ConfidentialTransfer_test.cpp +++ b/src/test/app/ConfidentialTransfer_test.cpp @@ -1644,7 +1644,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convertBack({ .account = bob, .amt = 30, - .proof = "123", }); } @@ -1693,7 +1692,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convertBack({ .account = bob, .amt = 30, - .proof = "123", }); } @@ -1723,10 +1721,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.generateKeyPair(bob); mptAlice.convertBack( - {.account = bob, - .amt = 30, - .proof = "123", - .err = temDISABLED}); + {.account = bob, .amt = 30, .err = temDISABLED}); } { @@ -1763,41 +1758,31 @@ class ConfidentialTransfer_test : public beast::unit_test::suite }); mptAlice.convertBack( - {.account = alice, - .amt = 30, - .proof = "123", - .err = temMALFORMED}); + {.account = alice, .amt = 30, .err = temMALFORMED}); mptAlice.convertBack( - {.account = bob, - .amt = 0, - .proof = "123", - .err = temBAD_AMOUNT}); + {.account = bob, .amt = 0, .err = temBAD_AMOUNT}); mptAlice.convertBack( {.account = bob, .amt = maxMPTokenAmount + 1, - .proof = "123", .err = temBAD_AMOUNT}); mptAlice.convertBack( {.account = bob, .amt = 30, - .proof = "123", .holderEncryptedAmt = Buffer{}, .err = temBAD_CIPHERTEXT}); mptAlice.convertBack( {.account = bob, .amt = 30, - .proof = "123", .issuerEncryptedAmt = Buffer{}, .err = temBAD_CIPHERTEXT}); mptAlice.convertBack( {.account = bob, .amt = 30, - .proof = "123", .holderEncryptedAmt = Buffer{badCiphertext, ecGamalEncryptedTotalLength}, .err = temBAD_CIPHERTEXT}); @@ -1805,7 +1790,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convertBack( {.account = bob, .amt = 30, - .proof = "123", .issuerEncryptedAmt = Buffer{badCiphertext, ecGamalEncryptedTotalLength}, .err = temBAD_CIPHERTEXT}); @@ -1840,10 +1824,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.generateKeyPair(bob); mptAlice.convertBack( - {.account = bob, - .amt = 30, - .proof = "123", - .err = tecOBJECT_NOT_FOUND}); + {.account = bob, .amt = 30, .err = tecOBJECT_NOT_FOUND}); } // tfMPTCanPrivacy is not set on issuance @@ -1867,10 +1848,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.generateKeyPair(bob); mptAlice.convertBack( - {.account = bob, - .amt = 30, - .proof = "123", - .err = tecNO_PERMISSION}); + {.account = bob, .amt = 30, .err = tecNO_PERMISSION}); } // no mptoken @@ -1892,10 +1870,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite {.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)}); mptAlice.convertBack( - {.account = bob, - .amt = 30, - .proof = "123", - .err = tecOBJECT_NOT_FOUND}); + {.account = bob, .amt = 30, .err = tecOBJECT_NOT_FOUND}); } // bob doesn't have encrypted balances @@ -1923,10 +1898,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.generateKeyPair(bob); mptAlice.convertBack( - {.account = bob, - .amt = 30, - .proof = "123", - .err = tecNO_PERMISSION}); + {.account = bob, .amt = 30, .err = tecNO_PERMISSION}); } // bob tries to convert back more than COA @@ -1974,10 +1946,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite }); mptAlice.convertBack( - {.account = bob, - .amt = 300, - .proof = "123", - .err = tecINSUFFICIENT_FUNDS}); + {.account = bob, .amt = 300, .err = tecINSUFFICIENT_FUNDS}); } // cannot convert if locked or unauth @@ -2014,19 +1983,21 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.mergeInbox({.account = bob}); mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); - mptAlice.convertBack( - {.account = bob, .amt = 10, .proof = "123", .err = tecLOCKED}); + mptAlice.convertBack({.account = bob, .amt = 10, .err = tecLOCKED}); mptAlice.set( {.account = alice, .holder = bob, .flags = tfMPTUnlock}); - mptAlice.convertBack({.account = bob, .amt = 10, .proof = "123"}); + mptAlice.convertBack({ + .account = bob, + .amt = 10, + }); mptAlice.authorize( {.account = alice, .holder = bob, .flags = tfMPTUnauthorize}); mptAlice.convertBack( - {.account = bob, .amt = 10, .proof = "123", .err = tecNO_AUTH}); + {.account = bob, .amt = 10, .err = tecNO_AUTH}); mptAlice.authorize({ .account = alice, @@ -2036,7 +2007,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convertBack({ .account = bob, .amt = 10, - .proof = "123", }); } } @@ -2926,8 +2896,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite // make sure there's no confidential outstanding balance // for the next toggle test - mptAlice.convertBack( - {.account = bob, .amt = amt, .proof = "123"}); + mptAlice.convertBack({ + .account = bob, + .amt = amt, + }); } }; @@ -2997,7 +2969,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite }); // bob convert back all confidential balance - mptAlice.convertBack({.account = bob, .amt = 50, .proof = "123"}); + mptAlice.convertBack({ + .account = bob, + .amt = 50, + }); // now clear lsfMPTCanPrivacy should succeed, // because there's no confidential outstanding balance diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index 70929c4e23..c98624b83a 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -805,13 +805,13 @@ MPTTester::getClawbackProof( } Buffer -MPTTester::getConvertProof( +MPTTester::generateEqualityZKP( Account const& holder, std::uint64_t amount, uint256 const& ctxHash, - CiphertextComponents holderCiphertext, - CiphertextComponents issuerCiphertext, - std::optional auditorCiphertext) const + CiphertextComponents const& holderCiphertext, + CiphertextComponents const& issuerCiphertext, + std::optional const& auditorCiphertext) const { if (!id_) Throw("MPT has not been created"); @@ -908,6 +908,46 @@ MPTTester::getConvertProof( return zkp; } +Buffer +MPTTester::getConvertProof( + Account const& holder, + std::uint64_t amount, + uint256 const& ctxHash, + CiphertextComponents holderCiphertext, + CiphertextComponents issuerCiphertext, + std::optional auditorCiphertext) const +{ + return generateEqualityZKP( + holder, + amount, + ctxHash, + holderCiphertext, + issuerCiphertext, + auditorCiphertext); +} + +Buffer +MPTTester::getConvertBackProof( + Account const& holder, + std::uint64_t amount, + uint256 const& ctxHash, + CiphertextComponents holderCiphertext, + CiphertextComponents issuerCiphertext, + std::optional auditorCiphertext) const +{ + Buffer const equalityZkp = generateEqualityZKP( + holder, + amount, + ctxHash, + holderCiphertext, + issuerCiphertext, + auditorCiphertext); + + // todo: incoporate pederson and range proof + + return equalityZkp; +} + std::optional MPTTester::getEncryptedBalance( Account const& account, @@ -1514,6 +1554,22 @@ MPTTester::getIssuanceOutstandingBalance() const return (*sle)[sfOutstandingAmount]; } +std::uint32_t +MPTTester::getMPTokenVersion(Account const account) const +{ + if (!id_) + Throw("Issuance ID does not exist"); + + auto const sle = env_.current()->read(keylet::mptoken(*id_, account)); + + // return 0 here instead of throwing an exception since tests for + // preclaim will check if the MPToken exists + if (!sle) + return 0; + + return (*sle)[~sfConfidentialBalanceVersion].value_or(0); +} + void MPTTester::convertBack(MPTConvertBack const& arg) { @@ -1535,17 +1591,26 @@ MPTTester::convertBack(MPTConvertBack const& arg) if (arg.amt) jv[sfMPTAmount.jsonName] = std::to_string(*arg.amt); + + CiphertextComponents holderCiphertext; if (arg.holderEncryptedAmt) jv[sfHolderEncryptedAmount.jsonName] = strHex(*arg.holderEncryptedAmt); else + { + holderCiphertext = encryptAmount(*arg.account, *arg.amt); jv[sfHolderEncryptedAmount.jsonName] = - strHex(encryptAmount(*arg.account, *arg.amt).ciphertext); + strHex(holderCiphertext.ciphertext); + } + CiphertextComponents issuerCiphertext; if (arg.issuerEncryptedAmt) jv[sfIssuerEncryptedAmount.jsonName] = strHex(*arg.issuerEncryptedAmt); else + { + issuerCiphertext = encryptAmount(issuer_, *arg.amt); jv[sfIssuerEncryptedAmount.jsonName] = - strHex(encryptAmount(issuer_, *arg.amt).ciphertext); + strHex(issuerCiphertext.ciphertext); + } std::optional auditorCiphertext; if (arg.auditorEncryptedAmt) @@ -1560,6 +1625,23 @@ MPTTester::convertBack(MPTConvertBack const& arg) if (arg.proof) jv[sfZKProof.jsonName] = *arg.proof; + else + { + auto const version = getMPTokenVersion(*arg.account); + + // if the caller generated ciphertexts themselves, they should also + // generate the proof themselves from the randomness factor + uint256 const ctxHash = getConvertBackContextHash( + arg.account->id(), env_.seq(*arg.account), *id_, *arg.amt, version); + Buffer proof = getConvertBackProof( + *arg.account, + *arg.amt, + ctxHash, + holderCiphertext, + issuerCiphertext, + auditorCiphertext); + jv[sfZKProof] = strHex(proof); + } auto const holderAmt = getBalance(*arg.account); auto const prevConfidentialOutstanding = getIssuanceConfidentialBalance(); diff --git a/src/test/jtx/mpt.h b/src/test/jtx/mpt.h index bb52e715e5..ce4af56a5d 100644 --- a/src/test/jtx/mpt.h +++ b/src/test/jtx/mpt.h @@ -437,6 +437,15 @@ public: Buffer const& privateKey, uint256 const& txHash) const; + Buffer + generateEqualityZKP( + Account const& holder, + std::uint64_t amount, + uint256 const& ctxHash, + CiphertextComponents const& holderCiphertext, + CiphertextComponents const& issuerCiphertext, + std::optional const& auditorCiphertext) const; + Buffer getConvertProof( Account const& holder, @@ -446,6 +455,18 @@ public: CiphertextComponents issuerCiphertext, std::optional auditorCiphertext) const; + Buffer + getConvertBackProof( + Account const& holder, + std::uint64_t amount, + uint256 const& ctxHash, + CiphertextComponents holderCiphertext, + CiphertextComponents issuerCiphertext, + std::optional auditorCiphertext) const; + + std::uint32_t + getMPTokenVersion(Account const account) const; + private: using SLEP = SLE::const_pointer; bool diff --git a/src/xrpld/app/tx/detail/ConfidentialConvert.cpp b/src/xrpld/app/tx/detail/ConfidentialConvert.cpp index 9c4dd61216..011294dfc1 100644 --- a/src/xrpld/app/tx/detail/ConfidentialConvert.cpp +++ b/src/xrpld/app/tx/detail/ConfidentialConvert.cpp @@ -31,9 +31,8 @@ ConfidentialConvert::preflight(PreflightContext const& ctx) ctx.tx[sfHolderElGamalPublicKey].length() != ecPubKeyLength) return temMALFORMED; - auto const expectedCount = - ctx.tx.isFieldPresent(sfAuditorEncryptedAmount) ? 3 : 2; - if (ctx.tx[sfZKProof].size() != expectedCount * ecEqualityProofLength) + if (ctx.tx[sfZKProof].size() != + getEqualityProofLength(ctx.tx.isFieldPresent(sfAuditorEncryptedAmount))) return temMALFORMED; return tesSUCCESS; @@ -61,9 +60,17 @@ ConfidentialConvert::preclaim(PreclaimContext const& ctx) return tecNO_PERMISSION; bool const hasAuditor = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount); + bool const requiresAuditor = + sleIssuance->isFieldPresent(sfAuditorElGamalPublicKey); - // tx must include auditor ciphertext if the issuance has enabled auditing - if (sleIssuance->isFieldPresent(sfAuditorElGamalPublicKey) && !hasAuditor) + // tx must include auditor ciphertext if the issuance has enabled + // auditing + if (requiresAuditor && !hasAuditor) + return tecNO_PERMISSION; + + // if auditing is not supported then user should not upload auditor + // ciphertext + if (!requiresAuditor && hasAuditor) return tecNO_PERMISSION; auto const sleMptoken = ctx.view.read( @@ -108,44 +115,26 @@ ConfidentialConvert::preclaim(PreclaimContext const& ctx) std::vector const zkps = getEqualityProofs(ctx.tx[sfZKProof]); - auto const& amount = ctx.tx[sfMPTAmount]; + // Prepare Auditor Info + std::optional auditor; + if (hasAuditor) + { + auditor.emplace( + EncryptedAmountInfo{ + (*sleIssuance)[sfAuditorElGamalPublicKey], + ctx.tx[sfAuditorEncryptedAmount]}); + } - // we already checked proof size in preflight, still do sanity check here - // since we are going to access individual vector entries - auto const expectedCount = ctx.tx[sfZKProof].size() / ecEqualityProofLength; - if (zkps.size() != expectedCount) - return tecINTERNAL; // LCOV_EXCL_LINE - - // check equality proof - if (!isTesSuccess(verifyEqualityProof( - amount, - zkps[0], - holderPubKey, - ctx.tx[sfHolderEncryptedAmount], - contextHash)) || - !isTesSuccess(verifyEqualityProof( - amount, - zkps[1], + return verifyEqualityProofs( + ctx.tx[sfMPTAmount], + zkps, + EncryptedAmountInfo{ + holderPubKey, ctx.tx[sfHolderEncryptedAmount]}, // Holder + EncryptedAmountInfo{ (*sleIssuance)[sfIssuerElGamalPublicKey], - ctx.tx[sfIssuerEncryptedAmount], - contextHash))) - { - return tecBAD_PROOF; - } - - // Verify Auditor proof if present - if (hasAuditor && - !isTesSuccess(verifyEqualityProof( - amount, - zkps[2], - (*sleIssuance)[sfAuditorElGamalPublicKey], - ctx.tx[sfAuditorEncryptedAmount], - contextHash))) - { - return tecBAD_PROOF; - } - - return tesSUCCESS; + ctx.tx[sfIssuerEncryptedAmount]}, // Issuer + auditor, + contextHash); } TER diff --git a/src/xrpld/app/tx/detail/ConfidentialConvertBack.cpp b/src/xrpld/app/tx/detail/ConfidentialConvertBack.cpp index f32bd33450..1f556d66d4 100644 --- a/src/xrpld/app/tx/detail/ConfidentialConvertBack.cpp +++ b/src/xrpld/app/tx/detail/ConfidentialConvertBack.cpp @@ -8,8 +8,20 @@ #include #include +#include + namespace ripple { +size_t +expectedProofLength(std::shared_ptr const& issuance) +{ + auto const equalityProofLength = getEqualityProofLength( + issuance->isFieldPresent(sfAuditorElGamalPublicKey)); + + // todo: add pederson and range proof length + return equalityProofLength; +} + NotTEC ConfidentialConvertBack::preflight(PreflightContext const& ctx) { @@ -26,10 +38,57 @@ ConfidentialConvertBack::preflight(PreflightContext const& ctx) if (ctx.tx[sfMPTAmount] == 0 || ctx.tx[sfMPTAmount] > maxMPTokenAmount) return temBAD_AMOUNT; - // todo: update with correct size of proof since it might also contain range - // proof - // if (ctx.tx[sfZKProof].length() != ecEqualityProofLength) - // return temMALFORMED; + return tesSUCCESS; +} + +TER +verifyProofs( + STTx const& tx, + std::shared_ptr const& issuance, + std::shared_ptr const& mptoken) +{ + if (expectedProofLength(issuance) != tx[sfZKProof].size()) + return tecBAD_PROOF; + + auto const mptIssuanceID = tx[sfMPTokenIssuanceID]; + auto const account = tx[sfAccount]; + auto const amount = tx[sfMPTAmount]; + + auto const contextHash = getConvertBackContextHash( + account, + tx[sfSequence], + mptIssuanceID, + amount, + (*mptoken)[~sfConfidentialBalanceVersion].value_or(0)); + + // Prepare Auditor Info + std::optional auditor; + bool const hasAuditor = issuance->isFieldPresent(sfAuditorElGamalPublicKey); + if (hasAuditor) + { + auditor.emplace( + EncryptedAmountInfo{ + (*issuance)[sfAuditorElGamalPublicKey], + tx[sfAuditorEncryptedAmount]}); + } + + // verify equality proofs + { + auto const equalityZkps = getEqualityProofs( + Slice{tx[sfZKProof].data(), getEqualityProofLength(hasAuditor)}); + + return verifyEqualityProofs( + amount, + equalityZkps, + EncryptedAmountInfo{ + (*mptoken)[sfHolderElGamalPublicKey], + tx[sfHolderEncryptedAmount]}, // Holder + EncryptedAmountInfo{ + (*issuance)[sfIssuerElGamalPublicKey], + tx[sfIssuerEncryptedAmount]}, // Issuer + auditor, // Optional auditor + contextHash); + } return tesSUCCESS; } @@ -37,27 +96,39 @@ ConfidentialConvertBack::preflight(PreflightContext const& ctx) TER ConfidentialConvertBack::preclaim(PreclaimContext const& ctx) { + auto const mptIssuanceID = ctx.tx[sfMPTokenIssuanceID]; + auto const account = ctx.tx[sfAccount]; + auto const amount = ctx.tx[sfMPTAmount]; + // ensure that issuance exists - auto const sleIssuance = - ctx.view.read(keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID])); + auto const sleIssuance = ctx.view.read(keylet::mptIssuance(mptIssuanceID)); if (!sleIssuance) return tecOBJECT_NOT_FOUND; if (!sleIssuance->isFlag(lsfMPTCanPrivacy)) return tecNO_PERMISSION; - // tx must include auditor ciphertext if the issuance has enabled auditing - if (sleIssuance->isFieldPresent(sfAuditorElGamalPublicKey) && - !ctx.tx.isFieldPresent(sfAuditorEncryptedAmount)) + bool const hasAuditor = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount); + bool const requiresAuditor = + sleIssuance->isFieldPresent(sfAuditorElGamalPublicKey); + + // tx must include auditor ciphertext if the issuance has enabled + // auditing + if (requiresAuditor && !hasAuditor) + return tecNO_PERMISSION; + + // if auditing is not supported then user should not upload auditor + // ciphertext + if (!requiresAuditor && hasAuditor) return tecNO_PERMISSION; // already checked in preflight, but should also check that issuer on // the issuance isn't the account either - if (sleIssuance->getAccountID(sfIssuer) == ctx.tx[sfAccount]) + if (sleIssuance->getAccountID(sfIssuer) == account) return tefINTERNAL; // LCOV_EXCL_LINE - auto const sleMptoken = ctx.view.read( - keylet::mptoken(ctx.tx[sfMPTokenIssuanceID], ctx.tx[sfAccount])); + auto const sleMptoken = + ctx.view.read(keylet::mptoken(mptIssuanceID, account)); if (!sleMptoken) return tecOBJECT_NOT_FOUND; @@ -70,15 +141,11 @@ ConfidentialConvertBack::preclaim(PreclaimContext const& ctx) // if the total circulating confidential balance is smaller than what the // holder is trying to convert back, we know for sure this txn should // fail - if ((*sleIssuance)[~sfConfidentialOutstandingAmount].value_or(0) < - ctx.tx[sfMPTAmount]) + if ((*sleIssuance)[~sfConfidentialOutstandingAmount].value_or(0) < amount) { return tecINSUFFICIENT_FUNDS; } - auto const mptIssuanceID = ctx.tx[sfMPTokenIssuanceID]; - auto const account = ctx.tx[sfAccount]; - // Check lock MPTIssue const mptIssue(mptIssuanceID); if (auto const ter = checkFrozen(ctx.view, account, mptIssue); @@ -90,31 +157,9 @@ ConfidentialConvertBack::preclaim(PreclaimContext const& ctx) !isTesSuccess(ter)) return ter; - // todo: need addtional parsing, the proof should contain multiple proofs - // auto checkEqualityProof = [&](auto const& encryptedAmount, - // auto const& pubKey) -> TER { - // return proveEquality( - // ctx.tx[sfZKProof], - // encryptedAmount, - // pubKey, - // ctx.tx[sfMPTAmount], - // ctx.tx.getTransactionID(), - // (*sleMptoken)[~sfConfidentialBalanceVersion].value_or(0)); - // }; - - // if (!isTesSuccess(checkEqualityProof( - // ctx.tx[sfHolderEncryptedAmount], - // (*sleMptoken)[sfHolderElGamalPublicKey])) || - // !isTesSuccess(checkEqualityProof( - // ctx.tx[sfIssuerEncryptedAmount], - // (*sleIssuance)[sfIssuerElGamalPublicKey]))) - // { - // return tecBAD_PROOF; - // } - - // todo: also check range proof that - // sfHolderEncryptedAmount <= sfConfidentialBalanceSpending AND - // sfIssuerEncryptedAmount <= sfIssuerEncryptedBalance + if (TER const res = verifyProofs(ctx.tx, sleIssuance, sleMptoken); + !isTesSuccess(res)) + return res; return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/ConfidentialSend.cpp b/src/xrpld/app/tx/detail/ConfidentialSend.cpp index 9502e68353..a2f1f9250e 100644 --- a/src/xrpld/app/tx/detail/ConfidentialSend.cpp +++ b/src/xrpld/app/tx/detail/ConfidentialSend.cpp @@ -88,9 +88,18 @@ ConfidentialSend::preclaim(PreclaimContext const& ctx) if (!sleIssuance->isFieldPresent(sfIssuerElGamalPublicKey)) return tecNO_PERMISSION; - // tx must include auditor ciphertext if the issuance has enabled auditing - if (sleIssuance->isFieldPresent(sfAuditorElGamalPublicKey) && - !ctx.tx.isFieldPresent(sfAuditorEncryptedAmount)) + bool const hasAuditor = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount); + bool const requiresAuditor = + sleIssuance->isFieldPresent(sfAuditorElGamalPublicKey); + + // tx must include auditor ciphertext if the issuance has enabled + // auditing + if (requiresAuditor && !hasAuditor) + return tecNO_PERMISSION; + + // if auditing is not supported then user should not upload auditor + // ciphertext + if (!requiresAuditor && hasAuditor) return tecNO_PERMISSION; // already checked in preflight, but should also check that issuer on the