mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-03 16:56:48 +00:00
refactor and check deposit preauth before ZK proof (#7085)
This commit is contained in:
@@ -67,6 +67,20 @@ checkArray(STArray const& credentials, unsigned maxSize, beast::Journal j);
|
||||
TER
|
||||
verifyValidDomain(ApplyView& view, AccountID const& account, uint256 domainID, beast::Journal j);
|
||||
|
||||
// Check DepositPreauth authorization
|
||||
TER
|
||||
checkDepositPreauth(
|
||||
STTx const& tx,
|
||||
ReadView const& view,
|
||||
AccountID const& src,
|
||||
AccountID const& dst,
|
||||
std::shared_ptr<SLE const> const& sleDst,
|
||||
beast::Journal j);
|
||||
|
||||
// Remove expired credentials
|
||||
TER
|
||||
cleanupExpiredCredentials(STTx const& tx, ApplyView& view, beast::Journal j);
|
||||
|
||||
// Check expired credentials and for existing DepositPreauth ledger object
|
||||
TER
|
||||
verifyDepositPreauth(
|
||||
|
||||
@@ -339,9 +339,9 @@ verifyValidDomain(ApplyView& view, AccountID const& account, uint256 domainID, b
|
||||
}
|
||||
|
||||
TER
|
||||
verifyDepositPreauth(
|
||||
checkDepositPreauth(
|
||||
STTx const& tx,
|
||||
ApplyView& view,
|
||||
ReadView const& view,
|
||||
AccountID const& src,
|
||||
AccountID const& dst,
|
||||
std::shared_ptr<SLE const> const& sleDst,
|
||||
@@ -353,20 +353,16 @@ verifyDepositPreauth(
|
||||
// 2. If src is deposit preauthorized by dst (either by account or by
|
||||
// credentials).
|
||||
|
||||
bool const credentialsPresent = tx.isFieldPresent(sfCredentialIDs);
|
||||
|
||||
if (credentialsPresent && credentials::removeExpired(view, tx.getFieldV256(sfCredentialIDs), j))
|
||||
return tecEXPIRED;
|
||||
|
||||
if (sleDst && ((sleDst->getFlags() & lsfDepositAuth) != 0u))
|
||||
{
|
||||
if (src != dst)
|
||||
{
|
||||
if (!view.exists(keylet::depositPreauth(dst, src)))
|
||||
{
|
||||
return !credentialsPresent ? tecNO_PERMISSION
|
||||
: credentials::authorizedDepositPreauth(
|
||||
view, tx.getFieldV256(sfCredentialIDs), dst);
|
||||
return !tx.isFieldPresent(sfCredentialIDs)
|
||||
? tecNO_PERMISSION
|
||||
: credentials::authorizedDepositPreauth(
|
||||
view, tx.getFieldV256(sfCredentialIDs), dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -374,4 +370,29 @@ verifyDepositPreauth(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
cleanupExpiredCredentials(STTx const& tx, ApplyView& view, beast::Journal j)
|
||||
{
|
||||
if (tx.isFieldPresent(sfCredentialIDs) &&
|
||||
credentials::removeExpired(view, tx.getFieldV256(sfCredentialIDs), j))
|
||||
return tecEXPIRED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
verifyDepositPreauth(
|
||||
STTx const& tx,
|
||||
ApplyView& view,
|
||||
AccountID const& src,
|
||||
AccountID const& dst,
|
||||
std::shared_ptr<SLE const> const& sleDst,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (auto const err = cleanupExpiredCredentials(tx, view, j); !isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
return checkDepositPreauth(tx, view, src, dst, sleDst, j);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -247,6 +247,12 @@ ConfidentialMPTSend::preclaim(PreclaimContext const& ctx)
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
// Check deposit preauth before the expensive ZK proof verification.
|
||||
// Uses read-only view.
|
||||
if (auto const err = checkDepositPreauth(ctx.tx, ctx.view, account, destination, sleDst, ctx.j);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
return verifySendProofs(ctx, sleSenderMPToken, sleDestinationMPToken, sleIssuance);
|
||||
}
|
||||
|
||||
@@ -264,8 +270,9 @@ ConfidentialMPTSend::doApply()
|
||||
if (!sleSenderMPToken || !sleDestinationMPToken || !sleDestAcct)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (auto err = verifyDepositPreauth(
|
||||
ctx_.tx, ctx_.view(), account_, destination, sleDestAcct, ctx_.journal);
|
||||
// Deposit preauth authorization was already verified in preclaim.
|
||||
// Remove any expired credentials.
|
||||
if (auto err = cleanupExpiredCredentials(ctx_.tx, ctx_.view(), ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
@@ -4167,6 +4168,137 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite
|
||||
// Now Carol can send with credentials
|
||||
mpt.send({.account = carol, .dest = bob, .amt = 10, .credentials = {{credIdx}}});
|
||||
}
|
||||
|
||||
auto const expireTime = 30;
|
||||
|
||||
// Lambda function that returns the credential index after creating a
|
||||
// credential that expires shortly after the current ledger time.
|
||||
auto createExpiringCredential = [&](Env& env, Account const& subject) -> std::string {
|
||||
auto jv = credentials::create(subject, dpIssuer, credType);
|
||||
auto const expiry =
|
||||
env.current()->header().parentCloseTime.time_since_epoch().count() + expireTime;
|
||||
jv[sfExpiration.jsonName] = expiry;
|
||||
env(jv);
|
||||
env.close();
|
||||
env(credentials::accept(subject, dpIssuer, credType));
|
||||
env.close();
|
||||
auto const credentials = credentials::ledgerEntry(env, subject, dpIssuer, credType);
|
||||
return credentials[jss::result][jss::index].asString();
|
||||
};
|
||||
|
||||
auto credentialDeleted = [&](Env& env, Account const& subject) -> bool {
|
||||
auto const credentials = credentials::ledgerEntry(env, subject, dpIssuer, credType);
|
||||
return credentials[jss::result].isMember(jss::error) &&
|
||||
credentials[jss::result][jss::error] == "entryNotFound";
|
||||
};
|
||||
|
||||
// TEST 4: Expired credential with matching depositPreauth entry.
|
||||
// checkDepositPreauth in preclaim returns tesSUCCESS (the expired
|
||||
// credential still exists and matches the depositPreauth key), so ZK
|
||||
// proofs run. cleanupExpiredCredentials in doApply then removes the
|
||||
// expired credential and returns tecEXPIRED.
|
||||
{
|
||||
Env env(*this, features);
|
||||
env.fund(XRP(50000), dpIssuer);
|
||||
env.close();
|
||||
|
||||
ConfidentialEnv confEnv{env, alice, {{bob, 100, 50}, {carol, 100, 50}}};
|
||||
auto& mpt = confEnv.mpt;
|
||||
env(fset(bob, asfDepositAuth));
|
||||
env.close();
|
||||
|
||||
auto const credIdx = createExpiringCredential(env, carol);
|
||||
|
||||
// Bob authorizes carol's credential type
|
||||
env(deposit::authCredentials(bob, {{.issuer = dpIssuer, .credType = credType}}));
|
||||
env.close();
|
||||
|
||||
// Advance ledger past credential expiration
|
||||
env.close(std::chrono::seconds(expireTime));
|
||||
|
||||
// Send fails with tecEXPIRED; the expired credential is cleaned up
|
||||
mpt.send({
|
||||
.account = carol,
|
||||
.dest = bob,
|
||||
.amt = 10,
|
||||
.credentials = {{credIdx}},
|
||||
.err = tecEXPIRED,
|
||||
});
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(credentialDeleted(env, carol));
|
||||
}
|
||||
|
||||
// TEST 5: Expired credential, destination has no depositAuth.
|
||||
// checkDepositPreauth in preclaim returns tesSUCCESS even with expired credentials,
|
||||
// because we want to keep the checkDepositPreauth part before the expensive proof
|
||||
// verification. cleanupExpiredCredentials in doApply removes the expired credential and
|
||||
// returns tecEXPIRED.
|
||||
{
|
||||
Env env(*this, features);
|
||||
env.fund(XRP(50000), dpIssuer);
|
||||
env.close();
|
||||
|
||||
ConfidentialEnv confEnv{env, alice, {{bob, 100, 50}, {carol, 100, 50}}};
|
||||
auto& mpt = confEnv.mpt;
|
||||
|
||||
auto const credIdx = createExpiringCredential(env, carol);
|
||||
|
||||
// Advance ledger past credential expiration
|
||||
env.close(std::chrono::seconds(expireTime));
|
||||
|
||||
// Send fails with tecEXPIRED; the expired credential is cleaned up
|
||||
mpt.send({
|
||||
.account = carol,
|
||||
.dest = bob,
|
||||
.amt = 10,
|
||||
.credentials = {{credIdx}},
|
||||
.err = tecEXPIRED,
|
||||
});
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(credentialDeleted(env, carol));
|
||||
}
|
||||
|
||||
// TEST 6: Expired credential, depositAuth enabled but credential
|
||||
// not authorized by bob.
|
||||
// checkDepositPreauth in preclaim calls checkDepositPreauth which
|
||||
// finds no match and returns tecNO_PERMISSION. doApply never runs, so
|
||||
// the expired credential is not cleaned up by this transaction. This is
|
||||
// a deliberate tradeoff: allowing doApply to run solely for cleanup
|
||||
// would require bypassing the preclaim short-circuit, forcing every
|
||||
// validator to run the expensive ZK proof verification before
|
||||
// discovering the authorization failure. Expired credentials here will
|
||||
// be cleaned up opportunistically by a future transaction that
|
||||
// references them.
|
||||
{
|
||||
Env env(*this, features);
|
||||
env.fund(XRP(50000), dpIssuer);
|
||||
env.close();
|
||||
|
||||
ConfidentialEnv confEnv{env, alice, {{bob, 100, 50}, {carol, 100, 50}}};
|
||||
auto& mpt = confEnv.mpt;
|
||||
env(fset(bob, asfDepositAuth));
|
||||
env.close();
|
||||
|
||||
auto const credIdx = createExpiringCredential(env, carol);
|
||||
|
||||
// Advance ledger past credential expiration
|
||||
env.close(std::chrono::seconds(expireTime));
|
||||
|
||||
// Fails with tecNO_PERMISSION.
|
||||
mpt.send({
|
||||
.account = carol,
|
||||
.dest = bob,
|
||||
.amt = 10,
|
||||
.credentials = {{credIdx}},
|
||||
.err = tecNO_PERMISSION,
|
||||
});
|
||||
env.close();
|
||||
|
||||
// Expired credential is not deleted
|
||||
BEAST_EXPECT(!credentialDeleted(env, carol));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -9881,7 +10013,7 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite
|
||||
testHomomorphicCiphertextModification(features);
|
||||
testConvertBackHomomorphicUnderflow(features);
|
||||
|
||||
// invalid curve points
|
||||
// Invalid curve points
|
||||
testSendInvalidCurvePoints(features);
|
||||
testSendWrongGroupPointInjection(features);
|
||||
testIdentityElementRejection(features);
|
||||
|
||||
Reference in New Issue
Block a user