Support ConfidentialSend equality proof (#6274)

* Support ConfidentialSend equality proof

* resolve conflicts

* Add version check in send
This commit is contained in:
yinyiqian1
2026-01-26 12:39:35 -05:00
committed by GitHub
parent 114adc0c57
commit 1297385b7e
9 changed files with 1187 additions and 492 deletions

View File

@@ -16,15 +16,38 @@
#include <secp256k1.h>
namespace ripple {
// Helper struct to bundle the ElGamal Public Key and the associated Ciphertext
struct ConfidentialRecipient
{
Slice const publicKey;
Slice const encryptedAmount;
};
inline void
incrementConfidentialVersion(STObject& mptoken)
{
// Retrieve current version and increment.
// Unsigned integer overflow is defined behavior in C++ (wraps to 0),
// which is acceptable here.
mptoken[sfConfidentialBalanceVersion] =
mptoken[~sfConfidentialBalanceVersion].value_or(0u) + 1u;
}
void
addCommonZKPFields(
Serializer& s,
std::uint16_t txType,
AccountID const& account,
std::uint32_t sequence,
uint192 const& issuanceID);
uint256
getSendContextHash(
AccountID const& account,
std::uint32_t sequence,
uint192 const& issuanceID,
std::uint64_t amount);
AccountID const& destination,
std::uint32_t version);
uint256
getClawbackContextHash(
@@ -101,6 +124,33 @@ verifyElGamalEncryption(
Slice const& pubKeySlice,
Slice const& ciphertext);
NotTEC
checkEncryptedAmountFormat(STObject const& object);
TER
verifyRevealedAmount(
std::uint64_t const amount,
Slice const& blindingFactor,
ConfidentialRecipient const& holder,
ConfidentialRecipient const& issuer,
std::optional<ConfidentialRecipient> const& auditor);
constexpr std::size_t
getConfidentialRecipientCount(bool hasAuditor)
{
return hasAuditor ? 4 : 3;
}
std::size_t
getMultiCiphertextEqualityProofSize(std::size_t nRecipients);
TER
verifyMultiCiphertextEqualityProof(
Slice const& proof,
std::vector<ConfidentialRecipient> const& recipients,
std::size_t const nRecipients,
uint256 const& contextHash);
TER
verifyClawbackEqualityProof(
uint64_t const amount,
@@ -109,24 +159,6 @@ verifyClawbackEqualityProof(
Slice const& ciphertext,
uint256 const& contextHash);
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);
// generates a 32 byte randomness factor to be used in encryption and proofs
Buffer
generateBlindingFactor();
@@ -392,6 +424,46 @@ secp256k1_mpt_pedersen_commit(
uint64_t amount,
unsigned char const* blinding_factor_rho /* 32 bytes */
);
// Multi-proof for same plaintexts
void
build_hash_input(
unsigned char* hash_out, // Output: 32-byte hash
size_t n,
secp256k1_pubkey const* R,
secp256k1_pubkey const* S,
secp256k1_pubkey const* Pk,
secp256k1_pubkey const* T_m,
secp256k1_pubkey const* T_rG,
secp256k1_pubkey const* T_rP,
unsigned char const* tx_id);
size_t
secp256k1_mpt_prove_same_plaintext_multi_size(size_t n);
int
secp256k1_mpt_prove_same_plaintext_multi(
secp256k1_context const* ctx,
unsigned char* proof_out,
size_t* proof_len,
uint64_t amount_m,
size_t n,
secp256k1_pubkey const* R,
secp256k1_pubkey const* S,
secp256k1_pubkey const* Pk,
unsigned char const* r_array,
unsigned char const* tx_id);
int
secp256k1_mpt_verify_same_plaintext_multi(
secp256k1_context const* ctx,
unsigned char const* proof,
size_t proof_len,
size_t n,
secp256k1_pubkey const* R,
secp256k1_pubkey const* S,
secp256k1_pubkey const* Pk,
unsigned char const* tx_id);
} // namespace ripple
#endif

View File

@@ -318,6 +318,9 @@ std::size_t constexpr ecBlindingFactorLength = 32;
/** Length of Schnorr ZKProof for public key registration */
std::size_t constexpr ecSchnorrProofLength = 65;
/** Length of ElGamal ciphertext equality proof */
std::size_t constexpr ecCiphertextEqualityProofLength = 261;
/** Length of ElGamal Pedersen linkage proof */
std::size_t constexpr ecPedersenProofLength = 195;

View File

@@ -11,14 +11,29 @@ addCommonZKPFields(
std::uint16_t txType,
AccountID const& account,
std::uint32_t sequence,
uint192 const& issuanceID,
std::uint64_t amount)
uint192 const& issuanceID)
{
s.add16(txType);
s.addBitString(account);
s.add32(sequence);
s.addBitString(issuanceID);
s.add64(amount);
}
uint256
getSendContextHash(
AccountID const& account,
std::uint32_t sequence,
uint192 const& issuanceID,
AccountID const& destination,
std::uint32_t version)
{
Serializer s;
addCommonZKPFields(s, ttCONFIDENTIAL_SEND, account, sequence, issuanceID);
s.addBitString(destination);
s.addInteger(version);
return s.getSHA512Half();
}
uint256
@@ -31,8 +46,9 @@ getClawbackContextHash(
{
Serializer s;
addCommonZKPFields(
s, ttCONFIDENTIAL_CLAWBACK, account, sequence, issuanceID, amount);
s, ttCONFIDENTIAL_CLAWBACK, account, sequence, issuanceID);
s.add64(amount);
s.addBitString(holder);
return s.getSHA512Half();
@@ -47,7 +63,9 @@ getConvertContextHash(
{
Serializer s;
addCommonZKPFields(
s, ttCONFIDENTIAL_CONVERT, account, sequence, issuanceID, amount);
s, ttCONFIDENTIAL_CONVERT, account, sequence, issuanceID);
s.add64(amount);
return s.getSHA512Half();
}
@@ -62,8 +80,9 @@ getConvertBackContextHash(
{
Serializer s;
addCommonZKPFields(
s, ttCONFIDENTIAL_CONVERT_BACK, account, sequence, issuanceID, amount);
s, ttCONFIDENTIAL_CONVERT_BACK, account, sequence, issuanceID);
s.add64(amount);
s.addInteger(version);
return s.getSHA512Half();
@@ -314,9 +333,9 @@ TER
verifyRevealedAmount(
std::uint64_t const amount,
Slice const& blindingFactor,
EncryptedAmountInfo const& holder,
EncryptedAmountInfo const& issuer,
std::optional<EncryptedAmountInfo> const& auditor)
ConfidentialRecipient const& holder,
ConfidentialRecipient const& issuer,
std::optional<ConfidentialRecipient> const& auditor)
{
if (auto const res = verifyElGamalEncryption(
amount, blindingFactor, holder.publicKey, holder.encryptedAmount);
@@ -348,6 +367,63 @@ verifyRevealedAmount(
return tesSUCCESS;
}
std::size_t
getMultiCiphertextEqualityProofSize(std::size_t nRecipients)
{
// Points (33 bytes): T_m (1) + T_rG (nRecipients) + T_rP (nRecipients) = 1
// + 2nRecipients Scalars (32 bytes): s_m (1) + s_r (nRecipients) = 1 +
// nRecipients
return ((1 + (2 * nRecipients)) * 33) + ((1 + nRecipients) * 32);
}
TER
verifyMultiCiphertextEqualityProof(
Slice const& proof,
std::vector<ConfidentialRecipient> const& recipients,
std::size_t const nRecipients,
uint256 const& contextHash)
{
if (recipients.size() != nRecipients)
return tecINTERNAL; // LCOV_EXCL_LINE
if (proof.size() != getMultiCiphertextEqualityProofSize(nRecipients))
return tecINTERNAL; // LCOV_EXCL_LINE
std::vector<secp256k1_pubkey> r(nRecipients);
std::vector<secp256k1_pubkey> s(nRecipients);
std::vector<secp256k1_pubkey> pk(nRecipients);
for (size_t i = 0; i < nRecipients; ++i)
{
auto const& recipient = recipients[i];
if (recipient.encryptedAmount.size() != ecGamalEncryptedTotalLength)
return tecINTERNAL; // LCOV_EXCL_LINE
if (!makeEcPair(recipient.encryptedAmount, r[i], s[i]))
return tecINTERNAL; // LCOV_EXCL_LINE
if (recipient.publicKey.size() != ecPubKeyLength)
return tecINTERNAL; // LCOV_EXCL_LINE
std::memcpy(pk[i].data, recipient.publicKey.data(), ecPubKeyLength);
}
int const result = secp256k1_mpt_verify_same_plaintext_multi(
secp256k1Context(),
proof.data(),
proof.size(),
nRecipients,
r.data(),
s.data(),
pk.data(),
contextHash.data());
if (result != 1)
return tecBAD_PROOF;
return tesSUCCESS;
}
TER
verifyClawbackEqualityProof(
uint64_t const amount,
@@ -1532,4 +1608,309 @@ secp256k1_mpt_pedersen_commit(
return 1;
}
// Multi-proof for same plaintexts
/**
* Builds the challenge hash: Domain || PublicInputs || Commitments || TxID
*/
void
build_hash_input(
unsigned char* hash_out, // Output: 32-byte hash
size_t n,
secp256k1_pubkey const* R,
secp256k1_pubkey const* S,
secp256k1_pubkey const* Pk,
secp256k1_pubkey const* T_m,
secp256k1_pubkey const* T_rG,
secp256k1_pubkey const* T_rP,
unsigned char const* tx_id)
{
SHA256_CTX sha_ctx;
char const* domain = "MPT_POK_SAME_PLAINTEXT_PROOF";
unsigned char buf[33];
size_t len = 33;
size_t i;
secp256k1_context* ser_ctx =
secp256k1_context_create(SECP256K1_CONTEXT_NONE);
SHA256_Init(&sha_ctx);
SHA256_Update(&sha_ctx, domain, strlen(domain));
// Public Inputs (R, S, Pk for each ciphertext)
for (i = 0; i < n; ++i)
{
secp256k1_ec_pubkey_serialize(
ser_ctx, buf, &len, &R[i], SECP256K1_EC_COMPRESSED);
SHA256_Update(&sha_ctx, buf, 33);
secp256k1_ec_pubkey_serialize(
ser_ctx, buf, &len, &S[i], SECP256K1_EC_COMPRESSED);
SHA256_Update(&sha_ctx, buf, 33);
secp256k1_ec_pubkey_serialize(
ser_ctx, buf, &len, &Pk[i], SECP256K1_EC_COMPRESSED);
SHA256_Update(&sha_ctx, buf, 33);
}
// Commitments
secp256k1_ec_pubkey_serialize(
ser_ctx, buf, &len, T_m, SECP256K1_EC_COMPRESSED);
SHA256_Update(&sha_ctx, buf, 33);
for (i = 0; i < n; ++i)
{
secp256k1_ec_pubkey_serialize(
ser_ctx, buf, &len, &T_rG[i], SECP256K1_EC_COMPRESSED);
SHA256_Update(&sha_ctx, buf, 33);
secp256k1_ec_pubkey_serialize(
ser_ctx, buf, &len, &T_rP[i], SECP256K1_EC_COMPRESSED);
SHA256_Update(&sha_ctx, buf, 33);
}
SHA256_Update(&sha_ctx, tx_id, 32);
SHA256_Final(hash_out, &sha_ctx);
secp256k1_context_destroy(ser_ctx);
}
/* --- Public API --- */
size_t
secp256k1_mpt_prove_same_plaintext_multi_size(size_t n)
{
// (1 point T_m + 2*N points T_r) * 33 + (1 scalar s_m + N scalars s_r) * 32
return ((1 + 2 * n) * 33) + ((1 + n) * 32);
}
int
secp256k1_mpt_prove_same_plaintext_multi(
secp256k1_context const* ctx,
unsigned char* proof_out,
size_t* proof_len,
uint64_t amount_m,
size_t n,
secp256k1_pubkey const* R,
secp256k1_pubkey const* S,
secp256k1_pubkey const* Pk,
unsigned char const* r_array,
unsigned char const* tx_id)
{
size_t required_len = secp256k1_mpt_prove_same_plaintext_multi_size(n);
if (*proof_len < required_len)
{
*proof_len = required_len;
return 0;
}
*proof_len = required_len;
unsigned char k_m[32];
unsigned char k_r[n][32];
secp256k1_pubkey T_m;
secp256k1_pubkey T_rG[n];
secp256k1_pubkey T_rP[n];
unsigned char e[32];
unsigned char s_m[32];
unsigned char s_r[n][32];
size_t i;
int ok = 1;
/* 1. Generate Randomness & Commitments */
if (!generate_random_scalar(ctx, k_m))
return 0;
if (!secp256k1_ec_pubkey_create(ctx, &T_m, k_m))
ok = 0;
for (i = 0; i < n; ++i)
{
if (!generate_random_scalar(ctx, k_r[i]))
ok = 0;
if (!secp256k1_ec_pubkey_create(ctx, &T_rG[i], k_r[i]))
ok = 0;
T_rP[i] = Pk[i];
if (!secp256k1_ec_pubkey_tweak_mul(ctx, &T_rP[i], k_r[i]))
ok = 0;
}
if (!ok)
return 0;
/* 2. Compute Challenge e */
build_hash_input(e, n, R, S, Pk, &T_m, T_rG, T_rP, tx_id);
// Ensure e is valid
if (!secp256k1_ec_seckey_verify(ctx, e))
return 0;
/* 3. Compute Responses */
/* s_m = k_m + e * m */
unsigned char m_scalar[32] = {0};
for (i = 0; i < 8; ++i)
m_scalar[31 - i] = (amount_m >> (i * 8)) & 0xFF;
memcpy(s_m, k_m, 32);
unsigned char term[32];
memcpy(term, m_scalar, 32);
if (!secp256k1_ec_seckey_tweak_mul(ctx, term, e))
return 0;
if (!secp256k1_ec_seckey_tweak_add(ctx, s_m, term))
return 0;
/* s_ri = k_ri + e * ri */
for (i = 0; i < n; ++i)
{
memcpy(s_r[i], k_r[i], 32);
memcpy(term, &r_array[i * 32], 32); // Extract r_i from flat array
if (!secp256k1_ec_seckey_tweak_mul(ctx, term, e))
return 0;
if (!secp256k1_ec_seckey_tweak_add(ctx, s_r[i], term))
return 0;
}
/* 4. Serialize Proof */
size_t offset = 0;
size_t len = 33;
// Points
secp256k1_ec_pubkey_serialize(
ctx, proof_out + offset, &len, &T_m, SECP256K1_EC_COMPRESSED);
offset += 33;
for (i = 0; i < n; ++i)
{
secp256k1_ec_pubkey_serialize(
ctx, proof_out + offset, &len, &T_rG[i], SECP256K1_EC_COMPRESSED);
offset += 33;
}
for (i = 0; i < n; ++i)
{
secp256k1_ec_pubkey_serialize(
ctx, proof_out + offset, &len, &T_rP[i], SECP256K1_EC_COMPRESSED);
offset += 33;
}
// Scalars
memcpy(proof_out + offset, s_m, 32);
offset += 32;
for (i = 0; i < n; ++i)
{
memcpy(proof_out + offset, s_r[i], 32);
offset += 32;
}
return 1;
}
int
secp256k1_mpt_verify_same_plaintext_multi(
secp256k1_context const* ctx,
unsigned char const* proof,
size_t proof_len,
size_t n,
secp256k1_pubkey const* R,
secp256k1_pubkey const* S,
secp256k1_pubkey const* Pk,
unsigned char const* tx_id)
{
if (proof_len != secp256k1_mpt_prove_same_plaintext_multi_size(n))
return 0;
/* Deserialize */
size_t offset = 0;
secp256k1_pubkey T_m;
secp256k1_pubkey T_rG[n];
secp256k1_pubkey T_rP[n];
unsigned char s_m[32];
unsigned char s_r[n][32];
size_t i;
if (!secp256k1_ec_pubkey_parse(ctx, &T_m, proof + offset, 33))
return 0;
offset += 33;
for (i = 0; i < n; ++i)
{
if (!secp256k1_ec_pubkey_parse(ctx, &T_rG[i], proof + offset, 33))
return 0;
offset += 33;
}
for (i = 0; i < n; ++i)
{
if (!secp256k1_ec_pubkey_parse(ctx, &T_rP[i], proof + offset, 33))
return 0;
offset += 33;
}
memcpy(s_m, proof + offset, 32);
offset += 32;
for (i = 0; i < n; ++i)
{
memcpy(s_r[i], proof + offset, 32);
offset += 32;
}
/* Recompute Challenge */
unsigned char e[32];
build_hash_input(e, n, R, S, Pk, &T_m, T_rG, T_rP, tx_id);
/* Verify Equations */
secp256k1_pubkey lhs, rhs, term, SmG;
secp256k1_pubkey const* add_pt[3];
unsigned char b1[33], b2[33];
size_t len;
// Precompute s_m * G
if (!secp256k1_ec_pubkey_create(ctx, &SmG, s_m))
return 0;
for (i = 0; i < n; ++i)
{
/* Check 1: s_ri * G == T_ri_G + e * R_i */
if (!secp256k1_ec_pubkey_create(ctx, &lhs, s_r[i]))
return 0;
term = R[i];
if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, e))
return 0;
add_pt[0] = &T_rG[i];
add_pt[1] = &term;
if (!secp256k1_ec_pubkey_combine(ctx, &rhs, add_pt, 2))
return 0;
len = 33;
secp256k1_ec_pubkey_serialize(
ctx, b1, &len, &lhs, SECP256K1_EC_COMPRESSED);
len = 33;
secp256k1_ec_pubkey_serialize(
ctx, b2, &len, &rhs, SECP256K1_EC_COMPRESSED);
if (memcmp(b1, b2, 33) != 0)
return 0;
/* Check 2: s_m * G + s_ri * P_i == T_m + T_ri_P + e * S_i */
/* LHS = SmG + s_r[i] * Pk[i] */
term = Pk[i];
if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, s_r[i]))
return 0;
add_pt[0] = &SmG;
add_pt[1] = &term;
if (!secp256k1_ec_pubkey_combine(ctx, &lhs, add_pt, 2))
return 0;
/* RHS = T_m + T_rP[i] + e * S[i] */
term = S[i];
if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, e))
return 0;
add_pt[0] = &T_m;
add_pt[1] = &T_rP[i];
add_pt[2] = &term;
if (!secp256k1_ec_pubkey_combine(ctx, &rhs, add_pt, 3))
return 0;
len = 33;
secp256k1_ec_pubkey_serialize(
ctx, b1, &len, &lhs, SECP256K1_EC_COMPRESSED);
len = 33;
secp256k1_ec_pubkey_serialize(
ctx, b2, &len, &rhs, SECP256K1_EC_COMPRESSED);
if (memcmp(b1, b2, 33) != 0)
return 0;
}
return 1;
}
} // namespace ripple

View File

@@ -9,14 +9,44 @@ namespace ripple {
class ConfidentialTransfer_test : public beast::unit_test::suite
{
// A 66-byte array of random unsigned char values
constexpr static unsigned char badCiphertext[ecGamalEncryptedTotalLength] =
{0x3E, 0x9A, 0x0F, 0x7C, 0x51, 0xD8, 0x22, 0x8B, 0x6E, 0x14, 0xC9,
0xF5, 0x4D, 0x6A, 0x03, 0x81, 0x77, 0x2B, 0xEE, 0x9F, 0x10, 0xC2,
0x57, 0x3D, 0x88, 0x65, 0x0C, 0xAB, 0xF1, 0x4E, 0x19, 0x96, 0x2A,
0x73, 0xDC, 0x44, 0xB8, 0x5F, 0x01, 0xEA, 0x87, 0x36, 0x60, 0xCE,
0x92, 0x25, 0x7D, 0x5B, 0xC0, 0x1E, 0x48, 0xF9, 0x84, 0x33, 0x67,
0xAD, 0x0B, 0xE3, 0x91, 0x50, 0xDA, 0x2F, 0x75, 0xC6, 0xBD, 0x42};
// Get a bad ciphertext with valid structure but cryptographic invalid for
// testing purposes. For preflight test purposes.
static Buffer const&
getBadCiphertext()
{
static Buffer const badCiphertext = []() {
Buffer buf(ecGamalEncryptedTotalLength);
std::memset(buf.data(), 0xFF, ecGamalEncryptedTotalLength);
buf.data()[0] = 0x02;
buf.data()[ecGamalEncryptedLength] = 0x02;
return buf;
}();
return badCiphertext;
}
// Get a trivial buffer that is structurally and mathematically valid, but
// contains invalid data that does not match the ledger state. For preclaim
// test purposes.
static Buffer const&
getTrivialCiphertext()
{
static Buffer const trivialCiphertext = []() {
Buffer buf(ecGamalEncryptedTotalLength);
std::memset(buf.data(), 0, ecGamalEncryptedTotalLength);
buf.data()[0] = 0x02;
buf.data()[ecGamalEncryptedLength] = 0x02;
buf.data()[ecGamalEncryptedLength - 1] = 0x01;
buf.data()[ecGamalEncryptedTotalLength - 1] = 0x01;
return buf;
}();
return trivialCiphertext;
}
void
testConvert(FeatureBitset features)
@@ -203,16 +233,14 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
{.account = bob,
.amt = 1,
.holderPubKey = mptAlice.getPubKey(bob),
.holderEncryptedAmt =
Buffer{badCiphertext, ecGamalEncryptedTotalLength},
.holderEncryptedAmt = getBadCiphertext(),
.err = temBAD_CIPHERTEXT});
mptAlice.convert(
{.account = bob,
.amt = 1,
.holderPubKey = mptAlice.getPubKey(bob),
.issuerEncryptedAmt =
Buffer{badCiphertext, ecGamalEncryptedTotalLength},
.issuerEncryptedAmt = getBadCiphertext(),
.err = temBAD_CIPHERTEXT});
// invalid pub key
@@ -845,7 +873,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
@@ -865,41 +892,18 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = bob,
.amt = 60,
.holderPubKey = mptAlice.getPubKey(bob),
.err = tesSUCCESS});
BEAST_EXPECT(mptAlice.getBalance(bob) == 40);
BEAST_EXPECT(
mptAlice.getDecryptedBalance(
bob, MPTTester::HOLDER_ENCRYPTED_INBOX) == 60);
BEAST_EXPECT(
mptAlice.getDecryptedBalance(
bob, MPTTester::HOLDER_ENCRYPTED_SPENDING) == 0);
BEAST_EXPECT(
mptAlice.getDecryptedBalance(
bob, MPTTester::ISSUER_ENCRYPTED_BALANCE) == 60);
.holderPubKey = mptAlice.getPubKey(bob)});
// bob merge inbox
mptAlice.mergeInbox({
.account = bob,
});
// carol convert 20 to confidential
mptAlice.convert(
{.account = carol,
.amt = 20,
.holderPubKey = mptAlice.getPubKey(carol),
.err = tesSUCCESS});
BEAST_EXPECT(mptAlice.getBalance(carol) == 30);
BEAST_EXPECT(
mptAlice.getDecryptedBalance(
carol, MPTTester::HOLDER_ENCRYPTED_INBOX) == 20);
BEAST_EXPECT(
mptAlice.getDecryptedBalance(
carol, MPTTester::HOLDER_ENCRYPTED_SPENDING) == 0);
BEAST_EXPECT(
mptAlice.getDecryptedBalance(
carol, MPTTester::ISSUER_ENCRYPTED_BALANCE) == 20);
.holderPubKey = mptAlice.getPubKey(carol)});
// carol merge inbox
mptAlice.mergeInbox({
@@ -907,32 +911,29 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
});
// bob sends 10 to carol
mptAlice.send(
{.account = bob,
.dest = carol,
.amt = 10, // will be encrypted internally
.proof = "123",
.err = tesSUCCESS});
mptAlice.send({
.account = bob,
.dest = carol,
.amt = 10,
});
// bob sends 1 to carol again
mptAlice.send(
{.account = bob,
.dest = carol,
.amt = 1,
.proof = "123",
.err = tesSUCCESS});
mptAlice.send({
.account = bob,
.dest = carol,
.amt = 1,
});
mptAlice.mergeInbox({
.account = carol,
});
// carol sends 15 backto bob
mptAlice.send(
{.account = carol,
.dest = bob,
.amt = 15,
.proof = "123",
.err = tesSUCCESS});
mptAlice.send({
.account = carol,
.dest = bob,
.amt = 15,
});
}
void
@@ -950,7 +951,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
@@ -973,22 +973,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = bob,
.amt = 60,
.holderPubKey = mptAlice.getPubKey(bob),
.err = tesSUCCESS});
BEAST_EXPECT(mptAlice.getBalance(bob) == 40);
BEAST_EXPECT(
mptAlice.getDecryptedBalance(
bob, MPTTester::HOLDER_ENCRYPTED_INBOX) == 60);
BEAST_EXPECT(
mptAlice.getDecryptedBalance(
bob, MPTTester::HOLDER_ENCRYPTED_SPENDING) == 0);
BEAST_EXPECT(
mptAlice.getDecryptedBalance(
bob, MPTTester::ISSUER_ENCRYPTED_BALANCE) == 60);
BEAST_EXPECT(
mptAlice.getDecryptedBalance(
bob, MPTTester::AUDITOR_ENCRYPTED_BALANCE) == 60);
.holderPubKey = mptAlice.getPubKey(bob)});
// bob merge inbox
mptAlice.mergeInbox({
@@ -998,22 +983,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = carol,
.amt = 20,
.holderPubKey = mptAlice.getPubKey(carol),
.err = tesSUCCESS});
BEAST_EXPECT(mptAlice.getBalance(carol) == 30);
BEAST_EXPECT(
mptAlice.getDecryptedBalance(
carol, MPTTester::HOLDER_ENCRYPTED_INBOX) == 20);
BEAST_EXPECT(
mptAlice.getDecryptedBalance(
carol, MPTTester::HOLDER_ENCRYPTED_SPENDING) == 0);
BEAST_EXPECT(
mptAlice.getDecryptedBalance(
carol, MPTTester::ISSUER_ENCRYPTED_BALANCE) == 20);
BEAST_EXPECT(
mptAlice.getDecryptedBalance(
carol, MPTTester::AUDITOR_ENCRYPTED_BALANCE) == 20);
.holderPubKey = mptAlice.getPubKey(carol)});
// carol merge inbox
mptAlice.mergeInbox({
@@ -1021,32 +991,17 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
});
// bob sends 10 to carol
mptAlice.send(
{.account = bob,
.dest = carol,
.amt = 10, // will be encrypted internally
.proof = "123",
.err = tesSUCCESS});
mptAlice.send({.account = bob, .dest = carol, .amt = 10});
// bob sends 1 to carol again
mptAlice.send(
{.account = bob,
.dest = carol,
.amt = 1,
.proof = "123",
.err = tesSUCCESS});
mptAlice.send({.account = bob, .dest = carol, .amt = 1});
mptAlice.mergeInbox({
.account = carol,
});
// carol sends 15 backto bob
mptAlice.send(
{.account = carol,
.dest = bob,
.amt = 15,
.proof = "123",
.err = tesSUCCESS});
mptAlice.send({.account = carol, .dest = bob, .amt = 15});
}
void
@@ -1071,7 +1026,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
{.account = bob,
.dest = carol,
.amt = 10,
.proof = "123",
.senderEncryptedAmt =
Buffer(ripple::ecGamalEncryptedTotalLength),
.destEncryptedAmt =
@@ -1090,9 +1044,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanPrivacy});
{.ownerCount = 1, .flags = tfMPTCanTransfer | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
mptAlice.authorize({.account = carol});
@@ -1104,12 +1056,23 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.pay(alice, bob, 100);
mptAlice.pay(alice, carol, 50);
mptAlice.convert({
.account = bob,
.amt = 50,
.holderPubKey = mptAlice.getPubKey(bob),
});
mptAlice.convert({
.account = carol,
.amt = 40,
.holderPubKey = mptAlice.getPubKey(carol),
});
// issuer can not be the same as sender
mptAlice.send(
{.account = alice,
.dest = carol,
.amt = 10,
.proof = "123",
.senderEncryptedAmt =
Buffer(ripple::ecGamalEncryptedTotalLength),
.destEncryptedAmt =
@@ -1123,7 +1086,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
{.account = bob,
.dest = bob,
.amt = 10,
.proof = "123",
.senderEncryptedAmt =
Buffer(ripple::ecGamalEncryptedTotalLength),
.destEncryptedAmt =
@@ -1137,7 +1099,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
{.account = bob,
.dest = carol,
.amt = 10,
.proof = "123",
.senderEncryptedAmt = Buffer(10),
.destEncryptedAmt =
Buffer(ripple::ecGamalEncryptedTotalLength),
@@ -1149,7 +1110,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
{.account = bob,
.dest = carol,
.amt = 10,
.proof = "123",
.senderEncryptedAmt =
Buffer(ripple::ecGamalEncryptedTotalLength),
.destEncryptedAmt = Buffer(10),
@@ -1161,7 +1121,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
{.account = bob,
.dest = carol,
.amt = 10,
.proof = "123",
.senderEncryptedAmt =
Buffer(ripple::ecGamalEncryptedTotalLength),
.destEncryptedAmt =
@@ -1169,43 +1128,46 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
.issuerEncryptedAmt = Buffer(10),
.err = temBAD_CIPHERTEXT});
auto const ciphertextHex = generatePlaceholderCiphertext();
// auto const ciphertextHex = generatePlaceholderCiphertext();
// sender encrypted amount malformed
mptAlice.send(
{.account = bob,
.dest = carol,
.amt = 10,
.proof = "123",
.senderEncryptedAmt =
Buffer(ripple::ecGamalEncryptedTotalLength),
.destEncryptedAmt = ciphertextHex,
.issuerEncryptedAmt = ciphertextHex,
.err = temBAD_CIPHERTEXT});
// dest encrypted amount malformed
mptAlice.send(
{.account = bob,
.dest = carol,
.amt = 10,
.proof = "123",
.senderEncryptedAmt = ciphertextHex,
.destEncryptedAmt =
Buffer(ripple::ecGamalEncryptedTotalLength),
.issuerEncryptedAmt = ciphertextHex,
.err = temBAD_CIPHERTEXT});
// issuer encrypted amount malformed
mptAlice.send(
{.account = bob,
.dest = carol,
.amt = 10,
.proof = "123",
.senderEncryptedAmt = ciphertextHex,
.destEncryptedAmt = ciphertextHex,
.issuerEncryptedAmt =
Buffer(ripple::ecGamalEncryptedTotalLength),
.err = temBAD_CIPHERTEXT});
// todo: proof length check
// invalid proof length
mptAlice.send(
{.account = bob,
.dest = carol,
.amt = 10,
.senderEncryptedAmt =
Buffer(ripple::ecGamalEncryptedTotalLength),
.destEncryptedAmt =
Buffer(ripple::ecGamalEncryptedTotalLength),
.issuerEncryptedAmt =
Buffer(ripple::ecGamalEncryptedTotalLength),
.proof = std::string(10, 'A'),
.err = temMALFORMED});
}
}
@@ -1265,8 +1227,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
.account = carol,
});
auto const ciphertextHex = generatePlaceholderCiphertext();
// issuance not found
{
Env env{*this, features};
@@ -1290,12 +1250,13 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
jv[jss::Destination] = carol.human();
jv[jss::TransactionType] = jss::ConfidentialSend;
jv[sfMPTokenIssuanceID] = to_string(mptAlice.issuanceID());
jv[sfSenderEncryptedAmount] = strHex(getTrivialCiphertext());
jv[sfDestinationEncryptedAmount] = strHex(getTrivialCiphertext());
jv[sfIssuerEncryptedAmount] = strHex(getTrivialCiphertext());
auto const encryptedAmt = strHex(ciphertextHex);
jv[sfSenderEncryptedAmount] = encryptedAmt;
jv[sfDestinationEncryptedAmount] = encryptedAmt;
jv[sfIssuerEncryptedAmount] = encryptedAmt;
jv[sfZKProof] = "123";
auto const dummyProofSize =
secp256k1_mpt_prove_same_plaintext_multi_size(3);
jv[sfZKProof.jsonName] = strHex(Buffer(dummyProofSize));
env(jv, ter(tecOBJECT_NOT_FOUND));
}
@@ -1307,9 +1268,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
{.account = bob,
.dest = unknown,
.amt = 10,
.proof = "123",
.destEncryptedAmt = ciphertextHex,
.issuerEncryptedAmt = ciphertextHex,
.destEncryptedAmt = getTrivialCiphertext(),
.issuerEncryptedAmt = getTrivialCiphertext(),
.err = tecNO_TARGET});
}
@@ -1319,13 +1279,11 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
{.account = bob,
.dest = dave,
.amt = 10,
.proof = "123",
.err = tecNO_PERMISSION});
mptAlice.send(
{.account = dave,
.dest = carol,
.amt = 10,
.proof = "123",
.err = tecNO_PERMISSION});
}
@@ -1335,8 +1293,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
{.account = bob,
.dest = eve,
.amt = 10,
.proof = "123",
.destEncryptedAmt = ciphertextHex,
.destEncryptedAmt = getTrivialCiphertext(),
.err = tecOBJECT_NOT_FOUND});
}
@@ -1345,16 +1302,11 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
// lock issuance
mptAlice.set({.account = alice, .flags = tfMPTLock});
mptAlice.send(
{.account = bob,
.dest = carol,
.amt = 10,
.proof = "123",
.err = tecLOCKED});
{.account = bob, .dest = carol, .amt = 10, .err = tecLOCKED});
// unlock issuance
mptAlice.set({.account = alice, .flags = tfMPTUnlock});
// now can send
mptAlice.send(
{.account = bob, .dest = carol, .amt = 1, .proof = "123"});
mptAlice.send({.account = bob, .dest = carol, .amt = 1});
}
// sender is locked
@@ -1362,17 +1314,12 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
// lock bob
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
mptAlice.send(
{.account = bob,
.dest = carol,
.amt = 10,
.proof = "123",
.err = tecLOCKED});
{.account = bob, .dest = carol, .amt = 10, .err = tecLOCKED});
// unlock bob
mptAlice.set(
{.account = alice, .holder = bob, .flags = tfMPTUnlock});
// now can send
mptAlice.send(
{.account = bob, .dest = carol, .amt = 2, .proof = "123"});
mptAlice.send({.account = bob, .dest = carol, .amt = 2});
}
// destination is locked
@@ -1381,17 +1328,12 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.set(
{.account = alice, .holder = carol, .flags = tfMPTLock});
mptAlice.send(
{.account = bob,
.dest = carol,
.amt = 10,
.proof = "123",
.err = tecLOCKED});
{.account = bob, .dest = carol, .amt = 10, .err = tecLOCKED});
// unlock carol
mptAlice.set(
{.account = alice, .holder = carol, .flags = tfMPTUnlock});
// now can send
mptAlice.send(
{.account = bob, .dest = carol, .amt = 3, .proof = "123"});
mptAlice.send({.account = bob, .dest = carol, .amt = 3});
}
// sender not authorized
@@ -1400,19 +1342,14 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.authorize(
{.account = alice, .holder = bob, .flags = tfMPTUnauthorize});
mptAlice.send(
{.account = bob,
.dest = carol,
.amt = 10,
.proof = "123",
.err = tecNO_AUTH});
{.account = bob, .dest = carol, .amt = 10, .err = tecNO_AUTH});
// authorize bob again
mptAlice.authorize({
.account = alice,
.holder = bob,
});
// now can send
mptAlice.send(
{.account = bob, .dest = carol, .amt = 4, .proof = "123"});
mptAlice.send({.account = bob, .dest = carol, .amt = 4});
}
// destination not authorized
@@ -1421,19 +1358,14 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.authorize(
{.account = alice, .holder = carol, .flags = tfMPTUnauthorize});
mptAlice.send(
{.account = bob,
.dest = carol,
.amt = 10,
.proof = "123",
.err = tecNO_AUTH});
{.account = bob, .dest = carol, .amt = 10, .err = tecNO_AUTH});
// authorize carol again
mptAlice.authorize({
.account = alice,
.holder = carol,
});
// now can send
mptAlice.send(
{.account = bob, .dest = carol, .amt = 5, .proof = "123"});
mptAlice.send({.account = bob, .dest = carol, .amt = 5});
}
// cannot send when MPTCanTransfer is not set
@@ -1445,9 +1377,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanLock | tfMPTCanPrivacy});
{.ownerCount = 1, .flags = tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
mptAlice.authorize({.account = carol});
@@ -1490,9 +1420,64 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
{.account = bob,
.dest = carol,
.amt = 10, // will be encrypted internally
.proof = "123",
.err = tecNO_AUTH});
}
// bad proof
{
Env env{*this, features};
Account const alice("alice");
Account const bob("bob");
Account const carol("carol");
MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
mptAlice.create(
{.ownerCount = 1,
.flags = tfMPTCanLock | tfMPTCanPrivacy | tfMPTCanTransfer});
mptAlice.authorize({.account = bob});
mptAlice.authorize({.account = carol});
mptAlice.pay(alice, bob, 100);
mptAlice.pay(alice, carol, 50);
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(bob);
mptAlice.generateKeyPair(carol);
mptAlice.set(
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.convert(
{.account = bob,
.amt = 60,
.holderPubKey = mptAlice.getPubKey(bob),
.err = tesSUCCESS});
mptAlice.mergeInbox({
.account = bob,
});
mptAlice.convert(
{.account = carol,
.amt = 20,
.holderPubKey = mptAlice.getPubKey(carol),
.err = tesSUCCESS});
mptAlice.mergeInbox({
.account = carol,
});
auto const dummyProofSize =
secp256k1_mpt_prove_same_plaintext_multi_size(3);
mptAlice.send(
{.account = bob,
.dest = carol,
.amt = 10,
.proof = strHex(Buffer(dummyProofSize)),
.err = tecBAD_PROOF});
}
}
void
@@ -1825,15 +1810,13 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convertBack(
{.account = bob,
.amt = 30,
.holderEncryptedAmt =
Buffer{badCiphertext, ecGamalEncryptedTotalLength},
.holderEncryptedAmt = getBadCiphertext(),
.err = temBAD_CIPHERTEXT});
mptAlice.convertBack(
{.account = bob,
.amt = 30,
.issuerEncryptedAmt =
Buffer{badCiphertext, ecGamalEncryptedTotalLength},
.issuerEncryptedAmt = getBadCiphertext(),
.err = temBAD_CIPHERTEXT});
}
}
@@ -2054,10 +2037,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
using namespace std::chrono;
Account const alice("alice"); // issuer
Account const bob("bob"); // holder
Account const alice("alice");
Account const bob("bob");
Account const carol("carol");
Account const dpIssuer("dpIssuer"); // holder
Account const dpIssuer("dpIssuer");
env.fund(XRP(50000), dpIssuer);
env.close();
@@ -2066,7 +2049,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
@@ -2086,16 +2068,16 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
env(fset(bob, asfDepositAuth));
env.close();
mptAlice.convert(
{.account = carol,
.amt = 50,
.holderPubKey = mptAlice.getPubKey(carol),
.err = tesSUCCESS});
mptAlice.convert(
{.account = bob,
.amt = 50,
.holderPubKey = mptAlice.getPubKey(bob),
.err = tesSUCCESS});
mptAlice.convert({
.account = carol,
.amt = 50,
.holderPubKey = mptAlice.getPubKey(carol),
});
mptAlice.convert({
.account = bob,
.amt = 50,
.holderPubKey = mptAlice.getPubKey(bob),
});
// carol merge inbox
mptAlice.mergeInbox({
@@ -2111,20 +2093,14 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.send(
{.account = carol,
.dest = bob,
.amt = 10, // will be encrypted internally
.proof = "123",
.amt = 10,
.err = tecNO_PERMISSION});
// Bob authorize alice
env(deposit::auth(bob, carol));
env.close();
mptAlice.send({
.account = carol,
.dest = bob,
.amt = 10, // will be encrypted internally
.proof = "123",
});
mptAlice.send({.account = carol, .dest = bob, .amt = 10});
// Create credentials
env(credentials::create(bob, dpIssuer, credType));
@@ -2137,8 +2113,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.send(
{.account = carol,
.dest = bob,
.amt = 10, // will be encrypted internally
.proof = "123",
.amt = 10,
.credentials = {{credIdx}}});
// Bob revoke authorization
@@ -2148,15 +2123,13 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.send(
{.account = carol,
.dest = bob,
.amt = 10, // will be encrypted internally
.proof = "123",
.amt = 10,
.err = tecNO_PERMISSION});
mptAlice.send(
{.account = carol,
.dest = bob,
.amt = 10, // will be encrypted internally
.proof = "123",
.amt = 10,
.credentials = {{credIdx}},
.err = tecNO_PERMISSION});
@@ -2167,15 +2140,13 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.send(
{.account = carol,
.dest = bob,
.amt = 10, // will be encrypted internally
.proof = "123",
.amt = 10,
.err = tecNO_PERMISSION});
mptAlice.send({
.account = carol,
.dest = bob,
.amt = 10, // will be encrypted internally
.proof = "123",
.amt = 10,
.credentials = {{credIdx}},
});
}
@@ -2252,8 +2223,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
// after send, bob's inbox balance is 50, spending balance
// remains 60. carol's inbox balance remains 0, spending balance
// drops to 70.
mptAlice.send(
{.account = carol, .dest = bob, .amt = 50, .proof = "123"});
mptAlice.send({.account = carol, .dest = bob, .amt = 50});
// alice clawback all confidential balance from bob, 110 in total.
// bob has balance in both inbox and spending. These balances should
@@ -2350,8 +2320,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
// after send, bob's inbox balance is 50, spending balance
// remains 60. carol's inbox balance remains 0, spending balance
// drops to 70.
mptAlice.send(
{.account = carol, .dest = bob, .amt = 50, .proof = "123"});
mptAlice.send({.account = carol, .dest = bob, .amt = 50});
// alice clawback all confidential balance from bob, 110 in total.
// bob has balance in both inbox and spending. These balances should
@@ -2406,7 +2375,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
@@ -2424,7 +2392,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
{.account = carol,
.holder = bob,
.amt = 10,
.proof = "123",
.err = temMALFORMED});
// invalid issuance ID, whose issuer is not alice
@@ -2448,7 +2415,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
{.account = alice,
.holder = alice,
.amt = 10,
.proof = "123",
.err = temMALFORMED});
// invalid amount
@@ -2456,10 +2422,9 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
{.account = alice,
.holder = bob,
.amt = 0,
.proof = "123",
.err = temBAD_AMOUNT});
// proof length invalid
// invalid proof length
mptAlice.confidentialClaw(
{.account = alice,
.holder = bob,
@@ -2801,10 +2766,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.mergeInbox({
.account = carol,
});
mptAlice.send(
{.account = bob, .dest = carol, .amt = 100, .proof = "123"});
mptAlice.send(
{.account = carol, .dest = bob, .amt = 100, .proof = "123"});
mptAlice.send({.account = bob, .dest = carol, .amt = 100});
mptAlice.send({.account = carol, .dest = bob, .amt = 100});
// verify proof fails with invalid clawback amount
// bob: 100 in inbox, 200 in spending
@@ -3067,15 +3030,18 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
uint64_t const amt = 10;
Buffer const blindingFactor = generateBlindingFactor();
Buffer const pcBlindingFactor = generateBlindingFactor();
uint64_t const spendingBalance = mptAlice.getDecryptedBalance(
auto const spendingBalance = mptAlice.getDecryptedBalance(
bob, MPTTester::HOLDER_ENCRYPTED_SPENDING);
BEAST_EXPECT(spendingBalance.has_value());
auto const encryptedSpendingBalance = mptAlice.getEncryptedBalance(
bob, MPTTester::HOLDER_ENCRYPTED_SPENDING);
BEAST_EXPECT(encryptedSpendingBalance);
BEAST_EXPECT(
encryptedSpendingBalance.has_value() &&
!encryptedSpendingBalance->empty());
Buffer const pedersenCommitment =
mptAlice.getPedersenCommitment(spendingBalance, pcBlindingFactor);
mptAlice.getPedersenCommitment(*spendingBalance, pcBlindingFactor);
Buffer const issuerCiphertext =
mptAlice.encryptAmount(alice, amt, blindingFactor);
Buffer const bobCiphertext =
@@ -3099,7 +3065,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
{
.pedersenCommitment =
badPedersenCommitment, // bad pedersen commitment
.amt = spendingBalance,
.amt = *spendingBalance,
.encryptedAmt = *encryptedSpendingBalance,
.blindingFactor = pcBlindingFactor,
});
@@ -3135,7 +3101,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
blindingFactor,
{
.pedersenCommitment = pedersenCommitment,
.amt = spendingBalance,
.amt = *spendingBalance,
.encryptedAmt = *encryptedSpendingBalance,
.blindingFactor = pcBlindingFactor,
});
@@ -3170,7 +3136,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
blindingFactor,
{
.pedersenCommitment = pedersenCommitment,
.amt = spendingBalance,
.amt = *spendingBalance,
.encryptedAmt = *encryptedSpendingBalance,
.blindingFactor =
generateBlindingFactor(), // bad blinding factor
@@ -3204,7 +3170,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
blindingFactor,
{
.pedersenCommitment = pedersenCommitment,
.amt = spendingBalance,
.amt = *spendingBalance,
.encryptedAmt = *encryptedSpendingBalance,
.blindingFactor = pcBlindingFactor,
});

File diff suppressed because it is too large Load Diff

View File

@@ -19,10 +19,6 @@ class MPTTester;
auto const MPTDEXFlags = tfMPTCanTrade | tfMPTCanTransfer;
// Generates a syntactically valid placeholder ciphertext
ripple::Buffer
generatePlaceholderCiphertext();
// Check flags settings on MPT create
class mptflags
{
@@ -434,10 +430,10 @@ public:
void
generateKeyPair(Account const& account);
Buffer
std::optional<Buffer>
getPubKey(Account const& account) const;
Buffer
std::optional<Buffer>
getPrivKey(Account const& account) const;
Buffer
@@ -446,10 +442,10 @@ public:
uint64_t const amt,
Buffer const& blindingFactor) const;
uint64_t
std::optional<uint64_t>
decryptAmount(Account const& account, Buffer const& amt) const;
uint64_t
std::optional<uint64_t>
getDecryptedBalance(
Account const& account,
EncryptedBalanceType balanceType) const;
@@ -457,15 +453,23 @@ public:
std::int64_t
getIssuanceOutstandingBalance() const;
Buffer
std::optional<Buffer>
getClawbackProof(
Account const& holder,
std::uint64_t amount,
Buffer const& privateKey,
uint256 const& txHash) const;
Buffer
getSchnorrProof(Account const& account, uint256 const& contextHash) const;
std::optional<Buffer>
getSchnorrProof(Account const& account, uint256 const& ctxHash) const;
std::optional<Buffer>
getConfidentialSendProof(
std::uint64_t const amount,
std::vector<ConfidentialRecipient> const& recipients,
Slice const& blindingFactor,
std::size_t const nRecipients,
uint256 const& contextHash) const;
Buffer
getConvertBackProof(

View File

@@ -139,11 +139,10 @@ ConfidentialConvert::preclaim(PreclaimContext const& ctx)
holderPubKey = (*sleMptoken)[sfHolderElGamalPublicKey];
}
// Prepare Auditor Info
std::optional<EncryptedAmountInfo> auditor;
std::optional<ConfidentialRecipient> auditor;
if (hasAuditor)
{
auditor.emplace(EncryptedAmountInfo{
auditor.emplace(ConfidentialRecipient{
(*sleIssuance)[sfAuditorElGamalPublicKey],
ctx.tx[sfAuditorEncryptedAmount]});
}

View File

@@ -62,14 +62,13 @@ verifyProofs(
(*mptoken)[~sfConfidentialBalanceVersion].value_or(0));
// Prepare Auditor Info
std::optional<EncryptedAmountInfo> auditor;
std::optional<ConfidentialRecipient> auditor;
bool const hasAuditor = issuance->isFieldPresent(sfAuditorElGamalPublicKey);
if (hasAuditor)
{
auditor.emplace(
EncryptedAmountInfo{
(*issuance)[sfAuditorElGamalPublicKey],
tx[sfAuditorEncryptedAmount]});
auditor.emplace(ConfidentialRecipient{
(*issuance)[sfAuditorElGamalPublicKey],
tx[sfAuditorEncryptedAmount]});
}
if (auto const ter = verifyRevealedAmount(
@@ -200,10 +199,6 @@ ConfidentialConvertBack::doApply()
(*sleIssuance)[sfConfidentialOutstandingAmount] =
(*sleIssuance)[sfConfidentialOutstandingAmount] - amtToConvertBack;
// it's fine if it reaches max uint32, it just resets to 0
(*sleMptoken)[sfConfidentialBalanceVersion] =
(*sleMptoken)[~sfConfidentialBalanceVersion].value_or(0u) + 1u;
std::optional<Slice> const auditorEc = ctx_.tx[~sfAuditorEncryptedAmount];
// homomorphically subtract holder's encrypted balance
@@ -245,6 +240,9 @@ ConfidentialConvertBack::doApply()
(*sleMptoken)[sfAuditorEncryptedBalance] = res;
}
// increment version
incrementConfidentialVersion(*sleMptoken);
view().update(sleIssuance);
view().update(sleMptoken);
return tesSUCCESS;

View File

@@ -43,11 +43,11 @@ ConfidentialSend::preflight(PreflightContext const& ctx)
ecGamalEncryptedTotalLength)
return temBAD_CIPHERTEXT;
if (hasAuditor && !isValidCiphertext(ctx.tx[sfAuditorEncryptedAmount]))
return temBAD_CIPHERTEXT;
// if (ctx.tx[sfZKProof].length() != ecEqualityProofLength)
// return temMALFORMED;
// Check the length of the ZKProof
auto const recipientCount = getConfidentialRecipientCount(hasAuditor);
if (ctx.tx[sfZKProof].length() !=
getMultiCiphertextEqualityProofSize(recipientCount))
return temMALFORMED;
// Check the encrypted amount formats, this is more expensive so put it at
// the end
@@ -56,6 +56,9 @@ ConfidentialSend::preflight(PreflightContext const& ctx)
!isValidCiphertext(ctx.tx[sfIssuerEncryptedAmount]))
return temBAD_CIPHERTEXT;
if (hasAuditor && !isValidCiphertext(ctx.tx[sfAuditorEncryptedAmount]))
return temBAD_CIPHERTEXT;
return tesSUCCESS;
}
@@ -152,6 +155,50 @@ ConfidentialSend::preclaim(PreclaimContext const& ctx)
!isTesSuccess(ter))
return ter;
auto const contextHash = getSendContextHash(
account,
ctx.tx[sfSequence],
mptIssuanceID,
destination,
(*sleSenderMPToken)[~sfConfidentialBalanceVersion].value_or(0));
auto const expectedRecipients = requiresAuditor ? 4 : 3;
// Prepare encrypted amount info for proof verification
std::vector<ConfidentialRecipient> recipients;
recipients.reserve(expectedRecipients);
// Add sender encrypted amount info
recipients.push_back(
{(*sleSenderMPToken)[sfHolderElGamalPublicKey],
ctx.tx[sfSenderEncryptedAmount]});
// Add destination encrypted amount info
recipients.push_back(
{(*sleDestinationMPToken)[sfHolderElGamalPublicKey],
ctx.tx[sfDestinationEncryptedAmount]});
// Add issuer encrypted amount info
recipients.push_back(
{(*sleIssuance)[sfIssuerElGamalPublicKey],
ctx.tx[sfIssuerEncryptedAmount]});
// Add auditor encrypted amount info if present
if (requiresAuditor)
{
recipients.push_back(
{(*sleIssuance)[sfAuditorElGamalPublicKey],
ctx.tx[sfAuditorEncryptedAmount]});
}
// Verify the multi-ciphertext equality proof
if (auto const ter = verifyMultiCiphertextEqualityProof(
ctx.tx[sfZKProof], recipients, expectedRecipients, contextHash);
!isTesSuccess(ter))
{
return ter;
}
return tesSUCCESS;
}
@@ -226,10 +273,6 @@ ConfidentialSend::doApply()
(*sleSender)[sfAuditorEncryptedBalance] = newAuditorEnc;
}
// Increment version
(*sleSender)[sfConfidentialBalanceVersion] =
(*sleSender)[~sfConfidentialBalanceVersion].value_or(0u) + 1u;
// Add to destination's inbox balance
{
Slice const curInbox = (*sleDestination)[sfConfidentialBalanceInbox];
@@ -270,6 +313,10 @@ ConfidentialSend::doApply()
(*sleDestination)[sfAuditorEncryptedBalance] = newAuditorEnc;
}
// increment version
incrementConfidentialVersion(*sleSender);
incrementConfidentialVersion(*sleDestination);
view().update(sleSender);
view().update(sleDestination);
return tesSUCCESS;