mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-03 16:56:48 +00:00
Add equality proof to ConvertBack and refactor to reduce redundancy (#6220)
This commit is contained in:
@@ -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<Buffer> const& zkps,
|
||||
EncryptedAmountInfo const& holder,
|
||||
EncryptedAmountInfo const& issuer,
|
||||
std::optional<EncryptedAmountInfo> 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
|
||||
|
||||
@@ -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<Buffer> const& zkps,
|
||||
EncryptedAmountInfo const& holder,
|
||||
EncryptedAmountInfo const& issuer,
|
||||
std::optional<EncryptedAmountInfo> 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<CiphertextComponents> auditorCiphertext) const
|
||||
CiphertextComponents const& holderCiphertext,
|
||||
CiphertextComponents const& issuerCiphertext,
|
||||
std::optional<CiphertextComponents> const& auditorCiphertext) const
|
||||
{
|
||||
if (!id_)
|
||||
Throw<std::runtime_error>("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<CiphertextComponents> 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<CiphertextComponents> auditorCiphertext) const
|
||||
{
|
||||
Buffer const equalityZkp = generateEqualityZKP(
|
||||
holder,
|
||||
amount,
|
||||
ctxHash,
|
||||
holderCiphertext,
|
||||
issuerCiphertext,
|
||||
auditorCiphertext);
|
||||
|
||||
// todo: incoporate pederson and range proof
|
||||
|
||||
return equalityZkp;
|
||||
}
|
||||
|
||||
std::optional<Buffer>
|
||||
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<std::runtime_error>("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<CiphertextComponents> 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();
|
||||
|
||||
@@ -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<CiphertextComponents> const& auditorCiphertext) const;
|
||||
|
||||
Buffer
|
||||
getConvertProof(
|
||||
Account const& holder,
|
||||
@@ -446,6 +455,18 @@ public:
|
||||
CiphertextComponents issuerCiphertext,
|
||||
std::optional<CiphertextComponents> auditorCiphertext) const;
|
||||
|
||||
Buffer
|
||||
getConvertBackProof(
|
||||
Account const& holder,
|
||||
std::uint64_t amount,
|
||||
uint256 const& ctxHash,
|
||||
CiphertextComponents holderCiphertext,
|
||||
CiphertextComponents issuerCiphertext,
|
||||
std::optional<CiphertextComponents> auditorCiphertext) const;
|
||||
|
||||
std::uint32_t
|
||||
getMPTokenVersion(Account const account) const;
|
||||
|
||||
private:
|
||||
using SLEP = SLE::const_pointer;
|
||||
bool
|
||||
|
||||
@@ -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<Buffer> const zkps = getEqualityProofs(ctx.tx[sfZKProof]);
|
||||
|
||||
auto const& amount = ctx.tx[sfMPTAmount];
|
||||
// Prepare Auditor Info
|
||||
std::optional<EncryptedAmountInfo> 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
|
||||
|
||||
@@ -8,8 +8,20 @@
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
size_t
|
||||
expectedProofLength(std::shared_ptr<SLE const> 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<SLE const> const& issuance,
|
||||
std::shared_ptr<SLE const> 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<EncryptedAmountInfo> 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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user