ConfidentialConvert with Equality Proof (#6177)

This commit is contained in:
Shawn Xie
2026-01-07 16:17:07 -05:00
committed by GitHub
parent 3e9dc276ed
commit 6c38086f17
9 changed files with 373 additions and 178 deletions

View File

@@ -34,6 +34,13 @@ getClawbackContextHash(
std::uint64_t amount,
AccountID const& holder);
uint256
getConvertContextHash(
AccountID const& account,
std::uint32_t sequence,
uint192 const& issuanceID,
std::uint64_t amount);
/**
* @brief Generates a new secp256k1 key pair.
*/
@@ -228,7 +235,8 @@ proveEquality(
uint256 const& txHash, // Transaction context data
std::uint32_t const spendVersion);
Buffer
// returns ciphertext and the blinding factor used
std::pair<Buffer, Buffer>
encryptAmount(uint64_t amt, Slice const& pubKeySlice);
Buffer
@@ -258,6 +266,17 @@ verifyEqualityProof(
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);
} // namespace ripple
#endif

View File

@@ -399,6 +399,7 @@ LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, mpt_issuance, ({
{sfDomainID, soeOPTIONAL},
{sfMutableFlags, soeDEFAULT},
{sfIssuerElGamalPublicKey, soeOPTIONAL},
{sfAuditorElGamalPublicKey, soeOPTIONAL},
{sfConfidentialOutstandingAmount, soeDEFAULT},
}))

View File

@@ -309,6 +309,9 @@ TYPED_SFIELD(sfHolderEncryptedAmount, VL, 38)
TYPED_SFIELD(sfIssuerEncryptedAmount, VL, 39)
TYPED_SFIELD(sfSenderEncryptedAmount, VL, 40)
TYPED_SFIELD(sfDestinationEncryptedAmount, VL, 41)
TYPED_SFIELD(sfAuditorEncryptedBalance, VL, 42)
TYPED_SFIELD(sfAuditorEncryptedAmount, VL, 43)
TYPED_SFIELD(sfAuditorElGamalPublicKey, VL, 44)
// account (common)
TYPED_SFIELD(sfAccount, ACCOUNT, 1)

View File

@@ -39,6 +39,20 @@ getClawbackContextHash(
return s.getSHA512Half();
}
uint256
getConvertContextHash(
AccountID const& account,
std::uint32_t sequence,
uint192 const& issuanceID,
std::uint64_t amount)
{
Serializer s;
addCommonZKPFields(
s, ttCONFIDENTIAL_CONVERT, account, sequence, issuanceID, amount);
return s.getSHA512Half();
}
int
secp256k1_elgamal_generate_keypair(
secp256k1_context const* ctx,
@@ -842,7 +856,7 @@ proveEquality(
return tesSUCCESS;
}
Buffer
std::pair<Buffer, Buffer>
encryptAmount(uint64_t amt, Slice const& pubKeySlice)
{
Buffer buf(ecGamalEncryptedTotalLength);
@@ -870,7 +884,7 @@ encryptAmount(uint64_t amt, Slice const& pubKeySlice)
Throw<std::runtime_error>(
"Failed to serialize into 66 byte compressed format");
return buf;
return std::make_pair(buf, Buffer(blindingFactor, 32));
}
Buffer
@@ -981,6 +995,36 @@ verifyEqualityProof(
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)
{
return tecBAD_PROOF;
}
return tesSUCCESS;
}
TER
verifyClawbackEqualityProof(
uint64_t const amount,
Slice const& proof,
Slice const& pubKeySlice,
Slice const& ciphertext,
uint256 const& contextHash)
{
secp256k1_pubkey c1, c2;
if (!makeEcPair(ciphertext, c1, c2))
return tecINTERNAL; // LCOV_EXCL_LINE
secp256k1_pubkey pubKey;
std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength);
if (secp256k1_equality_plaintext_verify(
secp256k1Context(),
proof.data(),
@@ -995,4 +1039,22 @@ verifyEqualityProof(
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;
}
} // namespace ripple

View File

@@ -47,26 +47,22 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert({
.account = bob,
.amt = 0,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
});
mptAlice.convert({
.account = bob,
.amt = 20,
.proof = "123",
});
mptAlice.convert({
.account = bob,
.amt = 40,
.proof = "123",
});
mptAlice.convert({
.account = bob,
.amt = 40,
.proof = "123",
});
}
@@ -76,104 +72,113 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
testcase("Convert preflight");
using namespace test::jtx;
Env env{*this, features - featureConfidentialTransfer};
Account const alice("alice");
Account const bob("bob");
MPTTester mptAlice(env, alice, {.holders = {bob}});
{
Env env{*this, features - featureConfidentialTransfer};
Account const alice("alice");
Account const bob("bob");
MPTTester mptAlice(env, alice, {.holders = {bob}});
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock});
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(bob);
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(bob);
mptAlice.set(
{.account = alice,
.pubKey = mptAlice.getPubKey(alice),
.err = temDISABLED});
mptAlice.set(
{.account = alice,
.pubKey = mptAlice.getPubKey(alice),
.err = temDISABLED});
mptAlice.convert(
{.account = bob,
.amt = 10,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
.err = temDISABLED});
mptAlice.convert(
{.account = bob,
.amt = 10,
.holderPubKey = mptAlice.getPubKey(bob),
.err = temDISABLED});
}
env.close();
{
Env env{*this, features};
Account const alice("alice");
Account const bob("bob");
MPTTester mptAlice(env, alice, {.holders = {bob}});
env.enableFeature(featureConfidentialTransfer);
env.close();
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock});
mptAlice.convert(
{.account = alice,
.amt = 10,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
.err = temMALFORMED});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.convert(
{.account = bob,
.amt = 10,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
.holderEncryptedAmt = Buffer{},
.err = temBAD_CIPHERTEXT});
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(bob);
mptAlice.convert(
{.account = bob,
.amt = 10,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
.issuerEncryptedAmt = Buffer{},
.err = temBAD_CIPHERTEXT});
mptAlice.convert(
{.account = alice,
.amt = 10,
.holderPubKey = mptAlice.getPubKey(bob),
.err = temMALFORMED});
mptAlice.convert(
{.account = bob,
.amt = maxMPTokenAmount + 1,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
.err = temBAD_AMOUNT});
mptAlice.convert(
{.account = bob,
.amt = 10,
.holderPubKey = mptAlice.getPubKey(bob),
.holderEncryptedAmt = Buffer{},
.err = temBAD_CIPHERTEXT});
mptAlice.convert(
{.account = bob,
.amt = 1,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
.holderEncryptedAmt =
Buffer{badCiphertext, ecGamalEncryptedTotalLength},
.err = temBAD_CIPHERTEXT});
mptAlice.convert(
{.account = bob,
.amt = 10,
.holderPubKey = mptAlice.getPubKey(bob),
.issuerEncryptedAmt = Buffer{},
.err = temBAD_CIPHERTEXT});
mptAlice.convert(
{.account = bob,
.amt = 1,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
.issuerEncryptedAmt =
Buffer{badCiphertext, ecGamalEncryptedTotalLength},
.err = temBAD_CIPHERTEXT});
mptAlice.convert(
{.account = bob,
.amt = maxMPTokenAmount + 1,
.holderPubKey = mptAlice.getPubKey(bob),
.err = temBAD_AMOUNT});
// invalid pub key
mptAlice.convert(
{.account = bob,
.amt = 10,
.proof = "123",
.holderPubKey = Buffer{},
.err = temMALFORMED});
mptAlice.convert(
{.account = bob,
.amt = 1,
.holderPubKey = mptAlice.getPubKey(bob),
.holderEncryptedAmt =
Buffer{badCiphertext, ecGamalEncryptedTotalLength},
.err = temBAD_CIPHERTEXT});
// todo: change to to check proof size
// mptAlice.convert(
// {.account = bob,
// .amt = 10,
// .proof = "123",
// .holderPubKey = mptAlice.getPubKey(bob),
// .err = temMALFORMED});
mptAlice.convert(
{.account = bob,
.amt = 1,
.holderPubKey = mptAlice.getPubKey(bob),
.issuerEncryptedAmt =
Buffer{badCiphertext, ecGamalEncryptedTotalLength},
.err = temBAD_CIPHERTEXT});
// invalid pub key
mptAlice.convert(
{.account = bob,
.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});
}
}
void
@@ -288,7 +293,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = bob,
.amt = 10,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
.err = tecNO_PERMISSION});
}
@@ -316,7 +320,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = bob,
.amt = 10,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
.err = tecNO_PERMISSION});
}
@@ -345,7 +348,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = bob,
.amt = 10,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
.err = tecOBJECT_NOT_FOUND});
}
@@ -371,7 +373,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = bob,
.amt = 10,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
.err = tecOBJECT_NOT_FOUND});
}
@@ -403,7 +404,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = bob,
.amt = 200,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
.err = tecINSUFFICIENT_FUNDS});
}
@@ -435,14 +435,12 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = bob,
.amt = 10,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob)});
// cannot upload pk again
mptAlice.convert(
{.account = bob,
.amt = 10,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
.err = tecDUPLICATE});
}
@@ -476,7 +474,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = bob,
.amt = 10,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
.err = tecINSUFFICIENT_FUNDS});
@@ -486,7 +483,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert({
.account = bob,
.amt = 10,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
});
}
@@ -524,7 +520,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = bob,
.amt = 10,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
.err = tecINSUFFICIENT_FUNDS});
@@ -537,7 +532,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert({
.account = bob,
.amt = 10,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
});
}
@@ -574,7 +568,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert({
.account = bob,
.amt = 40,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
});
@@ -612,7 +605,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert({
.account = bob,
.amt = 40,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
});
@@ -757,7 +749,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = bob,
.amt = 60,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
.err = tesSUCCESS});
@@ -780,7 +771,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = carol,
.amt = 20,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(carol),
.err = tesSUCCESS});
@@ -1032,13 +1022,11 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = bob,
.amt = 60,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
.err = tesSUCCESS});
mptAlice.convert(
{.account = carol,
.amt = 20,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(carol),
.err = tesSUCCESS});
@@ -1252,7 +1240,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = bob,
.amt = 60,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
.err = tesSUCCESS});
@@ -1264,7 +1251,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = carol,
.amt = 20,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(carol),
.err = tesSUCCESS});
@@ -1316,7 +1302,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert({
.account = bob,
.amt = 100,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
});
@@ -1356,14 +1341,12 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert({
.account = bob,
.amt = 100,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
});
mptAlice.convert({
.account = carol,
.amt = 0,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(carol),
});
@@ -1400,7 +1383,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert({
.account = bob,
.amt = 0,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
});
@@ -1437,7 +1419,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert({
.account = bob,
.amt = 0,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
});
@@ -1480,7 +1461,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert({
.account = bob,
.amt = 40,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
});
@@ -1559,7 +1539,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert({
.account = bob,
.amt = 40,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
});
@@ -1765,7 +1744,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert({
.account = bob,
.amt = 40,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
});
@@ -1776,7 +1754,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert({
.account = carol,
.amt = 40,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(carol),
});
@@ -1816,7 +1793,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert({
.account = bob,
.amt = 40,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
});
mptAlice.mergeInbox({.account = bob});
@@ -1892,13 +1868,11 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = carol,
.amt = 50,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(carol),
.err = tesSUCCESS});
mptAlice.convert(
{.account = bob,
.amt = 50,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
.err = tesSUCCESS});
@@ -2021,7 +1995,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = bob,
.amt = 60,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob)});
// bob merge inbox
@@ -2037,7 +2010,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = carol,
.amt = 120,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(carol)});
// carol merge inbox
@@ -2052,7 +2024,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = dave,
.amt = 200,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(dave)});
// setup: carol confidential send 50 to bob.
@@ -2215,7 +2186,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert({
.account = bob,
.amt = 60,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
});
mptAlice.mergeInbox({
@@ -2338,7 +2308,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = bob,
.amt = 60,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob)});
mptAlice.mergeInbox({
.account = bob,
@@ -2465,7 +2434,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = bob,
.amt = 500,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob)});
mptAlice.mergeInbox({
.account = bob,
@@ -2474,7 +2442,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = carol,
.amt = 1000,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(carol)});
// verify proof fails with invalid clawback amount
@@ -2506,7 +2473,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = bob,
.amt = 300,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob)});
mptAlice.mergeInbox({
.account = bob,
@@ -2514,7 +2480,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = carol,
.amt = 400,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(carol)});
mptAlice.mergeInbox({
.account = carol,
@@ -2625,14 +2590,12 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = bob,
.amt = amt,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
.err = expectedResult});
else
mptAlice.convert({
.account = bob,
.amt = amt,
.proof = "123",
.err = expectedResult,
});
@@ -2697,7 +2660,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = bob,
.amt = 50,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob)});
// set or clear lsfMPTCanPrivacy should fail because of
@@ -2729,14 +2691,16 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.convert(
{.account = bob,
.amt = 10,
.proof = "123",
.holderPubKey = mptAlice.getPubKey(bob),
.err = tecNO_PERMISSION});
// can set lsfMPTCanPrivacy again when there's no confidential
// outstanding balance
mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetPrivacy});
mptAlice.convert({.account = bob, .amt = 10, .proof = "123"});
mptAlice.convert({
.account = bob,
.amt = 10,
});
}
}

View File

@@ -775,6 +775,98 @@ MPTTester::getClawbackProof(
return proof;
}
Buffer
MPTTester::getConvertProof(
Account const& holder,
std::uint64_t amount,
uint256 const& ctxHash,
std::pair<Buffer, Buffer> holderCiphertext,
std::pair<Buffer, Buffer> issuerCiphertext,
std::optional<std::pair<Buffer, Buffer>> auditorCiphertext) const
{
if (!id_)
Throw<std::runtime_error>("MPT has not been created");
auto const sleHolder = env_.le(keylet::mptoken(*id_, holder.id()));
auto const sleIssuance = env_.le(keylet::mptIssuance(*id_));
size_t const zkpSize = auditorCiphertext ? 3 : 2;
size_t const zkpByteLength = zkpSize * ecEqualityProofLength;
if (!sleHolder || !sleIssuance || holderCiphertext.first.size() == 0 ||
issuerCiphertext.first.size() == 0)
return Buffer(zkpByteLength);
auto const generateProof = [amount, ctxHash](
Slice const& ciphertext,
Slice const& pubKey,
Slice const& randomness) {
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))
{
Throw<std::runtime_error>("Invalid Ciphertext");
}
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,
randomness.data(),
ctxHash.data()) != 1)
{
Throw<std::runtime_error>("Proof generation failed");
}
return proof;
};
Buffer zkp(zkpByteLength);
Buffer holderZkp = generateProof(
holderCiphertext.first, getPubKey(holder), holderCiphertext.second);
Buffer issuerZkp = generateProof(
issuerCiphertext.first, getPubKey(issuer_), issuerCiphertext.second);
// std::optional<Slice> auditorZkp;
// if (auditor)
// {
// Slice auditorPubKey(
// sleIssuance->getFieldVL(sfAuditorElGamalPublicKey).data(),
// sleIssuance->getFieldVL(sfAuditorElGamalPublicKey).size());
// Buffer auditorZkp = txArgs.auditorEncryptedAmt &&
// sleIssuance->isFieldPresent(sfIssuerElGamalPublicKey)
// ? generateProof(*txArgs.issuerEncryptedAmt, issuerPubKey)
// : getDummyProof();
// }
// 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();
return zkp;
}
std::optional<Buffer>
MPTTester::getEncryptedBalance(
Account const& account,
@@ -855,20 +947,39 @@ MPTTester::convert(MPTConvert const& arg)
if (arg.holderPubKey)
jv[sfHolderElGamalPublicKey.jsonName] = strHex(*arg.holderPubKey);
std::pair<Buffer, Buffer> holderCiphertext;
if (arg.holderEncryptedAmt)
jv[sfHolderEncryptedAmount.jsonName] = strHex(*arg.holderEncryptedAmt);
else
jv[sfHolderEncryptedAmount.jsonName] =
strHex(encryptAmount(*arg.account, *arg.amt));
{
holderCiphertext = encryptAmount(*arg.account, *arg.amt);
jv[sfHolderEncryptedAmount.jsonName] = strHex(holderCiphertext.first);
}
std::pair<Buffer, Buffer> issuerCiphertext;
if (arg.issuerEncryptedAmt)
jv[sfIssuerEncryptedAmount.jsonName] = strHex(*arg.issuerEncryptedAmt);
else
jv[sfIssuerEncryptedAmount.jsonName] =
strHex(encryptAmount(issuer_, *arg.amt));
{
issuerCiphertext = encryptAmount(issuer_, *arg.amt);
jv[sfIssuerEncryptedAmount.jsonName] = strHex(issuerCiphertext.first);
}
if (arg.proof)
jv[sfZKProof.jsonName] = *arg.proof;
else
{
uint256 const ctxHash = getConvertContextHash(
arg.account->id(), env_.seq(*arg.account), *id_, *arg.amt);
Buffer proof = getConvertProof(
*arg.account,
*arg.amt,
ctxHash,
holderCiphertext,
issuerCiphertext,
{});
jv[sfZKProof] = strHex(proof);
}
auto const holderAmt = getBalance(*arg.account);
auto const prevConfidentialOutstanding = getIssuanceConfidentialBalance();
@@ -965,18 +1076,19 @@ MPTTester::send(MPTConfidentialSend const& arg)
jv[sfSenderEncryptedAmount] = strHex(*arg.senderEncryptedAmt);
else
jv[sfSenderEncryptedAmount] =
strHex(encryptAmount(*arg.account, *arg.amt));
strHex(encryptAmount(*arg.account, *arg.amt).first);
if (arg.destEncryptedAmt)
jv[sfDestinationEncryptedAmount] = strHex(*arg.destEncryptedAmt);
else
jv[sfDestinationEncryptedAmount] =
strHex(encryptAmount(*arg.dest, *arg.amt));
strHex(encryptAmount(*arg.dest, *arg.amt).first);
if (arg.issuerEncryptedAmt)
jv[sfIssuerEncryptedAmount] = strHex(*arg.issuerEncryptedAmt);
else
jv[sfIssuerEncryptedAmount] = strHex(encryptAmount(issuer_, *arg.amt));
jv[sfIssuerEncryptedAmount] =
strHex(encryptAmount(issuer_, *arg.amt).first);
if (arg.proof)
jv[sfZKProof] = *arg.proof;
@@ -1179,7 +1291,7 @@ MPTTester::getPrivKey(Account const& account) const
Throw<std::runtime_error>("Account does not have private key");
}
Buffer
std::pair<Buffer, Buffer>
MPTTester::encryptAmount(Account const& account, uint64_t amt) const
{
return ripple::encryptAmount(amt, getPubKey(account));
@@ -1308,13 +1420,13 @@ MPTTester::convertBack(MPTConvertBack const& arg)
jv[sfHolderEncryptedAmount.jsonName] = strHex(*arg.holderEncryptedAmt);
else
jv[sfHolderEncryptedAmount.jsonName] =
strHex(encryptAmount(*arg.account, *arg.amt));
strHex(encryptAmount(*arg.account, *arg.amt).first);
if (arg.issuerEncryptedAmt)
jv[sfIssuerEncryptedAmount.jsonName] = strHex(*arg.issuerEncryptedAmt);
else
jv[sfIssuerEncryptedAmount.jsonName] =
strHex(encryptAmount(issuer_, *arg.amt));
strHex(encryptAmount(issuer_, *arg.amt).first);
if (arg.proof)
jv[sfZKProof.jsonName] = *arg.proof;

View File

@@ -175,6 +175,7 @@ struct MPTConvert
std::optional<Buffer> holderPubKey = std::nullopt;
std::optional<Buffer> holderEncryptedAmt = std::nullopt;
std::optional<Buffer> issuerEncryptedAmt = std::nullopt;
std::optional<Buffer> auditorEncryptedAmt = std::nullopt;
std::optional<std::uint32_t> ownerCount = std::nullopt;
std::optional<std::uint32_t> holderCount = std::nullopt;
std::optional<std::uint32_t> flags = std::nullopt;
@@ -400,7 +401,7 @@ public:
Buffer
getPrivKey(Account const& account) const;
Buffer
std::pair<Buffer, Buffer>
encryptAmount(Account const& account, uint64_t amt) const;
uint64_t
@@ -421,6 +422,15 @@ public:
Buffer const& privateKey,
uint256 const& txHash) const;
Buffer
getConvertProof(
Account const& holder,
std::uint64_t amount,
uint256 const& ctxHash,
std::pair<Buffer, Buffer> holderCiphertext,
std::pair<Buffer, Buffer> issuerCiphertext,
std::optional<std::pair<Buffer, Buffer>> auditorCiphertext) const;
private:
using SLEP = SLE::const_pointer;
bool

View File

@@ -89,7 +89,7 @@ ConfidentialClawback::preclaim(PreclaimContext const& ctx)
auto const contextHash = getClawbackContextHash(
account, ctx.tx[sfSequence], mptIssuanceID, amount, holder);
return verifyEqualityProof(
return verifyClawbackEqualityProof(
amount, ctx.tx[sfZKProof], pubKeySlice, ciphertext, contextHash);
}

View File

@@ -37,8 +37,10 @@ ConfidentialConvert::preflight(PreflightContext const& ctx)
ctx.tx[sfHolderElGamalPublicKey].length() != ecPubKeyLength)
return temMALFORMED;
// if (ctx.tx[sfZKProof].length() != ecEqualityProofLength)
// return temMALFORMED;
auto const expectedCount =
ctx.tx.isFieldPresent(sfAuditorEncryptedAmount) ? 3 : 2;
if (ctx.tx[sfZKProof].size() != expectedCount * ecEqualityProofLength)
return temMALFORMED;
return tesSUCCESS;
}
@@ -94,35 +96,56 @@ ConfidentialConvert::preclaim(PreclaimContext const& ctx)
ctx.tx.isFieldPresent(sfHolderElGamalPublicKey))
return tecDUPLICATE;
// auto const holderPubKey = ctx.tx.isFieldPresent(sfHolderElGamalPublicKey)
// ? ctx.tx[sfHolderElGamalPublicKey]
// : (*sleMptoken)[sfHolderElGamalPublicKey];
auto const holderPubKey = ctx.tx.isFieldPresent(sfHolderElGamalPublicKey)
? ctx.tx[sfHolderElGamalPublicKey]
: (*sleMptoken)[sfHolderElGamalPublicKey];
// auto const contextHash = getContextHash(
// ctx.tx[sfMPTokenIssuanceID],
// ctx.tx[sfMPTAmount],
// ctx.tx[sfAccount],
// ctx.tx.getTxnType());
auto const contextHash = getConvertContextHash(
ctx.tx[sfAccount],
ctx.tx[sfSequence],
ctx.tx[sfMPTokenIssuanceID],
ctx.tx[sfMPTAmount]);
// // check equality proof
// auto checkEqualityProof = [&](auto const& encryptedAmount,
// auto const& pubKey) -> TER {
// return verifyEqualityProof(
// ctx.tx[sfMPTAmount],
// ctx.tx[sfZKProof],
// pubKey,
// encryptedAmount,
// contextHash);
// };
bool const hasAuditor = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount);
// if (!isTesSuccess(checkEqualityProof(
// ctx.tx[sfHolderEncryptedAmount], holderPubKey)) ||
// !isTesSuccess(checkEqualityProof(
// ctx.tx[sfIssuerEncryptedAmount],
// (*sleIssuance)[sfIssuerElGamalPublicKey])))
// {
// return tecBAD_PROOF;
// }
std::vector<Buffer> const zkps = getEqualityProofs(ctx.tx[sfZKProof]);
auto const& amount = ctx.tx[sfMPTAmount];
// we already checked proof size in preflight, still do sanity check here
// since we are going to access individual vector entries
auto const expectedCount = ctx.tx[sfZKProof].size() / ecEqualityProofLength;
if (zkps.size() != expectedCount)
return tecINTERNAL; // LCOV_EXCL_LINE
// check equality proof
if (!isTesSuccess(verifyEqualityProof(
amount,
zkps[0],
holderPubKey,
ctx.tx[sfHolderEncryptedAmount],
contextHash)) ||
!isTesSuccess(verifyEqualityProof(
amount,
zkps[1],
(*sleIssuance)[sfIssuerElGamalPublicKey],
ctx.tx[sfIssuerEncryptedAmount],
contextHash)))
{
return tecBAD_PROOF;
}
// Verify Auditor proof if present
if (hasAuditor &&
!isTesSuccess(verifyEqualityProof(
amount,
zkps[2],
(*sleIssuance)[sfAuditorElGamalPublicKey],
ctx.tx[sfAuditorEncryptedAmount],
contextHash)))
{
return tecBAD_PROOF;
}
return tesSUCCESS;
}
@@ -196,19 +219,20 @@ ConfidentialConvert::doApply()
{
// encrypt sfConfidentialBalanceSpending with zero balance
Buffer out;
out = encryptAmount(0, (*sleMptoken)[sfHolderElGamalPublicKey]);
out =
encryptAmount(0, (*sleMptoken)[sfHolderElGamalPublicKey]).first;
(*sleMptoken)[sfConfidentialBalanceSpending] = out;
}
catch (std::exception const& e)
{
return tecINTERNAL;
return tecINTERNAL; // LCOV_EXCL_LINE
}
}
else
{
// both sfIssuerEncryptedBalance and sfConfidentialBalanceInbox should
// exist together
return tecINTERNAL;
return tecINTERNAL; // LCOV_EXCL_LINE
}
view().update(sleIssuance);