update sfBlindingFactor to type uint256 and refactor helper functions to return std::optional

This commit is contained in:
Shawn Xie
2026-03-19 14:04:03 -04:00
committed by GitHub
parent 9f4cf28aea
commit a43cf94ff7
10 changed files with 108 additions and 111 deletions

View File

@@ -25,8 +25,9 @@ namespace xrpl {
*/
struct ConfidentialRecipient
{
Slice publicKey; ///< The recipient's ElGamal public key (64 bytes).
Slice encryptedAmount; ///< The encrypted amount ciphertext (128 bytes).
Slice publicKey; ///< The recipient's ElGamal public key (size=xrpl::ecPubKeyLength).
Slice encryptedAmount; ///< The encrypted amount ciphertext
///< (size=xrpl::ecGamalEncryptedTotalLength).
};
/// Holds two secp256k1 public key components representing an ElGamal ciphertext (C1, C2).
@@ -73,8 +74,8 @@ addCommonZKPFields(
Serializer& s,
std::uint16_t txType,
AccountID const& account,
std::uint32_t sequence,
uint192 const& issuanceID);
uint192 const& issuanceID,
std::uint32_t sequence);
/**
* @brief Generates the context hash for ConfidentialMPTSend transactions.
@@ -227,9 +228,10 @@ homomorphicSubtract(Slice const& a, Slice const& b);
* using the provided blinding factor r.
*
* @param amt The plaintext amount to encrypt.
* @param pubKeySlice The recipient's ElGamal public key (64 bytes).
* @param blindingFactor The 32-byte randomness used as blinding factor r.
* @return The 66-byte ciphertext, or std::nullopt on failure.
* @param pubKeySlice The recipient's ElGamal public key (size=xrpl::ecPubKeyLength).
* @param blindingFactor The randomness used as blinding factor r
* (size=xrpl::ecBlindingFactorLength).
* @return The ciphertext (size=xrpl::ecGamalEncryptedTotalLength), or std::nullopt on failure.
*/
std::optional<Buffer>
encryptAmount(uint64_t const amt, Slice const& pubKeySlice, Slice const& blindingFactor);
@@ -240,10 +242,11 @@ encryptAmount(uint64_t const amt, Slice const& pubKeySlice, Slice const& blindin
* Creates a deterministic encryption of zero that is unique to the account
* and MPT issuance. Used to initialize confidential balance fields.
*
* @param pubKeySlice The holder's ElGamal public key (64 bytes).
* @param pubKeySlice The holder's ElGamal public key (size=xrpl::ecPubKeyLength).
* @param account The account ID of the token holder.
* @param mptId The MPToken Issuance ID.
* @return The 66-byte canonical zero ciphertext, or std::nullopt on failure.
* @return The canonical zero ciphertext (size=xrpl::ecGamalEncryptedTotalLength), or std::nullopt
* on failure.
*/
std::optional<Buffer>
encryptCanonicalZeroAmount(Slice const& pubKeySlice, AccountID const& account, MPTID const& mptId);
@@ -254,8 +257,8 @@ encryptCanonicalZeroAmount(Slice const& pubKeySlice, AccountID const& account, M
* Proves that the submitter knows the secret key corresponding to the
* provided public key, without revealing the secret key itself.
*
* @param pubKeySlice The ElGamal public key (64 bytes).
* @param proofSlice The Schnorr proof (65 bytes).
* @param pubKeySlice The ElGamal public key (size=xrpl::ecPubKeyLength).
* @param proofSlice The Schnorr proof (size=xrpl::ecSchnorrProofLength).
* @param contextHash The 256-bit context hash binding the proof.
* @return tesSUCCESS if valid, or an error code otherwise.
*/
@@ -269,9 +272,9 @@ verifySchnorrProof(Slice const& pubKeySlice, Slice const& proofSlice, uint256 co
* ciphertext was correctly constructed using ElGamal encryption.
*
* @param amount The revealed plaintext amount.
* @param blindingFactor The 32-byte blinding factor used in encryption.
* @param pubKeySlice The recipient's ElGamal public key (64 bytes).
* @param ciphertext The ciphertext to verify (66 bytes).
* @param blindingFactor The blinding factor used in encryption (size=xrpl::ecBlindingFactorLength).
* @param pubKeySlice The recipient's ElGamal public key (size=xrpl::ecPubKeyLength).
* @param ciphertext The ciphertext to verify (size=xrpl::ecGamalEncryptedTotalLength).
* @return tesSUCCESS if the encryption is valid, or an error code otherwise.
*/
TER
@@ -302,7 +305,8 @@ checkEncryptedAmountFormat(STObject const& object);
* issuer, and optionally the auditor using their respective public keys.
*
* @param amount The revealed plaintext amount.
* @param blindingFactor The 32-byte blinding factor used in all encryptions.
* @param blindingFactor The blinding factor used in all encryptions
* (size=xrpl::ecBlindingFactorLength).
* @param holder The holder's public key and encrypted amount.
* @param issuer The issuer's public key and encrypted amount.
* @param auditor Optional auditor's public key and encrypted amount.
@@ -331,6 +335,22 @@ getConfidentialRecipientCount(bool hasAuditor)
return hasAuditor ? 4 : 3;
}
/**
* @brief Returns the size of a multi-ciphertext equality proof.
*
* Computes the byte size required for a zero-knowledge proof that demonstrates
* multiple ciphertexts encrypt the same plaintext value. The size depends on
* the number of recipients.
*
* @param nRecipients The number of recipients (typically 3 or 4).
* @return The proof size in bytes.
*/
inline std::size_t
getEqualityProofSize(std::size_t nRecipients)
{
return secp256k1_mpt_proof_equality_shared_r_size(nRecipients);
}
/**
* @brief Verifies a multi-ciphertext equality proof.
*
@@ -462,11 +482,10 @@ verifyAggregatedBulletproof(
*
* @param balanceCommitment The compressed Pedersen commitment to the balance (33 bytes).
* @param amountCommitment The compressed Pedersen commitment to the amount (33 bytes).
* @param out Output buffer for the resulting remainder commitment (33 bytes).
* @return tesSUCCESS on success, tecINTERNAL on failure.
* @return The remainder commitment (33 bytes), or std::nullopt on failure.
*/
TER
computeSendRemainder(Slice const& balanceCommitment, Slice const& amountCommitment, Buffer& out);
std::optional<Buffer>
computeSendRemainder(Slice const& balanceCommitment, Slice const& amountCommitment);
/**
* @brief Computes the remainder commitment for ConvertBack.
@@ -476,9 +495,8 @@ computeSendRemainder(Slice const& balanceCommitment, Slice const& amountCommitme
*
* @param commitment The compressed Pedersen commitment (33 bytes).
* @param amount The amount to subtract (must be non-zero).
* @param out Output buffer for the resulting commitment (33 bytes).
* @return tesSUCCESS on success, tecINTERNAL on failure or if amount is 0.
* @return The remainder commitment (33 bytes), or std::nullopt on failure or if amount is 0.
*/
TER
computeConvertBackRemainder(Slice const& commitment, uint64_t amount, Buffer& out);
std::optional<Buffer>
computeConvertBackRemainder(Slice const& commitment, uint64_t amount);
} // namespace xrpl

View File

@@ -206,6 +206,7 @@ TYPED_SFIELD(sfParentBatchID, UINT256, 36)
TYPED_SFIELD(sfLoanBrokerID, UINT256, 37,
SField::sMD_PseudoAccount | SField::sMD_Default)
TYPED_SFIELD(sfLoanID, UINT256, 38)
TYPED_SFIELD(sfBlindingFactor, UINT256, 39)
// number (common)
TYPED_SFIELD(sfNumber, NUMBER, 1)
@@ -312,9 +313,8 @@ TYPED_SFIELD(sfDestinationEncryptedAmount, VL, 41)
TYPED_SFIELD(sfAuditorEncryptedBalance, VL, 42)
TYPED_SFIELD(sfAuditorEncryptedAmount, VL, 43)
TYPED_SFIELD(sfAuditorEncryptionKey, VL, 44)
TYPED_SFIELD(sfBlindingFactor, VL, 45)
TYPED_SFIELD(sfAmountCommitment, VL, 46)
TYPED_SFIELD(sfBalanceCommitment, VL, 47)
TYPED_SFIELD(sfAmountCommitment, VL, 45)
TYPED_SFIELD(sfBalanceCommitment, VL, 46)
// account (common)
TYPED_SFIELD(sfAccount, ACCOUNT, 1)

View File

@@ -32,6 +32,9 @@ namespace xrpl {
*/
class ConfidentialMPTSend : public Transactor
{
/// Size of two Pedersen linkage proofs (amount + balance)
static constexpr std::size_t doublePedersenProofLength = 2 * ecPedersenProofLength;
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};

View File

@@ -371,7 +371,7 @@ verifyMultiCiphertextEqualityProof(
if (recipients.size() != nRecipients)
return tecINTERNAL; // LCOV_EXCL_LINE
if (proof.size() != secp256k1_mpt_proof_equality_shared_r_size(nRecipients))
if (proof.size() != getEqualityProofSize(nRecipients))
return tecINTERNAL; // LCOV_EXCL_LINE
auto const ctx = secp256k1Context();
@@ -700,12 +700,12 @@ verifyAggregatedBulletproof(
return tesSUCCESS;
}
TER
computeSendRemainder(Slice const& balanceCommitment, Slice const& amountCommitment, Buffer& out)
std::optional<Buffer>
computeSendRemainder(Slice const& balanceCommitment, Slice const& amountCommitment)
{
if (balanceCommitment.size() != ecPedersenCommitmentLength ||
amountCommitment.size() != ecPedersenCommitmentLength)
return tecINTERNAL;
return std::nullopt;
auto const ctx = secp256k1Context();
@@ -714,7 +714,7 @@ computeSendRemainder(Slice const& balanceCommitment, Slice const& amountCommitme
ctx, &pcBalance, balanceCommitment.data(), ecPedersenCommitmentLength);
res != 1)
{
return tecINTERNAL;
return std::nullopt;
}
secp256k1_pubkey pcAmount;
@@ -722,13 +722,13 @@ computeSendRemainder(Slice const& balanceCommitment, Slice const& amountCommitme
ctx, &pcAmount, amountCommitment.data(), ecPedersenCommitmentLength);
res != 1)
{
return tecINTERNAL;
return std::nullopt;
}
// Negate PC_amount point to get -PC_amount
if (auto res = secp256k1_ec_pubkey_negate(ctx, &pcAmount); res != 1)
{
return tecINTERNAL;
return std::nullopt;
}
// Compute pcRem = pcBalance + (-pcAmount)
@@ -736,27 +736,28 @@ computeSendRemainder(Slice const& balanceCommitment, Slice const& amountCommitme
secp256k1_pubkey pcRem;
if (auto res = secp256k1_ec_pubkey_combine(ctx, &pcRem, summands, 2); res != 1)
{
return tecINTERNAL;
return std::nullopt;
}
// Serialize result to compressed format
Buffer out;
out.alloc(ecPedersenCommitmentLength);
size_t outLen = ecPedersenCommitmentLength;
if (auto res = secp256k1_ec_pubkey_serialize(
ctx, out.data(), &outLen, &pcRem, SECP256K1_EC_COMPRESSED);
res != 1)
res != 1 || outLen != ecPedersenCommitmentLength)
{
return tecINTERNAL;
return std::nullopt;
}
return tesSUCCESS;
return out;
}
TER
computeConvertBackRemainder(Slice const& commitment, uint64_t amount, Buffer& out)
std::optional<Buffer>
computeConvertBackRemainder(Slice const& commitment, uint64_t amount)
{
if (commitment.size() != ecPedersenCommitmentLength || amount == 0)
return tecINTERNAL; // LCOV_EXCL_LINE
return std::nullopt; // LCOV_EXCL_LINE
auto const ctx = secp256k1Context();
@@ -766,7 +767,7 @@ computeConvertBackRemainder(Slice const& commitment, uint64_t amount, Buffer& ou
ctx, &pcBalance, commitment.data(), ecPedersenCommitmentLength);
res != 1)
{
return tecINTERNAL; // LCOV_EXCL_LINE
return std::nullopt; // LCOV_EXCL_LINE
}
// Convert amount to 32-byte big-endian scalar
@@ -778,13 +779,13 @@ computeConvertBackRemainder(Slice const& commitment, uint64_t amount, Buffer& ou
secp256k1_pubkey mG;
if (auto res = secp256k1_ec_pubkey_create(ctx, &mG, mScalar); res != 1)
{
return tecINTERNAL; // LCOV_EXCL_LINE
return std::nullopt; // LCOV_EXCL_LINE
}
// Negate mG to get -mG
if (auto res = secp256k1_ec_pubkey_negate(ctx, &mG); res != 1)
{
return tecINTERNAL; // LCOV_EXCL_LINE
return std::nullopt; // LCOV_EXCL_LINE
}
// Compute pcRem = pcBalance + (-mG)
@@ -792,19 +793,20 @@ computeConvertBackRemainder(Slice const& commitment, uint64_t amount, Buffer& ou
secp256k1_pubkey pcRem;
if (auto res = secp256k1_ec_pubkey_combine(ctx, &pcRem, summands, 2); res != 1)
{
return tecINTERNAL; // LCOV_EXCL_LINE
return std::nullopt; // LCOV_EXCL_LINE
}
// Serialize result to compressed format
Buffer out;
out.alloc(ecPedersenCommitmentLength);
size_t outLen = ecPedersenCommitmentLength;
if (auto res = secp256k1_ec_pubkey_serialize(
ctx, out.data(), &outLen, &pcRem, SECP256K1_EC_COMPRESSED);
res != 1 || outLen != ecPedersenCommitmentLength)
{
return tecINTERNAL; // LCOV_EXCL_LINE
return std::nullopt; // LCOV_EXCL_LINE
}
return tesSUCCESS;
return out;
}
} // namespace xrpl

View File

@@ -56,7 +56,7 @@ ConfidentialMPTClawback::preclaim(PreclaimContext const& ctx)
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
// Sanity check: issuer must be the same as account
// Sanity check: account must be the same as issuer
if (sleIssuance->getAccountID(sfIssuer) != account)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -123,7 +123,7 @@ ConfidentialMPTClawback::doApply()
// Set holder's confidential balances to encrypted zero
(*sleHolderMPToken)[sfConfidentialBalanceInbox] = *encZeroForHolder;
(*sleHolderMPToken)[sfConfidentialBalanceSpending] = *encZeroForHolder;
(*sleHolderMPToken)[sfConfidentialBalanceSpending] = std::move(*encZeroForHolder);
(*sleHolderMPToken)[sfIssuerEncryptedBalance] = std::move(*encZeroForIssuer);
incrementConfidentialVersion(*sleHolderMPToken);

View File

@@ -22,9 +22,6 @@ ConfidentialMPTConvert::preflight(PreflightContext const& ctx)
if (ctx.tx[sfMPTAmount] > maxMPTokenAmount)
return temBAD_AMOUNT;
if (ctx.tx[sfBlindingFactor].size() != ecBlindingFactorLength)
return temMALFORMED;
if (ctx.tx.isFieldPresent(sfHolderEncryptionKey))
{
if (!isValidCompressedECPoint(ctx.tx[sfHolderEncryptionKey]))
@@ -35,13 +32,14 @@ ConfidentialMPTConvert::preflight(PreflightContext const& ctx)
if (!ctx.tx.isFieldPresent(sfZKProof))
return temMALFORMED;
// verify schnorr proof length when registerring holder ec public key
// verify schnorr proof length when registering holder ec public key
if (ctx.tx[sfZKProof].size() != ecSchnorrProofLength)
return temMALFORMED;
}
else
{
// zkp should not be present if public key was already set
// Either both sfHolderEncryptionKey and sfZKProof should be present, or both should be
// absent.
if (ctx.tx.isFieldPresent(sfZKProof))
return temMALFORMED;
}
@@ -66,7 +64,8 @@ ConfidentialMPTConvert::preclaim(PreclaimContext const& ctx)
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
if (!sleIssuance->isFlag(lsfMPTCanConfidentialAmount))
if (!sleIssuance->isFlag(lsfMPTCanConfidentialAmount) ||
!sleIssuance->isFieldPresent(sfIssuerEncryptionKey))
return tecNO_PERMISSION;
// already checked in preflight, but should also check that issuer on the
@@ -74,10 +73,6 @@ ConfidentialMPTConvert::preclaim(PreclaimContext const& ctx)
if (sleIssuance->getAccountID(sfIssuer) == account)
return tefINTERNAL; // LCOV_EXCL_LINE
// issuer has not uploaded their pub key yet
if (!sleIssuance->isFieldPresent(sfIssuerEncryptionKey))
return tecNO_PERMISSION;
bool const hasAuditor = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount);
bool const requiresAuditor = sleIssuance->isFieldPresent(sfAuditorEncryptionKey);
@@ -142,9 +137,10 @@ ConfidentialMPTConvert::preclaim(PreclaimContext const& ctx)
(*sleIssuance)[sfAuditorEncryptionKey], ctx.tx[sfAuditorEncryptedAmount]});
}
auto const blindingFactor = ctx.tx[sfBlindingFactor];
return verifyRevealedAmount(
amount,
ctx.tx[sfBlindingFactor],
Slice(blindingFactor.data(), blindingFactor.size()),
{holderPubKey, ctx.tx[sfHolderEncryptedAmount]},
{(*sleIssuance)[sfIssuerEncryptionKey], ctx.tx[sfIssuerEncryptedAmount]},
auditor);

View File

@@ -24,9 +24,6 @@ ConfidentialMPTConvertBack::preflight(PreflightContext const& ctx)
if (ctx.tx[sfMPTAmount] == 0 || ctx.tx[sfMPTAmount] > maxMPTokenAmount)
return temBAD_AMOUNT;
if (ctx.tx[sfBlindingFactor].size() != ecBlindingFactorLength)
return temMALFORMED;
if (!isValidCompressedECPoint(ctx.tx[sfBalanceCommitment]))
return temMALFORMED;
@@ -93,7 +90,7 @@ verifyProofs(
// verify revealed amount
if (auto const ter = verifyRevealedAmount(
amount,
blindingFactor,
Slice(blindingFactor.data(), blindingFactor.size()),
{holderPubKey, tx[sfHolderEncryptedAmount]},
{(*issuance)[sfIssuerEncryptionKey], tx[sfIssuerEncryptedAmount]},
auditor);
@@ -141,18 +138,18 @@ verifyProofs(
// verify bullet proof
{
// Compute PC_rem = PC_balance - mG (the commitment to the remaining balance)
Buffer pcRem;
if (auto const ter = computeConvertBackRemainder(tx[sfBalanceCommitment], amount, pcRem);
!isTesSuccess(ter))
if (auto pcRem = computeConvertBackRemainder(tx[sfBalanceCommitment], amount))
{
valid = false;
// The bulletproof verifies that the remaining balance is non-negative
std::vector<Slice> commitments{Slice(pcRem->data(), pcRem->size())};
if (auto const ter = verifyAggregatedBulletproof(bulletproof, commitments, contextHash);
!isTesSuccess(ter))
{
valid = false;
}
}
// The bulletproof verifies that the remaining balance is non-negative
std::vector<Slice> commitments{Slice(pcRem.data(), pcRem.size())};
if (auto const ter = verifyAggregatedBulletproof(bulletproof, commitments, contextHash);
!isTesSuccess(ter))
else
{
valid = false;
}

View File

@@ -40,11 +40,10 @@ ConfidentialMPTSend::preflight(PreflightContext const& ctx)
// Check the length of the ZKProof
auto const recipientCount = getConfidentialRecipientCount(hasAuditor);
auto const sizeEquality = secp256k1_mpt_proof_equality_shared_r_size(recipientCount);
auto const sizePedersenLinkage = 2 * ecPedersenProofLength;
auto const sizeEquality = getEqualityProofSize(recipientCount);
if (ctx.tx[sfZKProof].length() !=
sizeEquality + sizePedersenLinkage + ecDoubleBulletproofLength)
sizeEquality + doublePedersenProofLength + ecDoubleBulletproofLength)
return temMALFORMED;
// Check the Pedersen commitments are valid
@@ -86,7 +85,7 @@ verifySendProofs(
size_t currentOffset = 0;
// Extract equality proof
auto const sizeEquality = secp256k1_mpt_proof_equality_shared_r_size(recipientCount);
auto const sizeEquality = getEqualityProofSize(recipientCount);
if (remainingLength < sizeEquality)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -183,22 +182,15 @@ verifySendProofs(
// Verify Range Proof
{
Buffer pcRem;
// Derive PC_rem = PC_balance - PC_amount
if (auto const ter = computeSendRemainder(
ctx.tx[sfBalanceCommitment], ctx.tx[sfAmountCommitment], pcRem);
!isTesSuccess(ter))
{
valid = false;
}
else
if (auto pcRem =
computeSendRemainder(ctx.tx[sfBalanceCommitment], ctx.tx[sfAmountCommitment]))
{
// Aggregated commitments: [PC_amount, PC_rem]
// Prove that both the transfer amount and the remaining balance are in range
std::vector<Slice> commitments;
commitments.push_back(ctx.tx[sfAmountCommitment]);
commitments.push_back(Slice{pcRem.data(), pcRem.size()});
commitments.push_back(Slice{pcRem->data(), pcRem->size()});
if (auto const ter = verifyAggregatedBulletproof(rangeProof, commitments, contextHash);
!isTesSuccess(ter))
@@ -206,6 +198,10 @@ verifySendProofs(
valid = false;
}
}
else
{
valid = false;
}
}
if (!valid)
@@ -271,11 +267,6 @@ ConfidentialMPTSend::preclaim(PreclaimContext const& ctx)
!sleSenderMPToken->isFieldPresent(sfIssuerEncryptedBalance))
return tecNO_PERMISSION;
// Sanity check: MPToken's auditor field must be present if auditing is
// enabled
if (requiresAuditor && !sleSenderMPToken->isFieldPresent(sfAuditorEncryptedBalance))
return tefINTERNAL;
// Check destination's MPToken existence
auto const sleDestinationMPToken = ctx.view.read(keylet::mptoken(mptIssuanceID, destination));
if (!sleDestinationMPToken)
@@ -287,6 +278,13 @@ ConfidentialMPTSend::preclaim(PreclaimContext const& ctx)
!sleDestinationMPToken->isFieldPresent(sfIssuerEncryptedBalance))
return tecNO_PERMISSION;
// Sanity check: Both MPTokens' auditor fields must be present if auditing
// is enabled
if (requiresAuditor &&
(!sleSenderMPToken->isFieldPresent(sfAuditorEncryptedBalance) ||
!sleDestinationMPToken->isFieldPresent(sfAuditorEncryptedBalance)))
return tefINTERNAL; // LCOV_EXCL_LINE
// Check lock
MPTIssue const mptIssue(mptIssuanceID);
if (auto const ter = checkFrozen(ctx.view, account, mptIssue); !isTesSuccess(ter))

View File

@@ -71,7 +71,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
std::string
getTrivialSendProofHex(size_t nRecipients)
{
size_t const sizeEquality = secp256k1_mpt_proof_equality_shared_r_size(nRecipients);
size_t const sizeEquality = getEqualityProofSize(nRecipients);
size_t const totalSize =
sizeEquality + (2 * ecPedersenProofLength) + ecDoubleBulletproofLength;
@@ -365,15 +365,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
.err = temMALFORMED,
});
// blinding factor length is invalid
mptAlice.convert({
.account = alice,
.amt = 10,
.holderPubKey = mptAlice.getPubKey(bob),
.blindingFactor = makeZeroBuffer(10),
.err = temMALFORMED,
});
// Holder encrypted amount is empty (length 0)
mptAlice.convert({
.account = bob,
@@ -2952,14 +2943,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
.err = temBAD_AMOUNT,
});
// invalid blinding factor length
mptAlice.convertBack({
.account = alice,
.amt = 30,
.blindingFactor = Buffer{},
.err = temMALFORMED,
});
// Balance commitment has correct length but invalid EC point data
mptAlice.convertBack({
.account = bob,

View File

@@ -827,7 +827,7 @@ MPTTester::getConfidentialSendProof(
return std::nullopt;
}
size_t const sizeEquality = secp256k1_mpt_proof_equality_shared_r_size(nRecipients);
size_t const sizeEquality = getEqualityProofSize(nRecipients);
Buffer equalityProof(sizeEquality);
if (secp256k1_mpt_prove_equality_shared_r(