mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-15 20:08:34 +00:00
Compare commits
1 Commits
yinyi/mirr
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0a940a383 |
@@ -540,11 +540,6 @@ constexpr std::size_t kEcConvertBackProofLength =
|
||||
*/
|
||||
constexpr std::size_t kEcClawbackProofLength = SECP256K1_COMPACT_CLAWBACK_PROOF_SIZE;
|
||||
|
||||
/**
|
||||
* Length of compact equality proof.
|
||||
*/
|
||||
constexpr std::size_t kEcEqualityProofLength = 128;
|
||||
|
||||
/**
|
||||
* Extra base fee multiplier charged to confidential MPT transactions.
|
||||
*/
|
||||
|
||||
@@ -417,7 +417,6 @@ LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, mpt_issuance, ({
|
||||
{sfAuditorEncryptionKey, SoeOptional},
|
||||
{sfIssuerKeyEpoch, SoeOptional},
|
||||
{sfAuditorKeyEpoch, SoeOptional},
|
||||
{sfInitialIssuerEncryptionKey, SoeOptional},
|
||||
{sfConfidentialOutstandingAmount, SoeDefault},
|
||||
}))
|
||||
|
||||
@@ -439,7 +438,6 @@ LEDGER_ENTRY(ltMPTOKEN, 0x007f, MPToken, mptoken, ({
|
||||
{sfAuditorEncryptedBalance, SoeOptional},
|
||||
{sfIssuerKeyMirrorEpoch, SoeOptional},
|
||||
{sfAuditorKeyMirrorEpoch, SoeOptional},
|
||||
{sfIssuerMirrorEncryptionKey, SoeOptional},
|
||||
{sfHolderEncryptionKey, SoeOptional},
|
||||
}))
|
||||
|
||||
|
||||
@@ -329,8 +329,6 @@ TYPED_SFIELD(sfAuditorEncryptionKey, VL, 44)
|
||||
TYPED_SFIELD(sfAmountCommitment, VL, 45)
|
||||
TYPED_SFIELD(sfBalanceCommitment, VL, 46)
|
||||
TYPED_SFIELD(sfBytecode, VL, 47)
|
||||
TYPED_SFIELD(sfInitialIssuerEncryptionKey, VL, 48)
|
||||
TYPED_SFIELD(sfIssuerMirrorEncryptionKey, VL, 49)
|
||||
|
||||
// account (common)
|
||||
TYPED_SFIELD(sfAccount, ACCOUNT, 1)
|
||||
|
||||
@@ -1134,19 +1134,6 @@ TRANSACTION(ttSPONSORSHIP_SET, 91, SponsorshipSet,
|
||||
{sfRemainingOwnerCountDelta, SoeOptional},
|
||||
}))
|
||||
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/token/ConfidentialMPTMirrorUpdate.h>
|
||||
#endif
|
||||
TRANSACTION(ttCONFIDENTIAL_MPT_MIRROR_UPDATE, 92, ConfidentialMPTMirrorUpdate,
|
||||
({.delegable = Delegation::Delegable, .amendment = featureConfidentialMPTKeyRotation}),
|
||||
({
|
||||
{sfMPTokenIssuanceID, SoeRequired},
|
||||
{sfHolder, SoeOptional},
|
||||
{sfIssuerEncryptedAmount, SoeOptional},
|
||||
{sfAuditorEncryptedAmount, SoeOptional},
|
||||
{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
|
||||
|
||||
@@ -316,30 +316,6 @@ public:
|
||||
return this->sle_->isFieldPresent(sfAuditorKeyMirrorEpoch);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfIssuerMirrorEncryptionKey (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getIssuerMirrorEncryptionKey() const
|
||||
{
|
||||
if (hasIssuerMirrorEncryptionKey())
|
||||
return this->sle_->at(sfIssuerMirrorEncryptionKey);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfIssuerMirrorEncryptionKey is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasIssuerMirrorEncryptionKey() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfIssuerMirrorEncryptionKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfHolderEncryptionKey (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
@@ -565,17 +541,6 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfIssuerMirrorEncryptionKey (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
MPTokenBuilder&
|
||||
setIssuerMirrorEncryptionKey(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfIssuerMirrorEncryptionKey] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfHolderEncryptionKey (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
|
||||
@@ -399,30 +399,6 @@ public:
|
||||
return this->sle_->isFieldPresent(sfAuditorKeyEpoch);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfInitialIssuerEncryptionKey (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getInitialIssuerEncryptionKey() const
|
||||
{
|
||||
if (hasInitialIssuerEncryptionKey())
|
||||
return this->sle_->at(sfInitialIssuerEncryptionKey);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfInitialIssuerEncryptionKey is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasInitialIssuerEncryptionKey() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfInitialIssuerEncryptionKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfConfidentialOutstandingAmount (SoeDefault)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
@@ -694,17 +670,6 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfInitialIssuerEncryptionKey (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
MPTokenIssuanceBuilder&
|
||||
setInitialIssuerEncryptionKey(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfInitialIssuerEncryptionKey] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfConfidentialOutstandingAmount (SoeDefault)
|
||||
* @return Reference to this builder for method chaining.
|
||||
|
||||
@@ -1,266 +0,0 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBase.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
class ConfidentialMPTMirrorUpdateBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: ConfidentialMPTMirrorUpdate
|
||||
*
|
||||
* Type: ttCONFIDENTIAL_MPT_MIRROR_UPDATE (92)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureConfidentialMPTKeyRotation
|
||||
* Privileges: Privilege::NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use ConfidentialMPTMirrorUpdateBuilder to construct new transactions.
|
||||
*/
|
||||
class ConfidentialMPTMirrorUpdate : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttCONFIDENTIAL_MPT_MIRROR_UPDATE;
|
||||
|
||||
/**
|
||||
* @brief Construct a ConfidentialMPTMirrorUpdate transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit ConfidentialMPTMirrorUpdate(std::shared_ptr<STTx const> tx)
|
||||
: TransactionBase(std::move(tx))
|
||||
{
|
||||
// Verify transaction type
|
||||
if (tx_->getTxnType() != txType)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for ConfidentialMPTMirrorUpdate");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfMPTokenIssuanceID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT192::type::value_type
|
||||
getMPTokenIssuanceID() const
|
||||
{
|
||||
return this->tx_->at(sfMPTokenIssuanceID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfHolder (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
|
||||
getHolder() const
|
||||
{
|
||||
if (hasHolder())
|
||||
{
|
||||
return this->tx_->at(sfHolder);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfHolder is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasHolder() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfHolder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfIssuerEncryptedAmount (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getIssuerEncryptedAmount() const
|
||||
{
|
||||
if (hasIssuerEncryptedAmount())
|
||||
{
|
||||
return this->tx_->at(sfIssuerEncryptedAmount);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfIssuerEncryptedAmount is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasIssuerEncryptedAmount() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfIssuerEncryptedAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAuditorEncryptedAmount (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getAuditorEncryptedAmount() const
|
||||
{
|
||||
if (hasAuditorEncryptedAmount())
|
||||
{
|
||||
return this->tx_->at(sfAuditorEncryptedAmount);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfAuditorEncryptedAmount is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasAuditorEncryptedAmount() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfAuditorEncryptedAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfZKProof (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_VL::type::value_type
|
||||
getZKProof() const
|
||||
{
|
||||
return this->tx_->at(sfZKProof);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for ConfidentialMPTMirrorUpdate transactions.
|
||||
*
|
||||
* Provides a fluent interface for constructing transactions with method chaining.
|
||||
* Uses STObject internally for flexible transaction construction.
|
||||
* Inherits common field setters from TransactionBuilderBase.
|
||||
*/
|
||||
class ConfidentialMPTMirrorUpdateBuilder : public TransactionBuilderBase<ConfidentialMPTMirrorUpdateBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new ConfidentialMPTMirrorUpdateBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param mPTokenIssuanceID The sfMPTokenIssuanceID field value.
|
||||
* @param zKProof The sfZKProof field value.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
ConfidentialMPTMirrorUpdateBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::decay_t<typename SF_UINT192::type::value_type> const& mPTokenIssuanceID, std::decay_t<typename SF_VL::type::value_type> const& zKProof, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<ConfidentialMPTMirrorUpdateBuilder>(ttCONFIDENTIAL_MPT_MIRROR_UPDATE, account, sequence, fee)
|
||||
{
|
||||
setMPTokenIssuanceID(mPTokenIssuanceID);
|
||||
setZKProof(zKProof);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a ConfidentialMPTMirrorUpdateBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
ConfidentialMPTMirrorUpdateBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttCONFIDENTIAL_MPT_MIRROR_UPDATE)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for ConfidentialMPTMirrorUpdateBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transaction-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfMPTokenIssuanceID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
ConfidentialMPTMirrorUpdateBuilder&
|
||||
setMPTokenIssuanceID(std::decay_t<typename SF_UINT192::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMPTokenIssuanceID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfHolder (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
ConfidentialMPTMirrorUpdateBuilder&
|
||||
setHolder(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfHolder] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfIssuerEncryptedAmount (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
ConfidentialMPTMirrorUpdateBuilder&
|
||||
setIssuerEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfIssuerEncryptedAmount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAuditorEncryptedAmount (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
ConfidentialMPTMirrorUpdateBuilder&
|
||||
setAuditorEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfAuditorEncryptedAmount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfZKProof (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
ConfidentialMPTMirrorUpdateBuilder&
|
||||
setZKProof(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfZKProof] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the ConfidentialMPTMirrorUpdate wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
ConfidentialMPTMirrorUpdate
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return ConfidentialMPTMirrorUpdate{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
@@ -1,101 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Updates the encrypted mirror balances of a Confidential MPToken.
|
||||
*
|
||||
* @details
|
||||
* This transaction updates a single holder's mirrored confidential balances
|
||||
* (`sfIssuerEncryptedBalance` and/or `sfAuditorEncryptedBalance`) with the latest
|
||||
* ElGamal public keys defined on the `MPTokenIssuance`.
|
||||
*
|
||||
* It supports both issuer and holder self-migration modes, each mode supports multiple flows:
|
||||
* - Issuer mode: Submitted by the issuer.
|
||||
* 1. Issuer Key Rotation Migration: Re-encrypts the
|
||||
* holder's `sfIssuerEncryptedBalance` under the issuer's new ElGamal public key.
|
||||
*
|
||||
* 2. Auditor Key Rotation Migration: Re-encrypts the
|
||||
* holder's `sfAuditorEncryptedBalance` under the auditor's new ElGamal public key.
|
||||
*
|
||||
* 3. Simultaneous Rotation Migration: Updates both the issuer
|
||||
* and auditor encrypted balances in a single transaction to optimize network throughput.
|
||||
*
|
||||
* 4. Auditor Late-Registration Migration: When the issuer ElGamal
|
||||
* public key is already registered on the `MPTokenIssuance` object, the issuer can
|
||||
* register an auditor key at a later time through `MPTokenIssuanceSet`. Then the issuer uses this
|
||||
* flow to set the holder's initial `sfAuditorEncryptedBalance` on `MPToken` object.
|
||||
*
|
||||
* - Holder self-migration mode: Submitted by the holder. This is the recovery
|
||||
* path used when the issuer has permanently lost private key and can no longer perform
|
||||
* active re-encryption. The holder decrypts their own
|
||||
* `sfConfidentialBalanceSpending` with holder's private key to recover the balance and
|
||||
* re-encrypts it under the relevant new ElGamal public key(s).
|
||||
* @note All holder migration flows strictly require the holder's
|
||||
* `sfConfidentialBalanceInbox` to be canonically zero; the holder must run
|
||||
* `ConfidentialMPTMergeInbox` first so the spending balance reflects the
|
||||
* full balance.
|
||||
*
|
||||
* 5. Holder Issuer-Mirror Migration: Re-encrypts the holder's
|
||||
* `sfIssuerEncryptedBalance` under the issuer's new ElGamal public key.
|
||||
*
|
||||
* 6. Holder Auditor-Mirror Migration: Re-encrypts the holder's
|
||||
* `sfAuditorEncryptedBalance` under the auditor's new ElGamal public key, or
|
||||
* sets it for the first time when the auditor key was late-registered. Needed
|
||||
* only because the issuer, having lost sk_I, can no longer perform the auditor
|
||||
* re-encryption or initial registration in issuer mode (flows 2 and 4).
|
||||
*
|
||||
* 7. Simultaneous Holder Self-Migration: Updates both the issuer and auditor
|
||||
* encrypted balances in a single transaction (both keys have rotated).
|
||||
*/
|
||||
class ConfidentialMPTMirrorUpdate : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
|
||||
|
||||
explicit ConfidentialMPTMirrorUpdate(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static bool
|
||||
checkExtraFeatures(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static XRPAmount
|
||||
calculateBaseFee(ReadView const& view, STTx const& tx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,284 +0,0 @@
|
||||
#include <xrpl/tx/transactors/token/ConfidentialMPTMirrorUpdate.h>
|
||||
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/ConfidentialTransfer.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
bool
|
||||
ConfidentialMPTMirrorUpdate::checkExtraFeatures(PreflightContext const& ctx)
|
||||
{
|
||||
// Key rotation makes sense only when featureConfidentialTransfer is enabled.
|
||||
return ctx.rules.enabled(featureConfidentialTransfer);
|
||||
}
|
||||
|
||||
NotTEC
|
||||
ConfidentialMPTMirrorUpdate::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
auto const account = ctx.tx[sfAccount];
|
||||
auto const issuer = MPTIssue(ctx.tx[sfMPTokenIssuanceID]).getIssuer();
|
||||
auto const holder = ctx.tx[~sfHolder];
|
||||
bool const hasHolder = holder.has_value();
|
||||
|
||||
// The rotation mode is determined by the presence of the
|
||||
// Holder field: Holder present is issuer mode, Holder absent is
|
||||
// holder self-migration.
|
||||
if (hasHolder)
|
||||
{
|
||||
// Issuer mode: account must be the issuer
|
||||
if (account != issuer)
|
||||
return temMALFORMED;
|
||||
|
||||
if (account == *holder)
|
||||
return temMALFORMED;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Holder self-migration: the submitter is the holder, account must not be the issuer.
|
||||
if (account == issuer)
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
// At least one ciphertext will be updated.
|
||||
bool const hasIssuerAmount = ctx.tx.isFieldPresent(sfIssuerEncryptedAmount);
|
||||
bool const hasAuditorAmount = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount);
|
||||
if (!hasIssuerAmount && !hasAuditorAmount)
|
||||
return temMALFORMED;
|
||||
|
||||
// Check the length of the encrypted amounts. Length check is cheaper than format check so put
|
||||
// it before the format check.
|
||||
if (hasIssuerAmount && ctx.tx[sfIssuerEncryptedAmount].length() != kEcGamalEncryptedTotalLength)
|
||||
return temBAD_CIPHERTEXT;
|
||||
|
||||
if (hasAuditorAmount &&
|
||||
ctx.tx[sfAuditorEncryptedAmount].length() != kEcGamalEncryptedTotalLength)
|
||||
return temBAD_CIPHERTEXT;
|
||||
|
||||
// Check the encrypted amount formats. It is more expensive so put it at the end of preflight.
|
||||
if (hasIssuerAmount && !isValidCiphertext(ctx.tx[sfIssuerEncryptedAmount]))
|
||||
return temBAD_CIPHERTEXT;
|
||||
|
||||
if (hasAuditorAmount && !isValidCiphertext(ctx.tx[sfAuditorEncryptedAmount]))
|
||||
return temBAD_CIPHERTEXT;
|
||||
|
||||
// todo: check zkproof
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
ConfidentialMPTMirrorUpdate::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
return Transactor::calculateBaseFee(view, tx, kConfidentialFeeMultiplier);
|
||||
}
|
||||
|
||||
TER
|
||||
ConfidentialMPTMirrorUpdate::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
// Check if account exists
|
||||
auto const account = ctx.tx[sfAccount];
|
||||
if (!ctx.view.exists(keylet::account(account)))
|
||||
return terNO_ACCOUNT; // LCOV_EXCL_LINE
|
||||
|
||||
// The issuance must exist and have confidential balances enabled with a
|
||||
// registered issuer encryption key; otherwise there is no mirror to update.
|
||||
auto const mptIssuanceID = ctx.tx[sfMPTokenIssuanceID];
|
||||
auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// The issuance must have confidential balances enabled with a registered issuer encryption key.
|
||||
if (!sleIssuance->isFlag(lsfMPTCanHoldConfidentialBalance) ||
|
||||
!sleIssuance->isFieldPresent(sfIssuerEncryptionKey))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Sanity check: preflight already enforced the issuer holder combination
|
||||
// under different rotation modes.
|
||||
auto const holder = ctx.tx[~sfHolder];
|
||||
bool const hasHolder = holder.has_value();
|
||||
auto const issuer = sleIssuance->getAccountID(sfIssuer);
|
||||
if (hasHolder ? (issuer != account) : (issuer == account))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE(
|
||||
"xrpl::ConfidentialMPTMirrorUpdate::preclaim : invalid issuer holder combination");
|
||||
return tefINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// The holder is sfHolder in issuer mode and is sfAccount in holder mode.
|
||||
auto const holderID = hasHolder ? *holder : account;
|
||||
|
||||
// In issuer mode, the holder must exist. In holder mode, the account existence was checked
|
||||
// already.
|
||||
if (hasHolder && !ctx.view.exists(keylet::account(holderID)))
|
||||
return tecNO_TARGET;
|
||||
|
||||
// In either issuer or holder mode, check the existence of the MPToken object.
|
||||
auto const sleMptoken = ctx.view.read(keylet::mptoken(mptIssuanceID, holderID));
|
||||
if (!sleMptoken)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// The holder must already hold an issuer confidential balance.
|
||||
if (!sleMptoken->isFieldPresent(sfIssuerEncryptedBalance))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
bool const hasIssuerAmount = ctx.tx.isFieldPresent(sfIssuerEncryptedAmount);
|
||||
bool const hasAuditorAmount = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount);
|
||||
|
||||
// Migrating the auditor mirror requires the issuance to have a registered
|
||||
// auditor encryption key.
|
||||
if (hasAuditorAmount && !sleIssuance->isFieldPresent(sfAuditorEncryptionKey))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// A issuer mirror may only be re-encrypted while it is stale, reject if it is already current.
|
||||
if (hasIssuerAmount && isIssuerMirrorCurrent(*sleIssuance, *sleMptoken))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (hasAuditorAmount)
|
||||
{
|
||||
// An issuer-mode auditor-only migration: the issuer mirror must already be up to date.
|
||||
if (hasHolder && !hasIssuerAmount && !isIssuerMirrorCurrent(*sleIssuance, *sleMptoken))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// A auditor mirror may only be re-encrypted while it is stale, reject if it is already
|
||||
// current. isAuditorMirrorCurrent reports an absent auditor mirror as stale, which is what
|
||||
// allows an auditor-only migration to create one for the first time.
|
||||
if (isAuditorMirrorCurrent(*sleIssuance, *sleMptoken))
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// Holder self-migration re-encrypts the mirror from the holder's own
|
||||
// spending balance, which reflects the holder's full balance only once the
|
||||
// inbox has been merged into it. Require the inbox to be canonical zero,
|
||||
// i.e. ConfidentialMPTMergeInbox has already been applied.
|
||||
if (!hasHolder)
|
||||
{
|
||||
// Sanity check: a holder that already carries an issuer mirror
|
||||
// necessarily has a holder encryption key and a spending balance
|
||||
if (!sleMptoken->isFieldPresent(sfHolderEncryptionKey) ||
|
||||
!sleMptoken->isFieldPresent(sfConfidentialBalanceSpending))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE(
|
||||
"xrpl::ConfidentialMPTMirrorUpdate::preclaim : an issuer mirror implies a holder "
|
||||
"key and spending balance");
|
||||
return tefINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const expectedZeroInbox = encryptCanonicalZeroAmount(
|
||||
(*sleMptoken)[sfHolderEncryptionKey], holderID, mptIssuanceID);
|
||||
if (!expectedZeroInbox)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE(
|
||||
"xrpl::ConfidentialMPTMirrorUpdate::preclaim : canonical zero encryption cannot "
|
||||
"fail for an already-valid holder public key");
|
||||
return tefINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
bool const inboxIsCanonicalZero = sleMptoken->isFieldPresent(sfConfidentialBalanceInbox) &&
|
||||
Slice((*sleMptoken)[sfConfidentialBalanceInbox]) == Slice(*expectedZeroInbox);
|
||||
if (!inboxIsCanonicalZero)
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// todo: check zkproof
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
ConfidentialMPTMirrorUpdate::doApply()
|
||||
{
|
||||
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
|
||||
|
||||
auto const sleIssuance = view().read(keylet::mptokenIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE(
|
||||
"xrpl::ConfidentialMPTMirrorUpdate::doApply : preclaim already validated the "
|
||||
"issuance exists");
|
||||
return tefINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// The holderID is sfHolder in issuer mode and sfAccount in holder mode.
|
||||
auto const holder = ctx_.tx[~sfHolder];
|
||||
auto const holderID = holder.value_or(accountID_);
|
||||
|
||||
auto sleMptoken = view().peek(keylet::mptoken(mptIssuanceID, holderID));
|
||||
if (!sleMptoken)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE(
|
||||
"xrpl::ConfidentialMPTMirrorUpdate::doApply : preclaim already validated the "
|
||||
"MPToken exists");
|
||||
return tefINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// Re-encrypt the requested mirror(s) and advance the corresponding mirror
|
||||
// epoch to match the issuance key epoch. Only
|
||||
// set the epoch field when it is non-zero, matching the issuance convention
|
||||
// that an absent epoch means zero.
|
||||
if (ctx_.tx.isFieldPresent(sfIssuerEncryptedAmount))
|
||||
{
|
||||
(*sleMptoken)[sfIssuerEncryptedBalance] = ctx_.tx[sfIssuerEncryptedAmount];
|
||||
std::uint32_t const issuerKeyEpoch = (*sleIssuance)[~sfIssuerKeyEpoch].value_or(0);
|
||||
if (issuerKeyEpoch != 0)
|
||||
(*sleMptoken)[sfIssuerKeyMirrorEpoch] = issuerKeyEpoch;
|
||||
}
|
||||
|
||||
if (ctx_.tx.isFieldPresent(sfAuditorEncryptedAmount))
|
||||
{
|
||||
(*sleMptoken)[sfAuditorEncryptedBalance] = ctx_.tx[sfAuditorEncryptedAmount];
|
||||
std::uint32_t const auditorKeyEpoch = (*sleIssuance)[~sfAuditorKeyEpoch].value_or(0);
|
||||
if (auditorKeyEpoch != 0)
|
||||
(*sleMptoken)[sfAuditorKeyMirrorEpoch] = auditorKeyEpoch;
|
||||
}
|
||||
|
||||
view().update(sleMptoken);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
ConfidentialMPTMirrorUpdate::visitInvariantEntry(
|
||||
bool,
|
||||
std::shared_ptr<SLE const> const&,
|
||||
std::shared_ptr<SLE const> const&)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
ConfidentialMPTMirrorUpdate::finalizeInvariants(
|
||||
STTx const&,
|
||||
TER,
|
||||
XRPAmount,
|
||||
ReadView const&,
|
||||
beast::Journal const&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2191,6 +2191,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.account = bob,
|
||||
.dest = bob,
|
||||
.amt = 10,
|
||||
.proof = getTrivialSendProofHex(),
|
||||
.err = temMALFORMED,
|
||||
});
|
||||
|
||||
@@ -2897,22 +2898,6 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
auto& mptAlice = confEnv.mpt;
|
||||
|
||||
{
|
||||
// Bob has 60, tries to send 70. Invalid remaining balance.
|
||||
mptAlice.send({
|
||||
.account = bob,
|
||||
.dest = carol,
|
||||
.amt = 70,
|
||||
.err = tecBAD_PROOF,
|
||||
});
|
||||
|
||||
// Bob has 60, tries to send 61. Invalid remaining balance.
|
||||
mptAlice.send({
|
||||
.account = bob,
|
||||
.dest = carol,
|
||||
.amt = 61,
|
||||
.err = tecBAD_PROOF,
|
||||
});
|
||||
|
||||
// Bob has 60, sends 60. Remainder is exactly 0. Valid remaining balance.
|
||||
mptAlice.send({
|
||||
.account = bob,
|
||||
@@ -2933,12 +2918,12 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
});
|
||||
|
||||
// Bob has 100, tries to send 2^64-1. Invalid remaining balance.
|
||||
mptAlice.send({
|
||||
.account = bob,
|
||||
.dest = carol,
|
||||
.amt = std::numeric_limits<std::uint64_t>::max(),
|
||||
.err = tecBAD_PROOF,
|
||||
});
|
||||
{
|
||||
ConfidentialSendSetup const setup(
|
||||
mptAlice, bob, carol, alice, std::numeric_limits<std::uint64_t>::max());
|
||||
auto const forged = getForgedSendProof(mptAlice, env, bob, carol, setup);
|
||||
mptAlice.send(setup.sendArgs(bob, carol, forged, tecBAD_PROOF));
|
||||
}
|
||||
|
||||
// Bob sends 1, remaining 99.
|
||||
mptAlice.send({
|
||||
@@ -2947,14 +2932,6 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.amt = 1,
|
||||
.err = tesSUCCESS,
|
||||
});
|
||||
|
||||
// Bob sends 100, but only has 99. Invalid remaining balance.
|
||||
mptAlice.send({
|
||||
.account = bob,
|
||||
.dest = carol,
|
||||
.amt = 100,
|
||||
.err = tecBAD_PROOF,
|
||||
});
|
||||
}
|
||||
|
||||
// send when spending balance is 0 (key registered, inbox merged, but nothing converted)
|
||||
@@ -2971,18 +2948,13 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
|
||||
// Trying to send any amount with 0 spending balance must fail:
|
||||
// the range proof for < 0 is invalid.
|
||||
mptAlice2.send({
|
||||
.account = bob2,
|
||||
.dest = carol2,
|
||||
.amt = 1,
|
||||
.err = tecBAD_PROOF,
|
||||
});
|
||||
ConfidentialSendSetup const setup(mptAlice2, bob2, carol2, alice2, 1);
|
||||
auto const forged = getForgedSendProof(mptAlice2, env2, bob2, carol2, setup);
|
||||
mptAlice2.send(setup.sendArgs(bob2, carol2, forged, tecBAD_PROOF));
|
||||
|
||||
BEAST_EXPECT(
|
||||
mptAlice2.getDecryptedBalance(bob2, MPTTester::holderEncryptedSpending) == 0);
|
||||
}
|
||||
|
||||
// todo: test m exceeding range, require using scala and refactor
|
||||
}
|
||||
|
||||
/* The equality proof library and range proof library do not
|
||||
@@ -3462,7 +3434,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const convertBackContextHash =
|
||||
getConvertBackContextHash(bob.id(), mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
convertBackAmt,
|
||||
convertBackContextHash,
|
||||
@@ -3472,6 +3444,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
{
|
||||
json::Value jv;
|
||||
@@ -3483,7 +3457,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
jv[sfIssuerEncryptedAmount.jsonName] = strHex(convertBackIssuerCiphertext);
|
||||
jv[sfBlindingFactor.jsonName] = strHex(convertBackBlindingFactor);
|
||||
jv[sfBalanceCommitment.jsonName] = strHex(pedersenCommitment);
|
||||
jv[sfZKProof.jsonName] = strHex(proof);
|
||||
jv[sfZKProof.jsonName] = strHex(requireOptionalRef(proof, "Missing proof"));
|
||||
|
||||
env(jv, Ter(tesSUCCESS));
|
||||
}
|
||||
@@ -5283,7 +5257,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
Buffer const badPedersenCommitment =
|
||||
mptAlice.getPedersenCommitment(1, pcBlindingFactor);
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -5293,6 +5267,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5313,7 +5289,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -5323,6 +5299,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = generateBlindingFactor(), // wrong blinding factor
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5337,22 +5315,26 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
}
|
||||
|
||||
// Test 3: Proof generated with wrong balance value.
|
||||
// The proof claims balance=1 but the encrypted spending balance contains
|
||||
// the actual balance. Verification fails because the values don't match.
|
||||
// The sigma proof claims balance=20 but the pedersen commitment and
|
||||
// encrypted spending balance were built for the actual balance (40).
|
||||
// we cannot call mpt_get_convert_back_proof because it has client-side
|
||||
// verification.
|
||||
{
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
uint64_t constexpr claimedBalance = 20; // wrong: real balance is 40
|
||||
|
||||
auto const proof = getForgedConvertBackProof(
|
||||
mptAlice,
|
||||
bob,
|
||||
claimedBalance,
|
||||
spendingBalance,
|
||||
amt,
|
||||
contextHash,
|
||||
{
|
||||
.pedersenCommitment = pedersenCommitment,
|
||||
.amt = 1, // wrong balance
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
pedersenCommitment,
|
||||
encryptedSpendingBalance,
|
||||
pcBlindingFactor,
|
||||
contextHash);
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5375,7 +5357,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
Buffer const badPedersenCommitment =
|
||||
mptAlice.getPedersenCommitment(1, pcBlindingFactor);
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -5385,6 +5367,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5405,7 +5389,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
{
|
||||
uint256 const badContextHash{1};
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
badContextHash, // wrong context hash
|
||||
@@ -5415,6 +5399,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5434,7 +5420,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -5444,6 +5430,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5919,22 +5907,26 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
// linkage, and that the remaining balance is non-negative.
|
||||
|
||||
// Test 1: Proof generated with wrong balance value.
|
||||
// The sigma proof claims balance=1 but the spending balance contains the
|
||||
// actual balance. The compact proof's balance-linkage check fails.
|
||||
// The sigma proof claims balance=20 but the pedersen commitment and
|
||||
// encrypted spending balance were built for the actual balance (40).
|
||||
// we cannot call mpt_get_convert_back_proof because it has client-side
|
||||
// verification.
|
||||
{
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
uint64_t constexpr claimedBalance = 20; // wrong: real balance is 40
|
||||
|
||||
auto const proof = getForgedConvertBackProof(
|
||||
mptAlice,
|
||||
bob,
|
||||
claimedBalance,
|
||||
spendingBalance,
|
||||
amt,
|
||||
contextHash,
|
||||
{
|
||||
.pedersenCommitment = pedersenCommitment,
|
||||
.amt = 1, // wrong balance (actual balance is ~40)
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
pedersenCommitment,
|
||||
encryptedSpendingBalance,
|
||||
pcBlindingFactor,
|
||||
contextHash);
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5956,7 +5948,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -5966,6 +5958,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = generateBlindingFactor(), // wrong blinding factor
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5985,7 +5979,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
// makes the proof invalid for this transaction, preventing replay attacks.
|
||||
{
|
||||
uint256 const badContextHash{1};
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
badContextHash, // wrong context hash
|
||||
@@ -5995,6 +5989,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -6014,7 +6010,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -6024,6 +6020,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -6073,7 +6071,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
Buffer const bobCiphertext = mptAlice.encryptAmount(bob, amt, blindingFactor);
|
||||
auto const version = mptAlice.getMPTokenVersion(bob);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
makeContextHash(env, mptAlice, alice, bob, carol, version),
|
||||
@@ -6084,6 +6082,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
encryptedSpendingBalance, "Missing encrypted spending balance"),
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -6173,7 +6173,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHashA =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), currentSeq, version);
|
||||
|
||||
Buffer const proofA = mptAlice.getConvertBackProof(
|
||||
auto const proofA = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amtA,
|
||||
contextHashA,
|
||||
@@ -6183,6 +6183,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proofA.has_value()))
|
||||
return;
|
||||
|
||||
// Construct Transaction B with Amount m2 = 20 and attach Proof pi
|
||||
uint64_t const amtB = 20;
|
||||
@@ -6254,7 +6256,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const oldContextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), currentSeq, versionV);
|
||||
|
||||
Buffer const oldProof = mptAlice.getConvertBackProof(
|
||||
auto const oldProof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
oldContextHash,
|
||||
@@ -6264,6 +6266,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalanceV,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(oldProof.has_value()))
|
||||
return;
|
||||
|
||||
// Submit and verify failure
|
||||
mptAlice.convertBack({
|
||||
@@ -6326,7 +6330,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), currentVersion);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -6336,6 +6340,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = spendingBalEnc,
|
||||
.blindingFactor = pcBf,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
// Submit transaction with Divergent Ciphertexts
|
||||
// Holder Ciphertext encrypts 11. Issuer Ciphertext encrypts 10.
|
||||
@@ -6469,7 +6475,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), currentVersion);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
1,
|
||||
contextHash,
|
||||
@@ -6479,6 +6485,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = underflowedCt,
|
||||
.blindingFactor = pcBf,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -7741,7 +7749,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const convertBackCtxHash =
|
||||
getConvertBackContextHash(bob.id(), mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const convertBackProof = mptAlice.getConvertBackProof(
|
||||
auto const convertBackProof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
sendAmount,
|
||||
convertBackCtxHash,
|
||||
@@ -7751,14 +7759,18 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpending,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(convertBackProof.has_value()))
|
||||
return;
|
||||
|
||||
// Resize the convertBack proof to match the expected send proof
|
||||
// size so it passes preflight's size check and reaches the actual
|
||||
// ZK verification in doApply.
|
||||
auto const expectedSendSize = kEcSendProofLength;
|
||||
Buffer resizedProof(expectedSendSize);
|
||||
auto const copyLen = std::min(convertBackProof.size(), expectedSendSize);
|
||||
std::memcpy(resizedProof.data(), convertBackProof.data(), copyLen);
|
||||
Buffer const& convertBackProofRef =
|
||||
requireOptionalRef(convertBackProof, "Missing proof");
|
||||
auto const copyLen = std::min(convertBackProofRef.size(), expectedSendSize);
|
||||
std::memcpy(resizedProof.data(), convertBackProofRef.data(), copyLen);
|
||||
// Zero-pad the rest (if convertBack proof is shorter)
|
||||
if (copyLen < expectedSendSize)
|
||||
std::memset(resizedProof.data() + copyLen, 0, expectedSendSize - copyLen);
|
||||
|
||||
@@ -124,6 +124,87 @@ protected:
|
||||
return proof;
|
||||
}
|
||||
|
||||
// Forges a ConvertBack proof (compact sigma + single bulletproof) whose
|
||||
// sigma component claims claimedBalance (which may be wrong) while binding
|
||||
// to the real pedersen commitment and encrypted spending balance
|
||||
// ciphertext already on the ledger. The bulletproof component is built
|
||||
// from realBalance so it stays honest.
|
||||
// mpt_get_convert_back_proof does not allow to build a proof whose amount
|
||||
// exceeds the holder's claimed balance.
|
||||
static Buffer
|
||||
getForgedConvertBackProof(
|
||||
test::jtx::MPTTester& mpt,
|
||||
test::jtx::Account const& holder,
|
||||
uint64_t claimedBalance,
|
||||
uint64_t realBalance,
|
||||
uint64_t amt,
|
||||
Buffer const& pedersenCommitment,
|
||||
Buffer const& encryptedSpendingBalance,
|
||||
Buffer const& pcBlindingFactor,
|
||||
uint256 const& contextHash)
|
||||
{
|
||||
if (pedersenCommitment.size() != kCompressedEcPointLength)
|
||||
Throw<std::runtime_error>("getForgedConvertBackProof: bad pedersenCommitment length");
|
||||
if (encryptedSpendingBalance.size() != kEcGamalEncryptedTotalLength)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"getForgedConvertBackProof: bad encryptedSpendingBalance length");
|
||||
}
|
||||
if (amt > realBalance)
|
||||
Throw<std::runtime_error>("getForgedConvertBackProof: amt exceeds realBalance");
|
||||
|
||||
auto* const ctx = mpt_secp256k1_context();
|
||||
auto const holderPubKey = requireOptional(mpt.getPubKey(holder), "Missing holder pubkey");
|
||||
auto const holderPrivKey =
|
||||
requireOptional(mpt.getPrivKey(holder), "Missing holder privkey");
|
||||
|
||||
secp256k1_pubkey pkHolder;
|
||||
if (secp256k1_ec_pubkey_parse(
|
||||
ctx, &pkHolder, holderPubKey.data(), kCompressedEcPointLength) != 1)
|
||||
Throw<std::runtime_error>("Failed to parse holder's public key");
|
||||
|
||||
secp256k1_pubkey pcB;
|
||||
if (secp256k1_ec_pubkey_parse(
|
||||
ctx, &pcB, pedersenCommitment.data(), kCompressedEcPointLength) != 1)
|
||||
Throw<std::runtime_error>("Failed to parse pedersen commitment");
|
||||
|
||||
secp256k1_pubkey b1, b2;
|
||||
if (secp256k1_ec_pubkey_parse(
|
||||
ctx, &b1, encryptedSpendingBalance.data(), kCompressedEcPointLength) != 1 ||
|
||||
secp256k1_ec_pubkey_parse(
|
||||
ctx,
|
||||
&b2,
|
||||
encryptedSpendingBalance.data() + kCompressedEcPointLength,
|
||||
kCompressedEcPointLength) != 1)
|
||||
Throw<std::runtime_error>("Failed to parse balance ciphertext");
|
||||
|
||||
Buffer sigmaProof(SECP256K1_COMPACT_CONVERTBACK_PROOF_SIZE);
|
||||
if (secp256k1_compact_convertback_prove(
|
||||
ctx,
|
||||
sigmaProof.data(),
|
||||
claimedBalance,
|
||||
holderPrivKey.data(),
|
||||
pcBlindingFactor.data(),
|
||||
&pkHolder,
|
||||
&b1,
|
||||
&b2,
|
||||
&pcB,
|
||||
contextHash.data()) != 1)
|
||||
Throw<std::runtime_error>("Failed to generate convertback sigma proof");
|
||||
|
||||
auto const forgedBulletproof =
|
||||
getForgedSingleBulletproof(realBalance - amt, pcBlindingFactor, contextHash);
|
||||
|
||||
Buffer proof(kEcConvertBackProofLength);
|
||||
std::memcpy(proof.data(), sigmaProof.data(), SECP256K1_COMPACT_CONVERTBACK_PROOF_SIZE);
|
||||
std::memcpy(
|
||||
proof.data() + SECP256K1_COMPACT_CONVERTBACK_PROOF_SIZE,
|
||||
forgedBulletproof.data(),
|
||||
kEcSingleBulletproofLength);
|
||||
|
||||
return proof;
|
||||
}
|
||||
|
||||
// Get a bad ciphertext with valid structure but cryptographic invalid for
|
||||
// testing purposes. For preflight test purposes.
|
||||
static Buffer const&
|
||||
@@ -347,6 +428,111 @@ protected:
|
||||
}
|
||||
};
|
||||
|
||||
// Forges a ConfidentialMPTSend proof (compact sigma + double bulletproof)
|
||||
// for setup.sendAmount against setup's real balance commitment/ciphertext.
|
||||
// mpt_get_confidential_send_proof does not allow to build a proof whose amount
|
||||
// exceeds the sender's claimed balance.
|
||||
static Buffer
|
||||
getForgedSendProof(
|
||||
test::jtx::MPTTester& mpt,
|
||||
test::jtx::Env& env,
|
||||
test::jtx::Account const& sender,
|
||||
test::jtx::Account const& dest,
|
||||
ConfidentialSendSetup const& setup)
|
||||
{
|
||||
auto* const ctx = mpt_secp256k1_context();
|
||||
|
||||
secp256k1_pubkey c1;
|
||||
std::vector<secp256k1_pubkey> c2Vec(setup.recipients.size());
|
||||
std::vector<secp256k1_pubkey> pkVec(setup.recipients.size());
|
||||
for (std::size_t i = 0; i < setup.recipients.size(); ++i)
|
||||
{
|
||||
auto const& r = setup.recipients[i];
|
||||
if (i == 0 &&
|
||||
secp256k1_ec_pubkey_parse(
|
||||
ctx, &c1, r.encryptedAmount.data(), kCompressedEcPointLength) != 1)
|
||||
Throw<std::runtime_error>("Failed to parse C1");
|
||||
if (secp256k1_ec_pubkey_parse(
|
||||
ctx,
|
||||
&c2Vec[i],
|
||||
r.encryptedAmount.data() + kCompressedEcPointLength,
|
||||
kCompressedEcPointLength) != 1)
|
||||
Throw<std::runtime_error>("Failed to parse C2");
|
||||
if (secp256k1_ec_pubkey_parse(
|
||||
ctx, &pkVec[i], r.publicKey.data(), kCompressedEcPointLength) != 1)
|
||||
Throw<std::runtime_error>("Failed to parse recipient pubkey");
|
||||
}
|
||||
|
||||
secp256k1_pubkey pkSender, pcAmount, pcBalance, b1, b2;
|
||||
if (secp256k1_ec_pubkey_parse(
|
||||
ctx, &pkSender, setup.senderPubKey.data(), kCompressedEcPointLength) != 1 ||
|
||||
secp256k1_ec_pubkey_parse(
|
||||
ctx, &pcAmount, setup.amountCommitment.data(), kCompressedEcPointLength) != 1 ||
|
||||
secp256k1_ec_pubkey_parse(
|
||||
ctx, &pcBalance, setup.balanceCommitment.data(), kCompressedEcPointLength) != 1 ||
|
||||
secp256k1_ec_pubkey_parse(
|
||||
ctx, &b1, setup.prevEncryptedSpending.data(), kCompressedEcPointLength) != 1 ||
|
||||
secp256k1_ec_pubkey_parse(
|
||||
ctx,
|
||||
&b2,
|
||||
setup.prevEncryptedSpending.data() + kCompressedEcPointLength,
|
||||
kCompressedEcPointLength) != 1)
|
||||
Throw<std::runtime_error>("Failed to parse commitments/ciphertext");
|
||||
|
||||
Buffer const senderPrivKey =
|
||||
requireOptional(mpt.getPrivKey(sender), "Missing sender privkey");
|
||||
auto const ctxHash = getSendContextHash(
|
||||
sender.id(), mpt.issuanceID(), env.seq(sender), dest.id(), setup.version);
|
||||
|
||||
Buffer sigmaProof(SECP256K1_COMPACT_STANDARD_PROOF_SIZE);
|
||||
if (secp256k1_compact_standard_prove(
|
||||
ctx,
|
||||
sigmaProof.data(),
|
||||
setup.sendAmount,
|
||||
setup.prevSpending,
|
||||
setup.blindingFactor.data(),
|
||||
senderPrivKey.data(),
|
||||
setup.balanceBlindingFactor.data(),
|
||||
setup.recipients.size(),
|
||||
&c1,
|
||||
c2Vec.data(),
|
||||
pkVec.data(),
|
||||
&pcAmount,
|
||||
&pkSender,
|
||||
&pcBalance,
|
||||
&b1,
|
||||
&b2,
|
||||
ctxHash.data()) != 1)
|
||||
Throw<std::runtime_error>("Failed to generate sigma proof");
|
||||
|
||||
// Wraps (mod 2^64) for overdrafts, unlike the ledger's own homomorphic
|
||||
// commitment subtraction (mod the curve order) — that mismatch is
|
||||
// exactly what makes the forged proof fail verification.
|
||||
// Computed without a wrapping `uint64` subtract: Clang UBSan treats
|
||||
// unsigned overflow as fatal (see incrementConfidentialVersion).
|
||||
std::uint64_t const remaining = setup.sendAmount <= setup.prevSpending
|
||||
? setup.prevSpending - setup.sendAmount
|
||||
: ~setup.sendAmount + setup.prevSpending + 1;
|
||||
|
||||
Buffer negAmountBf(kEcBlindingFactorLength);
|
||||
Buffer remainingBf(kEcBlindingFactorLength);
|
||||
secp256k1_mpt_scalar_negate(negAmountBf.data(), setup.amountBlindingFactor.data());
|
||||
secp256k1_mpt_scalar_add(
|
||||
remainingBf.data(), setup.balanceBlindingFactor.data(), negAmountBf.data());
|
||||
|
||||
auto const forgedBulletproof = getForgedBulletproof(
|
||||
{setup.sendAmount, remaining}, {setup.amountBlindingFactor, remainingBf}, ctxHash);
|
||||
|
||||
Buffer combinedProof(kEcSendProofLength);
|
||||
std::memcpy(combinedProof.data(), sigmaProof.data(), SECP256K1_COMPACT_STANDARD_PROOF_SIZE);
|
||||
std::memcpy(
|
||||
combinedProof.data() + SECP256K1_COMPACT_STANDARD_PROOF_SIZE,
|
||||
forgedBulletproof.data(),
|
||||
kEcDoubleBulletproofLength);
|
||||
|
||||
return combinedProof;
|
||||
}
|
||||
|
||||
// Helper that wraps the boilerplate setup: Env + MPT creation, funding, key
|
||||
// generation, and seeding each holder with a confidential balance.
|
||||
// The caller supplies the issuer and any number of holders.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -78,7 +78,7 @@ fillFee(json::Value& jv, ReadView const& view)
|
||||
auto const txType = jv[jss::TransactionType].asString();
|
||||
if (txType == jss::ConfidentialMPTConvert || txType == jss::ConfidentialMPTConvertBack ||
|
||||
txType == jss::ConfidentialMPTSend || txType == jss::ConfidentialMPTMergeInbox ||
|
||||
txType == jss::ConfidentialMPTClawback || txType == jss::ConfidentialMPTMirrorUpdate)
|
||||
txType == jss::ConfidentialMPTClawback)
|
||||
{
|
||||
jv[jss::Fee] = to_string(base * (kConfidentialFeeMultiplier + 1));
|
||||
}
|
||||
|
||||
@@ -362,24 +362,6 @@ struct MPTConfidentialClawback
|
||||
std::optional<TER> err = std::nullopt;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Arguments for building a ConfidentialMPTMirrorUpdate test transaction.
|
||||
*/
|
||||
struct MPTMirrorUpdate
|
||||
{
|
||||
std::optional<Account> account = std::nullopt;
|
||||
std::optional<Account> holder = std::nullopt;
|
||||
std::optional<MPTID> id = std::nullopt;
|
||||
std::optional<Buffer> issuerEncryptedAmount = std::nullopt;
|
||||
std::optional<Buffer> auditorEncryptedAmount = std::nullopt;
|
||||
std::optional<Buffer> zkProof = std::nullopt;
|
||||
std::optional<XRPAmount> fee = std::nullopt;
|
||||
std::optional<std::uint32_t> flags = std::nullopt;
|
||||
std::optional<std::uint32_t> ownerCount = std::nullopt;
|
||||
std::optional<std::uint32_t> holderCount = std::nullopt;
|
||||
std::optional<TER> err = std::nullopt;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Stores the parameters that are exclusively used to generate a
|
||||
* Pedersen linkage proof.
|
||||
@@ -602,9 +584,6 @@ public:
|
||||
void
|
||||
confidentialClaw(MPTConfidentialClawback const& arg = MPTConfidentialClawback{});
|
||||
|
||||
void
|
||||
mirrorUpdate(MPTMirrorUpdate const& arg = MPTMirrorUpdate{});
|
||||
|
||||
[[nodiscard]] bool
|
||||
checkDomainID(std::optional<uint256> expected) const;
|
||||
|
||||
@@ -763,7 +742,7 @@ public:
|
||||
PedersenProofParams const& amountParams,
|
||||
PedersenProofParams const& balanceParams) const;
|
||||
|
||||
[[nodiscard]] Buffer
|
||||
[[nodiscard]] std::optional<Buffer>
|
||||
getConvertBackProof(
|
||||
Account const& holder,
|
||||
std::uint64_t const amount,
|
||||
@@ -862,15 +841,28 @@ private:
|
||||
[[nodiscard]] std::uint32_t
|
||||
getFlags(std::optional<Account> const& holder) const;
|
||||
|
||||
/**
|
||||
* @brief Sets sfMPTokenIssuanceID on jv, falling back to id_ if arg's id is
|
||||
* not set.
|
||||
*
|
||||
* @param jv The JSON object to set the field on.
|
||||
* @param id The explicit issuance ID override from the caller, if any.
|
||||
*/
|
||||
void
|
||||
setIssuanceIdField(json::Value& jv, std::optional<MPTID> const& id) const;
|
||||
|
||||
[[nodiscard]] std::uint32_t
|
||||
ticketOrSeq(
|
||||
std::optional<std::uint32_t> const& ticketSeq,
|
||||
std::optional<Account> const& account) const;
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
fillConversionCiphertexts(
|
||||
T const& arg,
|
||||
json::Value& jv,
|
||||
Buffer& holderCiphertext,
|
||||
Buffer& issuerCiphertext,
|
||||
std::optional<Buffer>& auditorCiphertext,
|
||||
Buffer& blindingFactor) const;
|
||||
Account const& account,
|
||||
std::uint64_t const amount) const;
|
||||
};
|
||||
|
||||
} // namespace xrpl::test::jtx
|
||||
|
||||
@@ -38,7 +38,6 @@ TEST(MPTokenIssuanceTests, BuilderSettersRoundTrip)
|
||||
auto const auditorEncryptionKeyValue = canonical_VL();
|
||||
auto const issuerKeyEpochValue = canonical_UINT32();
|
||||
auto const auditorKeyEpochValue = canonical_UINT32();
|
||||
auto const initialIssuerEncryptionKeyValue = canonical_VL();
|
||||
auto const confidentialOutstandingAmountValue = canonical_UINT64();
|
||||
|
||||
MPTokenIssuanceBuilder builder{
|
||||
@@ -62,7 +61,6 @@ TEST(MPTokenIssuanceTests, BuilderSettersRoundTrip)
|
||||
builder.setAuditorEncryptionKey(auditorEncryptionKeyValue);
|
||||
builder.setIssuerKeyEpoch(issuerKeyEpochValue);
|
||||
builder.setAuditorKeyEpoch(auditorKeyEpochValue);
|
||||
builder.setInitialIssuerEncryptionKey(initialIssuerEncryptionKeyValue);
|
||||
builder.setConfidentialOutstandingAmount(confidentialOutstandingAmountValue);
|
||||
|
||||
builder.setLedgerIndex(index);
|
||||
@@ -206,14 +204,6 @@ TEST(MPTokenIssuanceTests, BuilderSettersRoundTrip)
|
||||
EXPECT_TRUE(entry.hasAuditorKeyEpoch());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = initialIssuerEncryptionKeyValue;
|
||||
auto const actualOpt = entry.getInitialIssuerEncryptionKey();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfInitialIssuerEncryptionKey");
|
||||
EXPECT_TRUE(entry.hasInitialIssuerEncryptionKey());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = confidentialOutstandingAmountValue;
|
||||
auto const actualOpt = entry.getConfidentialOutstandingAmount();
|
||||
@@ -253,7 +243,6 @@ TEST(MPTokenIssuanceTests, BuilderFromSleRoundTrip)
|
||||
auto const auditorEncryptionKeyValue = canonical_VL();
|
||||
auto const issuerKeyEpochValue = canonical_UINT32();
|
||||
auto const auditorKeyEpochValue = canonical_UINT32();
|
||||
auto const initialIssuerEncryptionKeyValue = canonical_VL();
|
||||
auto const confidentialOutstandingAmountValue = canonical_UINT64();
|
||||
|
||||
auto sle = std::make_shared<SLE>(MPTokenIssuance::entryType, index);
|
||||
@@ -276,7 +265,6 @@ TEST(MPTokenIssuanceTests, BuilderFromSleRoundTrip)
|
||||
sle->at(sfAuditorEncryptionKey) = auditorEncryptionKeyValue;
|
||||
sle->at(sfIssuerKeyEpoch) = issuerKeyEpochValue;
|
||||
sle->at(sfAuditorKeyEpoch) = auditorKeyEpochValue;
|
||||
sle->at(sfInitialIssuerEncryptionKey) = initialIssuerEncryptionKeyValue;
|
||||
sle->at(sfConfidentialOutstandingAmount) = confidentialOutstandingAmountValue;
|
||||
|
||||
MPTokenIssuanceBuilder builderFromSle{sle};
|
||||
@@ -504,19 +492,6 @@ TEST(MPTokenIssuanceTests, BuilderFromSleRoundTrip)
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfAuditorKeyEpoch");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = initialIssuerEncryptionKeyValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getInitialIssuerEncryptionKey();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getInitialIssuerEncryptionKey();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfInitialIssuerEncryptionKey");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfInitialIssuerEncryptionKey");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = confidentialOutstandingAmountValue;
|
||||
|
||||
@@ -618,8 +593,6 @@ TEST(MPTokenIssuanceTests, OptionalFieldsReturnNullopt)
|
||||
EXPECT_FALSE(entry.getIssuerKeyEpoch().has_value());
|
||||
EXPECT_FALSE(entry.hasAuditorKeyEpoch());
|
||||
EXPECT_FALSE(entry.getAuditorKeyEpoch().has_value());
|
||||
EXPECT_FALSE(entry.hasInitialIssuerEncryptionKey());
|
||||
EXPECT_FALSE(entry.getInitialIssuerEncryptionKey().has_value());
|
||||
EXPECT_FALSE(entry.hasConfidentialOutstandingAmount());
|
||||
EXPECT_FALSE(entry.getConfidentialOutstandingAmount().has_value());
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ TEST(MPTokenTests, BuilderSettersRoundTrip)
|
||||
auto const auditorEncryptedBalanceValue = canonical_VL();
|
||||
auto const issuerKeyMirrorEpochValue = canonical_UINT32();
|
||||
auto const auditorKeyMirrorEpochValue = canonical_UINT32();
|
||||
auto const issuerMirrorEncryptionKeyValue = canonical_VL();
|
||||
auto const holderEncryptionKeyValue = canonical_VL();
|
||||
|
||||
MPTokenBuilder builder{
|
||||
@@ -54,7 +53,6 @@ TEST(MPTokenTests, BuilderSettersRoundTrip)
|
||||
builder.setAuditorEncryptedBalance(auditorEncryptedBalanceValue);
|
||||
builder.setIssuerKeyMirrorEpoch(issuerKeyMirrorEpochValue);
|
||||
builder.setAuditorKeyMirrorEpoch(auditorKeyMirrorEpochValue);
|
||||
builder.setIssuerMirrorEncryptionKey(issuerMirrorEncryptionKeyValue);
|
||||
builder.setHolderEncryptionKey(holderEncryptionKeyValue);
|
||||
|
||||
builder.setLedgerIndex(index);
|
||||
@@ -168,14 +166,6 @@ TEST(MPTokenTests, BuilderSettersRoundTrip)
|
||||
EXPECT_TRUE(entry.hasAuditorKeyMirrorEpoch());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = issuerMirrorEncryptionKeyValue;
|
||||
auto const actualOpt = entry.getIssuerMirrorEncryptionKey();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfIssuerMirrorEncryptionKey");
|
||||
EXPECT_TRUE(entry.hasIssuerMirrorEncryptionKey());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = holderEncryptionKeyValue;
|
||||
auto const actualOpt = entry.getHolderEncryptionKey();
|
||||
@@ -211,7 +201,6 @@ TEST(MPTokenTests, BuilderFromSleRoundTrip)
|
||||
auto const auditorEncryptedBalanceValue = canonical_VL();
|
||||
auto const issuerKeyMirrorEpochValue = canonical_UINT32();
|
||||
auto const auditorKeyMirrorEpochValue = canonical_UINT32();
|
||||
auto const issuerMirrorEncryptionKeyValue = canonical_VL();
|
||||
auto const holderEncryptionKeyValue = canonical_VL();
|
||||
|
||||
auto sle = std::make_shared<SLE>(MPToken::entryType, index);
|
||||
@@ -230,7 +219,6 @@ TEST(MPTokenTests, BuilderFromSleRoundTrip)
|
||||
sle->at(sfAuditorEncryptedBalance) = auditorEncryptedBalanceValue;
|
||||
sle->at(sfIssuerKeyMirrorEpoch) = issuerKeyMirrorEpochValue;
|
||||
sle->at(sfAuditorKeyMirrorEpoch) = auditorKeyMirrorEpochValue;
|
||||
sle->at(sfIssuerMirrorEncryptionKey) = issuerMirrorEncryptionKeyValue;
|
||||
sle->at(sfHolderEncryptionKey) = holderEncryptionKeyValue;
|
||||
|
||||
MPTokenBuilder builderFromSle{sle};
|
||||
@@ -409,19 +397,6 @@ TEST(MPTokenTests, BuilderFromSleRoundTrip)
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfAuditorKeyMirrorEpoch");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = issuerMirrorEncryptionKeyValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getIssuerMirrorEncryptionKey();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getIssuerMirrorEncryptionKey();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfIssuerMirrorEncryptionKey");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfIssuerMirrorEncryptionKey");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = holderEncryptionKeyValue;
|
||||
|
||||
@@ -515,8 +490,6 @@ TEST(MPTokenTests, OptionalFieldsReturnNullopt)
|
||||
EXPECT_FALSE(entry.getIssuerKeyMirrorEpoch().has_value());
|
||||
EXPECT_FALSE(entry.hasAuditorKeyMirrorEpoch());
|
||||
EXPECT_FALSE(entry.getAuditorKeyMirrorEpoch().has_value());
|
||||
EXPECT_FALSE(entry.hasIssuerMirrorEncryptionKey());
|
||||
EXPECT_FALSE(entry.getIssuerMirrorEncryptionKey().has_value());
|
||||
EXPECT_FALSE(entry.hasHolderEncryptionKey());
|
||||
EXPECT_FALSE(entry.getHolderEncryptionKey().has_value());
|
||||
}
|
||||
|
||||
@@ -1,255 +0,0 @@
|
||||
// Auto-generated unit tests for transaction ConfidentialMPTMirrorUpdate
|
||||
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <protocol_autogen/TestHelpers.h>
|
||||
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Seed.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol_autogen/transactions/ConfidentialMPTMirrorUpdate.h>
|
||||
#include <xrpl/protocol_autogen/transactions/AccountSet.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
// 1 & 4) Set fields via builder setters, build, then read them back via
|
||||
// wrapper getters. After build(), validate() should succeed.
|
||||
TEST(TransactionsConfidentialMPTMirrorUpdateTests, BuilderSettersRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testConfidentialMPTMirrorUpdate"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 1;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const mPTokenIssuanceIDValue = canonical_UINT192();
|
||||
auto const holderValue = canonical_ACCOUNT();
|
||||
auto const issuerEncryptedAmountValue = canonical_VL();
|
||||
auto const auditorEncryptedAmountValue = canonical_VL();
|
||||
auto const zKProofValue = canonical_VL();
|
||||
|
||||
ConfidentialMPTMirrorUpdateBuilder builder{
|
||||
accountValue,
|
||||
mPTokenIssuanceIDValue,
|
||||
zKProofValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
// Set optional fields
|
||||
builder.setHolder(holderValue);
|
||||
builder.setIssuerEncryptedAmount(issuerEncryptedAmountValue);
|
||||
builder.setAuditorEncryptedAmount(auditorEncryptedAmountValue);
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(tx.validate(reason)) << reason;
|
||||
|
||||
// Verify signing was applied
|
||||
EXPECT_FALSE(tx.getSigningPubKey().empty());
|
||||
EXPECT_TRUE(tx.hasTxnSignature());
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(tx.getAccount(), accountValue);
|
||||
EXPECT_EQ(tx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(tx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = mPTokenIssuanceIDValue;
|
||||
auto const actual = tx.getMPTokenIssuanceID();
|
||||
expectEqualField(expected, actual, "sfMPTokenIssuanceID");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = zKProofValue;
|
||||
auto const actual = tx.getZKProof();
|
||||
expectEqualField(expected, actual, "sfZKProof");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
{
|
||||
auto const& expected = holderValue;
|
||||
auto const actualOpt = tx.getHolder();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfHolder should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfHolder");
|
||||
EXPECT_TRUE(tx.hasHolder());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = issuerEncryptedAmountValue;
|
||||
auto const actualOpt = tx.getIssuerEncryptedAmount();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfIssuerEncryptedAmount should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfIssuerEncryptedAmount");
|
||||
EXPECT_TRUE(tx.hasIssuerEncryptedAmount());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = auditorEncryptedAmountValue;
|
||||
auto const actualOpt = tx.getAuditorEncryptedAmount();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfAuditorEncryptedAmount should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfAuditorEncryptedAmount");
|
||||
EXPECT_TRUE(tx.hasAuditorEncryptedAmount());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper,
|
||||
// and verify all fields match.
|
||||
TEST(TransactionsConfidentialMPTMirrorUpdateTests, BuilderFromStTxRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testConfidentialMPTMirrorUpdateFromTx"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 2;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const mPTokenIssuanceIDValue = canonical_UINT192();
|
||||
auto const holderValue = canonical_ACCOUNT();
|
||||
auto const issuerEncryptedAmountValue = canonical_VL();
|
||||
auto const auditorEncryptedAmountValue = canonical_VL();
|
||||
auto const zKProofValue = canonical_VL();
|
||||
|
||||
// Build an initial transaction
|
||||
ConfidentialMPTMirrorUpdateBuilder initialBuilder{
|
||||
accountValue,
|
||||
mPTokenIssuanceIDValue,
|
||||
zKProofValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
initialBuilder.setHolder(holderValue);
|
||||
initialBuilder.setIssuerEncryptedAmount(issuerEncryptedAmountValue);
|
||||
initialBuilder.setAuditorEncryptedAmount(auditorEncryptedAmountValue);
|
||||
|
||||
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
||||
|
||||
// Create builder from existing STTx
|
||||
ConfidentialMPTMirrorUpdateBuilder builderFromTx{initialTx.getSTTx()};
|
||||
|
||||
auto rebuiltTx = builderFromTx.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(rebuiltTx.validate(reason)) << reason;
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(rebuiltTx.getAccount(), accountValue);
|
||||
EXPECT_EQ(rebuiltTx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(rebuiltTx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = mPTokenIssuanceIDValue;
|
||||
auto const actual = rebuiltTx.getMPTokenIssuanceID();
|
||||
expectEqualField(expected, actual, "sfMPTokenIssuanceID");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = zKProofValue;
|
||||
auto const actual = rebuiltTx.getZKProof();
|
||||
expectEqualField(expected, actual, "sfZKProof");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
{
|
||||
auto const& expected = holderValue;
|
||||
auto const actualOpt = rebuiltTx.getHolder();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfHolder should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfHolder");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = issuerEncryptedAmountValue;
|
||||
auto const actualOpt = rebuiltTx.getIssuerEncryptedAmount();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfIssuerEncryptedAmount should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfIssuerEncryptedAmount");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = auditorEncryptedAmountValue;
|
||||
auto const actualOpt = rebuiltTx.getAuditorEncryptedAmount();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfAuditorEncryptedAmount should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfAuditorEncryptedAmount");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 3) Verify wrapper throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsConfidentialMPTMirrorUpdateTests, WrapperThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongType"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(ConfidentialMPTMirrorUpdate{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 4) Verify builder throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsConfidentialMPTMirrorUpdateTests, BuilderThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongTypeBuilder"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(ConfidentialMPTMirrorUpdateBuilder{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 5) Build with only required fields and verify optional fields return nullopt.
|
||||
TEST(TransactionsConfidentialMPTMirrorUpdateTests, OptionalFieldsReturnNullopt)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testConfidentialMPTMirrorUpdateNullopt"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 3;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific required field values
|
||||
auto const mPTokenIssuanceIDValue = canonical_UINT192();
|
||||
auto const zKProofValue = canonical_VL();
|
||||
|
||||
ConfidentialMPTMirrorUpdateBuilder builder{
|
||||
accountValue,
|
||||
mPTokenIssuanceIDValue,
|
||||
zKProofValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
// Do NOT set optional fields
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
// Verify optional fields are not present
|
||||
EXPECT_FALSE(tx.hasHolder());
|
||||
EXPECT_FALSE(tx.getHolder().has_value());
|
||||
EXPECT_FALSE(tx.hasIssuerEncryptedAmount());
|
||||
EXPECT_FALSE(tx.getIssuerEncryptedAmount().has_value());
|
||||
EXPECT_FALSE(tx.hasAuditorEncryptedAmount());
|
||||
EXPECT_FALSE(tx.getAuditorEncryptedAmount().has_value());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user