Files
rippled/src/libxrpl/protocol/ConfidentialTransfer.cpp

476 lines
13 KiB
C++

#include <xrpl/protocol/ConfidentialTransfer.h>
#include <xrpl/protocol/Protocol.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
namespace xrpl {
void
addCommonZKPFields(
Serializer& s,
std::uint16_t txType,
AccountID const& account,
std::uint32_t sequence,
uint192 const& issuanceID)
{
s.add16(txType);
s.addBitString(account);
s.add32(sequence);
s.addBitString(issuanceID);
}
uint256
getSendContextHash(
AccountID const& account,
std::uint32_t sequence,
uint192 const& issuanceID,
AccountID const& destination,
std::uint32_t version)
{
Serializer s;
addCommonZKPFields(s, ttCONFIDENTIAL_MPT_SEND, account, sequence, issuanceID);
s.addBitString(destination);
s.addInteger(version);
return s.getSHA512Half();
}
uint256
getClawbackContextHash(
AccountID const& account,
std::uint32_t sequence,
uint192 const& issuanceID,
std::uint64_t amount,
AccountID const& holder)
{
Serializer s;
addCommonZKPFields(s, ttCONFIDENTIAL_MPT_CLAWBACK, account, sequence, issuanceID);
s.add64(amount);
s.addBitString(holder);
return s.getSHA512Half();
}
uint256
getConvertContextHash(AccountID const& account, std::uint32_t sequence, uint192 const& issuanceID, std::uint64_t amount)
{
Serializer s;
addCommonZKPFields(s, ttCONFIDENTIAL_MPT_CONVERT, account, sequence, issuanceID);
s.add64(amount);
return s.getSHA512Half();
}
uint256
getConvertBackContextHash(
AccountID const& account,
std::uint32_t sequence,
uint192 const& issuanceID,
std::uint64_t amount,
std::uint32_t version)
{
Serializer s;
addCommonZKPFields(s, ttCONFIDENTIAL_MPT_CONVERT_BACK, account, sequence, issuanceID);
s.add64(amount);
s.addInteger(version);
return s.getSHA512Half();
}
bool
makeEcPair(Slice const& buffer, secp256k1_pubkey& out1, secp256k1_pubkey& out2)
{
auto parsePubKey = [](Slice const& slice, secp256k1_pubkey& out) {
return secp256k1_ec_pubkey_parse(
secp256k1Context(), &out, reinterpret_cast<unsigned char const*>(slice.data()), slice.length());
};
Slice s1{buffer.data(), ecGamalEncryptedLength};
Slice s2{buffer.data() + ecGamalEncryptedLength, ecGamalEncryptedLength};
int const ret1 = parsePubKey(s1, out1);
int const ret2 = parsePubKey(s2, out2);
return ret1 == 1 && ret2 == 1;
}
bool
serializeEcPair(secp256k1_pubkey const& in1, secp256k1_pubkey const& in2, Buffer& buffer)
{
auto serializePubKey = [](secp256k1_pubkey const& pub, unsigned char* out) {
size_t outLen = ecGamalEncryptedLength; // 33 bytes
int const ret = secp256k1_ec_pubkey_serialize(secp256k1Context(), out, &outLen, &pub, SECP256K1_EC_COMPRESSED);
return ret == 1 && outLen == ecGamalEncryptedLength;
};
unsigned char* ptr = buffer.data();
bool const res1 = serializePubKey(in1, ptr);
bool const res2 = serializePubKey(in2, ptr + ecGamalEncryptedLength);
return res1 && res2;
}
bool
isValidCiphertext(Slice const& buffer)
{
secp256k1_pubkey key1;
secp256k1_pubkey key2;
return makeEcPair(buffer, key1, key2);
}
TER
homomorphicAdd(Slice const& a, Slice const& b, Buffer& out)
{
if (a.length() != ecGamalEncryptedTotalLength || b.length() != ecGamalEncryptedTotalLength)
return tecINTERNAL;
secp256k1_pubkey aC1;
secp256k1_pubkey aC2;
secp256k1_pubkey bC1;
secp256k1_pubkey bC2;
if (!makeEcPair(a, aC1, aC2) || !makeEcPair(b, bC1, bC2))
return tecINTERNAL;
secp256k1_pubkey sumC1;
secp256k1_pubkey sumC2;
if (secp256k1_elgamal_add(secp256k1Context(), &sumC1, &sumC2, &aC1, &aC2, &bC1, &bC2) != 1)
return tecINTERNAL;
if (!serializeEcPair(sumC1, sumC2, out))
return tecINTERNAL;
return tesSUCCESS;
}
TER
homomorphicSubtract(Slice const& a, Slice const& b, Buffer& out)
{
if (a.length() != ecGamalEncryptedTotalLength || b.length() != ecGamalEncryptedTotalLength)
return tecINTERNAL;
secp256k1_pubkey aC1;
secp256k1_pubkey aC2;
secp256k1_pubkey bC1;
secp256k1_pubkey bC2;
if (!makeEcPair(a, aC1, aC2) || !makeEcPair(b, bC1, bC2))
return tecINTERNAL;
secp256k1_pubkey diffC1;
secp256k1_pubkey diffC2;
if (secp256k1_elgamal_subtract(secp256k1Context(), &diffC1, &diffC2, &aC1, &aC2, &bC1, &bC2) != 1)
return tecINTERNAL;
if (!serializeEcPair(diffC1, diffC2, out))
return tecINTERNAL;
return tesSUCCESS;
}
Buffer
generateBlindingFactor()
{
unsigned char blindingFactor[ecBlindingFactorLength];
// todo: might need to be updated using another RNG
if (RAND_bytes(blindingFactor, ecBlindingFactorLength) != 1)
Throw<std::runtime_error>("Failed to generate random number");
return Buffer(blindingFactor, ecBlindingFactorLength);
}
std::optional<Buffer>
encryptAmount(uint64_t const amt, Slice const& pubKeySlice, Slice const& blindingFactor)
{
if (blindingFactor.size() != ecBlindingFactorLength)
return std::nullopt;
secp256k1_pubkey c1, c2, pubKey;
std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength);
if (!secp256k1_elgamal_encrypt(secp256k1Context(), &c1, &c2, &pubKey, amt, blindingFactor.data()))
return std::nullopt;
Buffer buf(ecGamalEncryptedTotalLength);
if (!serializeEcPair(c1, c2, buf))
return std::nullopt;
return buf;
}
std::optional<Buffer>
encryptCanonicalZeroAmount(Slice const& pubKeySlice, AccountID const& account, MPTID const& mptId)
{
if (pubKeySlice.size() != ecPubKeyLength)
return std::nullopt; // LCOV_EXCL_LINE
secp256k1_pubkey c1, c2, pubKey;
std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength);
if (!generate_canonical_encrypted_zero(secp256k1Context(), &c1, &c2, &pubKey, account.data(), mptId.data()))
return std::nullopt;
Buffer buf(ecGamalEncryptedTotalLength);
if (!serializeEcPair(c1, c2, buf))
return std::nullopt;
return buf;
}
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;
std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength);
if (secp256k1_mpt_pok_sk_verify(secp256k1Context(), proofSlice.data(), &pubKey, contextHash.data()) != 1)
return tecBAD_PROOF;
return tesSUCCESS;
}
TER
verifyElGamalEncryption(
std::uint64_t const amount,
Slice const& blindingFactor,
Slice const& pubKeySlice,
Slice const& ciphertext)
{
if (blindingFactor.size() != ecBlindingFactorLength)
return tecINTERNAL; // LCOV_EXCL_LINE
if (pubKeySlice.size() != ecPubKeyLength)
return tecINTERNAL; // LCOV_EXCL_LINE
secp256k1_pubkey pubKey;
std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength);
secp256k1_pubkey c1, c2;
if (!makeEcPair(ciphertext, c1, c2))
return tecINTERNAL; // LCOV_EXCL_LINE
if (secp256k1_elgamal_verify_encryption(secp256k1Context(), &c1, &c2, &pubKey, amount, blindingFactor.data()) != 1)
return tecBAD_PROOF;
return tesSUCCESS;
}
TER
verifyRevealedAmount(
std::uint64_t const amount,
Slice const& blindingFactor,
ConfidentialRecipient const& holder,
ConfidentialRecipient const& issuer,
std::optional<ConfidentialRecipient> const& auditor)
{
if (auto const res = verifyElGamalEncryption(amount, blindingFactor, holder.publicKey, holder.encryptedAmount);
!isTesSuccess(res))
{
return res;
}
if (auto const res = verifyElGamalEncryption(amount, blindingFactor, issuer.publicKey, issuer.encryptedAmount);
!isTesSuccess(res))
{
return res;
}
if (auditor)
{
if (auto const res =
verifyElGamalEncryption(amount, blindingFactor, auditor->publicKey, auditor->encryptedAmount);
!isTesSuccess(res))
{
return res;
}
}
return tesSUCCESS;
}
std::size_t
getMultiCiphertextEqualityProofSize(std::size_t nRecipients)
{
// Points (33 bytes): T_m (1) + T_rG (nRecipients) + T_rP (nRecipients) = 1
// + 2nRecipients Scalars (32 bytes): s_m (1) + s_r (nRecipients) = 1 +
// nRecipients
return ((1 + (2 * nRecipients)) * 33) + ((1 + nRecipients) * 32);
}
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() != getMultiCiphertextEqualityProofSize(nRecipients))
return tecINTERNAL; // LCOV_EXCL_LINE
std::vector<secp256k1_pubkey> r(nRecipients);
std::vector<secp256k1_pubkey> s(nRecipients);
std::vector<secp256k1_pubkey> pk(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 (!makeEcPair(recipient.encryptedAmount, r[i], s[i]))
return tecINTERNAL; // LCOV_EXCL_LINE
if (recipient.publicKey.size() != ecPubKeyLength)
return tecINTERNAL; // LCOV_EXCL_LINE
std::memcpy(pk[i].data, recipient.publicKey.data(), ecPubKeyLength);
}
int const result = secp256k1_mpt_verify_same_plaintext_multi(
secp256k1Context(), proof.data(), proof.size(), nRecipients, r.data(), s.data(), pk.data(), contextHash.data());
if (result != 1)
return tecBAD_PROOF;
return tesSUCCESS;
}
TER
verifyClawbackEqualityProof(
uint64_t const amount,
Slice const& proof,
Slice const& pubKeySlice,
Slice const& ciphertext,
uint256 const& contextHash)
{
secp256k1_pubkey c1, c2;
if (!makeEcPair(ciphertext, c1, c2))
return tecINTERNAL; // LCOV_EXCL_LINE
secp256k1_pubkey pubKey;
std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength);
// 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 (secp256k1_equality_plaintext_verify(
secp256k1Context(), proof.data(), &pubKey, &c2, &c1, amount, contextHash.data()) != 1)
{
return tecBAD_PROOF;
}
return tesSUCCESS;
}
NotTEC
checkEncryptedAmountFormat(STObject const& object)
{
if (object[sfHolderEncryptedAmount].length() != ecGamalEncryptedTotalLength ||
object[sfIssuerEncryptedAmount].length() != ecGamalEncryptedTotalLength)
return temBAD_CIPHERTEXT;
bool const hasAuditor = object.isFieldPresent(sfAuditorEncryptedAmount);
if (hasAuditor && object[sfAuditorEncryptedAmount].length() != ecGamalEncryptedTotalLength)
return temBAD_CIPHERTEXT;
if (!isValidCiphertext(object[sfHolderEncryptedAmount]) || !isValidCiphertext(object[sfIssuerEncryptedAmount]))
return temBAD_CIPHERTEXT;
if (hasAuditor && !isValidCiphertext(object[sfAuditorEncryptedAmount]))
return temBAD_CIPHERTEXT;
return tesSUCCESS;
}
TER
verifyAmountPcmLinkage(
Slice const& proof,
Slice const& encAmt,
Slice const& pubKeySlice,
Slice const& pcmSlice,
uint256 const& contextHash)
{
if (proof.length() != ecPedersenProofLength)
return tecINTERNAL;
secp256k1_pubkey c1, c2;
if (!makeEcPair(encAmt, c1, c2))
return tecINTERNAL; // LCOV_EXCL_LINE
secp256k1_pubkey pubKey;
if (pubKeySlice.size() != ecPubKeyLength)
return tecINTERNAL; // LCOV_EXCL_LINE
secp256k1_pubkey pcm;
if (pcmSlice.size() != ecPedersenCommitmentLength)
return tecINTERNAL; // LCOV_EXCL_LINE
std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength);
std::memcpy(pcm.data, pcmSlice.data(), ecPedersenCommitmentLength);
if (secp256k1_elgamal_pedersen_link_verify(
secp256k1Context(), proof.data(), &c1, &c2, &pubKey, &pcm, contextHash.data()) != 1)
{
return tecBAD_PROOF;
}
return tesSUCCESS;
}
TER
verifyBalancePcmLinkage(
Slice const& proof,
Slice const& encAmt,
Slice const& pubKeySlice,
Slice const& pcmSlice,
uint256 const& contextHash)
{
if (proof.length() != ecPedersenProofLength)
return tecINTERNAL;
secp256k1_pubkey c1;
secp256k1_pubkey c2;
if (!makeEcPair(encAmt, c1, c2))
return tecINTERNAL; // LCOV_EXCL_LINE
secp256k1_pubkey pubKey;
if (pubKeySlice.size() != ecPubKeyLength)
return tecINTERNAL; // LCOV_EXCL_LINE
secp256k1_pubkey pcm;
if (pcmSlice.size() != ecPedersenCommitmentLength)
return tecINTERNAL; // LCOV_EXCL_LINE
std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength);
std::memcpy(pcm.data, pcmSlice.data(), ecPubKeyLength);
// Note: c2, c1 order - the linkage proof expects the message-containing
// component (c2 = m*G + r*Pk) before the blinding component (c1 = r*G).
if (secp256k1_elgamal_pedersen_link_verify(
secp256k1Context(), proof.data(), &pubKey, &c2, &c1, &pcm, contextHash.data()) != 1)
{
return tecBAD_PROOF;
}
return tesSUCCESS;
}
} // namespace xrpl