mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-24 15:40:26 +00:00
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:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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},
|
||||
}))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user