support new design to reveal blinding factor (#6237)

* reveal blinding factor and optimize
* schnorr proof is added for registering holder pub key
* clean env.close that already closed
* clean up the lib functions
This commit is contained in:
yinyiqian1
2026-01-19 13:07:19 -05:00
committed by GitHub
parent e3da98e310
commit 75d143a2a0
10 changed files with 928 additions and 871 deletions

View File

@@ -48,6 +48,108 @@ getConvertBackContextHash(
uint192 const& issuanceID,
std::uint64_t amount,
std::uint32_t version);
// breaks a 66-byte encrypted amount into two 33-byte components
// then parses each 33-byte component into 64-byte secp256k1_pubkey format
bool
makeEcPair(Slice const& buffer, secp256k1_pubkey& out1, secp256k1_pubkey& out2);
// serialize two secp256k1_pubkey components back into compressed 66-byte form
bool
serializeEcPair(
secp256k1_pubkey const& in1,
secp256k1_pubkey const& in2,
Buffer& buffer);
/**
* @brief Verifies that a buffer contains two valid, parsable EC public keys.
* @param buffer The input buffer containing two concatenated components.
* @return true if both components can be parsed successfully, false otherwise.
*/
bool
isValidCiphertext(Slice const& buffer);
TER
homomorphicAdd(Slice const& a, Slice const& b, Buffer& out);
TER
homomorphicSubtract(Slice const& a, Slice const& b, Buffer& out);
// returns ciphertext and the blinding factor used
std::optional<Buffer>
encryptAmount(
uint64_t const amt,
Slice const& pubKeySlice,
Slice const& blindingFactor);
Buffer
encryptCanonicalZeroAmount(
Slice const& pubKeySlice,
AccountID const& account,
MPTID const& mptId);
TER
verifySchnorrProof(
Slice const& pubKeySlice,
Slice const& proofSlice,
uint256 const& contextHash);
TER
verifyElGamalEncryption(
std::uint64_t const amount,
Slice const& blindingFactor,
Slice const& pubKeySlice,
Slice const& ciphertext);
TER
verifyClawbackEqualityProof(
uint64_t const amount,
Slice const& proof,
Slice const& pubKeySlice,
Slice const& ciphertext,
uint256 const& contextHash);
std::vector<Buffer>
getEqualityProofs(Slice const& zkp);
NotTEC
checkEncryptedAmountFormat(STObject const& object);
// Helper struct to bundle the ElGamal Public Key and the associated Ciphertext
struct EncryptedAmountInfo
{
Slice const publicKey;
Slice const encryptedAmount;
};
TER
verifyRevealedAmount(
std::uint64_t const amount,
Slice const& blindingFactor,
EncryptedAmountInfo const& holder,
EncryptedAmountInfo const& issuer,
std::optional<EncryptedAmountInfo> const& auditor);
// returns the number of entries
size_t inline getEqualityProofSize(bool const hasAuditor)
{
// Be careful if we ever need to change the numbers below, it will be a
// breaking change!
return (hasAuditor ? 3 : 2);
}
// returns the total byte length of all the equality proofs combined
size_t inline getEqualityProofLength(bool const hasAuditor)
{
return getEqualityProofSize(hasAuditor) * ecEqualityProofLength;
}
// generates a 32 byte randomness factor to be used in encryption and proofs
Buffer
generateBlindingFactor();
// The following functions belong to the mpt-crypto library,
// they will be finally removed and we will use conan2 to manage the dependency.
/**
* @brief Generates a new secp256k1 key pair.
*/
@@ -207,125 +309,42 @@ secp256k1_equality_plaintext_verify(
uint64_t amount,
unsigned char const* tx_context_id);
// breaks a 66-byte encrypted amount into two 33-byte components
// then parses each 33-byte component into 64-byte secp256k1_pubkey format
bool
makeEcPair(Slice const& buffer, secp256k1_pubkey& out1, secp256k1_pubkey& out2);
void
build_pok_challenge(
unsigned char* e,
secp256k1_context const* ctx,
secp256k1_pubkey const* pk,
secp256k1_pubkey const* T,
unsigned char const* context_id);
// serialize two secp256k1_pubkey components back into compressed 66-byte form
bool
serializeEcPair(
secp256k1_pubkey const& in1,
secp256k1_pubkey const& in2,
Buffer& buffer);
/** Proof of Knowledge of Secret Key for Registration */
int
secp256k1_mpt_pok_sk_prove(
secp256k1_context const* ctx,
unsigned char* proof, /* Expected size: 65 bytes */
secp256k1_pubkey const* pk,
unsigned char const* sk,
unsigned char const* context_id);
int
secp256k1_mpt_pok_sk_verify(
secp256k1_context const* ctx,
unsigned char const* proof, /* Expected size: 65 bytes */
secp256k1_pubkey const* pk,
unsigned char const* context_id);
/**
* @brief Verifies that a buffer contains two valid, parsable EC public keys.
* @param buffer The input buffer containing two concatenated components.
* @return true if both components can be parsed successfully, false otherwise.
* Verifies that (c1, c2) is a valid ElGamal encryption of 'amount'
* for 'pubkey_Q' using the revealed 'blinding_factor'.
*/
bool
isValidCiphertext(Slice const& buffer);
TER
homomorphicAdd(Slice const& a, Slice const& b, Buffer& out);
TER
homomorphicSubtract(Slice const& a, Slice const& b, Buffer& out);
TER
proveEquality(
Slice const& proof,
Slice const& encAmt, // encrypted amount
Slice const& pubkey,
uint64_t const amount,
uint256 const& txHash, // Transaction context data
std::uint32_t const spendVersion);
// returns ciphertext and the blinding factor used
Buffer
encryptAmount(
uint64_t const amt,
Slice const& pubKeySlice,
Slice const& blindingFactor);
Buffer
encryptCanonicalZeroAmount(
Slice const& pubKeySlice,
AccountID const& account,
MPTID const& mptId);
TER
verifyConfidentialSendProof(
Slice const& proof,
Slice const& encSenderBalance,
Slice const& encSenderAmt,
Slice const& encDestAmt,
Slice const& encIssuerAmt,
Slice const& senderPubKey,
Slice const& destPubKey,
Slice const& issuerPubKey,
std::uint32_t const version,
uint256 const& txHash);
TER
verifyEqualityProof(
uint64_t const amount,
Slice const& proof,
Slice const& pubKeySlice,
Slice const& ciphertext,
uint256 const& contextHash);
TER
verifyClawbackEqualityProof(
uint64_t const amount,
Slice const& proof,
Slice const& pubKeySlice,
Slice const& ciphertext,
uint256 const& contextHash);
std::vector<Buffer>
getEqualityProofs(Slice const& zkp);
NotTEC
checkEncryptedAmountFormat(STObject const& object);
// Helper struct to bundle the ElGamal Public Key and the associated Ciphertext
struct EncryptedAmountInfo
{
Slice const publicKey;
Slice const encryptedAmount;
};
/**
* Verifies equality proofs for Holder, Issuer, and optionally Auditor.
*/
TER
verifyEqualityProofs(
std::uint64_t amount,
std::vector<Buffer> const& zkps,
EncryptedAmountInfo const& holder,
EncryptedAmountInfo const& issuer,
std::optional<EncryptedAmountInfo> const& auditor,
uint256 const& contextHash);
// returns the number of entries
size_t inline getEqualityProofSize(bool const hasAuditor)
{
// Be careful if we ever need to change the numbers below, it will be a
// breaking change!
return (hasAuditor ? 3 : 2);
}
// returns the total byte length of all the equality proofs combined
size_t inline getEqualityProofLength(bool const hasAuditor)
{
return getEqualityProofSize(hasAuditor) * ecEqualityProofLength;
}
// generates a 32 byte randomness factor to be used in encryption and proofs
Buffer
generateBlindingFactor();
int
secp256k1_elgamal_verify_encryption(
secp256k1_context const* ctx,
secp256k1_pubkey const* c1,
secp256k1_pubkey const* c2,
secp256k1_pubkey const* pubkey_Q,
uint64_t amount,
unsigned char const* blinding_factor);
} // namespace ripple

View File

@@ -314,6 +314,9 @@ std::size_t constexpr ecPrivKeyLength = 32;
/** Length of the EC blinding factor */
std::size_t constexpr ecBlindingFactorLength = 32;
/** Length of Schnorr ZKProof for public key registration */
std::size_t constexpr ecSchnorrProofLength = 65;
} // namespace ripple
#endif

View File

@@ -312,6 +312,7 @@ TYPED_SFIELD(sfDestinationEncryptedAmount, VL, 41)
TYPED_SFIELD(sfAuditorEncryptedBalance, VL, 42)
TYPED_SFIELD(sfAuditorEncryptedAmount, VL, 43)
TYPED_SFIELD(sfAuditorElGamalPublicKey, VL, 44)
TYPED_SFIELD(sfBlindingFactor, VL, 45)
// account (common)
TYPED_SFIELD(sfAccount, ACCOUNT, 1)

View File

@@ -1075,7 +1075,8 @@ TRANSACTION(ttCONFIDENTIAL_CONVERT, 85, ConfidentialConvert,
{sfHolderEncryptedAmount, soeREQUIRED},
{sfIssuerEncryptedAmount, soeREQUIRED},
{sfAuditorEncryptedAmount, soeOPTIONAL},
{sfZKProof, soeREQUIRED},
{sfBlindingFactor, soeREQUIRED},
{sfZKProof, soeOPTIONAL},
}))
/** This transaction type merges MPT inbox. */
@@ -1104,6 +1105,7 @@ TRANSACTION(ttCONFIDENTIAL_CONVERT_BACK, 87, ConfidentialConvertBack,
{sfHolderEncryptedAmount, soeREQUIRED},
{sfIssuerEncryptedAmount, soeREQUIRED},
{sfAuditorEncryptedAmount, soeOPTIONAL},
{sfBlindingFactor, soeREQUIRED},
{sfZKProof, soeREQUIRED},
}))

View File

@@ -70,6 +70,357 @@ getConvertBackContextHash(
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)
{
// Local/temporary variables to pass to makeEcPair.
// Their contents will be discarded when the function returns.
secp256k1_pubkey key1;
secp256k1_pubkey key2;
// Call makeEcPair and return its result.
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)
{
Buffer buf(ecGamalEncryptedTotalLength);
// Allocate ciphertext placeholders
secp256k1_pubkey c1, c2;
secp256k1_pubkey pubKey;
if (blindingFactor.size() != ecBlindingFactorLength)
return std::nullopt;
std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength);
// Encrypt the amount
if (!secp256k1_elgamal_encrypt(
secp256k1Context(), &c1, &c2, &pubKey, amt, blindingFactor.data()))
return std::nullopt;
// Serialize the ciphertext pair into the buffer
if (!serializeEcPair(c1, c2, buf))
return std::nullopt;
return buf;
}
Buffer
encryptCanonicalZeroAmount(
Slice const& pubKeySlice,
AccountID const& account,
MPTID const& mptId)
{
Buffer buf(ecGamalEncryptedTotalLength);
// Allocate ciphertext placeholders
secp256k1_pubkey c1, c2;
secp256k1_pubkey pubKey;
std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength);
// Encrypt the amount
if (!generate_canonical_encrypted_zero(
secp256k1Context(),
&c1,
&c2,
&pubKey,
account.data(),
mptId.data()))
Throw<std::runtime_error>("Failed to encrypt amount");
// Serialize the ciphertext pair into the buffer
if (!serializeEcPair(c1, c2, buf))
Throw<std::runtime_error>(
"Failed to serialize into 66 byte compressed format");
return buf;
}
TER
verifySchnorrProof(
Slice const& pubKeySlice,
Slice const& proofSlice,
uint256 const& contextHash)
{
// sanity check proof length
if (proofSlice.size() != ecSchnorrProofLength)
return tecINTERNAL; // LCOV_EXCL_LINE
// sanity check public key length
if (pubKeySlice.size() != ecPubKeyLength)
return tecINTERNAL; // LCOV_EXCL_LINE
secp256k1_pubkey pubKey;
std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength);
int result = secp256k1_mpt_pok_sk_verify(
secp256k1Context(), proofSlice.data(), &pubKey, contextHash.data());
if (result != 1)
return tecBAD_PROOF;
return tesSUCCESS;
}
TER
verifyElGamalEncryption(
std::uint64_t const amount,
Slice const& blindingFactor,
Slice const& pubKeySlice,
Slice const& ciphertext)
{
// sanity check blinding factor length
if (blindingFactor.size() != ecBlindingFactorLength)
return tecINTERNAL; // LCOV_EXCL_LINE
// sanity check public key length
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
int result = secp256k1_elgamal_verify_encryption(
secp256k1Context(), &c1, &c2, &pubKey, amount, blindingFactor.data());
if (result != 1)
{
return tecBAD_PROOF;
}
return tesSUCCESS;
}
TER
verifyRevealedAmount(
std::uint64_t const amount,
Slice const& blindingFactor,
EncryptedAmountInfo const& holder,
EncryptedAmountInfo const& issuer,
std::optional<EncryptedAmountInfo> 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;
}
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);
if (secp256k1_equality_plaintext_verify(
secp256k1Context(),
proof.data(),
&pubKey,
&c2,
&c1,
amount,
contextHash.data()) != 1)
{
return tecBAD_PROOF;
}
return tesSUCCESS;
}
std::vector<Buffer>
getEqualityProofs(Slice const& zkp)
{
if (zkp.size() % ecEqualityProofLength != 0)
return {};
auto const count = zkp.size() / ecEqualityProofLength;
std::vector<Buffer> zkps;
zkps.reserve(count);
for (size_t i = 0; i < count; ++i)
zkps.emplace_back(
zkp.data() + (i * ecEqualityProofLength), ecEqualityProofLength);
return zkps;
}
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;
}
// The following functions belong to the mpt-crypto library,
// they will be finally removed and we will use conan2 to manage the dependency.
int
secp256k1_elgamal_generate_keypair(
secp256k1_context const* ctx,
@@ -96,7 +447,6 @@ secp256k1_elgamal_generate_keypair(
}
// ... implementation of secp256k1_elgamal_encrypt ...
int
secp256k1_elgamal_encrypt(
secp256k1_context const* ctx,
@@ -337,7 +687,6 @@ build_hash_input(
}
// The canonical encrypted zero
int
generate_canonical_encrypted_zero(
secp256k1_context const* ctx,
@@ -724,437 +1073,177 @@ secp256k1_equality_plaintext_verify(
return 1; /* Both equations passed */
}
bool
makeEcPair(Slice const& buffer, secp256k1_pubkey& out1, secp256k1_pubkey& out2)
void
build_pok_challenge(
unsigned char* e,
secp256k1_context const* ctx,
secp256k1_pubkey const* pk,
secp256k1_pubkey const* T,
unsigned char const* context_id)
{
auto parsePubKey = [](Slice const& slice, secp256k1_pubkey& out) {
return secp256k1_ec_pubkey_parse(
secp256k1Context(),
&out,
reinterpret_cast<unsigned char const*>(slice.data()),
slice.length());
};
SHA256_CTX sha;
unsigned char buf[33];
size_t len = 33;
Slice s1{buffer.data(), ecGamalEncryptedLength};
Slice s2{buffer.data() + ecGamalEncryptedLength, ecGamalEncryptedLength};
SHA256_Init(&sha);
// Domain Separator from LaTeX spec
SHA256_Update(&sha, "MPT_POK_SK_REGISTER", 19);
int const ret1 = parsePubKey(s1, out1);
int const ret2 = parsePubKey(s2, out2);
secp256k1_ec_pubkey_serialize(ctx, buf, &len, pk, SECP256K1_EC_COMPRESSED);
SHA256_Update(&sha, buf, 33);
return ret1 == 1 && ret2 == 1;
len = 33;
secp256k1_ec_pubkey_serialize(ctx, buf, &len, T, SECP256K1_EC_COMPRESSED);
SHA256_Update(&sha, buf, 33);
SHA256_Update(&sha, context_id, 32);
SHA256_Final(e, &sha);
}
bool
serializeEcPair(
secp256k1_pubkey const& in1,
secp256k1_pubkey const& in2,
Buffer& buffer)
int
secp256k1_mpt_pok_sk_prove(
secp256k1_context const* ctx,
unsigned char* proof,
secp256k1_pubkey const* pk,
unsigned char const* sk,
unsigned char const* context_id)
{
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 k[32], e[32], s[32];
secp256k1_pubkey T;
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)
{
// Local/temporary variables to pass to makeEcPair.
// Their contents will be discarded when the function returns.
secp256k1_pubkey key1;
secp256k1_pubkey key2;
// Call makeEcPair and return its result.
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;
}
TER
proveEquality(
Slice const& proof,
Slice const& encAmt, // encrypted amount
Slice const& pubkey,
uint64_t const amount,
uint256 const& txHash, // Transaction context data
std::uint32_t const spendVersion)
{
if (proof.length() != ecEqualityProofLength)
return tecINTERNAL;
secp256k1_pubkey c1;
secp256k1_pubkey c2;
if (!makeEcPair(encAmt, c1, c2))
return tecINTERNAL;
// todo: might need to change how its hashed
Serializer s;
s.addRaw(txHash.data(), txHash.bytes);
s.add32(spendVersion);
// auto const txContextId = s.getSHA512Half();
// todo: support equality
// if (secp256k1_equality_verify(
// secp256k1Context(),
// reinterpret_cast<unsigned char const*>(proof.data()),
// proof.length(), // Length of the proof byte array (98 bytes)
// &c1,
// &c2,
// reinterpret_cast<unsigned char const*>(pubkey.data()),
// amount,
// txContextId.data(), // Transaction context data
// txContextId.bytes // Length of context data
// ) != 1)
// return tecBAD_PROOF;
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);
}
Buffer
encryptAmount(
uint64_t const amt,
Slice const& pubKeySlice,
Slice const& blindingFactor)
{
Buffer buf(ecGamalEncryptedTotalLength);
// Allocate ciphertext placeholders
secp256k1_pubkey c1, c2;
if (blindingFactor.size() != 32)
Throw<std::runtime_error>("Random factor is not 32 bytes");
secp256k1_pubkey pubKey;
std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength);
// Encrypt the amount
if (!secp256k1_elgamal_encrypt(
secp256k1Context(), &c1, &c2, &pubKey, amt, blindingFactor.data()))
Throw<std::runtime_error>("Failed to encrypt amount");
// Serialize the ciphertext pair into the buffer
if (!serializeEcPair(c1, c2, buf))
Throw<std::runtime_error>(
"Failed to serialize into 66 byte compressed format");
return buf;
}
Buffer
encryptCanonicalZeroAmount(
Slice const& pubKeySlice,
AccountID const& account,
MPTID const& mptId)
{
Buffer buf(ecGamalEncryptedTotalLength);
// Allocate ciphertext placeholders
secp256k1_pubkey c1, c2;
secp256k1_pubkey pubKey;
std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength);
// Encrypt the amount
if (!generate_canonical_encrypted_zero(
secp256k1Context(),
&c1,
&c2,
&pubKey,
account.data(),
mptId.data()))
Throw<std::runtime_error>("Failed to encrypt amount");
// Serialize the ciphertext pair into the buffer
if (!serializeEcPair(c1, c2, buf))
Throw<std::runtime_error>(
"Failed to serialize into 66 byte compressed format");
return buf;
}
TER
verifyConfidentialSendProof(
Slice const& proof,
Slice const& encSenderBalance,
Slice const& encSenderAmt,
Slice const& encDestAmt,
Slice const& encIssuerAmt,
Slice const& senderPubKey,
Slice const& destPubKey,
Slice const& issuerPubKey,
std::uint32_t const version,
uint256 const& txHash)
{
// if (proof.length() != ecConfidentialSendProofLength)
// return tecINTERNAL;
secp256k1_pubkey balC1, balC2;
if (!makeEcPair(encSenderBalance, balC1, balC2))
return tecINTERNAL;
secp256k1_pubkey senderC1, senderC2;
if (!makeEcPair(encSenderAmt, senderC1, senderC2))
return tecINTERNAL;
secp256k1_pubkey destC1, destC2;
if (!makeEcPair(encDestAmt, destC1, destC2))
return tecINTERNAL;
secp256k1_pubkey issuerC1, issuerC2;
if (!makeEcPair(encIssuerAmt, issuerC1, issuerC2))
return tecINTERNAL;
Serializer s;
s.addRaw(txHash.data(), txHash.bytes);
s.add32(version);
// auto const txContextId = s.getSHA512Half();
// todo: equality and range proof verification
// if (secp256k1_equal_range_verify(
// secp256k1Context(),
// reinterpret_cast<unsigned char const*>(proof.data()),
// proof.length(),
// txContextId.data(),
// &balC1,
// &balC2,
// &senderC1,
// &senderC2,
// reinterpret_cast<unsigned char const*>(senderPubKey.data()),
// &destC1,
// &destC2,
// reinterpret_cast<unsigned char const*>(destPubKey.data()),
// &issuerC1,
// &issuerC2,
// reinterpret_cast<unsigned char const*>(issuerPubKey.data()),
// txContextId.data(),
// txContextId.bytes) != 1)
// return tecBAD_PROOF;
return tesSUCCESS;
}
TER
verifyEqualityProof(
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);
if (secp256k1_equality_plaintext_verify(
secp256k1Context(),
proof.data(),
&c1,
&c2,
&pubKey,
amount,
contextHash.data()) != 1)
// 1. Sample k and T = kG
do
{
return tecBAD_PROOF;
if (RAND_bytes(k, 32) != 1)
return 0;
} while (!secp256k1_ec_seckey_verify(ctx, k));
if (!secp256k1_ec_pubkey_create(ctx, &T, k))
return 0;
// 2. Challenge e
build_pok_challenge(e, ctx, pk, &T, context_id);
// 3. Response s = k + e*sk (mod n)
memcpy(s, sk, 32);
if (!secp256k1_ec_seckey_tweak_mul(ctx, s, e))
return 0;
if (!secp256k1_ec_seckey_tweak_add(ctx, s, k))
return 0;
// 4. Serialize Proof: T (33 bytes) || s (32 bytes)
size_t clen = 33;
secp256k1_ec_pubkey_serialize(
ctx, proof, &clen, &T, SECP256K1_EC_COMPRESSED);
memcpy(proof + 33, s, 32);
return 1;
}
int
secp256k1_mpt_pok_sk_verify(
secp256k1_context const* ctx,
unsigned char const* proof,
secp256k1_pubkey const* pk,
unsigned char const* context_id)
{
secp256k1_pubkey T, lhs, rhs, ePk;
unsigned char e[32], s[32];
// 1. Parse T and s
if (!secp256k1_ec_pubkey_parse(ctx, &T, proof, 33))
return 0;
memcpy(s, proof + 33, 32);
// 2. Challenge e
build_pok_challenge(e, ctx, pk, &T, context_id);
// 3. Verify sG = T + ePk
// LHS = s*G
if (!secp256k1_ec_pubkey_create(ctx, &lhs, s))
return 0;
// RHS = T + e*Pk
ePk = *pk;
if (!secp256k1_ec_pubkey_tweak_mul(ctx, &ePk, e))
return 0;
secp256k1_pubkey const* addends[2] = {&T, &ePk};
if (!secp256k1_ec_pubkey_combine(ctx, &rhs, addends, 2))
return 0;
// 4. Compare serialized points
unsigned char ser_lhs[33], ser_rhs[33];
size_t clen = 33;
secp256k1_ec_pubkey_serialize(
ctx, ser_lhs, &clen, &lhs, SECP256K1_EC_COMPRESSED);
clen = 33;
secp256k1_ec_pubkey_serialize(
ctx, ser_rhs, &clen, &rhs, SECP256K1_EC_COMPRESSED);
return memcmp(ser_lhs, ser_rhs, 33) == 0;
}
int
secp256k1_elgamal_verify_encryption(
secp256k1_context const* ctx,
secp256k1_pubkey const* c1,
secp256k1_pubkey const* c2,
secp256k1_pubkey const* pubkey_Q,
uint64_t amount,
unsigned char const* blinding_factor)
{
secp256k1_pubkey expected_c1, mG, s_shared, expected_c2;
unsigned char amount_scalar[32] = {0};
unsigned char ser1[33], ser2[33];
size_t len = 33;
if (secp256k1_ec_pubkey_create(ctx, &expected_c1, blinding_factor) != 1)
{
return 0;
}
return tesSUCCESS;
}
secp256k1_ec_pubkey_serialize(ctx, ser1, &len, c1, SECP256K1_EC_COMPRESSED);
len = 33;
secp256k1_ec_pubkey_serialize(
ctx, ser2, &len, &expected_c1, SECP256K1_EC_COMPRESSED);
if (memcmp(ser1, ser2, 33) != 0)
return 0;
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
// Calculate Shared Secret S = k * Q
s_shared = *pubkey_Q;
if (secp256k1_ec_pubkey_tweak_mul(ctx, &s_shared, blinding_factor) != 1)
return 0;
secp256k1_pubkey pubKey;
std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength);
if (secp256k1_equality_plaintext_verify(
secp256k1Context(),
proof.data(),
&pubKey,
&c2,
&c1,
amount,
contextHash.data()) != 1)
// Compare C2
if (amount == 0)
{
return tecBAD_PROOF;
expected_c2 = s_shared;
}
else
{
for (int i = 0; i < 8; ++i)
{
amount_scalar[31 - i] = (amount >> (i * 8)) & 0xFF;
}
if (secp256k1_ec_pubkey_create(ctx, &mG, amount_scalar) != 1)
return 0;
// Combine M + S
secp256k1_pubkey const* pts[2] = {&mG, &s_shared};
if (secp256k1_ec_pubkey_combine(ctx, &expected_c2, pts, 2) != 1)
return 0;
}
return tesSUCCESS;
}
len = 33;
secp256k1_ec_pubkey_serialize(ctx, ser1, &len, c2, SECP256K1_EC_COMPRESSED);
len = 33;
secp256k1_ec_pubkey_serialize(
ctx, ser2, &len, &expected_c2, SECP256K1_EC_COMPRESSED);
if (memcmp(ser1, ser2, 33) != 0)
return 0;
std::vector<Buffer>
getEqualityProofs(Slice const& zkp)
{
if (zkp.size() % ecEqualityProofLength != 0)
return {};
auto const count = zkp.size() / ecEqualityProofLength;
std::vector<Buffer> zkps;
zkps.reserve(count);
for (size_t i = 0; i < count; ++i)
zkps.emplace_back(
zkp.data() + (i * ecEqualityProofLength), ecEqualityProofLength);
return zkps;
}
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
verifyEqualityProofs(
std::uint64_t amount,
std::vector<Buffer> const& zkps,
EncryptedAmountInfo const& holder,
EncryptedAmountInfo const& issuer,
std::optional<EncryptedAmountInfo> const& auditor,
uint256 const& contextHash)
{
// Sanity check: Ensure we have enough proofs
size_t const required = getEqualityProofSize(auditor.has_value());
if (zkps.size() != required)
return tecINTERNAL; // LCOV_EXCL_LINE
// 1. Verify Holder Proof (Index 0)
if (!isTesSuccess(verifyEqualityProof(
amount,
zkps[0],
holder.publicKey,
holder.encryptedAmount,
contextHash)))
return tecBAD_PROOF;
// 2. Verify Issuer Proof (Index 1)
if (!isTesSuccess(verifyEqualityProof(
amount,
zkps[1],
issuer.publicKey,
issuer.encryptedAmount,
contextHash)))
return tecBAD_PROOF;
// 3. Verify Auditor Proof (Index 2) - if applicable
if (auditor)
{
if (!isTesSuccess(verifyEqualityProof(
amount,
zkps[2],
auditor->publicKey,
auditor->encryptedAmount,
contextHash)))
return tecBAD_PROOF;
}
return tesSUCCESS;
return 1; // Success: Encryption is valid
}
} // namespace ripple

View File

@@ -35,9 +35,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
@@ -86,9 +84,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(auditor);
@@ -135,9 +131,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
.flags = tfMPTCanTransfer | tfMPTCanLock});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(bob);
@@ -166,9 +160,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
.flags = tfMPTCanTransfer | tfMPTCanLock});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(bob);
@@ -179,6 +171,14 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
.holderPubKey = mptAlice.getPubKey(bob),
.err = temMALFORMED});
// blinding factor length is invalid
mptAlice.convert(
{.account = alice,
.amt = 10,
.holderPubKey = mptAlice.getPubKey(bob),
.blindingFactor = Buffer(10),
.err = temMALFORMED});
mptAlice.convert(
{.account = bob,
.amt = 10,
@@ -221,14 +221,89 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
.amt = 10,
.holderPubKey = Buffer{},
.err = temMALFORMED});
}
// todo: change to to check proof size
// mptAlice.convert(
// {.account = bob,
// .amt = 10,
// .proof = "123",
// .holderPubKey = mptAlice.getPubKey(bob),
// .err = temMALFORMED});
// when registering holder pub key, the transaction must include a
// Schnorr proof of knowledge for the corresponding secret key
{
Env env{*this, features};
Account const alice("alice");
Account const bob("bob");
MPTTester mptAlice(env, alice, {.holders = {bob}});
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
mptAlice.pay(alice, bob, 100);
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(bob);
mptAlice.set(
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.convert(
{.account = bob,
.amt = 10,
.fillSchnorrProof = false,
.holderPubKey = mptAlice.getPubKey(bob),
.err = temMALFORMED});
mptAlice.convert(
{.account = bob,
.amt = 0,
.fillSchnorrProof = false,
.holderPubKey = mptAlice.getPubKey(bob),
.err = temMALFORMED});
// proof length is invalid
mptAlice.convert(
{.account = bob,
.amt = 10,
.proof = std::string(10, 'A'),
.holderPubKey = mptAlice.getPubKey(bob),
.err = temMALFORMED});
}
// when holder pub key already registered, Schnorr proof must not be
// provided
{
Env env{*this, features};
Account const alice("alice");
Account const bob("bob");
MPTTester mptAlice(env, alice, {.holders = {bob}});
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
mptAlice.pay(alice, bob, 100);
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(bob);
mptAlice.set(
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
// this will register bob's pub key,
// and convert 10 to confidential balance
mptAlice.convert({
.account = bob,
.amt = 10,
.holderPubKey = mptAlice.getPubKey(bob),
});
// proof must not be provided after pub key was registered
mptAlice.convert(
{.account = bob,
.amt = 20,
.fillSchnorrProof = true,
.err = temMALFORMED});
}
}
@@ -245,14 +320,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
MPTTester mptAlice(env, alice, {.holders = {bob}});
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock});
{.ownerCount = 1, .flags = tfMPTCanTransfer | tfMPTCanLock});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(bob);
@@ -272,13 +343,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(bob);
@@ -298,14 +366,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
// no tfMPTCanPrivacy flag enabled
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock});
{.ownerCount = 1, .flags = tfMPTCanTransfer | tfMPTCanLock});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(bob);
@@ -331,14 +395,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
MPTTester mptAlice(env, alice, {.holders = {bob}});
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock});
{.ownerCount = 1, .flags = tfMPTCanTransfer | tfMPTCanLock});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(bob);
@@ -359,13 +419,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(bob);
@@ -386,7 +443,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
@@ -414,7 +470,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.generateKeyPair(alice);
@@ -439,13 +494,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
@@ -470,13 +522,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
@@ -507,13 +556,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
@@ -549,15 +595,12 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTRequireAuth |
tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
mptAlice.authorize({.account = alice, .holder = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
@@ -589,7 +632,34 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
});
}
// todo: test well formed proof
// invalid proof when registering holder pub key
{
Env env{*this, features};
Account const alice("alice");
Account const bob("bob");
MPTTester mptAlice(env, alice, {.holders = {bob}});
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
mptAlice.pay(alice, bob, 100);
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(bob);
mptAlice.set(
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.convert(
{.account = bob,
.amt = 10,
.proof = std::string(ecSchnorrProofLength * 2, 'A'),
.holderPubKey = mptAlice.getPubKey(bob),
.err = tecBAD_PROOF});
}
}
void
@@ -604,13 +674,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
@@ -642,13 +709,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
@@ -714,9 +778,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
.flags = tfMPTCanTransfer | tfMPTCanLock});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(bob);
@@ -757,9 +819,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
@@ -1006,7 +1066,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.create();
mptAlice.authorize({.account = bob});
mptAlice.authorize({.account = carol});
env.close();
mptAlice.send(
{.account = bob,
@@ -1044,7 +1103,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.pay(alice, bob, 100);
mptAlice.pay(alice, carol, 50);
env.close();
// issuer can not be the same as sender
mptAlice.send(
@@ -1179,7 +1237,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
// fund bob, carol (not dave or eve)
mptAlice.pay(alice, bob, 100);
mptAlice.pay(alice, carol, 50);
env.close();
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(bob);
@@ -1187,7 +1244,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(dave);
mptAlice.set(
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
env.close();
// bob and carol convert some funds to confidential
mptAlice.convert(
@@ -1228,7 +1284,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
// destroy the issuance
mptAlice.destroy();
env.close();
Json::Value jv;
jv[jss::Account] = bob.human();
@@ -1455,13 +1510,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
@@ -1497,9 +1549,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.authorize({.account = bob});
mptAlice.authorize({.account = carol});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
@@ -1541,9 +1591,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.generateKeyPair(alice);
mptAlice.set(
@@ -1573,13 +1620,9 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.generateKeyPair(alice);
mptAlice.set(
@@ -1620,9 +1663,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
@@ -1665,9 +1706,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(auditor);
@@ -1713,9 +1752,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
.flags = tfMPTCanTransfer | tfMPTCanLock});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(bob);
@@ -1736,9 +1773,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
@@ -1768,6 +1803,13 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
.amt = maxMPTokenAmount + 1,
.err = temBAD_AMOUNT});
// invalid blinding factor length
mptAlice.convertBack(
{.account = alice,
.amt = 30,
.blindingFactor = Buffer{},
.err = temMALFORMED});
mptAlice.convertBack(
{.account = bob,
.amt = 30,
@@ -1840,9 +1882,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
.flags = tfMPTCanTransfer | tfMPTCanLock});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(bob);
@@ -1886,9 +1926,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
@@ -1916,10 +1954,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.authorize({.account = bob});
mptAlice.authorize({.account = carol});
env.close();
mptAlice.pay(alice, bob, 100);
mptAlice.pay(alice, carol, 100);
env.close();
mptAlice.generateKeyPair(alice);
@@ -1964,9 +2000,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.authorize({.account = bob});
mptAlice.authorize({.account = alice, .holder = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
@@ -2352,7 +2386,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.create();
mptAlice.authorize({.account = bob});
env.close();
mptAlice.confidentialClaw(
{.account = alice,
@@ -2385,7 +2418,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.pay(alice, bob, 100);
mptAlice.pay(alice, carol, 50);
env.close();
// only issuer can clawback
mptAlice.confidentialClaw(
@@ -2520,7 +2552,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.set(
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
env.close();
mptAlice.confidentialClaw(
{.account = alice,
@@ -2538,7 +2569,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.create({.flags = tfMPTCanClawback | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
mptAlice.generateKeyPair(alice);
env.close();
mptAlice.confidentialClaw(
{.account = alice,
@@ -2561,7 +2591,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
// destroy the issuance
mptAlice.destroy();
env.close();
Json::Value jv;
jv[jss::Account] = alice.human();

View File

@@ -140,8 +140,7 @@ MPTTester::MPTTester(MPTInitDef const& arg)
{
}
MPTTester::
operator MPT() const
MPTTester::operator MPT() const
{
if (!id_)
Throw<std::runtime_error>("MPT has not been created");
@@ -695,8 +694,7 @@ MPTTester::mpt(std::int64_t amount) const
return ripple::test::jtx::MPT(issuer_.name(), *id_)(amount);
}
MPTTester::
operator Asset() const
MPTTester::operator Asset() const
{
if (!id_)
Throw<std::runtime_error>("MPT has not been created");
@@ -805,122 +803,30 @@ MPTTester::getClawbackProof(
}
Buffer
MPTTester::generateEqualityZKP(
Account const& holder,
std::uint64_t amount,
uint256 const& ctxHash,
Buffer const& holderCiphertext,
Buffer const& issuerCiphertext,
std::optional<Buffer> const& auditorCiphertext,
Buffer const& blindingFactor) const
MPTTester::getSchnorrProof(Account const& account, uint256 const& ctxHash) const
{
if (!id_)
Throw<std::runtime_error>("MPT has not been created");
auto const pubKey = getPubKey(account);
auto const privKey = getPrivKey(account);
auto const sleHolder = env_.le(keylet::mptoken(*id_, holder.id()));
auto const sleIssuance = env_.le(keylet::mptIssuance(*id_));
if (pubKey.size() != ecPubKeyLength)
Throw<std::runtime_error>("Invalid public key size");
size_t const zkpSize = auditorCiphertext ? 3 : 2;
size_t const zkpByteLength = zkpSize * ecEqualityProofLength;
secp256k1_pubkey pk;
std::memcpy(pk.data, pubKey.data(), ecPubKeyLength);
if (!sleHolder || !sleIssuance || holderCiphertext.size() == 0 ||
issuerCiphertext.size() == 0)
return Buffer(zkpByteLength);
Buffer proof(ecSchnorrProofLength);
auto const generateProof = [amount, ctxHash](
Buffer const& ciphertext,
Buffer const& pubKey,
Buffer const& blindingFactor) {
secp256k1_pubkey c1, c2;
auto const ctx = secp256k1Context();
if (!secp256k1_ec_pubkey_parse(
ctx, &c1, ciphertext.data(), ecGamalEncryptedLength) ||
!secp256k1_ec_pubkey_parse(
ctx,
&c2,
ciphertext.data() + ecGamalEncryptedLength,
ecGamalEncryptedLength))
{
return Buffer(ecEqualityProofLength);
}
secp256k1_pubkey pk;
std::memcpy(pk.data, pubKey.data(), ecPubKeyLength);
Buffer proof(ecEqualityProofLength);
if (secp256k1_equality_plaintext_prove(
ctx,
proof.data(),
&c1,
&c2,
&pk,
amount,
blindingFactor.data(),
ctxHash.data()) != 1)
{
Throw<std::runtime_error>("Proof generation failed");
}
return proof;
};
Buffer zkp(zkpByteLength);
Buffer holderZkp =
generateProof(holderCiphertext, getPubKey(holder), blindingFactor);
Buffer issuerZkp =
generateProof(issuerCiphertext, getPubKey(issuer_), blindingFactor);
// Pointer arithmetic to copy data into place
std::uint8_t* ptr = zkp.data();
// Copy Holder
std::memcpy(ptr, holderZkp.data(), holderZkp.size());
ptr += holderZkp.size();
// Copy Issuer
std::memcpy(ptr, issuerZkp.data(), issuerZkp.size());
ptr += issuerZkp.size();
if (auditorCiphertext)
if (secp256k1_mpt_pok_sk_prove(
secp256k1Context(),
proof.data(),
&pk,
privKey.data(),
ctxHash.data()) != 1)
{
Buffer auditorZkp(ecEqualityProofLength);
if (sleIssuance->isFieldPresent(sfAuditorElGamalPublicKey))
{
Buffer const auditorPubKey(
sleIssuance->getFieldVL(sfAuditorElGamalPublicKey).data(),
sleIssuance->getFieldVL(sfAuditorElGamalPublicKey).size());
auditorZkp = generateProof(
*auditorCiphertext, auditorPubKey, blindingFactor);
}
// Copy auditor
std::memcpy(ptr, auditorZkp.data(), auditorZkp.size());
ptr += auditorZkp.size();
Throw<std::runtime_error>("Schnorr Proof generation failed");
}
return zkp;
}
Buffer
MPTTester::getConvertProof(
Account const& holder,
std::uint64_t amount,
uint256 const& ctxHash,
Buffer const& holderCiphertext,
Buffer const& issuerCiphertext,
std::optional<Buffer> const& auditorCiphertext,
Buffer const& blindingFactor) const
{
return generateEqualityZKP(
holder,
amount,
ctxHash,
holderCiphertext,
issuerCiphertext,
auditorCiphertext,
blindingFactor);
return proof;
}
Buffer
@@ -933,18 +839,9 @@ MPTTester::getConvertBackProof(
std::optional<Buffer> const& auditorCiphertext,
Buffer const& blindingFactor) const
{
Buffer const equalityZkp = generateEqualityZKP(
holder,
amount,
ctxHash,
holderCiphertext,
issuerCiphertext,
auditorCiphertext,
blindingFactor);
// todo: incoporate pederson and range proof
return equalityZkp;
return Buffer{};
}
std::optional<Buffer>
@@ -1086,23 +983,20 @@ MPTTester::convert(MPTConvert const& arg)
auditorCiphertext,
blindingFactor);
jv[sfBlindingFactor.jsonName] = strHex(blindingFactor);
if (arg.proof)
jv[sfZKProof.jsonName] = *arg.proof;
else
else if (arg.fillSchnorrProof.value_or(arg.holderPubKey.has_value()))
{
// if the caller generated ciphertexts themselves, they should also
// generate the proof themselves from the blinding factor
uint256 const ctxHash = getConvertContextHash(
// whether to automatically generate and attach a Schnorr proof:
// if fillSchnorrProof is explicitly set, follow its value;
// otherwise, default to generating the proof only if holder pub key is
// present.
auto const ctxHash = getConvertContextHash(
arg.account->id(), env_.seq(*arg.account), *id_, *arg.amt);
Buffer proof = getConvertProof(
*arg.account,
*arg.amt,
ctxHash,
holderCiphertext,
issuerCiphertext,
auditorCiphertext,
blindingFactor);
jv[sfZKProof] = strHex(proof);
Buffer proof = getSchnorrProof(*arg.account, ctxHash);
jv[sfZKProof.jsonName] = strHex(proof);
}
auto const holderAmt = getBalance(*arg.account);
@@ -1473,7 +1367,15 @@ MPTTester::encryptAmount(
uint64_t const amt,
Buffer const& blindingFactor) const
{
return ripple::encryptAmount(amt, getPubKey(account), blindingFactor);
auto const result =
ripple::encryptAmount(amt, getPubKey(account), blindingFactor);
if (result)
return *result;
// Return a dummy buffer on failure to allow testing of
// failures that occur prior to encryption.
return Buffer(ecGamalEncryptedTotalLength);
}
uint64_t
@@ -1634,6 +1536,8 @@ MPTTester::convertBack(MPTConvertBack const& arg)
auditorCiphertext,
blindingFactor);
jv[sfBlindingFactor] = strHex(blindingFactor);
if (arg.proof)
jv[sfZKProof.jsonName] = *arg.proof;
else

View File

@@ -176,10 +176,17 @@ struct MPTConvert
std::optional<MPTID> id = std::nullopt;
std::optional<std::uint64_t> amt = std::nullopt;
std::optional<std::string> proof = std::nullopt;
// indicates whether to autofill schnorr proof.
// default : auto generate proof if holderPubKey is present.
// true: force proof generation.
// false: force proof omission.
std::optional<bool> fillSchnorrProof = std::nullopt;
std::optional<Buffer> holderPubKey = std::nullopt;
std::optional<Buffer> holderEncryptedAmt = std::nullopt;
std::optional<Buffer> issuerEncryptedAmt = std::nullopt;
std::optional<Buffer> auditorEncryptedAmt = std::nullopt;
// not an txn param, only used for autofilling
std::optional<Buffer> blindingFactor = std::nullopt;
std::optional<std::uint32_t> ownerCount = std::nullopt;
@@ -447,24 +454,7 @@ public:
uint256 const& txHash) const;
Buffer
generateEqualityZKP(
Account const& holder,
std::uint64_t amount,
uint256 const& ctxHash,
Buffer const& holderCiphertext,
Buffer const& issuerCiphertext,
std::optional<Buffer> const& auditorCiphertext,
Buffer const& blindingFactor) const;
Buffer
getConvertProof(
Account const& holder,
std::uint64_t amount,
uint256 const& ctxHash,
Buffer const& holderCiphertext,
Buffer const& issuerCiphertext,
std::optional<Buffer> const& auditorCiphertext,
Buffer const& blindingFactor) const;
getSchnorrProof(Account const& account, uint256 const& ctxHash) const;
Buffer
getConvertBackProof(

View File

@@ -21,19 +21,37 @@ ConfidentialConvert::preflight(PreflightContext const& ctx)
if (MPTIssue(ctx.tx[sfMPTokenIssuanceID]).getIssuer() == ctx.tx[sfAccount])
return temMALFORMED;
if (auto const res = checkEncryptedAmountFormat(ctx.tx); !isTesSuccess(res))
return res;
if (ctx.tx[sfMPTAmount] > maxMPTokenAmount)
return temBAD_AMOUNT;
if (ctx.tx.isFieldPresent(sfHolderElGamalPublicKey) &&
ctx.tx[sfHolderElGamalPublicKey].length() != ecPubKeyLength)
if (ctx.tx[sfBlindingFactor].size() != ecBlindingFactorLength)
return temMALFORMED;
if (ctx.tx[sfZKProof].size() !=
getEqualityProofLength(ctx.tx.isFieldPresent(sfAuditorEncryptedAmount)))
return temMALFORMED;
if (ctx.tx.isFieldPresent(sfHolderElGamalPublicKey))
{
if (ctx.tx[sfHolderElGamalPublicKey].length() != ecPubKeyLength)
return temMALFORMED;
// proof of knowledge of the secret key corresponding to the provided
// public key is needed when holder ec public key is being set.
if (!ctx.tx.isFieldPresent(sfZKProof))
return temMALFORMED;
// verify schnorr proof length when registerring holder ec public key
if (ctx.tx[sfZKProof].size() != ecSchnorrProofLength)
return temMALFORMED;
}
else
{
// zkp should not be present if public key was already set
if (ctx.tx.isFieldPresent(sfZKProof))
return temMALFORMED;
}
// check encrypted amount format after the above basic checks
// this check is more expensive so put it at the end
if (auto const res = checkEncryptedAmountFormat(ctx.tx); !isTesSuccess(res))
return res;
return tesSUCCESS;
}
@@ -41,9 +59,12 @@ ConfidentialConvert::preflight(PreflightContext const& ctx)
TER
ConfidentialConvert::preclaim(PreclaimContext const& ctx)
{
auto const account = ctx.tx[sfAccount];
auto const issuanceID = ctx.tx[sfMPTokenIssuanceID];
auto const amount = ctx.tx[sfMPTAmount];
// ensure that issuance exists
auto const sleIssuance =
ctx.view.read(keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID]));
auto const sleIssuance = ctx.view.read(keylet::mptIssuance(issuanceID));
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
@@ -52,7 +73,7 @@ ConfidentialConvert::preclaim(PreclaimContext const& ctx)
// already checked in preflight, but should also check that issuer on the
// issuance isn't the account either
if (sleIssuance->getAccountID(sfIssuer) == ctx.tx[sfAccount])
if (sleIssuance->getAccountID(sfIssuer) == account)
return tefINTERNAL; // LCOV_EXCL_LINE
// issuer has not uploaded their pub key yet
@@ -73,18 +94,16 @@ ConfidentialConvert::preclaim(PreclaimContext const& ctx)
if (!requiresAuditor && hasAuditor)
return tecNO_PERMISSION;
auto const sleMptoken = ctx.view.read(
keylet::mptoken(ctx.tx[sfMPTokenIssuanceID], ctx.tx[sfAccount]));
auto const sleMptoken = ctx.view.read(keylet::mptoken(issuanceID, account));
if (!sleMptoken)
return tecOBJECT_NOT_FOUND;
auto const mptIssue = MPTIssue{ctx.tx[sfMPTokenIssuanceID]};
auto const mptIssue = MPTIssue{issuanceID};
STAmount const mptAmount = STAmount(
MPTAmount{static_cast<MPTAmount::value_type>(ctx.tx[sfMPTAmount])},
mptIssue);
MPTAmount{static_cast<MPTAmount::value_type>(amount)}, mptIssue);
if (accountHolds(
ctx.view,
ctx.tx[sfAccount],
account,
mptIssue,
FreezeHandling::fhZERO_IF_FROZEN,
AuthHandling::ahZERO_IF_UNAUTHORIZED,
@@ -103,38 +122,42 @@ ConfidentialConvert::preclaim(PreclaimContext const& ctx)
ctx.tx.isFieldPresent(sfHolderElGamalPublicKey))
return tecDUPLICATE;
auto const holderPubKey = ctx.tx.isFieldPresent(sfHolderElGamalPublicKey)
? ctx.tx[sfHolderElGamalPublicKey]
: (*sleMptoken)[sfHolderElGamalPublicKey];
Slice holderPubKey;
if (ctx.tx.isFieldPresent(sfHolderElGamalPublicKey))
{
holderPubKey = ctx.tx[sfHolderElGamalPublicKey];
auto const contextHash = getConvertContextHash(
ctx.tx[sfAccount],
ctx.tx[sfSequence],
ctx.tx[sfMPTokenIssuanceID],
ctx.tx[sfMPTAmount]);
auto const contextHash = getConvertContextHash(
account, ctx.tx[sfSequence], issuanceID, amount);
std::vector<Buffer> const zkps = getEqualityProofs(ctx.tx[sfZKProof]);
// when register new pk, verify through schnorr proof
if (!isTesSuccess(verifySchnorrProof(
holderPubKey, ctx.tx[sfZKProof], contextHash)))
{
return tecBAD_PROOF;
}
}
else
{
holderPubKey = (*sleMptoken)[sfHolderElGamalPublicKey];
}
// Prepare Auditor Info
std::optional<EncryptedAmountInfo> auditor;
if (hasAuditor)
{
auditor.emplace(
EncryptedAmountInfo{
(*sleIssuance)[sfAuditorElGamalPublicKey],
ctx.tx[sfAuditorEncryptedAmount]});
auditor.emplace(EncryptedAmountInfo{
(*sleIssuance)[sfAuditorElGamalPublicKey],
ctx.tx[sfAuditorEncryptedAmount]});
}
return verifyEqualityProofs(
ctx.tx[sfMPTAmount],
zkps,
EncryptedAmountInfo{
holderPubKey, ctx.tx[sfHolderEncryptedAmount]}, // Holder
EncryptedAmountInfo{
(*sleIssuance)[sfIssuerElGamalPublicKey],
ctx.tx[sfIssuerEncryptedAmount]}, // Issuer
auditor,
contextHash);
return verifyRevealedAmount(
amount,
ctx.tx[sfBlindingFactor],
{holderPubKey, ctx.tx[sfHolderEncryptedAmount]},
{(*sleIssuance)[sfIssuerElGamalPublicKey],
ctx.tx[sfIssuerEncryptedAmount]},
auditor);
}
TER
@@ -219,20 +242,16 @@ ConfidentialConvert::doApply()
if (auditorEc)
(*sleMptoken)[sfAuditorEncryptedBalance] = *auditorEc;
try
{
// encrypt sfConfidentialBalanceSpending with zero balance
Buffer out;
out = encryptAmount(
0,
(*sleMptoken)[sfHolderElGamalPublicKey],
generateBlindingFactor());
(*sleMptoken)[sfConfidentialBalanceSpending] = out;
}
catch (std::exception const& e)
{
// encrypt sfConfidentialBalanceSpending with zero balance
auto const zeroBalance = encryptAmount(
0,
(*sleMptoken)[sfHolderElGamalPublicKey],
generateBlindingFactor());
if (!zeroBalance)
return tecINTERNAL; // LCOV_EXCL_LINE
}
(*sleMptoken)[sfConfidentialBalanceSpending] = *zeroBalance;
}
else
{

View File

@@ -28,16 +28,21 @@ ConfidentialConvertBack::preflight(PreflightContext const& ctx)
if (!ctx.rules.enabled(featureConfidentialTransfer))
return temDISABLED;
// issuer cannot convert
// issuer cannot convert back
if (MPTIssue(ctx.tx[sfMPTokenIssuanceID]).getIssuer() == ctx.tx[sfAccount])
return temMALFORMED;
if (auto const res = checkEncryptedAmountFormat(ctx.tx); !isTesSuccess(res))
return res;
if (ctx.tx[sfMPTAmount] == 0 || ctx.tx[sfMPTAmount] > maxMPTokenAmount)
return temBAD_AMOUNT;
if (ctx.tx[sfBlindingFactor].size() != ecBlindingFactorLength)
return temMALFORMED;
// check encrypted amount format after the above basic checks
// this check is more expensive so put it at the end
if (auto const res = checkEncryptedAmountFormat(ctx.tx); !isTesSuccess(res))
return res;
return tesSUCCESS;
}
@@ -47,47 +52,43 @@ verifyProofs(
std::shared_ptr<SLE const> const& issuance,
std::shared_ptr<SLE const> const& mptoken)
{
if (expectedProofLength(issuance) != tx[sfZKProof].size())
return tecBAD_PROOF;
if (!mptoken->isFieldPresent(sfHolderElGamalPublicKey))
return tecINTERNAL; // LCOV_EXCL_LINE
auto const mptIssuanceID = tx[sfMPTokenIssuanceID];
auto const account = tx[sfAccount];
// auto const mptIssuanceID = tx[sfMPTokenIssuanceID];
// auto const account = tx[sfAccount];
auto const amount = tx[sfMPTAmount];
auto const blindingFactor = tx[sfBlindingFactor];
auto const holderPubKey = (*mptoken)[sfHolderElGamalPublicKey];
auto const contextHash = getConvertBackContextHash(
account,
tx[sfSequence],
mptIssuanceID,
amount,
(*mptoken)[~sfConfidentialBalanceVersion].value_or(0));
// todo: commented out for now, will use for range proof
// auto const contextHash = getConvertBackContextHash(
// account,
// tx[sfSequence],
// mptIssuanceID,
// amount,
// (*mptoken)[~sfConfidentialBalanceVersion].value_or(0));
// Prepare Auditor Info
std::optional<EncryptedAmountInfo> auditor;
bool const hasAuditor = issuance->isFieldPresent(sfAuditorElGamalPublicKey);
if (hasAuditor)
{
auditor.emplace(
EncryptedAmountInfo{
(*issuance)[sfAuditorElGamalPublicKey],
tx[sfAuditorEncryptedAmount]});
auditor.emplace(EncryptedAmountInfo{
(*issuance)[sfAuditorElGamalPublicKey],
tx[sfAuditorEncryptedAmount]});
}
// verify equality proofs
{
auto const equalityZkps = getEqualityProofs(
Slice{tx[sfZKProof].data(), getEqualityProofLength(hasAuditor)});
return verifyEqualityProofs(
if (auto const ter = verifyRevealedAmount(
amount,
equalityZkps,
EncryptedAmountInfo{
(*mptoken)[sfHolderElGamalPublicKey],
tx[sfHolderEncryptedAmount]}, // Holder
EncryptedAmountInfo{
(*issuance)[sfIssuerElGamalPublicKey],
tx[sfIssuerEncryptedAmount]}, // Issuer
auditor, // Optional auditor
contextHash);
blindingFactor,
{holderPubKey, tx[sfHolderEncryptedAmount]},
{(*issuance)[sfIssuerElGamalPublicKey],
tx[sfIssuerEncryptedAmount]},
auditor);
!isTesSuccess(ter))
{
return ter;
}
return tesSUCCESS;