mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-07 02:36:47 +00:00
250 lines
8.6 KiB
C++
250 lines
8.6 KiB
C++
#include <xrpl/ledger/View.h>
|
|
#include <xrpl/protocol/ConfidentialTransfer.h>
|
|
#include <xrpl/protocol/Feature.h>
|
|
#include <xrpl/protocol/Indexes.h>
|
|
#include <xrpl/protocol/LedgerFormats.h>
|
|
#include <xrpl/protocol/TER.h>
|
|
#include <xrpl/protocol/TxFlags.h>
|
|
#include <xrpl/tx/transactors/token/ConfidentialMPTConvert.h>
|
|
|
|
namespace xrpl {
|
|
|
|
NotTEC
|
|
ConfidentialMPTConvert::preflight(PreflightContext const& ctx)
|
|
{
|
|
if (!ctx.rules.enabled(featureConfidentialTransfer))
|
|
return temDISABLED;
|
|
|
|
// issuer cannot convert
|
|
if (MPTIssue(ctx.tx[sfMPTokenIssuanceID]).getIssuer() == ctx.tx[sfAccount])
|
|
return temMALFORMED;
|
|
|
|
if (ctx.tx[sfMPTAmount] > maxMPTokenAmount)
|
|
return temBAD_AMOUNT;
|
|
|
|
if (ctx.tx.isFieldPresent(sfHolderEncryptionKey))
|
|
{
|
|
if (!isValidCompressedECPoint(ctx.tx[sfHolderEncryptionKey]))
|
|
return temMALFORMED;
|
|
|
|
// proof of knowledge of the secret key corresponding to the provided
|
|
// public key is needed when holder ec public key is being set.
|
|
if (!ctx.tx.isFieldPresent(sfZKProof))
|
|
return temMALFORMED;
|
|
|
|
// verify schnorr proof length when registering holder ec public key
|
|
if (ctx.tx[sfZKProof].size() != ecSchnorrProofLength)
|
|
return temMALFORMED;
|
|
}
|
|
else
|
|
{
|
|
// Either both sfHolderEncryptionKey and sfZKProof should be present, or both should be
|
|
// absent.
|
|
if (ctx.tx.isFieldPresent(sfZKProof))
|
|
return temMALFORMED;
|
|
}
|
|
|
|
// check encrypted amount format after the above basic checks
|
|
// this check is more expensive so put it at the end
|
|
if (auto const res = checkEncryptedAmountFormat(ctx.tx); !isTesSuccess(res))
|
|
return res;
|
|
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
TER
|
|
ConfidentialMPTConvert::preclaim(PreclaimContext const& ctx)
|
|
{
|
|
auto const account = ctx.tx[sfAccount];
|
|
auto const issuanceID = ctx.tx[sfMPTokenIssuanceID];
|
|
auto const amount = ctx.tx[sfMPTAmount];
|
|
|
|
// ensure that issuance exists
|
|
auto const sleIssuance = ctx.view.read(keylet::mptIssuance(issuanceID));
|
|
if (!sleIssuance)
|
|
return tecOBJECT_NOT_FOUND;
|
|
|
|
if (!sleIssuance->isFlag(lsfMPTCanConfidentialAmount) ||
|
|
!sleIssuance->isFieldPresent(sfIssuerEncryptionKey))
|
|
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) == account)
|
|
return tefINTERNAL; // LCOV_EXCL_LINE
|
|
|
|
bool const hasAuditor = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount);
|
|
bool const requiresAuditor = sleIssuance->isFieldPresent(sfAuditorEncryptionKey);
|
|
|
|
// tx must include auditor ciphertext if the issuance has enabled
|
|
// auditing, and must not include it if auditing is not enabled
|
|
if (requiresAuditor != hasAuditor)
|
|
return tecNO_PERMISSION;
|
|
|
|
auto const sleMptoken = ctx.view.read(keylet::mptoken(issuanceID, account));
|
|
if (!sleMptoken)
|
|
return tecOBJECT_NOT_FOUND;
|
|
|
|
auto const mptIssue = MPTIssue{issuanceID};
|
|
STAmount const mptAmount =
|
|
STAmount(MPTAmount{static_cast<MPTAmount::value_type>(amount)}, mptIssue);
|
|
if (accountHolds(
|
|
ctx.view,
|
|
account,
|
|
mptIssue,
|
|
FreezeHandling::fhZERO_IF_FROZEN,
|
|
AuthHandling::ahZERO_IF_UNAUTHORIZED,
|
|
ctx.j) < mptAmount)
|
|
{
|
|
return tecINSUFFICIENT_FUNDS;
|
|
}
|
|
|
|
auto const hasHolderKeyOnLedger = sleMptoken->isFieldPresent(sfHolderEncryptionKey);
|
|
auto const hasHolderKeyInTx = ctx.tx.isFieldPresent(sfHolderEncryptionKey);
|
|
|
|
// must have pk to convert
|
|
if (!hasHolderKeyOnLedger && !hasHolderKeyInTx)
|
|
return tecNO_PERMISSION;
|
|
|
|
// can't update if there's already a pk
|
|
if (hasHolderKeyOnLedger && hasHolderKeyInTx)
|
|
return tecDUPLICATE;
|
|
|
|
Slice holderPubKey;
|
|
if (hasHolderKeyInTx)
|
|
{
|
|
holderPubKey = ctx.tx[sfHolderEncryptionKey];
|
|
|
|
auto const contextHash =
|
|
getConvertContextHash(account, issuanceID, ctx.tx.getSeqProxy().value());
|
|
|
|
// when register new pk, verify through schnorr proof
|
|
if (!isTesSuccess(verifySchnorrProof(holderPubKey, ctx.tx[sfZKProof], contextHash)))
|
|
{
|
|
return tecBAD_PROOF;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
holderPubKey = (*sleMptoken)[sfHolderEncryptionKey];
|
|
}
|
|
|
|
std::optional<ConfidentialRecipient> auditor;
|
|
if (hasAuditor)
|
|
{
|
|
auditor.emplace(
|
|
ConfidentialRecipient{
|
|
(*sleIssuance)[sfAuditorEncryptionKey], ctx.tx[sfAuditorEncryptedAmount]});
|
|
}
|
|
|
|
auto const blindingFactor = ctx.tx[sfBlindingFactor];
|
|
return verifyRevealedAmount(
|
|
amount,
|
|
Slice(blindingFactor.data(), blindingFactor.size()),
|
|
{holderPubKey, ctx.tx[sfHolderEncryptedAmount]},
|
|
{(*sleIssuance)[sfIssuerEncryptionKey], ctx.tx[sfIssuerEncryptedAmount]},
|
|
auditor);
|
|
}
|
|
|
|
TER
|
|
ConfidentialMPTConvert::doApply()
|
|
{
|
|
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
|
|
|
|
auto sleMptoken = view().peek(keylet::mptoken(mptIssuanceID, account_));
|
|
if (!sleMptoken)
|
|
return tecINTERNAL; // LCOV_EXCL_LINE
|
|
|
|
auto sleIssuance = view().peek(keylet::mptIssuance(mptIssuanceID));
|
|
if (!sleIssuance)
|
|
return tecINTERNAL; // LCOV_EXCL_LINE
|
|
|
|
auto const amtToConvert = ctx_.tx[sfMPTAmount];
|
|
auto const amt = (*sleMptoken)[~sfMPTAmount].value_or(0);
|
|
|
|
if (ctx_.tx.isFieldPresent(sfHolderEncryptionKey))
|
|
(*sleMptoken)[sfHolderEncryptionKey] = ctx_.tx[sfHolderEncryptionKey];
|
|
|
|
// Converting decreases regular balance and increases confidential outstanding.
|
|
// The confidential outstanding tracks total tokens in confidential form globally.
|
|
(*sleMptoken)[sfMPTAmount] = amt - amtToConvert;
|
|
(*sleIssuance)[sfConfidentialOutstandingAmount] =
|
|
(*sleIssuance)[~sfConfidentialOutstandingAmount].value_or(0) + amtToConvert;
|
|
|
|
Slice const holderEc = ctx_.tx[sfHolderEncryptedAmount];
|
|
Slice const issuerEc = ctx_.tx[sfIssuerEncryptedAmount];
|
|
|
|
auto const auditorEc = ctx_.tx[~sfAuditorEncryptedAmount];
|
|
|
|
// Two cases for Convert:
|
|
// 1. Holder already has confidential balances -> homomorphically add to inbox
|
|
// 2. First-time convert -> initialize all confidential balance fields
|
|
if (sleMptoken->isFieldPresent(sfIssuerEncryptedBalance) &&
|
|
sleMptoken->isFieldPresent(sfConfidentialBalanceInbox) &&
|
|
sleMptoken->isFieldPresent(sfConfidentialBalanceSpending))
|
|
{
|
|
// Case 1: Add to existing inbox balance (holder will merge later)
|
|
{
|
|
auto sum = homomorphicAdd(holderEc, (*sleMptoken)[sfConfidentialBalanceInbox]);
|
|
if (!sum)
|
|
return tecINTERNAL; // LCOV_EXCL_LINE
|
|
|
|
(*sleMptoken)[sfConfidentialBalanceInbox] = std::move(*sum);
|
|
}
|
|
|
|
// homomorphically add issuer's encrypted balance
|
|
{
|
|
auto sum = homomorphicAdd(issuerEc, (*sleMptoken)[sfIssuerEncryptedBalance]);
|
|
if (!sum)
|
|
return tecINTERNAL; // LCOV_EXCL_LINE
|
|
|
|
(*sleMptoken)[sfIssuerEncryptedBalance] = std::move(*sum);
|
|
}
|
|
|
|
// homomorphically add auditor's encrypted balance
|
|
if (auditorEc)
|
|
{
|
|
auto sum = homomorphicAdd(*auditorEc, (*sleMptoken)[sfAuditorEncryptedBalance]);
|
|
if (!sum)
|
|
return tecINTERNAL; // LCOV_EXCL_LINE
|
|
|
|
(*sleMptoken)[sfAuditorEncryptedBalance] = std::move(*sum);
|
|
}
|
|
}
|
|
else if (
|
|
!sleMptoken->isFieldPresent(sfIssuerEncryptedBalance) &&
|
|
!sleMptoken->isFieldPresent(sfConfidentialBalanceInbox) &&
|
|
!sleMptoken->isFieldPresent(sfConfidentialBalanceSpending))
|
|
{
|
|
// Case 2: First-time convert - initialize all confidential fields
|
|
(*sleMptoken)[sfConfidentialBalanceInbox] = holderEc;
|
|
(*sleMptoken)[sfIssuerEncryptedBalance] = issuerEc;
|
|
(*sleMptoken)[sfConfidentialBalanceVersion] = 0;
|
|
|
|
if (auditorEc)
|
|
(*sleMptoken)[sfAuditorEncryptedBalance] = *auditorEc;
|
|
|
|
// Spending balance starts at zero. Must use canonical zero encryption
|
|
// (deterministic ciphertext) so the ledger state is reproducible.
|
|
auto zeroBalance = encryptCanonicalZeroAmount(
|
|
(*sleMptoken)[sfHolderEncryptionKey], account_, mptIssuanceID);
|
|
|
|
if (!zeroBalance)
|
|
return tecINTERNAL; // LCOV_EXCL_LINE
|
|
|
|
(*sleMptoken)[sfConfidentialBalanceSpending] = std::move(*zeroBalance);
|
|
}
|
|
else
|
|
{
|
|
// both sfIssuerEncryptedBalance and sfConfidentialBalanceInbox should
|
|
// exist together
|
|
return tecINTERNAL; // LCOV_EXCL_LINE
|
|
}
|
|
|
|
view().update(sleIssuance);
|
|
view().update(sleMptoken);
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
} // namespace xrpl
|