mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-29 10:00:30 +00:00
Handle edge cases checks in flag setting and token deletion (#6596)
This commit is contained in:
@@ -404,48 +404,39 @@ Buffer
|
||||
generateBlindingFactor();
|
||||
|
||||
/**
|
||||
* @brief Verifies the cryptographic link between an ElGamal Ciphertext and a
|
||||
* Pedersen Commitment for a transaction Amount.
|
||||
* @brief Distinguishes the two algebraic structures used in
|
||||
* ElGamal-Pedersen linkage proofs.
|
||||
*
|
||||
* It proves that the ElGamal ciphertext `encAmt` encrypts the same value `m`
|
||||
* as the Pedersen Commitment `pcmSlice`, using the randomness `r`.
|
||||
* Proves Enc(m) <-> Pcm(m)
|
||||
*
|
||||
* @param proof The Zero Knowledge Proof bytes.
|
||||
* @param encAmt The ElGamal ciphertext of the amount (C1, C2).
|
||||
* @param pubKeySlice The sender's public key.
|
||||
* @param pcmSlice The Pedersen Commitment to the amount.
|
||||
* @param contextHash The unique context hash for this transaction.
|
||||
* @return tesSUCCESS if the proof is valid, or an error code otherwise.
|
||||
* - amount: The ciphertext was created with randomness `r`.
|
||||
* Verification order: C1, C2, Pk, Pcm.
|
||||
* - balance: The ciphertext was created with the secret key `s`.
|
||||
* Verification order: Pk, C2, C1, Pcm (swaps Pk <-> C1).
|
||||
*/
|
||||
TER
|
||||
verifyAmountPcmLinkage(
|
||||
Slice const& proof,
|
||||
Slice const& encAmt,
|
||||
Slice const& pubKeySlice,
|
||||
Slice const& pcmSlice,
|
||||
uint256 const& contextHash);
|
||||
enum class PcmLinkageType { amount, balance };
|
||||
|
||||
/**
|
||||
* @brief Verifies the cryptographic link between an ElGamal Ciphertext and a
|
||||
* Pedersen Commitment for an account Balance.
|
||||
* Pedersen Commitment.
|
||||
*
|
||||
* It proves that the ElGamal ciphertext `encAmt` encrypts the same value `b`
|
||||
* as the Pedersen Commitment `pcmSlice`, using the secret key `s`.
|
||||
* Proves Enc(b) <-> Pcm(b)
|
||||
* Proves that the ElGamal ciphertext `encAmt` encrypts the same value
|
||||
* as the Pedersen Commitment `pcmSlice`.
|
||||
*
|
||||
* Note: Swaps arguments (Pk <-> C1) to accommodate the different algebraic
|
||||
* structure.
|
||||
* The `type` parameter selects the argument ordering passed to the
|
||||
* underlying secp256k1 verification call to accommodate the different
|
||||
* algebraic structures used for amounts (randomness `r`) vs balances
|
||||
* (secret key `s`).
|
||||
*
|
||||
* @param type Whether this is an amount or balance linkage proof.
|
||||
* @param proof The Zero Knowledge Proof bytes.
|
||||
* @param encAmt The ElGamal ciphertext of the balance (C1, C2).
|
||||
* @param encAmt The ElGamal ciphertext (C1, C2).
|
||||
* @param pubKeySlice The sender's public key.
|
||||
* @param pcmSlice The Pedersen Commitment to the balance.
|
||||
* @param pcmSlice The Pedersen Commitment.
|
||||
* @param contextHash The unique context hash for this transaction.
|
||||
* @return tesSUCCESS if the proof is valid, or an error code otherwise.
|
||||
*/
|
||||
TER
|
||||
verifyBalancePcmLinkage(
|
||||
verifyPcmLinkage(
|
||||
PcmLinkageType type,
|
||||
Slice const& proof,
|
||||
Slice const& encAmt,
|
||||
Slice const& pubKeySlice,
|
||||
|
||||
@@ -1739,6 +1739,13 @@ removeEmptyHolding(
|
||||
if (mptoken->at(sfMPTAmount) != 0)
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
// Don't delete if the token still has confidential balances
|
||||
if (mptoken->isFieldPresent(sfConfidentialBalanceInbox) ||
|
||||
mptoken->isFieldPresent(sfConfidentialBalanceSpending))
|
||||
{
|
||||
return tecHAS_OBLIGATIONS;
|
||||
}
|
||||
|
||||
return authorizeMPToken(
|
||||
view,
|
||||
{}, // priorBalance
|
||||
|
||||
@@ -512,7 +512,8 @@ checkEncryptedAmountFormat(STObject const& object)
|
||||
}
|
||||
|
||||
TER
|
||||
verifyAmountPcmLinkage(
|
||||
verifyPcmLinkage(
|
||||
PcmLinkageType type,
|
||||
Slice const& proof,
|
||||
Slice const& encAmt,
|
||||
Slice const& pubKeySlice,
|
||||
@@ -548,7 +549,13 @@ verifyAmountPcmLinkage(
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
if (auto res = secp256k1_elgamal_pedersen_link_verify(
|
||||
// For amount linkage (randomness r): order is C1, C2, Pk, Pcm.
|
||||
// For balance linkage (secret key s): order is Pk, C2, C1, Pcm
|
||||
// (swaps Pk <-> C1 to accommodate the different algebraic structure).
|
||||
int res;
|
||||
if (type == PcmLinkageType::amount)
|
||||
{
|
||||
res = secp256k1_elgamal_pedersen_link_verify(
|
||||
secp256k1Context(),
|
||||
proof.data(),
|
||||
&pair->c1,
|
||||
@@ -556,54 +563,10 @@ verifyAmountPcmLinkage(
|
||||
&pubKey,
|
||||
&pcm,
|
||||
contextHash.data());
|
||||
res != 1)
|
||||
{
|
||||
return tecBAD_PROOF;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
verifyBalancePcmLinkage(
|
||||
Slice const& proof,
|
||||
Slice const& encAmt,
|
||||
Slice const& pubKeySlice,
|
||||
Slice const& pcmSlice,
|
||||
uint256 const& contextHash)
|
||||
{
|
||||
if (proof.length() != ecPedersenProofLength)
|
||||
return tecINTERNAL;
|
||||
|
||||
auto const pair = makeEcPair(encAmt);
|
||||
if (!pair)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (pubKeySlice.size() != ecPubKeyLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (pcmSlice.size() != ecPedersenCommitmentLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
secp256k1_pubkey pubKey;
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
secp256k1Context(), &pubKey, pubKeySlice.data(), ecPubKeyLength);
|
||||
res != 1)
|
||||
else
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
secp256k1_pubkey pcm;
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
secp256k1Context(), &pcm, pcmSlice.data(), ecPedersenCommitmentLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// Note: c2, c1 order - the linkage proof expects the message-containing
|
||||
// component (c2 = m*G + r*Pk) before the blinding component (c1 = r*G).
|
||||
if (auto res = secp256k1_elgamal_pedersen_link_verify(
|
||||
res = secp256k1_elgamal_pedersen_link_verify(
|
||||
secp256k1Context(),
|
||||
proof.data(),
|
||||
&pubKey,
|
||||
@@ -611,11 +574,11 @@ verifyBalancePcmLinkage(
|
||||
&pair->c1,
|
||||
&pcm,
|
||||
contextHash.data());
|
||||
res != 1)
|
||||
{
|
||||
return tecBAD_PROOF;
|
||||
}
|
||||
|
||||
if (res != 1)
|
||||
return tecBAD_PROOF;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,8 @@ verifyProofs(
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// verify el gamal pedersen linkage
|
||||
if (auto const ter = verifyBalancePcmLinkage(
|
||||
if (auto const ter = verifyPcmLinkage(
|
||||
PcmLinkageType::balance,
|
||||
pedersenProof,
|
||||
(*mptoken)[sfConfidentialBalanceSpending],
|
||||
holderPubKey,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/ConfidentialTransfer.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -46,6 +47,16 @@ ConfidentialMPTMergeInbox::preclaim(PreclaimContext const& ctx)
|
||||
!sleMptoken->isFieldPresent(sfHolderEncryptionKey))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Check lock
|
||||
auto const account = ctx.tx[sfAccount];
|
||||
MPTIssue const mptIssue(ctx.tx[sfMPTokenIssuanceID]);
|
||||
if (auto const ter = checkFrozen(ctx.view, account, mptIssue); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// Check auth
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue, account); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -157,7 +157,8 @@ verifySendProofs(
|
||||
}
|
||||
|
||||
// Verify amount linkage
|
||||
if (auto const ter = verifyAmountPcmLinkage(
|
||||
if (auto const ter = verifyPcmLinkage(
|
||||
PcmLinkageType::amount,
|
||||
amountLinkageProof,
|
||||
ctx.tx[sfSenderEncryptedAmount],
|
||||
(*sleSenderMPToken)[sfHolderEncryptionKey],
|
||||
@@ -169,7 +170,8 @@ verifySendProofs(
|
||||
}
|
||||
|
||||
// Verify balance linkage
|
||||
if (auto const ter = verifyBalancePcmLinkage(
|
||||
if (auto const ter = verifyPcmLinkage(
|
||||
PcmLinkageType::balance,
|
||||
balanceLinkageProof,
|
||||
(*sleSenderMPToken)[sfConfidentialBalanceSpending],
|
||||
(*sleSenderMPToken)[sfHolderEncryptionKey],
|
||||
|
||||
@@ -137,6 +137,11 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx)
|
||||
if (hasAuditorElGamalKey && !hasIssuerElGamalKey)
|
||||
return temMALFORMED;
|
||||
|
||||
// Cannot set keys while clearing confidential amount
|
||||
if ((hasIssuerElGamalKey || hasAuditorElGamalKey) && mutableFlags &&
|
||||
(*mutableFlags & tmfMPTClearCanConfidentialAmount))
|
||||
return temINVALID_FLAG;
|
||||
|
||||
if (hasIssuerElGamalKey && !isValidCompressedECPoint(ctx.tx[sfIssuerEncryptionKey]))
|
||||
return temMALFORMED;
|
||||
|
||||
@@ -291,14 +296,20 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// Check if the transaction is enabling confidential amounts
|
||||
bool const enablesConfidentialAmount =
|
||||
mutableFlags && (*mutableFlags & tmfMPTSetCanConfidentialAmount);
|
||||
|
||||
// Encryption keys can only be set if confidential amounts are already
|
||||
// enabled on the issuance OR if the transaction is enabling it
|
||||
if (ctx.tx.isFieldPresent(sfIssuerEncryptionKey) &&
|
||||
!sleMptIssuance->isFlag(lsfMPTCanConfidentialAmount))
|
||||
!sleMptIssuance->isFlag(lsfMPTCanConfidentialAmount) && !enablesConfidentialAmount)
|
||||
{
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfAuditorEncryptionKey) &&
|
||||
!sleMptIssuance->isFlag(lsfMPTCanConfidentialAmount))
|
||||
!sleMptIssuance->isFlag(lsfMPTCanConfidentialAmount) && !enablesConfidentialAmount)
|
||||
{
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <test/jtx.h>
|
||||
#include <test/jtx/ticket.h>
|
||||
#include <test/jtx/trust.h>
|
||||
#include <test/jtx/vault.h>
|
||||
|
||||
#include <xrpl/protocol/ConfidentialTransfer.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
@@ -536,6 +537,85 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testSet(FeatureBitset features)
|
||||
{
|
||||
testcase("Set");
|
||||
using namespace test::jtx;
|
||||
|
||||
// Set keys on issuance that already has confidential amounts enabled
|
||||
{
|
||||
Env env{*this, features};
|
||||
Account const alice("alice");
|
||||
Account const auditor("auditor");
|
||||
MPTTester mptAlice(env, alice, {.holders = {}, .auditor = auditor});
|
||||
|
||||
mptAlice.create({
|
||||
.ownerCount = 1,
|
||||
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanConfidentialAmount,
|
||||
});
|
||||
|
||||
mptAlice.generateKeyPair(alice);
|
||||
mptAlice.generateKeyPair(auditor);
|
||||
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.issuerPubKey = mptAlice.getPubKey(alice),
|
||||
.auditorPubKey = mptAlice.getPubKey(auditor),
|
||||
});
|
||||
}
|
||||
|
||||
// Enable confidential amounts flag only (no keys)
|
||||
{
|
||||
Env env{*this, features};
|
||||
Account const alice("alice");
|
||||
MPTTester mptAlice(env, alice, {.holders = {}});
|
||||
|
||||
mptAlice.create({
|
||||
.ownerCount = 1,
|
||||
.flags = tfMPTCanTransfer | tfMPTCanLock,
|
||||
});
|
||||
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.mutableFlags = tmfMPTSetCanConfidentialAmount,
|
||||
});
|
||||
}
|
||||
|
||||
// Set keys when enabling confidential amounts in the same tx
|
||||
{
|
||||
Env env{*this, features};
|
||||
Account const alice("alice");
|
||||
Account const auditor("auditor");
|
||||
MPTTester mptAlice(env, alice, {.holders = {}, .auditor = auditor});
|
||||
|
||||
mptAlice.create({
|
||||
.ownerCount = 1,
|
||||
.flags = tfMPTCanTransfer | tfMPTCanLock,
|
||||
});
|
||||
|
||||
mptAlice.generateKeyPair(alice);
|
||||
mptAlice.generateKeyPair(auditor);
|
||||
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.mutableFlags = tmfMPTSetCanConfidentialAmount,
|
||||
.issuerPubKey = mptAlice.getPubKey(alice),
|
||||
.auditorPubKey = mptAlice.getPubKey(auditor),
|
||||
});
|
||||
|
||||
// Verify lsfMPTCanConfidentialAmount flag is set
|
||||
BEAST_EXPECT(mptAlice.checkFlags(
|
||||
lsfMPTCanTransfer | lsfMPTCanLock | lsfMPTCanConfidentialAmount));
|
||||
|
||||
// Verify keys are persisted on the issuance
|
||||
auto const sle = env.le(keylet::mptIssuance(mptAlice.issuanceID()));
|
||||
BEAST_EXPECT(sle);
|
||||
BEAST_EXPECT(sle->isFieldPresent(sfIssuerEncryptionKey));
|
||||
BEAST_EXPECT(sle->isFieldPresent(sfAuditorEncryptionKey));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testSetPreflight(FeatureBitset features)
|
||||
{
|
||||
@@ -633,6 +713,14 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
|
||||
.err = temMALFORMED,
|
||||
});
|
||||
|
||||
// Cannot set keys while clearing confidential amount
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.mutableFlags = tmfMPTClearCanConfidentialAmount,
|
||||
.issuerPubKey = mptAlice.getPubKey(alice),
|
||||
.err = temINVALID_FLAG,
|
||||
});
|
||||
|
||||
// Cannot set Holder and auditor Keys in the same transaction
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
@@ -641,31 +729,174 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
|
||||
.err = temMALFORMED,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// issuance has disabled confidential transfer
|
||||
void
|
||||
testSetPreclaim(FeatureBitset features)
|
||||
{
|
||||
testcase("Set preclaim");
|
||||
using namespace test::jtx;
|
||||
|
||||
// Cannot set issuer key if confidential amounts not enabled
|
||||
{
|
||||
Env env{*this, features};
|
||||
Account const alice("alice");
|
||||
MPTTester mptAlice(env, alice, {.holders = {}});
|
||||
|
||||
mptAlice.create({
|
||||
.ownerCount = 1,
|
||||
.flags = tfMPTCanTransfer | tfMPTCanLock,
|
||||
});
|
||||
|
||||
mptAlice.generateKeyPair(alice);
|
||||
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.issuerPubKey = mptAlice.getPubKey(alice),
|
||||
.err = tecNO_PERMISSION,
|
||||
});
|
||||
}
|
||||
|
||||
// Cannot update issuer public key once set
|
||||
{
|
||||
Env env{*this, features};
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob}});
|
||||
|
||||
// no tfMPTCanConfidentialAmount flag enabled
|
||||
mptAlice.create({
|
||||
.ownerCount = 1,
|
||||
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanConfidentialAmount,
|
||||
});
|
||||
|
||||
mptAlice.generateKeyPair(alice);
|
||||
mptAlice.generateKeyPair(bob);
|
||||
|
||||
// First set issuer key - should succeed
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.issuerPubKey = mptAlice.getPubKey(alice),
|
||||
});
|
||||
|
||||
// Try to update issuer key - should fail
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.issuerPubKey = mptAlice.getPubKey(bob),
|
||||
.err = tecNO_PERMISSION,
|
||||
});
|
||||
}
|
||||
|
||||
// Cannot update issuer and auditor public keys once set
|
||||
// Note: trying to set only auditor key fails in preflight (temMALFORMED)
|
||||
// so we must provide both keys, which fails on issuer key check first
|
||||
{
|
||||
Env env{*this, features};
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
Account const auditor("auditor");
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob}, .auditor = auditor});
|
||||
|
||||
mptAlice.create({
|
||||
.ownerCount = 1,
|
||||
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanConfidentialAmount,
|
||||
});
|
||||
|
||||
mptAlice.generateKeyPair(alice);
|
||||
mptAlice.generateKeyPair(bob);
|
||||
mptAlice.generateKeyPair(auditor);
|
||||
|
||||
// Set issuer and auditor keys - should succeed
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.issuerPubKey = mptAlice.getPubKey(alice),
|
||||
.auditorPubKey = mptAlice.getPubKey(auditor),
|
||||
});
|
||||
|
||||
// Try to update both keys - fails on issuer key check first
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.issuerPubKey = mptAlice.getPubKey(bob),
|
||||
.auditorPubKey = mptAlice.getPubKey(alice),
|
||||
.err = tecNO_PERMISSION,
|
||||
});
|
||||
}
|
||||
|
||||
// Cannot set auditor key if confidential amounts not enabled
|
||||
{
|
||||
Env env{*this, features};
|
||||
Account const alice("alice");
|
||||
Account const auditor("auditor");
|
||||
MPTTester mptAlice(env, alice, {.holders = {}, .auditor = auditor});
|
||||
|
||||
mptAlice.create({
|
||||
.ownerCount = 1,
|
||||
.flags = tfMPTCanTransfer | tfMPTCanLock,
|
||||
});
|
||||
|
||||
mptAlice.authorize({
|
||||
.account = bob,
|
||||
});
|
||||
mptAlice.pay(alice, bob, 100);
|
||||
|
||||
mptAlice.generateKeyPair(alice);
|
||||
mptAlice.generateKeyPair(bob);
|
||||
mptAlice.generateKeyPair(auditor);
|
||||
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.issuerPubKey = mptAlice.getPubKey(alice),
|
||||
.auditorPubKey = mptAlice.getPubKey(auditor),
|
||||
.err = tecNO_PERMISSION,
|
||||
});
|
||||
}
|
||||
|
||||
// Cannot set keys when mutation of canConfidentialAmount is disallowed
|
||||
{
|
||||
Env env{*this, features};
|
||||
Account const alice("alice");
|
||||
MPTTester mptAlice(env, alice, {.holders = {}});
|
||||
|
||||
// Create with tmfMPTCannotMutateCanConfidentialAmount
|
||||
mptAlice.create({
|
||||
.ownerCount = 1,
|
||||
.flags = tfMPTCanTransfer | tfMPTCanLock,
|
||||
.mutableFlags = tmfMPTCannotMutateCanConfidentialAmount,
|
||||
});
|
||||
|
||||
mptAlice.generateKeyPair(alice);
|
||||
|
||||
// Trying to enable confidential amounts and set keys fails
|
||||
// because the issuance cannot mutate canConfidentialAmount
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.mutableFlags = tmfMPTSetCanConfidentialAmount,
|
||||
.issuerPubKey = mptAlice.getPubKey(alice),
|
||||
.err = tecNO_PERMISSION,
|
||||
});
|
||||
}
|
||||
|
||||
// Set issuer key first, then auditor key in a separate tx
|
||||
{
|
||||
Env env{*this, features};
|
||||
Account const alice("alice");
|
||||
Account const auditor("auditor");
|
||||
MPTTester mptAlice(env, alice, {.holders = {}, .auditor = auditor});
|
||||
|
||||
mptAlice.create({
|
||||
.ownerCount = 1,
|
||||
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanConfidentialAmount,
|
||||
});
|
||||
|
||||
mptAlice.generateKeyPair(alice);
|
||||
mptAlice.generateKeyPair(auditor);
|
||||
|
||||
// Set issuer key only
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.issuerPubKey = mptAlice.getPubKey(alice),
|
||||
});
|
||||
|
||||
// Set auditor key in a separate tx - requires issuer key in tx
|
||||
// (preflight enforces auditor key requires issuer key)
|
||||
// This fails because issuer key is already set on ledger
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.issuerPubKey = mptAlice.getPubKey(alice),
|
||||
.auditorPubKey = mptAlice.getPubKey(auditor),
|
||||
.err = tecNO_PERMISSION,
|
||||
});
|
||||
}
|
||||
@@ -1340,6 +1571,116 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
|
||||
.err = tecNO_PERMISSION,
|
||||
});
|
||||
}
|
||||
|
||||
// holder is locked
|
||||
{
|
||||
Env env{*this, features};
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob}});
|
||||
|
||||
mptAlice.create({
|
||||
.ownerCount = 1,
|
||||
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanConfidentialAmount,
|
||||
});
|
||||
|
||||
mptAlice.authorize({
|
||||
.account = bob,
|
||||
});
|
||||
mptAlice.pay(alice, bob, 100);
|
||||
|
||||
mptAlice.generateKeyPair(alice);
|
||||
mptAlice.generateKeyPair(bob);
|
||||
|
||||
mptAlice.set({.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
|
||||
|
||||
mptAlice.convert({
|
||||
.account = bob,
|
||||
.amt = 50,
|
||||
.holderPubKey = mptAlice.getPubKey(bob),
|
||||
});
|
||||
|
||||
// lock bob
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.holder = bob,
|
||||
.flags = tfMPTLock,
|
||||
});
|
||||
|
||||
mptAlice.mergeInbox({
|
||||
.account = bob,
|
||||
.err = tecLOCKED,
|
||||
});
|
||||
|
||||
// unlock bob
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.holder = bob,
|
||||
.flags = tfMPTUnlock,
|
||||
});
|
||||
|
||||
// should succeed now
|
||||
mptAlice.mergeInbox({
|
||||
.account = bob,
|
||||
});
|
||||
}
|
||||
|
||||
// holder not authorized
|
||||
{
|
||||
Env env{*this, features};
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob}});
|
||||
|
||||
mptAlice.create({
|
||||
.ownerCount = 1,
|
||||
.flags =
|
||||
tfMPTCanTransfer | tfMPTCanLock | tfMPTCanConfidentialAmount | tfMPTRequireAuth,
|
||||
});
|
||||
|
||||
mptAlice.authorize({
|
||||
.account = bob,
|
||||
});
|
||||
mptAlice.authorize({
|
||||
.account = alice,
|
||||
.holder = bob,
|
||||
});
|
||||
mptAlice.pay(alice, bob, 100);
|
||||
|
||||
mptAlice.generateKeyPair(alice);
|
||||
mptAlice.generateKeyPair(bob);
|
||||
|
||||
mptAlice.set({.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
|
||||
|
||||
mptAlice.convert({
|
||||
.account = bob,
|
||||
.amt = 50,
|
||||
.holderPubKey = mptAlice.getPubKey(bob),
|
||||
});
|
||||
|
||||
// unauthorize bob
|
||||
mptAlice.authorize({
|
||||
.account = alice,
|
||||
.holder = bob,
|
||||
.flags = tfMPTUnauthorize,
|
||||
});
|
||||
|
||||
mptAlice.mergeInbox({
|
||||
.account = bob,
|
||||
.err = tecNO_AUTH,
|
||||
});
|
||||
|
||||
// authorize bob again
|
||||
mptAlice.authorize({
|
||||
.account = alice,
|
||||
.holder = bob,
|
||||
});
|
||||
|
||||
// should succeed now
|
||||
mptAlice.mergeInbox({
|
||||
.account = bob,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -2599,6 +2940,85 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
|
||||
.flags = tfMPTUnauthorize,
|
||||
});
|
||||
}
|
||||
|
||||
// removeEmptyHolding: vault share MPToken with confidential balance
|
||||
// fields should not be deleted on VaultWithdraw
|
||||
{
|
||||
Env env{*this, features | featureSingleAssetVault};
|
||||
Account const issuer("issuer");
|
||||
Account const owner("owner");
|
||||
Account const depositor("depositor");
|
||||
|
||||
MPTTester mptt{env, issuer, {.holders = {owner, depositor}}};
|
||||
mptt.create({
|
||||
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanClawback,
|
||||
});
|
||||
PrettyAsset asset = mptt.issuanceID();
|
||||
mptt.authorize({.account = owner});
|
||||
mptt.authorize({.account = depositor});
|
||||
env(pay(issuer, depositor, asset(1000)));
|
||||
env.close();
|
||||
|
||||
test::jtx::Vault vault{env};
|
||||
auto [tx, vaultKeylet] = vault.create({.owner = owner, .asset = asset});
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
// Get the share MPTID from vault
|
||||
auto const vaultSle = env.le(vaultKeylet);
|
||||
BEAST_EXPECT(vaultSle != nullptr);
|
||||
MPTID share = vaultSle->at(sfShareMPTID);
|
||||
|
||||
// Depositor deposits into vault
|
||||
tx = vault.deposit(
|
||||
{.depositor = depositor, .id = vaultKeylet.key, .amount = asset(100)});
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
// Verify depositor has share tokens
|
||||
auto shareMpt = env.le(keylet::mptoken(share, depositor.id()));
|
||||
BEAST_EXPECT(shareMpt != nullptr);
|
||||
|
||||
// Inject confidential balance fields on the share MPToken
|
||||
// to simulate a scenario where vault shares somehow have
|
||||
// confidential balances
|
||||
env.app().openLedger().modify([&](OpenView& view, beast::Journal) {
|
||||
// Set lsfMPTCanConfidentialAmount on the share issuance
|
||||
// so the invariant allows encrypted fields on the MPToken
|
||||
auto issuance = std::const_pointer_cast<SLE>(view.read(keylet::mptIssuance(share)));
|
||||
if (!issuance)
|
||||
return false;
|
||||
issuance->setFlag(lsfMPTCanConfidentialAmount);
|
||||
view.rawReplace(issuance);
|
||||
|
||||
auto const k = keylet::mptoken(share, depositor.id());
|
||||
auto sle = std::const_pointer_cast<SLE>(view.read(k));
|
||||
if (!sle)
|
||||
return false;
|
||||
// Inject dummy confidential balance fields
|
||||
Buffer dummyCiphertext(ecGamalEncryptedTotalLength);
|
||||
std::memset(dummyCiphertext.data(), 0, ecGamalEncryptedTotalLength);
|
||||
dummyCiphertext.data()[0] = ecCompressedPrefixEvenY;
|
||||
dummyCiphertext.data()[ecGamalEncryptedLength] = ecCompressedPrefixEvenY;
|
||||
dummyCiphertext.data()[ecGamalEncryptedLength - 1] = 0x01;
|
||||
dummyCiphertext.data()[ecGamalEncryptedTotalLength - 1] = 0x01;
|
||||
sle->setFieldVL(sfConfidentialBalanceSpending, dummyCiphertext);
|
||||
sle->setFieldVL(sfConfidentialBalanceInbox, dummyCiphertext);
|
||||
sle->setFieldVL(sfIssuerEncryptedBalance, dummyCiphertext);
|
||||
view.rawReplace(sle);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Withdraw everything - which should fail because of the confidential balance fields
|
||||
tx = vault.withdraw(
|
||||
{.depositor = depositor, .id = vaultKeylet.key, .amount = asset(100)});
|
||||
env(tx);
|
||||
|
||||
// The share MPToken should still exist because the
|
||||
// withdrawal failed due to confidential balance obligations
|
||||
shareMpt = env.le(keylet::mptoken(share, depositor.id()));
|
||||
BEAST_EXPECT(shareMpt != nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -6102,7 +6522,9 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
|
||||
testMergeInboxPreflight(features);
|
||||
testMergeInboxPreclaim(features);
|
||||
|
||||
testSet(features);
|
||||
testSetPreflight(features);
|
||||
testSetPreclaim(features);
|
||||
|
||||
// ConfidentialMPTSend
|
||||
testSend(features);
|
||||
|
||||
Reference in New Issue
Block a user