mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-29 15:37:57 +00:00
Integrate mpt-crypto SDK lib for on-chain verification (#6679)
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
"protobuf/6.32.1#f481fd276fc23a33b85a3ed1e898b693%1765850161.038",
|
||||
"openssl/3.5.5#05a4ac5b7323f7a329b2db1391d9941f%1770229825.601",
|
||||
"nudb/2.0.9#0432758a24204da08fee953ec9ea03cb%1769436073.32",
|
||||
"mpt-crypto/0.1.0-rc2#575de3d495f539e3e5eba957b324d260%1771955268.105",
|
||||
"mpt-crypto/0.2.0-rc1#ed3f241f69d8b9ebf80069d1923d93a8%1773853481.755",
|
||||
"lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1765850143.914",
|
||||
"libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1765842973.492",
|
||||
"libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1765842973.03",
|
||||
|
||||
@@ -31,7 +31,7 @@ class Xrpl(ConanFile):
|
||||
"ed25519/2015.03",
|
||||
"grpc/1.72.0",
|
||||
"libarchive/3.8.1",
|
||||
"mpt-crypto/0.1.0-rc2",
|
||||
"mpt-crypto/0.2.0-rc1",
|
||||
"nudb/2.0.9",
|
||||
"openssl/3.5.5",
|
||||
"secp256k1/0.7.1",
|
||||
|
||||
@@ -56,27 +56,6 @@ incrementConfidentialVersion(STObject& mptoken)
|
||||
mptoken[~sfConfidentialBalanceVersion].value_or(0u) + 1u;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds common fields to a serializer for ZKP context hash generation.
|
||||
*
|
||||
* Serializes the transaction type, account, issuance ID and sequence/ticket number
|
||||
* into the provided serializer. These fields form the base of all context
|
||||
* hashes used in zero-knowledge proofs.
|
||||
*
|
||||
* @param s The serializer to append fields to.
|
||||
* @param txType The transaction type identifier.
|
||||
* @param account The account ID of the transaction sender.
|
||||
* @param issuanceID The MPToken Issuance ID.
|
||||
* @param sequence The transaction sequence number or ticket number.
|
||||
*/
|
||||
void
|
||||
addCommonZKPFields(
|
||||
Serializer& s,
|
||||
std::uint16_t txType,
|
||||
AccountID const& account,
|
||||
uint192 const& issuanceID,
|
||||
std::uint32_t sequence);
|
||||
|
||||
/**
|
||||
* @brief Generates the context hash for ConfidentialMPTSend transactions.
|
||||
*
|
||||
@@ -265,25 +244,6 @@ encryptCanonicalZeroAmount(Slice const& pubKeySlice, AccountID const& account, M
|
||||
TER
|
||||
verifySchnorrProof(Slice const& pubKeySlice, Slice const& proofSlice, uint256 const& contextHash);
|
||||
|
||||
/**
|
||||
* @brief Verifies that a ciphertext correctly encrypts a revealed amount.
|
||||
*
|
||||
* Given the plaintext amount and blinding factor, verifies that the
|
||||
* ciphertext was correctly constructed using ElGamal encryption.
|
||||
*
|
||||
* @param amount The revealed plaintext amount.
|
||||
* @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
|
||||
verifyElGamalEncryption(
|
||||
uint64_t const amount,
|
||||
Slice const& blindingFactor,
|
||||
Slice const& pubKeySlice,
|
||||
Slice const& ciphertext);
|
||||
|
||||
/**
|
||||
* @brief Validates the format of encrypted amount fields in a transaction.
|
||||
*
|
||||
@@ -351,25 +311,6 @@ getEqualityProofSize(std::size_t nRecipients)
|
||||
return secp256k1_mpt_proof_equality_shared_r_size(nRecipients);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Verifies a multi-ciphertext equality proof.
|
||||
*
|
||||
* Proves that all ciphertexts in the recipients vector encrypt the same
|
||||
* plaintext amount, without revealing the amount itself.
|
||||
*
|
||||
* @param proof The zero-knowledge proof bytes.
|
||||
* @param recipients Vector of recipients with their public keys and ciphertexts.
|
||||
* @param nRecipients The number of recipients (must match recipients.size()).
|
||||
* @param contextHash The 256-bit context hash binding the proof.
|
||||
* @return tesSUCCESS if the proof is valid, or an error code otherwise.
|
||||
*/
|
||||
TER
|
||||
verifyMultiCiphertextEqualityProof(
|
||||
Slice const& proof,
|
||||
std::vector<ConfidentialRecipient> const& recipients,
|
||||
std::size_t const nRecipients,
|
||||
uint256 const& contextHash);
|
||||
|
||||
/**
|
||||
* @brief Verifies a clawback equality proof.
|
||||
*
|
||||
@@ -462,21 +403,63 @@ verifyAggregatedBulletproof(
|
||||
uint256 const& contextHash);
|
||||
|
||||
/**
|
||||
* @brief Computes the remainder commitment for ConfidentialMPTSend.
|
||||
* @brief Verifies all zero-knowledge proofs for a ConfidentialMPTSend transaction.
|
||||
*
|
||||
* Given a balance commitment PC_bal = m_bal*G + rho_bal*H and an amount
|
||||
* commitment PC_amt = m_amt*G + rho_amt*H, this function computes:
|
||||
* PC_rem = PC_bal - PC_amt = (m_bal - m_amt)*G + (rho_bal - rho_amt)*H
|
||||
* This function calls mpt_verify_send_proof API in the mpt-crypto utility lib, which verifies the
|
||||
* equality proof, amount linkage, balance linkage, and range proof.
|
||||
* Equality proof: Proves the same value is encrypted for the sender, receiver, issuer, and auditor.
|
||||
* Amount linkage: Proves the send amount matches the amount Pedersen commitment.
|
||||
* Balance linkage: Proves the sender's balance matches the balance Pedersen
|
||||
* commitment.
|
||||
* Range proof: Proves the amount and the remaining balance are within range [0, 2^64-1].
|
||||
*
|
||||
* This derived commitment is used in an aggregated range proof to ensure
|
||||
* the sender maintains a non-negative balance (m_bal - m_amt >= 0).
|
||||
*
|
||||
* @param balanceCommitment The compressed Pedersen commitment to the balance (33 bytes).
|
||||
* @param amountCommitment The compressed Pedersen commitment to the amount (33 bytes).
|
||||
* @return The remainder commitment (33 bytes), or std::nullopt on failure.
|
||||
* @param proof The full proof blob.
|
||||
* @param sender The sender's public key and encrypted amount.
|
||||
* @param destination The destination's public key and encrypted amount.
|
||||
* @param issuer The issuer's public key and encrypted amount.
|
||||
* @param auditor The auditor's public key and encrypted amount if present.
|
||||
* @param spendingBalance The sender's current spending balance ciphertext.
|
||||
* @param amountCommitment The Pedersen commitment to the send amount.
|
||||
* @param balanceCommitment The Pedersen commitment to the sender's balance.
|
||||
* @param contextHash The context hash binding the proof.
|
||||
* @return tesSUCCESS if all proofs are valid, or an error code otherwise.
|
||||
*/
|
||||
std::optional<Buffer>
|
||||
computeSendRemainder(Slice const& balanceCommitment, Slice const& amountCommitment);
|
||||
TER
|
||||
verifySendProof(
|
||||
Slice const& proof,
|
||||
ConfidentialRecipient const& sender,
|
||||
ConfidentialRecipient const& destination,
|
||||
ConfidentialRecipient const& issuer,
|
||||
std::optional<ConfidentialRecipient> const& auditor,
|
||||
Slice const& spendingBalance,
|
||||
Slice const& amountCommitment,
|
||||
Slice const& balanceCommitment,
|
||||
uint256 const& contextHash);
|
||||
|
||||
/**
|
||||
* @brief Verifies all zero-knowledge proofs for a ConfidentialMPTConvertBack transaction.
|
||||
*
|
||||
* This function calls mpt_verify_convert_back_proof API in the mpt-crypto utility lib, which
|
||||
* verifies the balance linkage proof and range proof. Balance linkage proof: proves the balance
|
||||
* commitment matches the spending ciphertext. Range proof: proves the remaining balance after
|
||||
* convert back is within range [0, 2^64-1].
|
||||
*
|
||||
* @param proof The full proof blob.
|
||||
* @param pubKeySlice The holder's public key.
|
||||
* @param spendingBalance The holder's spending balance ciphertext.
|
||||
* @param balanceCommitment The Pedersen commitment to the balance.
|
||||
* @param amount The amount being converted back to public.
|
||||
* @param contextHash The context hash binding the proof.
|
||||
* @return tesSUCCESS if all proofs are valid, or an error code otherwise.
|
||||
*/
|
||||
TER
|
||||
verifyConvertBackProof(
|
||||
Slice const& proof,
|
||||
Slice const& pubKeySlice,
|
||||
Slice const& spendingBalance,
|
||||
Slice const& balanceCommitment,
|
||||
uint64_t amount,
|
||||
uint256 const& contextHash);
|
||||
|
||||
/**
|
||||
* @brief Computes the remainder commitment for ConvertBack.
|
||||
|
||||
@@ -1,28 +1,37 @@
|
||||
#include <xrpl/protocol/ConfidentialTransfer.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
|
||||
#include <boost/endian/conversion.hpp>
|
||||
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <utility/mpt_utility.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
static constexpr std::uint32_t defaultVersion = 0;
|
||||
|
||||
void
|
||||
addCommonZKPFields(
|
||||
Serializer& s,
|
||||
std::uint16_t txType,
|
||||
AccountID const& account,
|
||||
uint192 const& issuanceID,
|
||||
std::uint32_t sequence)
|
||||
/**
|
||||
* @brief Converts an XRPL AccountID to mpt-crypto lib C struct.
|
||||
*
|
||||
* @param account The AccountID.
|
||||
* @return The equivalent mpt-crypto lib account_id struct.
|
||||
*/
|
||||
account_id
|
||||
toAccountId(AccountID const& account)
|
||||
{
|
||||
// TxCommonHash = hash(TxType || Account || IssuanceID || SequenceOrTicket)
|
||||
s.add16(txType);
|
||||
s.addBitString(account);
|
||||
s.addBitString(issuanceID);
|
||||
s.add32(sequence);
|
||||
account_id res;
|
||||
std::memcpy(res.bytes, account.data(), kMPT_ACCOUNT_ID_SIZE);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Converts an XRPL uint192 to mpt-crypto lib C struct.
|
||||
*
|
||||
* @param i The XRPL MPTokenIssuance ID.
|
||||
* @return The equivalent mpt-crypto lib mpt_issuance_id struct.
|
||||
*/
|
||||
mpt_issuance_id
|
||||
toIssuanceId(uint192 const& issuance)
|
||||
{
|
||||
mpt_issuance_id res;
|
||||
std::memcpy(res.bytes, issuance.data(), kMPT_ISSUANCE_ID_SIZE);
|
||||
return res;
|
||||
}
|
||||
|
||||
uint256
|
||||
@@ -33,14 +42,15 @@ getSendContextHash(
|
||||
AccountID const& destination,
|
||||
std::uint32_t version)
|
||||
{
|
||||
Serializer s;
|
||||
addCommonZKPFields(s, ttCONFIDENTIAL_MPT_SEND, account, issuanceID, sequence);
|
||||
|
||||
// TxSpecific = identity || freshness
|
||||
s.addBitString(destination);
|
||||
s.addInteger(version);
|
||||
|
||||
return s.getSHA512Half();
|
||||
uint256 result;
|
||||
mpt_get_send_context_hash(
|
||||
toAccountId(account),
|
||||
toIssuanceId(issuanceID),
|
||||
sequence,
|
||||
toAccountId(destination),
|
||||
version,
|
||||
result.data());
|
||||
return result;
|
||||
}
|
||||
|
||||
uint256
|
||||
@@ -50,27 +60,23 @@ getClawbackContextHash(
|
||||
std::uint32_t sequence,
|
||||
AccountID const& holder)
|
||||
{
|
||||
Serializer s;
|
||||
addCommonZKPFields(s, ttCONFIDENTIAL_MPT_CLAWBACK, account, issuanceID, sequence);
|
||||
|
||||
// TxSpecific = identity || freshness
|
||||
s.addBitString(holder);
|
||||
s.addInteger(defaultVersion);
|
||||
|
||||
return s.getSHA512Half();
|
||||
uint256 result;
|
||||
mpt_get_clawback_context_hash(
|
||||
toAccountId(account),
|
||||
toIssuanceId(issuanceID),
|
||||
sequence,
|
||||
toAccountId(holder),
|
||||
result.data());
|
||||
return result;
|
||||
}
|
||||
|
||||
uint256
|
||||
getConvertContextHash(AccountID const& account, uint192 const& issuanceID, std::uint32_t sequence)
|
||||
{
|
||||
Serializer s;
|
||||
addCommonZKPFields(s, ttCONFIDENTIAL_MPT_CONVERT, account, issuanceID, sequence);
|
||||
|
||||
// TxSpecific = identity || freshness
|
||||
s.addBitString(account);
|
||||
s.addInteger(defaultVersion);
|
||||
|
||||
return s.getSHA512Half();
|
||||
uint256 result;
|
||||
mpt_get_convert_context_hash(
|
||||
toAccountId(account), toIssuanceId(issuanceID), sequence, result.data());
|
||||
return result;
|
||||
}
|
||||
|
||||
uint256
|
||||
@@ -80,14 +86,10 @@ getConvertBackContextHash(
|
||||
std::uint32_t sequence,
|
||||
std::uint32_t version)
|
||||
{
|
||||
Serializer s;
|
||||
addCommonZKPFields(s, ttCONFIDENTIAL_MPT_CONVERT_BACK, account, issuanceID, sequence);
|
||||
|
||||
// TxSpecific = identity || freshness
|
||||
s.addBitString(account);
|
||||
s.addInteger(version);
|
||||
|
||||
return s.getSHA512Half();
|
||||
uint256 result;
|
||||
mpt_get_convert_back_context_hash(
|
||||
toAccountId(account), toIssuanceId(issuanceID), sequence, version, result.data());
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<EcPair>
|
||||
@@ -216,29 +218,14 @@ generateBlindingFactor()
|
||||
std::optional<Buffer>
|
||||
encryptAmount(uint64_t const amt, Slice const& pubKeySlice, Slice const& blindingFactor)
|
||||
{
|
||||
if (blindingFactor.size() != ecBlindingFactorLength)
|
||||
if (blindingFactor.size() != ecBlindingFactorLength || pubKeySlice.size() != ecPubKeyLength)
|
||||
return std::nullopt;
|
||||
|
||||
if (pubKeySlice.size() != ecPubKeyLength)
|
||||
Buffer out(ecGamalEncryptedTotalLength);
|
||||
if (mpt_encrypt_amount(amt, pubKeySlice.data(), blindingFactor.data(), out.data()) != 0)
|
||||
return std::nullopt;
|
||||
|
||||
EcPair pair;
|
||||
secp256k1_pubkey pubKey;
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
secp256k1Context(), &pubKey, pubKeySlice.data(), ecPubKeyLength);
|
||||
res != 1)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (auto res = secp256k1_elgamal_encrypt(
|
||||
secp256k1Context(), &pair.c1, &pair.c2, &pubKey, amt, blindingFactor.data());
|
||||
res != 1)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return serializeEcPair(pair);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::optional<Buffer>
|
||||
@@ -266,66 +253,6 @@ encryptCanonicalZeroAmount(Slice const& pubKeySlice, AccountID const& account, M
|
||||
return serializeEcPair(pair);
|
||||
}
|
||||
|
||||
TER
|
||||
verifySchnorrProof(Slice const& pubKeySlice, Slice const& proofSlice, uint256 const& contextHash)
|
||||
{
|
||||
if (proofSlice.size() != ecSchnorrProofLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (pubKeySlice.size() != ecPubKeyLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
secp256k1_pubkey pubKey;
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
secp256k1Context(), &pubKey, pubKeySlice.data(), ecPubKeyLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
if (auto res = secp256k1_mpt_pok_sk_verify(
|
||||
secp256k1Context(), proofSlice.data(), &pubKey, contextHash.data());
|
||||
res != 1)
|
||||
{
|
||||
return tecBAD_PROOF;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
verifyElGamalEncryption(
|
||||
uint64_t const amount,
|
||||
Slice const& blindingFactor,
|
||||
Slice const& pubKeySlice,
|
||||
Slice const& ciphertext)
|
||||
{
|
||||
if (ciphertext.size() != ecGamalEncryptedTotalLength ||
|
||||
blindingFactor.size() != ecBlindingFactorLength || pubKeySlice.size() != ecPubKeyLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
secp256k1_pubkey pubKey;
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
secp256k1Context(), &pubKey, pubKeySlice.data(), ecPubKeyLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
auto const pair = makeEcPair(ciphertext);
|
||||
if (!pair)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (auto res = secp256k1_elgamal_verify_encryption(
|
||||
secp256k1Context(), &pair->c1, &pair->c2, &pubKey, amount, blindingFactor.data());
|
||||
res != 1)
|
||||
{
|
||||
return tecBAD_PROOF;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
verifyRevealedAmount(
|
||||
uint64_t const amount,
|
||||
@@ -334,151 +261,37 @@ verifyRevealedAmount(
|
||||
ConfidentialRecipient const& issuer,
|
||||
std::optional<ConfidentialRecipient> const& auditor)
|
||||
{
|
||||
if (auto const res = verifyElGamalEncryption(
|
||||
amount, blindingFactor, holder.publicKey, holder.encryptedAmount);
|
||||
!isTesSuccess(res))
|
||||
{
|
||||
return res;
|
||||
}
|
||||
if (blindingFactor.size() != ecBlindingFactorLength ||
|
||||
holder.publicKey.size() != ecPubKeyLength ||
|
||||
holder.encryptedAmount.size() != ecGamalEncryptedTotalLength ||
|
||||
issuer.publicKey.size() != ecPubKeyLength ||
|
||||
issuer.encryptedAmount.size() != ecGamalEncryptedTotalLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (auto const res = verifyElGamalEncryption(
|
||||
amount, blindingFactor, issuer.publicKey, issuer.encryptedAmount);
|
||||
!isTesSuccess(res))
|
||||
{
|
||||
return res;
|
||||
}
|
||||
auto toParticipant = [](ConfidentialRecipient const& r) {
|
||||
mpt_confidential_participant p;
|
||||
std::memcpy(p.pubkey, r.publicKey.data(), kMPT_PUBKEY_SIZE);
|
||||
std::memcpy(p.ciphertext, r.encryptedAmount.data(), kMPT_ELGAMAL_TOTAL_SIZE);
|
||||
return p;
|
||||
};
|
||||
|
||||
auto const holderP = toParticipant(holder);
|
||||
auto const issuerP = toParticipant(issuer);
|
||||
|
||||
mpt_confidential_participant auditorP;
|
||||
mpt_confidential_participant const* auditorPtr = nullptr;
|
||||
if (auditor)
|
||||
{
|
||||
if (auto const res = verifyElGamalEncryption(
|
||||
amount, blindingFactor, auditor->publicKey, auditor->encryptedAmount);
|
||||
!isTesSuccess(res))
|
||||
{
|
||||
return res;
|
||||
}
|
||||
if (auditor->publicKey.size() != ecPubKeyLength ||
|
||||
auditor->encryptedAmount.size() != ecGamalEncryptedTotalLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
auditorP = toParticipant(*auditor);
|
||||
auditorPtr = &auditorP;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
verifyMultiCiphertextEqualityProof(
|
||||
Slice const& proof,
|
||||
std::vector<ConfidentialRecipient> const& recipients,
|
||||
std::size_t const nRecipients,
|
||||
uint256 const& contextHash)
|
||||
{
|
||||
if (recipients.size() != nRecipients)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (proof.size() != getEqualityProofSize(nRecipients))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const ctx = secp256k1Context();
|
||||
|
||||
secp256k1_pubkey c1;
|
||||
std::vector<secp256k1_pubkey> c2_vec(nRecipients);
|
||||
std::vector<secp256k1_pubkey> pk_vec(nRecipients);
|
||||
|
||||
for (size_t i = 0; i < nRecipients; ++i)
|
||||
{
|
||||
auto const& recipient = recipients[i];
|
||||
|
||||
if (recipient.encryptedAmount.size() != ecGamalEncryptedTotalLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (recipient.publicKey.size() != ecPubKeyLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// Parse Shared C1 from the first recipient only
|
||||
if (i == 0)
|
||||
{
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
ctx, &c1, recipient.encryptedAmount.data(), ecGamalEncryptedLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// All C1 bytes must be the same
|
||||
if (std::memcmp(
|
||||
recipient.encryptedAmount.data(),
|
||||
recipients[0].encryptedAmount.data(),
|
||||
ecGamalEncryptedLength) != 0)
|
||||
{
|
||||
return tecBAD_PROOF;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
ctx,
|
||||
&c2_vec[i],
|
||||
recipient.encryptedAmount.data() + ecGamalEncryptedLength,
|
||||
ecGamalEncryptedLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
ctx, &pk_vec[i], recipient.publicKey.data(), ecPubKeyLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
if (auto res = secp256k1_mpt_verify_equality_shared_r(
|
||||
ctx, proof.data(), nRecipients, &c1, c2_vec.data(), pk_vec.data(), contextHash.data());
|
||||
res != 1)
|
||||
{
|
||||
if (mpt_verify_revealed_amount(amount, blindingFactor.data(), &holderP, &issuerP, auditorPtr) !=
|
||||
0)
|
||||
return tecBAD_PROOF;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
verifyClawbackEqualityProof(
|
||||
uint64_t const amount,
|
||||
Slice const& proof,
|
||||
Slice const& pubKeySlice,
|
||||
Slice const& ciphertext,
|
||||
uint256 const& contextHash)
|
||||
{
|
||||
if (ciphertext.size() != ecGamalEncryptedTotalLength || pubKeySlice.size() != ecPubKeyLength ||
|
||||
proof.size() != ecEqualityProofLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const pair = makeEcPair(ciphertext);
|
||||
if (!pair)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
secp256k1_pubkey pubKey;
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
secp256k1Context(), &pubKey, pubKeySlice.data(), ecPubKeyLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// Note: c2, c1 order - the proof is generated with c2 first (the encrypted
|
||||
// message component) because the equality proof structure expects the
|
||||
// message-containing term before the blinding term.
|
||||
if (auto res = secp256k1_equality_plaintext_verify(
|
||||
secp256k1Context(),
|
||||
proof.data(),
|
||||
&pubKey,
|
||||
&pair->c2,
|
||||
&pair->c1,
|
||||
amount,
|
||||
contextHash.data());
|
||||
res != 1)
|
||||
{
|
||||
return tecBAD_PROOF;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -511,6 +324,37 @@ checkEncryptedAmountFormat(STObject const& object)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
verifySchnorrProof(Slice const& pubKeySlice, Slice const& proofSlice, uint256 const& contextHash)
|
||||
{
|
||||
if (proofSlice.size() != ecSchnorrProofLength || pubKeySlice.size() != ecPubKeyLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (mpt_verify_convert_proof(proofSlice.data(), pubKeySlice.data(), contextHash.data()) != 0)
|
||||
return tecBAD_PROOF;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
verifyClawbackEqualityProof(
|
||||
uint64_t const amount,
|
||||
Slice const& proof,
|
||||
Slice const& pubKeySlice,
|
||||
Slice const& ciphertext,
|
||||
uint256 const& contextHash)
|
||||
{
|
||||
if (ciphertext.size() != ecGamalEncryptedTotalLength || pubKeySlice.size() != ecPubKeyLength ||
|
||||
proof.size() != ecEqualityProofLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (mpt_verify_clawback_proof(
|
||||
proof.data(), amount, pubKeySlice.data(), ciphertext.data(), contextHash.data()) != 0)
|
||||
return tecBAD_PROOF;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
verifyPcmLinkage(
|
||||
PcmLinkageType type,
|
||||
@@ -520,65 +364,30 @@ verifyPcmLinkage(
|
||||
Slice const& pcmSlice,
|
||||
uint256 const& contextHash)
|
||||
{
|
||||
if (proof.length() != ecPedersenProofLength)
|
||||
if (proof.length() != ecPedersenProofLength || pubKeySlice.size() != ecPubKeyLength ||
|
||||
pcmSlice.size() != ecPedersenCommitmentLength ||
|
||||
encAmt.size() != ecGamalEncryptedTotalLength)
|
||||
return tecINTERNAL;
|
||||
|
||||
auto const pair = makeEcPair(encAmt);
|
||||
if (!pair)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (pubKeySlice.size() != ecPubKeyLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (pcmSlice.size() != ecPedersenCommitmentLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
secp256k1_pubkey pubKey;
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
secp256k1Context(), &pubKey, pubKeySlice.data(), ecPubKeyLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
secp256k1_pubkey pcm;
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
secp256k1Context(), &pcm, pcmSlice.data(), ecPedersenCommitmentLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// For amount linkage (randomness r): order is C1, C2, Pk, Pcm.
|
||||
// For balance linkage (secret key s): order is Pk, C2, C1, Pcm
|
||||
// (swaps Pk <-> C1 to accommodate the different algebraic structure).
|
||||
int res;
|
||||
if (type == PcmLinkageType::amount)
|
||||
{
|
||||
res = secp256k1_elgamal_pedersen_link_verify(
|
||||
res = mpt_verify_amount_linkage(
|
||||
secp256k1Context(),
|
||||
proof.data(),
|
||||
&pair->c1,
|
||||
&pair->c2,
|
||||
&pubKey,
|
||||
&pcm,
|
||||
encAmt.data(),
|
||||
pubKeySlice.data(),
|
||||
pcmSlice.data(),
|
||||
contextHash.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
res = secp256k1_elgamal_pedersen_link_verify(
|
||||
secp256k1Context(),
|
||||
proof.data(),
|
||||
&pubKey,
|
||||
&pair->c2,
|
||||
&pair->c1,
|
||||
&pcm,
|
||||
contextHash.data());
|
||||
res = mpt_verify_balance_linkage(
|
||||
proof.data(), encAmt.data(), pubKeySlice.data(), pcmSlice.data(), contextHash.data());
|
||||
}
|
||||
|
||||
if (res != 1)
|
||||
if (res != 0)
|
||||
return tecBAD_PROOF;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -588,8 +397,6 @@ verifyAggregatedBulletproof(
|
||||
std::vector<Slice> const& compressedCommitments,
|
||||
uint256 const& contextHash)
|
||||
{
|
||||
// 1. Validate input lengths
|
||||
// This function could support any power-of-2 m, but current usage only requires m=1 or m=2
|
||||
std::size_t const m = compressedCommitments.size();
|
||||
if (m != 1 && m != 2)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
@@ -599,121 +406,106 @@ verifyAggregatedBulletproof(
|
||||
if (proof.size() != expectedProofLen)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// 2. Prepare Pedersen Commitments, parse from compressed format
|
||||
auto const ctx = secp256k1Context();
|
||||
std::vector<secp256k1_pubkey> commitments(m);
|
||||
std::vector<uint8_t const*> commitmentPtrs(m);
|
||||
for (size_t i = 0; i < m; ++i)
|
||||
{
|
||||
// Sanity check length
|
||||
if (compressedCommitments[i].size() != ecPedersenCommitmentLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
ctx, &commitments[i], compressedCommitments[i].data(), ecPedersenCommitmentLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
commitmentPtrs[i] = compressedCommitments[i].data();
|
||||
}
|
||||
|
||||
// 3. Prepare Generator Vectors (G_vec, H_vec)
|
||||
// The range proof requires vectors of size 64 * m
|
||||
std::size_t const n = 64 * m;
|
||||
std::vector<secp256k1_pubkey> G_vec(n);
|
||||
std::vector<secp256k1_pubkey> H_vec(n);
|
||||
|
||||
// Retrieve deterministic generators "G" and "H"
|
||||
if (auto res =
|
||||
secp256k1_mpt_get_generator_vector(ctx, G_vec.data(), n, (unsigned char const*)"G", 1);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
if (auto res =
|
||||
secp256k1_mpt_get_generator_vector(ctx, H_vec.data(), n, (unsigned char const*)"H", 1);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// 4. Prepare Base Generator (pk_base / H)
|
||||
secp256k1_pubkey pk_base;
|
||||
if (auto res = secp256k1_mpt_get_h_generator(ctx, &pk_base); res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// 5. Verify the Proof
|
||||
if (auto res = secp256k1_bulletproof_verify_agg(
|
||||
ctx,
|
||||
G_vec.data(),
|
||||
H_vec.data(),
|
||||
reinterpret_cast<unsigned char const*>(proof.data()),
|
||||
proof.size(),
|
||||
commitments.data(),
|
||||
m,
|
||||
&pk_base,
|
||||
contextHash.data());
|
||||
res != 1)
|
||||
{
|
||||
if (mpt_verify_aggregated_bulletproof(
|
||||
proof.data(), proof.size(), commitmentPtrs.data(), m, contextHash.data()) != 0)
|
||||
return tecBAD_PROOF;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
std::optional<Buffer>
|
||||
computeSendRemainder(Slice const& balanceCommitment, Slice const& amountCommitment)
|
||||
TER
|
||||
verifySendProof(
|
||||
Slice const& proof,
|
||||
ConfidentialRecipient const& sender,
|
||||
ConfidentialRecipient const& destination,
|
||||
ConfidentialRecipient const& issuer,
|
||||
std::optional<ConfidentialRecipient> const& auditor,
|
||||
Slice const& spendingBalance,
|
||||
Slice const& amountCommitment,
|
||||
Slice const& balanceCommitment,
|
||||
uint256 const& contextHash)
|
||||
{
|
||||
if (balanceCommitment.size() != ecPedersenCommitmentLength ||
|
||||
amountCommitment.size() != ecPedersenCommitmentLength)
|
||||
return std::nullopt;
|
||||
auto const recipientCount = getConfidentialRecipientCount(auditor.has_value());
|
||||
auto const expectedProofSize = getEqualityProofSize(recipientCount) +
|
||||
2 * ecPedersenProofLength + ecDoubleBulletproofLength;
|
||||
|
||||
auto const ctx = secp256k1Context();
|
||||
if (proof.size() != expectedProofSize || sender.publicKey.size() != ecPubKeyLength ||
|
||||
sender.encryptedAmount.size() != ecGamalEncryptedTotalLength ||
|
||||
destination.publicKey.size() != ecPubKeyLength ||
|
||||
destination.encryptedAmount.size() != ecGamalEncryptedTotalLength ||
|
||||
issuer.publicKey.size() != ecPubKeyLength ||
|
||||
issuer.encryptedAmount.size() != ecGamalEncryptedTotalLength ||
|
||||
spendingBalance.size() != ecGamalEncryptedTotalLength ||
|
||||
amountCommitment.size() != ecPedersenCommitmentLength ||
|
||||
balanceCommitment.size() != ecPedersenCommitmentLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
secp256k1_pubkey pcBalance;
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
ctx, &pcBalance, balanceCommitment.data(), ecPedersenCommitmentLength);
|
||||
res != 1)
|
||||
auto makeParticipant = [](ConfidentialRecipient const& r) {
|
||||
mpt_confidential_participant p;
|
||||
std::memcpy(p.pubkey, r.publicKey.data(), kMPT_PUBKEY_SIZE);
|
||||
std::memcpy(p.ciphertext, r.encryptedAmount.data(), kMPT_ELGAMAL_TOTAL_SIZE);
|
||||
return p;
|
||||
};
|
||||
|
||||
std::vector<mpt_confidential_participant> participants(recipientCount);
|
||||
participants[0] = makeParticipant(sender);
|
||||
participants[1] = makeParticipant(destination);
|
||||
participants[2] = makeParticipant(issuer);
|
||||
if (auditor)
|
||||
{
|
||||
return std::nullopt;
|
||||
if (auditor->publicKey.size() != ecPubKeyLength ||
|
||||
auditor->encryptedAmount.size() != ecGamalEncryptedTotalLength)
|
||||
return tecINTERNAL;
|
||||
participants[3] = makeParticipant(*auditor);
|
||||
}
|
||||
|
||||
secp256k1_pubkey pcAmount;
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
ctx, &pcAmount, amountCommitment.data(), ecPedersenCommitmentLength);
|
||||
res != 1)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
if (mpt_verify_send_proof(
|
||||
proof.data(),
|
||||
proof.size(),
|
||||
participants.data(),
|
||||
static_cast<uint8_t>(recipientCount),
|
||||
spendingBalance.data(),
|
||||
amountCommitment.data(),
|
||||
balanceCommitment.data(),
|
||||
contextHash.data()) != 0)
|
||||
return tecBAD_PROOF;
|
||||
|
||||
// Negate PC_amount point to get -PC_amount
|
||||
if (auto res = secp256k1_ec_pubkey_negate(ctx, &pcAmount); res != 1)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// Compute pcRem = pcBalance + (-pcAmount)
|
||||
secp256k1_pubkey const* summands[2] = {&pcBalance, &pcAmount};
|
||||
secp256k1_pubkey pcRem;
|
||||
if (auto res = secp256k1_ec_pubkey_combine(ctx, &pcRem, summands, 2); res != 1)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
TER
|
||||
verifyConvertBackProof(
|
||||
Slice const& proof,
|
||||
Slice const& pubKeySlice,
|
||||
Slice const& spendingBalance,
|
||||
Slice const& balanceCommitment,
|
||||
uint64_t amount,
|
||||
uint256 const& contextHash)
|
||||
{
|
||||
if (proof.size() != ecPedersenProofLength + ecSingleBulletproofLength ||
|
||||
pubKeySlice.size() != ecPubKeyLength ||
|
||||
spendingBalance.size() != ecGamalEncryptedTotalLength ||
|
||||
balanceCommitment.size() != ecPedersenCommitmentLength)
|
||||
return tecINTERNAL; // 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 std::nullopt;
|
||||
}
|
||||
if (mpt_verify_convert_back_proof(
|
||||
proof.data(),
|
||||
pubKeySlice.data(),
|
||||
spendingBalance.data(),
|
||||
balanceCommitment.data(),
|
||||
amount,
|
||||
contextHash.data()) != 0)
|
||||
return tecBAD_PROOF;
|
||||
|
||||
return out;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
std::optional<Buffer>
|
||||
@@ -722,54 +514,12 @@ computeConvertBackRemainder(Slice const& commitment, uint64_t amount)
|
||||
if (commitment.size() != ecPedersenCommitmentLength || amount == 0)
|
||||
return std::nullopt; // LCOV_EXCL_LINE
|
||||
|
||||
auto const ctx = secp256k1Context();
|
||||
|
||||
// Parse commitment from compressed format
|
||||
secp256k1_pubkey pcBalance;
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
ctx, &pcBalance, commitment.data(), ecPedersenCommitmentLength);
|
||||
res != 1)
|
||||
{
|
||||
return std::nullopt; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// Convert amount to 32-byte big-endian scalar
|
||||
unsigned char mScalar[32] = {0};
|
||||
uint64_t amountBigEndian = boost::endian::native_to_big(amount);
|
||||
std::memcpy(&mScalar[24], &amountBigEndian, sizeof(amountBigEndian));
|
||||
|
||||
// Compute mG = amount * G
|
||||
secp256k1_pubkey mG;
|
||||
if (auto res = secp256k1_ec_pubkey_create(ctx, &mG, mScalar); res != 1)
|
||||
{
|
||||
return std::nullopt; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// Negate mG to get -mG
|
||||
if (auto res = secp256k1_ec_pubkey_negate(ctx, &mG); res != 1)
|
||||
{
|
||||
return std::nullopt; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// Compute pcRem = pcBalance + (-mG)
|
||||
secp256k1_pubkey const* summands[2] = {&pcBalance, &mG};
|
||||
secp256k1_pubkey pcRem;
|
||||
if (auto res = secp256k1_ec_pubkey_combine(ctx, &pcRem, summands, 2); res != 1)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (mpt_compute_convert_back_remainder(commitment.data(), amount, out.data()) != 0)
|
||||
return std::nullopt; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -125,6 +125,10 @@ ConfidentialMPTConvert::preclaim(PreclaimContext const& ctx)
|
||||
if (hasHolderKeyOnLedger && hasHolderKeyInTx)
|
||||
return tecDUPLICATE;
|
||||
|
||||
// Run all verifications before returning any error to prevent timing attacks
|
||||
// that could reveal which proof failed.
|
||||
bool valid = true;
|
||||
|
||||
Slice holderPubKey;
|
||||
if (hasHolderKeyInTx)
|
||||
{
|
||||
@@ -133,11 +137,10 @@ ConfidentialMPTConvert::preclaim(PreclaimContext const& ctx)
|
||||
auto const contextHash =
|
||||
getConvertContextHash(account, issuanceID, ctx.tx.getSeqProxy().value());
|
||||
|
||||
// when register new pk, verify through schnorr proof
|
||||
if (auto const ter = verifySchnorrProof(holderPubKey, ctx.tx[sfZKProof], contextHash);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
return ter;
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -154,12 +157,21 @@ ConfidentialMPTConvert::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
auto const blindingFactor = ctx.tx[sfBlindingFactor];
|
||||
return verifyRevealedAmount(
|
||||
amount,
|
||||
Slice(blindingFactor.data(), blindingFactor.size()),
|
||||
{holderPubKey, ctx.tx[sfHolderEncryptedAmount]},
|
||||
{(*sleIssuance)[sfIssuerEncryptionKey], ctx.tx[sfIssuerEncryptedAmount]},
|
||||
auditor);
|
||||
if (auto const ter = verifyRevealedAmount(
|
||||
amount,
|
||||
Slice(blindingFactor.data(), blindingFactor.size()),
|
||||
{holderPubKey, ctx.tx[sfHolderEncryptedAmount]},
|
||||
{(*sleIssuance)[sfIssuerEncryptionKey], ctx.tx[sfIssuerEncryptedAmount]},
|
||||
auditor);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!valid)
|
||||
return tecBAD_PROOF;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
|
||||
@@ -87,7 +87,6 @@ verifyProofs(
|
||||
// that could reveal which proof failed.
|
||||
bool valid = true;
|
||||
|
||||
// verify revealed amount
|
||||
if (auto const ter = verifyRevealedAmount(
|
||||
amount,
|
||||
Slice(blindingFactor.data(), blindingFactor.size()),
|
||||
@@ -99,49 +98,18 @@ verifyProofs(
|
||||
valid = false;
|
||||
}
|
||||
|
||||
// Extract proof components
|
||||
ProofReader reader(tx[sfZKProof]);
|
||||
|
||||
auto const pedersenProof = reader.read(ecPedersenProofLength);
|
||||
auto const bulletproof = reader.read(ecSingleBulletproofLength);
|
||||
|
||||
if (!pedersenProof || !bulletproof || !reader.done())
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// verify el gamal pedersen linkage
|
||||
if (auto const ter = verifyPcmLinkage(
|
||||
PcmLinkageType::balance,
|
||||
*pedersenProof,
|
||||
(*mptoken)[sfConfidentialBalanceSpending],
|
||||
if (auto const ter = verifyConvertBackProof(
|
||||
tx[sfZKProof],
|
||||
holderPubKey,
|
||||
(*mptoken)[sfConfidentialBalanceSpending],
|
||||
tx[sfBalanceCommitment],
|
||||
amount,
|
||||
contextHash);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
|
||||
// verify bullet proof
|
||||
{
|
||||
// Compute PC_rem = PC_balance - mG (the commitment to the remaining balance)
|
||||
if (auto pcRem = computeConvertBackRemainder(tx[sfBalanceCommitment], amount))
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid)
|
||||
return tecBAD_PROOF;
|
||||
|
||||
|
||||
@@ -79,37 +79,13 @@ verifySendProofs(
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const hasAuditor = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount);
|
||||
auto const recipientCount = getConfidentialRecipientCount(hasAuditor);
|
||||
|
||||
// Extract proof components
|
||||
ProofReader reader(ctx.tx[sfZKProof]);
|
||||
|
||||
auto const equalityProof = reader.read(getEqualityProofSize(recipientCount));
|
||||
auto const amountLinkageProof = reader.read(ecPedersenProofLength);
|
||||
auto const balanceLinkageProof = reader.read(ecPedersenProofLength);
|
||||
auto const rangeProof = reader.read(ecDoubleBulletproofLength);
|
||||
|
||||
if (!equalityProof || !amountLinkageProof || !balanceLinkageProof || !rangeProof ||
|
||||
!reader.done())
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// Prepare recipient list
|
||||
std::vector<ConfidentialRecipient> recipients;
|
||||
recipients.reserve(recipientCount);
|
||||
|
||||
recipients.push_back(
|
||||
{(*sleSenderMPToken)[sfHolderEncryptionKey], ctx.tx[sfSenderEncryptedAmount]});
|
||||
recipients.push_back(
|
||||
{(*sleDestinationMPToken)[sfHolderEncryptionKey], ctx.tx[sfDestinationEncryptedAmount]});
|
||||
recipients.push_back({(*sleIssuance)[sfIssuerEncryptionKey], ctx.tx[sfIssuerEncryptedAmount]});
|
||||
|
||||
std::optional<ConfidentialRecipient> auditor;
|
||||
if (hasAuditor)
|
||||
{
|
||||
recipients.push_back(
|
||||
{(*sleIssuance)[sfAuditorEncryptionKey], ctx.tx[sfAuditorEncryptedAmount]});
|
||||
}
|
||||
auditor.emplace(
|
||||
ConfidentialRecipient{
|
||||
(*sleIssuance)[sfAuditorEncryptionKey], ctx.tx[sfAuditorEncryptedAmount]});
|
||||
|
||||
// Prepare the context hash
|
||||
auto const contextHash = getSendContextHash(
|
||||
ctx.tx[sfAccount],
|
||||
ctx.tx[sfMPTokenIssuanceID],
|
||||
@@ -117,75 +93,16 @@ verifySendProofs(
|
||||
ctx.tx[sfDestination],
|
||||
(*sleSenderMPToken)[~sfConfidentialBalanceVersion].value_or(0));
|
||||
|
||||
// Use a boolean flag to track validity instead of returning early on failure to prevent leaking
|
||||
// information about which proof failed through timing differences
|
||||
bool valid = true;
|
||||
|
||||
// Verify the multi-ciphertext equality proof
|
||||
if (auto const ter = verifyMultiCiphertextEqualityProof(
|
||||
*equalityProof, recipients, recipientCount, contextHash);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
|
||||
// Verify amount linkage
|
||||
if (auto const ter = verifyPcmLinkage(
|
||||
PcmLinkageType::amount,
|
||||
*amountLinkageProof,
|
||||
ctx.tx[sfSenderEncryptedAmount],
|
||||
(*sleSenderMPToken)[sfHolderEncryptionKey],
|
||||
ctx.tx[sfAmountCommitment],
|
||||
contextHash);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
|
||||
// Verify balance linkage
|
||||
if (auto const ter = verifyPcmLinkage(
|
||||
PcmLinkageType::balance,
|
||||
*balanceLinkageProof,
|
||||
(*sleSenderMPToken)[sfConfidentialBalanceSpending],
|
||||
(*sleSenderMPToken)[sfHolderEncryptionKey],
|
||||
ctx.tx[sfBalanceCommitment],
|
||||
contextHash);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
|
||||
// Verify Range Proof
|
||||
{
|
||||
// Derive PC_rem = PC_balance - PC_amount
|
||||
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()});
|
||||
|
||||
if (auto const ter = verifyAggregatedBulletproof(*rangeProof, commitments, contextHash);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "ConfidentialMPTSend: One or more cryptographic proofs failed.";
|
||||
return tecBAD_PROOF;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
return verifySendProof(
|
||||
ctx.tx[sfZKProof],
|
||||
{(*sleSenderMPToken)[sfHolderEncryptionKey], ctx.tx[sfSenderEncryptedAmount]},
|
||||
{(*sleDestinationMPToken)[sfHolderEncryptionKey], ctx.tx[sfDestinationEncryptedAmount]},
|
||||
{(*sleIssuance)[sfIssuerEncryptionKey], ctx.tx[sfIssuerEncryptedAmount]},
|
||||
auditor,
|
||||
(*sleSenderMPToken)[sfConfidentialBalanceSpending],
|
||||
ctx.tx[sfAmountCommitment],
|
||||
ctx.tx[sfBalanceCommitment],
|
||||
contextHash);
|
||||
}
|
||||
|
||||
TER
|
||||
|
||||
Reference in New Issue
Block a user