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

@@ -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(