mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-29 15:37:57 +00:00
MergeInbox (#5922)
This commit is contained in:
@@ -94,7 +94,19 @@ secp256k1_elgamal_subtract(
|
||||
secp256k1_pubkey const* b_c2);
|
||||
|
||||
/**
|
||||
* @brief Generates the canonical encrypted zero for a given trust line.
|
||||
* @brief Generates the canonical encrypted zero for a given MPT token instance.
|
||||
*
|
||||
* This ciphertext represents a zero balance for a specific account's holding
|
||||
* of a token defined by its MPTokenIssuanceID.
|
||||
*
|
||||
* @param[in] ctx A pointer to a valid secp256k1 context.
|
||||
* @param[out] enc_zero_c1 The C1 component of the canonical ciphertext.
|
||||
* @param[out] enc_zero_c2 The C2 component of the canonical ciphertext.
|
||||
* @param[in] pubkey The ElGamal public key of the account holder.
|
||||
* @param[in] account_id A pointer to the 20-byte AccountID.
|
||||
* @param[in] mpt_issuance_id A pointer to the 24-byte MPTokenIssuanceID.
|
||||
*
|
||||
* @return 1 on success, 0 on failure.
|
||||
*/
|
||||
SECP256K1_API int
|
||||
generate_canonical_encrypted_zero(
|
||||
@@ -102,9 +114,9 @@ generate_canonical_encrypted_zero(
|
||||
secp256k1_pubkey* enc_zero_c1,
|
||||
secp256k1_pubkey* enc_zero_c2,
|
||||
secp256k1_pubkey const* pubkey,
|
||||
char const* acct,
|
||||
char const* issuer,
|
||||
char const* curr);
|
||||
unsigned char const* account_id, // 20 bytes
|
||||
unsigned char const* mpt_issuance_id // 24 bytes
|
||||
);
|
||||
|
||||
// breaks a 66-byte encrypted amount into two 33-byte components
|
||||
// then parses each 33-byte component into 64-byte secp256k1_pubkey format
|
||||
@@ -133,6 +145,11 @@ proveEquality(
|
||||
Buffer
|
||||
encryptAmount(uint64_t amt, Slice const& pubKeySlice);
|
||||
|
||||
Buffer
|
||||
encryptCanonicalZeroAmount(
|
||||
Slice const& pubKeySlice,
|
||||
AccountID const& account,
|
||||
MPTID const& mptId);
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -962,6 +962,34 @@ TRANSACTION(ttCONFIDENTIAL_CONVERT, 72, ConfidentialConvert,
|
||||
{sfZKProof, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type merges MPT inbox. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
#include <xrpld/app/tx/detail/ConfidentialMergeInbox.h>
|
||||
#endif
|
||||
TRANSACTION(ttCONFIDENTIAL_MERGE_INBOX, 73, ConfidentialMergeInbox,
|
||||
Delegation::delegatable,
|
||||
featureConfidentialTransfer,
|
||||
noPriv,
|
||||
({
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type converts back into public MPT balance. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
#include <xrpld/app/tx/detail/ConfidentialConvertBack.h>
|
||||
#endif
|
||||
TRANSACTION(ttCONFIDENTIAL_CONVERT_BACK, 74, ConfidentialConvertBack,
|
||||
Delegation::delegatable,
|
||||
featureConfidentialTransfer,
|
||||
noPriv,
|
||||
({
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
{sfMPTAmount, soeREQUIRED},
|
||||
{sfHolderEncryptedAmount, soeREQUIRED},
|
||||
{sfIssuerEncryptedAmount, soeREQUIRED},
|
||||
{sfZKProof, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This system-generated transaction type is used to update the status of the various amendments.
|
||||
|
||||
For details, see: https://xrpl.org/amendments.html
|
||||
|
||||
@@ -122,7 +122,6 @@ secp256k1_elgamal_decrypt(
|
||||
secp256k1_pubkey const* c2,
|
||||
unsigned char const* privkey)
|
||||
{
|
||||
/* C90-compliant variable declarations */
|
||||
secp256k1_pubkey S, M, G_point, current_M, next_M;
|
||||
secp256k1_pubkey const* points_to_add[2];
|
||||
unsigned char c2_bytes[33], s_bytes[33], m_bytes[33], current_m_bytes[33];
|
||||
@@ -266,29 +265,60 @@ secp256k1_elgamal_subtract(
|
||||
return 1; // Success
|
||||
}
|
||||
|
||||
// Helper function to concatenate data for hashing
|
||||
static void
|
||||
build_hash_input(
|
||||
unsigned char* output_buffer,
|
||||
size_t buffer_size,
|
||||
unsigned char const* account_id, // 20 bytes
|
||||
unsigned char const* mpt_issuance_id // 24 bytes
|
||||
)
|
||||
{
|
||||
char const* domain_separator = "EncZero";
|
||||
size_t domain_len = strlen(domain_separator);
|
||||
size_t offset = 0;
|
||||
|
||||
// Ensure buffer is large enough (should be checked by caller if necessary)
|
||||
// Size = strlen("EncZero") + 20 + 24 = 7 + 20 + 24 = 51 bytes
|
||||
|
||||
memcpy(output_buffer + offset, domain_separator, domain_len);
|
||||
offset += domain_len;
|
||||
|
||||
memcpy(output_buffer + offset, account_id, 20);
|
||||
offset += 20;
|
||||
|
||||
memcpy(output_buffer + offset, mpt_issuance_id, 24);
|
||||
// offset += 24; // Final size is offset + 24
|
||||
}
|
||||
|
||||
// The canonical encrypted zero
|
||||
|
||||
int
|
||||
generate_canonical_encrypted_zero(
|
||||
secp256k1_context const* ctx,
|
||||
secp256k1_pubkey* enc_zero_c1,
|
||||
secp256k1_pubkey* enc_zero_c2,
|
||||
secp256k1_pubkey const* pubkey,
|
||||
char const* acct,
|
||||
char const* issuer,
|
||||
char const* curr)
|
||||
unsigned char const* account_id, // 20 bytes
|
||||
unsigned char const* mpt_issuance_id // 24 bytes
|
||||
)
|
||||
{
|
||||
unsigned char deterministic_scalar[32];
|
||||
char input_str[256];
|
||||
unsigned char hash_input[51]; // Size calculated above
|
||||
|
||||
/* 1. Create the input string for hashing */
|
||||
snprintf(input_str, sizeof(input_str), "EncZero%s%s%s", acct, issuer, curr);
|
||||
/* 1. Create the input buffer for hashing */
|
||||
build_hash_input(
|
||||
hash_input, sizeof(hash_input), account_id, mpt_issuance_id);
|
||||
|
||||
/* 2. Hash the string to create the deterministic scalar 'r' */
|
||||
/* 2. Hash the buffer to create the deterministic scalar 'r' */
|
||||
do
|
||||
{
|
||||
SHA256(
|
||||
(unsigned char*)input_str, strlen(input_str), deterministic_scalar);
|
||||
// Hash the concatenated bytes
|
||||
SHA256(hash_input, sizeof(hash_input), deterministic_scalar);
|
||||
|
||||
/* Note: If the hash output could be invalid (0 or >= n),
|
||||
* you might need to add a nonce/counter to hash_input
|
||||
* and re-hash in a loop until a valid scalar is produced. */
|
||||
} while (secp256k1_ec_seckey_verify(ctx, deterministic_scalar) != 1);
|
||||
|
||||
/* 3. Encrypt the amount 0 using the deterministic scalar */
|
||||
@@ -442,4 +472,42 @@ encryptAmount(uint64_t amt, Slice const& pubKeySlice)
|
||||
return buf;
|
||||
}
|
||||
|
||||
Buffer
|
||||
encryptCanonicalZeroAmount(
|
||||
Slice const& pubKeySlice,
|
||||
AccountID const& account,
|
||||
MPTID const& mptId)
|
||||
{
|
||||
Buffer buf(ecGamalEncryptedTotalLength);
|
||||
|
||||
// Allocate ciphertext placeholders
|
||||
secp256k1_pubkey c1, c2;
|
||||
|
||||
// Prepare a random blinding factor
|
||||
unsigned char blinding_factor[32];
|
||||
if (RAND_bytes(blinding_factor, 32) != 1)
|
||||
Throw<std::runtime_error>("Failed to generate random number");
|
||||
|
||||
secp256k1_pubkey pubKey;
|
||||
|
||||
std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength);
|
||||
|
||||
// Encrypt the amount
|
||||
if (!generate_canonical_encrypted_zero(
|
||||
secp256k1Context(),
|
||||
&c1,
|
||||
&c2,
|
||||
&pubKey,
|
||||
account.data(),
|
||||
mptId.data()))
|
||||
Throw<std::runtime_error>("Failed to encrypt amount");
|
||||
|
||||
// Serialize the ciphertext pair into the buffer
|
||||
if (!serializeEcPair(c1, c2, buf))
|
||||
Throw<std::runtime_error>(
|
||||
"Failed to serialize into 66 byte compressed format");
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -413,6 +413,50 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
|
||||
// todo: test well formed proof
|
||||
}
|
||||
|
||||
void
|
||||
testMergeInbox(FeatureBitset features)
|
||||
{
|
||||
testcase("Merge inbox");
|
||||
using namespace test::jtx;
|
||||
Env env{*this, features};
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob}});
|
||||
|
||||
mptAlice.create(
|
||||
{.ownerCount = 1,
|
||||
.holderCount = 0,
|
||||
.flags = tfMPTCanTransfer | tfMPTCanLock});
|
||||
|
||||
mptAlice.authorize({.account = bob});
|
||||
env.close();
|
||||
mptAlice.pay(alice, bob, 100);
|
||||
env.close();
|
||||
|
||||
mptAlice.generateKeyPair(alice);
|
||||
|
||||
mptAlice.set({.account = alice, .pubKey = mptAlice.getPubKey(alice)});
|
||||
|
||||
mptAlice.generateKeyPair(bob);
|
||||
|
||||
mptAlice.convert({
|
||||
.account = bob,
|
||||
.amt = 40,
|
||||
.proof = "123",
|
||||
.holderPubKey = mptAlice.getPubKey(bob),
|
||||
});
|
||||
|
||||
env.close();
|
||||
mptAlice.printMPT(bob);
|
||||
|
||||
mptAlice.mergeInbox({
|
||||
.account = bob,
|
||||
});
|
||||
|
||||
env.close();
|
||||
mptAlice.printMPT(bob);
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeats(FeatureBitset features)
|
||||
{
|
||||
@@ -420,6 +464,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
|
||||
testConvertPreflight(features);
|
||||
testConvertPreclaim(features);
|
||||
|
||||
testMergeInbox(features);
|
||||
testSetPreflight(features);
|
||||
}
|
||||
|
||||
|
||||
@@ -787,6 +787,43 @@ MPTTester::getDecryptedBalance(
|
||||
: 0;
|
||||
};
|
||||
|
||||
void
|
||||
MPTTester::mergeInbox(MPTMergeInbox const& arg)
|
||||
{
|
||||
Json::Value jv;
|
||||
if (arg.account)
|
||||
jv[sfAccount] = arg.account->human();
|
||||
else
|
||||
Throw<std::runtime_error>("Account not specified");
|
||||
if (arg.id)
|
||||
jv[sfMPTokenIssuanceID] = to_string(*arg.id);
|
||||
else
|
||||
{
|
||||
if (!id_)
|
||||
Throw<std::runtime_error>("MPT has not been created");
|
||||
jv[sfMPTokenIssuanceID] = to_string(*id_);
|
||||
}
|
||||
jv[sfTransactionType] = jss::ConfidentialMergeInbox;
|
||||
uint64_t preInboxBalance =
|
||||
getDecryptedBalance(*arg.account, HOLDER_ENCRYPTED_INBOX);
|
||||
uint64_t prevSpendingBalance =
|
||||
getDecryptedBalance(*arg.account, HOLDER_ENCRYPTED_SPENDING);
|
||||
|
||||
if (submit(arg, jv) == tesSUCCESS)
|
||||
{
|
||||
uint64_t postInboxBalance =
|
||||
getDecryptedBalance(*arg.account, HOLDER_ENCRYPTED_INBOX);
|
||||
uint64_t postSpendingBalance =
|
||||
getDecryptedBalance(*arg.account, HOLDER_ENCRYPTED_SPENDING);
|
||||
|
||||
env_.require(requireAny([&]() -> bool {
|
||||
return postSpendingBalance ==
|
||||
preInboxBalance + prevSpendingBalance &&
|
||||
postInboxBalance == 0;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
|
||||
@@ -166,6 +166,16 @@ struct MPTConvert
|
||||
std::optional<TER> err = std::nullopt;
|
||||
};
|
||||
|
||||
struct MPTMergeInbox
|
||||
{
|
||||
std::optional<Account> account = std::nullopt;
|
||||
std::optional<MPTID> id = std::nullopt;
|
||||
std::optional<std::uint32_t> ownerCount = std::nullopt;
|
||||
std::optional<std::uint32_t> holderCount = std::nullopt;
|
||||
std::optional<std::uint32_t> flags = std::nullopt;
|
||||
std::optional<TER> err = std::nullopt;
|
||||
};
|
||||
|
||||
class MPTTester
|
||||
{
|
||||
Env& env_;
|
||||
@@ -200,6 +210,9 @@ public:
|
||||
void
|
||||
convert(MPTConvert const& arg = MPTConvert{});
|
||||
|
||||
void
|
||||
mergeInbox(MPTMergeInbox const& arg = MPTMergeInbox{});
|
||||
|
||||
[[nodiscard]] bool
|
||||
checkDomainID(std::optional<uint256> expected) const;
|
||||
|
||||
|
||||
174
src/xrpld/app/tx/detail/ConfidentialConvertBack.cpp
Normal file
174
src/xrpld/app/tx/detail/ConfidentialConvertBack.cpp
Normal file
@@ -0,0 +1,174 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/tx/detail/ConfidentialConvertBack.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>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
NotTEC
|
||||
ConfidentialConvertBack::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[sfHolderEncryptedAmount].length() !=
|
||||
ecGamalEncryptedTotalLength ||
|
||||
ctx.tx[sfIssuerEncryptedAmount].length() != ecGamalEncryptedTotalLength)
|
||||
return temMALFORMED;
|
||||
|
||||
if (ctx.tx[sfMPTAmount] == 0)
|
||||
return temMALFORMED;
|
||||
|
||||
// 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
|
||||
ConfidentialConvertBack::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
// ensure that issuance exists
|
||||
auto const sleIssuance =
|
||||
ctx.view.read(keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID]));
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
if (sleIssuance->isFlag(lsfMPTNoConfidentialTransfer))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const sleMptoken = ctx.view.read(
|
||||
keylet::mptoken(ctx.tx[sfMPTokenIssuanceID], ctx.tx[sfAccount]));
|
||||
if (!sleMptoken)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
if (!sleMptoken->isFieldPresent(sfConfidentialBalanceSpending) ||
|
||||
!sleMptoken->isFieldPresent(sfHolderElGamalPublicKey))
|
||||
{
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
}
|
||||
|
||||
// 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->isFieldPresent(sfConfidentialOutstandingAmount) ||
|
||||
(*sleIssuance)[sfConfidentialOutstandingAmount] < ctx.tx[sfMPTAmount])
|
||||
{
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
ConfidentialConvertBack::doApply()
|
||||
{
|
||||
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
|
||||
|
||||
auto sleMptoken = view().peek(keylet::mptoken(mptIssuanceID, account_));
|
||||
if (!sleMptoken)
|
||||
return tecINTERNAL;
|
||||
|
||||
auto sleIssuance = view().peek(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
return tecINTERNAL;
|
||||
|
||||
auto const amtToConvertBack = ctx_.tx[sfMPTAmount];
|
||||
auto const amt = (*sleMptoken)[~sfMPTAmount].value_or(0);
|
||||
|
||||
(*sleMptoken)[sfMPTAmount] = amt + amtToConvertBack;
|
||||
(*sleIssuance)[sfConfidentialOutstandingAmount] =
|
||||
(*sleIssuance)[sfConfidentialOutstandingAmount] - amtToConvertBack;
|
||||
|
||||
// it's fine if it reaches max uint32, it just resets to 0
|
||||
(*sleMptoken)[sfConfidentialBalanceVersion] =
|
||||
(*sleMptoken)[~sfConfidentialBalanceVersion].value_or(0u) + 1u;
|
||||
|
||||
// todo: support homomophic sub
|
||||
// // homomorphically subtract holder's encrypted balance
|
||||
// {
|
||||
// Buffer res(ecGamalEncryptedTotalLength);
|
||||
// if (TER const ter = homomorphicSub(
|
||||
// (*sleMptoken)[sfConfidentialBalanceSpending],
|
||||
// ctx_.tx[sfHolderEncryptedAmount],
|
||||
// res);
|
||||
// isTesSuccess(ter))
|
||||
// return tecINTERNAL;
|
||||
|
||||
// (*sleMptoken)[sfConfidentialBalanceSpending] = res;
|
||||
// }
|
||||
|
||||
// // homomorphically subtract issuer's encrypted balance
|
||||
// {
|
||||
// Buffer res(ecGamalEncryptedTotalLength);
|
||||
// if (TER const ter = homomorphicSub(
|
||||
// (*sleMptoken)[sfIssuerEncryptedBalance],
|
||||
// ctx_.tx[sfIssuerEncryptedAmount],
|
||||
// res);
|
||||
// isTesSuccess(ter))
|
||||
// return tecINTERNAL;
|
||||
|
||||
// (*sleMptoken)[sfIssuerEncryptedBalance] = res;
|
||||
// }
|
||||
|
||||
view().update(sleIssuance);
|
||||
view().update(sleMptoken);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
48
src/xrpld/app/tx/detail/ConfidentialConvertBack.h
Normal file
48
src/xrpld/app/tx/detail/ConfidentialConvertBack.h
Normal file
@@ -0,0 +1,48 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TX_CONFIDENTIALCONVERTBACK_H_INCLUDED
|
||||
#define RIPPLE_TX_CONFIDENTIALCONVERTBACK_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/tx/detail/Transactor.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class ConfidentialConvertBack : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit ConfidentialConvertBack(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
91
src/xrpld/app/tx/detail/ConfidentialMergeInbox.cpp
Normal file
91
src/xrpld/app/tx/detail/ConfidentialMergeInbox.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/tx/detail/ConfidentialMergeInbox.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>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
NotTEC
|
||||
ConfidentialMergeInbox::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled(featureConfidentialTransfer))
|
||||
return temDISABLED;
|
||||
|
||||
// issuer cannot merge
|
||||
if (MPTIssue(ctx.tx[sfMPTokenIssuanceID]).getIssuer() == ctx.tx[sfAccount])
|
||||
return temMALFORMED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
ConfidentialMergeInbox::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const sleMptoken = ctx.view.read(
|
||||
keylet::mptoken(ctx.tx[sfMPTokenIssuanceID], ctx.tx[sfAccount]));
|
||||
if (!sleMptoken)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
if (!sleMptoken->isFieldPresent(sfConfidentialBalanceInbox) ||
|
||||
!sleMptoken->isFieldPresent(sfConfidentialBalanceSpending))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
ConfidentialMergeInbox::doApply()
|
||||
{
|
||||
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
|
||||
auto sleMptoken = view().peek(keylet::mptoken(mptIssuanceID, account_));
|
||||
if (!sleMptoken)
|
||||
return tecINTERNAL;
|
||||
|
||||
// homomorphically add holder's encrypted balance
|
||||
Buffer sum(ecGamalEncryptedTotalLength);
|
||||
if (TER const ter = homomorphicAdd(
|
||||
(*sleMptoken)[sfConfidentialBalanceSpending],
|
||||
(*sleMptoken)[sfConfidentialBalanceInbox],
|
||||
sum);
|
||||
!isTesSuccess(ter))
|
||||
return tecINTERNAL;
|
||||
|
||||
(*sleMptoken)[sfConfidentialBalanceSpending] = sum;
|
||||
|
||||
Buffer zeroEncyption;
|
||||
zeroEncyption = encryptCanonicalZeroAmount(
|
||||
(*sleMptoken)[sfHolderElGamalPublicKey], account_, mptIssuanceID);
|
||||
(*sleMptoken)[sfConfidentialBalanceInbox] = zeroEncyption;
|
||||
|
||||
// it's fine if it reaches max uint32, it just resets to 0
|
||||
(*sleMptoken)[sfConfidentialBalanceVersion] =
|
||||
(*sleMptoken)[~sfConfidentialBalanceVersion].value_or(0u) + 1u;
|
||||
|
||||
view().update(sleMptoken);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
48
src/xrpld/app/tx/detail/ConfidentialMergeInbox.h
Normal file
48
src/xrpld/app/tx/detail/ConfidentialMergeInbox.h
Normal file
@@ -0,0 +1,48 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TX_CONFIDENTIALMERGEINBOX_H_INCLUDED
|
||||
#define RIPPLE_TX_CONFIDENTIALMERGEINBOX_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/tx/detail/Transactor.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class ConfidentialMergeInbox : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit ConfidentialMergeInbox(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user