mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-15 20:08:34 +00:00
Compare commits
1 Commits
pratik/ote
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0a940a383 |
@@ -2191,6 +2191,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.account = bob,
|
||||
.dest = bob,
|
||||
.amt = 10,
|
||||
.proof = getTrivialSendProofHex(),
|
||||
.err = temMALFORMED,
|
||||
});
|
||||
|
||||
@@ -2897,22 +2898,6 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
auto& mptAlice = confEnv.mpt;
|
||||
|
||||
{
|
||||
// Bob has 60, tries to send 70. Invalid remaining balance.
|
||||
mptAlice.send({
|
||||
.account = bob,
|
||||
.dest = carol,
|
||||
.amt = 70,
|
||||
.err = tecBAD_PROOF,
|
||||
});
|
||||
|
||||
// Bob has 60, tries to send 61. Invalid remaining balance.
|
||||
mptAlice.send({
|
||||
.account = bob,
|
||||
.dest = carol,
|
||||
.amt = 61,
|
||||
.err = tecBAD_PROOF,
|
||||
});
|
||||
|
||||
// Bob has 60, sends 60. Remainder is exactly 0. Valid remaining balance.
|
||||
mptAlice.send({
|
||||
.account = bob,
|
||||
@@ -2933,12 +2918,12 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
});
|
||||
|
||||
// Bob has 100, tries to send 2^64-1. Invalid remaining balance.
|
||||
mptAlice.send({
|
||||
.account = bob,
|
||||
.dest = carol,
|
||||
.amt = std::numeric_limits<std::uint64_t>::max(),
|
||||
.err = tecBAD_PROOF,
|
||||
});
|
||||
{
|
||||
ConfidentialSendSetup const setup(
|
||||
mptAlice, bob, carol, alice, std::numeric_limits<std::uint64_t>::max());
|
||||
auto const forged = getForgedSendProof(mptAlice, env, bob, carol, setup);
|
||||
mptAlice.send(setup.sendArgs(bob, carol, forged, tecBAD_PROOF));
|
||||
}
|
||||
|
||||
// Bob sends 1, remaining 99.
|
||||
mptAlice.send({
|
||||
@@ -2947,14 +2932,6 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.amt = 1,
|
||||
.err = tesSUCCESS,
|
||||
});
|
||||
|
||||
// Bob sends 100, but only has 99. Invalid remaining balance.
|
||||
mptAlice.send({
|
||||
.account = bob,
|
||||
.dest = carol,
|
||||
.amt = 100,
|
||||
.err = tecBAD_PROOF,
|
||||
});
|
||||
}
|
||||
|
||||
// send when spending balance is 0 (key registered, inbox merged, but nothing converted)
|
||||
@@ -2971,18 +2948,13 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
|
||||
// Trying to send any amount with 0 spending balance must fail:
|
||||
// the range proof for < 0 is invalid.
|
||||
mptAlice2.send({
|
||||
.account = bob2,
|
||||
.dest = carol2,
|
||||
.amt = 1,
|
||||
.err = tecBAD_PROOF,
|
||||
});
|
||||
ConfidentialSendSetup const setup(mptAlice2, bob2, carol2, alice2, 1);
|
||||
auto const forged = getForgedSendProof(mptAlice2, env2, bob2, carol2, setup);
|
||||
mptAlice2.send(setup.sendArgs(bob2, carol2, forged, tecBAD_PROOF));
|
||||
|
||||
BEAST_EXPECT(
|
||||
mptAlice2.getDecryptedBalance(bob2, MPTTester::holderEncryptedSpending) == 0);
|
||||
}
|
||||
|
||||
// todo: test m exceeding range, require using scala and refactor
|
||||
}
|
||||
|
||||
/* The equality proof library and range proof library do not
|
||||
@@ -3462,7 +3434,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const convertBackContextHash =
|
||||
getConvertBackContextHash(bob.id(), mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
convertBackAmt,
|
||||
convertBackContextHash,
|
||||
@@ -3472,6 +3444,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
{
|
||||
json::Value jv;
|
||||
@@ -3483,7 +3457,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
jv[sfIssuerEncryptedAmount.jsonName] = strHex(convertBackIssuerCiphertext);
|
||||
jv[sfBlindingFactor.jsonName] = strHex(convertBackBlindingFactor);
|
||||
jv[sfBalanceCommitment.jsonName] = strHex(pedersenCommitment);
|
||||
jv[sfZKProof.jsonName] = strHex(proof);
|
||||
jv[sfZKProof.jsonName] = strHex(requireOptionalRef(proof, "Missing proof"));
|
||||
|
||||
env(jv, Ter(tesSUCCESS));
|
||||
}
|
||||
@@ -5283,7 +5257,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
Buffer const badPedersenCommitment =
|
||||
mptAlice.getPedersenCommitment(1, pcBlindingFactor);
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -5293,6 +5267,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5313,7 +5289,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -5323,6 +5299,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = generateBlindingFactor(), // wrong blinding factor
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5337,22 +5315,26 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
}
|
||||
|
||||
// Test 3: Proof generated with wrong balance value.
|
||||
// The proof claims balance=1 but the encrypted spending balance contains
|
||||
// the actual balance. Verification fails because the values don't match.
|
||||
// The sigma proof claims balance=20 but the pedersen commitment and
|
||||
// encrypted spending balance were built for the actual balance (40).
|
||||
// we cannot call mpt_get_convert_back_proof because it has client-side
|
||||
// verification.
|
||||
{
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
uint64_t constexpr claimedBalance = 20; // wrong: real balance is 40
|
||||
|
||||
auto const proof = getForgedConvertBackProof(
|
||||
mptAlice,
|
||||
bob,
|
||||
claimedBalance,
|
||||
spendingBalance,
|
||||
amt,
|
||||
contextHash,
|
||||
{
|
||||
.pedersenCommitment = pedersenCommitment,
|
||||
.amt = 1, // wrong balance
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
pedersenCommitment,
|
||||
encryptedSpendingBalance,
|
||||
pcBlindingFactor,
|
||||
contextHash);
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5375,7 +5357,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
Buffer const badPedersenCommitment =
|
||||
mptAlice.getPedersenCommitment(1, pcBlindingFactor);
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -5385,6 +5367,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5405,7 +5389,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
{
|
||||
uint256 const badContextHash{1};
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
badContextHash, // wrong context hash
|
||||
@@ -5415,6 +5399,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5434,7 +5420,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -5444,6 +5430,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5919,22 +5907,26 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
// linkage, and that the remaining balance is non-negative.
|
||||
|
||||
// Test 1: Proof generated with wrong balance value.
|
||||
// The sigma proof claims balance=1 but the spending balance contains the
|
||||
// actual balance. The compact proof's balance-linkage check fails.
|
||||
// The sigma proof claims balance=20 but the pedersen commitment and
|
||||
// encrypted spending balance were built for the actual balance (40).
|
||||
// we cannot call mpt_get_convert_back_proof because it has client-side
|
||||
// verification.
|
||||
{
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
uint64_t constexpr claimedBalance = 20; // wrong: real balance is 40
|
||||
|
||||
auto const proof = getForgedConvertBackProof(
|
||||
mptAlice,
|
||||
bob,
|
||||
claimedBalance,
|
||||
spendingBalance,
|
||||
amt,
|
||||
contextHash,
|
||||
{
|
||||
.pedersenCommitment = pedersenCommitment,
|
||||
.amt = 1, // wrong balance (actual balance is ~40)
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
pedersenCommitment,
|
||||
encryptedSpendingBalance,
|
||||
pcBlindingFactor,
|
||||
contextHash);
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5956,7 +5948,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -5966,6 +5958,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = generateBlindingFactor(), // wrong blinding factor
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5985,7 +5979,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
// makes the proof invalid for this transaction, preventing replay attacks.
|
||||
{
|
||||
uint256 const badContextHash{1};
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
badContextHash, // wrong context hash
|
||||
@@ -5995,6 +5989,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -6014,7 +6010,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -6024,6 +6020,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -6073,7 +6071,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
Buffer const bobCiphertext = mptAlice.encryptAmount(bob, amt, blindingFactor);
|
||||
auto const version = mptAlice.getMPTokenVersion(bob);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
makeContextHash(env, mptAlice, alice, bob, carol, version),
|
||||
@@ -6084,6 +6082,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
encryptedSpendingBalance, "Missing encrypted spending balance"),
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -6173,7 +6173,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHashA =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), currentSeq, version);
|
||||
|
||||
Buffer const proofA = mptAlice.getConvertBackProof(
|
||||
auto const proofA = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amtA,
|
||||
contextHashA,
|
||||
@@ -6183,6 +6183,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proofA.has_value()))
|
||||
return;
|
||||
|
||||
// Construct Transaction B with Amount m2 = 20 and attach Proof pi
|
||||
uint64_t const amtB = 20;
|
||||
@@ -6254,7 +6256,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const oldContextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), currentSeq, versionV);
|
||||
|
||||
Buffer const oldProof = mptAlice.getConvertBackProof(
|
||||
auto const oldProof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
oldContextHash,
|
||||
@@ -6264,6 +6266,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalanceV,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(oldProof.has_value()))
|
||||
return;
|
||||
|
||||
// Submit and verify failure
|
||||
mptAlice.convertBack({
|
||||
@@ -6326,7 +6330,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), currentVersion);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -6336,6 +6340,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = spendingBalEnc,
|
||||
.blindingFactor = pcBf,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
// Submit transaction with Divergent Ciphertexts
|
||||
// Holder Ciphertext encrypts 11. Issuer Ciphertext encrypts 10.
|
||||
@@ -6469,7 +6475,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), currentVersion);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
1,
|
||||
contextHash,
|
||||
@@ -6479,6 +6485,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = underflowedCt,
|
||||
.blindingFactor = pcBf,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -7741,7 +7749,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const convertBackCtxHash =
|
||||
getConvertBackContextHash(bob.id(), mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const convertBackProof = mptAlice.getConvertBackProof(
|
||||
auto const convertBackProof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
sendAmount,
|
||||
convertBackCtxHash,
|
||||
@@ -7751,14 +7759,18 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpending,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(convertBackProof.has_value()))
|
||||
return;
|
||||
|
||||
// Resize the convertBack proof to match the expected send proof
|
||||
// size so it passes preflight's size check and reaches the actual
|
||||
// ZK verification in doApply.
|
||||
auto const expectedSendSize = kEcSendProofLength;
|
||||
Buffer resizedProof(expectedSendSize);
|
||||
auto const copyLen = std::min(convertBackProof.size(), expectedSendSize);
|
||||
std::memcpy(resizedProof.data(), convertBackProof.data(), copyLen);
|
||||
Buffer const& convertBackProofRef =
|
||||
requireOptionalRef(convertBackProof, "Missing proof");
|
||||
auto const copyLen = std::min(convertBackProofRef.size(), expectedSendSize);
|
||||
std::memcpy(resizedProof.data(), convertBackProofRef.data(), copyLen);
|
||||
// Zero-pad the rest (if convertBack proof is shorter)
|
||||
if (copyLen < expectedSendSize)
|
||||
std::memset(resizedProof.data() + copyLen, 0, expectedSendSize - copyLen);
|
||||
|
||||
@@ -124,6 +124,87 @@ protected:
|
||||
return proof;
|
||||
}
|
||||
|
||||
// Forges a ConvertBack proof (compact sigma + single bulletproof) whose
|
||||
// sigma component claims claimedBalance (which may be wrong) while binding
|
||||
// to the real pedersen commitment and encrypted spending balance
|
||||
// ciphertext already on the ledger. The bulletproof component is built
|
||||
// from realBalance so it stays honest.
|
||||
// mpt_get_convert_back_proof does not allow to build a proof whose amount
|
||||
// exceeds the holder's claimed balance.
|
||||
static Buffer
|
||||
getForgedConvertBackProof(
|
||||
test::jtx::MPTTester& mpt,
|
||||
test::jtx::Account const& holder,
|
||||
uint64_t claimedBalance,
|
||||
uint64_t realBalance,
|
||||
uint64_t amt,
|
||||
Buffer const& pedersenCommitment,
|
||||
Buffer const& encryptedSpendingBalance,
|
||||
Buffer const& pcBlindingFactor,
|
||||
uint256 const& contextHash)
|
||||
{
|
||||
if (pedersenCommitment.size() != kCompressedEcPointLength)
|
||||
Throw<std::runtime_error>("getForgedConvertBackProof: bad pedersenCommitment length");
|
||||
if (encryptedSpendingBalance.size() != kEcGamalEncryptedTotalLength)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"getForgedConvertBackProof: bad encryptedSpendingBalance length");
|
||||
}
|
||||
if (amt > realBalance)
|
||||
Throw<std::runtime_error>("getForgedConvertBackProof: amt exceeds realBalance");
|
||||
|
||||
auto* const ctx = mpt_secp256k1_context();
|
||||
auto const holderPubKey = requireOptional(mpt.getPubKey(holder), "Missing holder pubkey");
|
||||
auto const holderPrivKey =
|
||||
requireOptional(mpt.getPrivKey(holder), "Missing holder privkey");
|
||||
|
||||
secp256k1_pubkey pkHolder;
|
||||
if (secp256k1_ec_pubkey_parse(
|
||||
ctx, &pkHolder, holderPubKey.data(), kCompressedEcPointLength) != 1)
|
||||
Throw<std::runtime_error>("Failed to parse holder's public key");
|
||||
|
||||
secp256k1_pubkey pcB;
|
||||
if (secp256k1_ec_pubkey_parse(
|
||||
ctx, &pcB, pedersenCommitment.data(), kCompressedEcPointLength) != 1)
|
||||
Throw<std::runtime_error>("Failed to parse pedersen commitment");
|
||||
|
||||
secp256k1_pubkey b1, b2;
|
||||
if (secp256k1_ec_pubkey_parse(
|
||||
ctx, &b1, encryptedSpendingBalance.data(), kCompressedEcPointLength) != 1 ||
|
||||
secp256k1_ec_pubkey_parse(
|
||||
ctx,
|
||||
&b2,
|
||||
encryptedSpendingBalance.data() + kCompressedEcPointLength,
|
||||
kCompressedEcPointLength) != 1)
|
||||
Throw<std::runtime_error>("Failed to parse balance ciphertext");
|
||||
|
||||
Buffer sigmaProof(SECP256K1_COMPACT_CONVERTBACK_PROOF_SIZE);
|
||||
if (secp256k1_compact_convertback_prove(
|
||||
ctx,
|
||||
sigmaProof.data(),
|
||||
claimedBalance,
|
||||
holderPrivKey.data(),
|
||||
pcBlindingFactor.data(),
|
||||
&pkHolder,
|
||||
&b1,
|
||||
&b2,
|
||||
&pcB,
|
||||
contextHash.data()) != 1)
|
||||
Throw<std::runtime_error>("Failed to generate convertback sigma proof");
|
||||
|
||||
auto const forgedBulletproof =
|
||||
getForgedSingleBulletproof(realBalance - amt, pcBlindingFactor, contextHash);
|
||||
|
||||
Buffer proof(kEcConvertBackProofLength);
|
||||
std::memcpy(proof.data(), sigmaProof.data(), SECP256K1_COMPACT_CONVERTBACK_PROOF_SIZE);
|
||||
std::memcpy(
|
||||
proof.data() + SECP256K1_COMPACT_CONVERTBACK_PROOF_SIZE,
|
||||
forgedBulletproof.data(),
|
||||
kEcSingleBulletproofLength);
|
||||
|
||||
return proof;
|
||||
}
|
||||
|
||||
// Get a bad ciphertext with valid structure but cryptographic invalid for
|
||||
// testing purposes. For preflight test purposes.
|
||||
static Buffer const&
|
||||
@@ -347,6 +428,111 @@ protected:
|
||||
}
|
||||
};
|
||||
|
||||
// Forges a ConfidentialMPTSend proof (compact sigma + double bulletproof)
|
||||
// for setup.sendAmount against setup's real balance commitment/ciphertext.
|
||||
// mpt_get_confidential_send_proof does not allow to build a proof whose amount
|
||||
// exceeds the sender's claimed balance.
|
||||
static Buffer
|
||||
getForgedSendProof(
|
||||
test::jtx::MPTTester& mpt,
|
||||
test::jtx::Env& env,
|
||||
test::jtx::Account const& sender,
|
||||
test::jtx::Account const& dest,
|
||||
ConfidentialSendSetup const& setup)
|
||||
{
|
||||
auto* const ctx = mpt_secp256k1_context();
|
||||
|
||||
secp256k1_pubkey c1;
|
||||
std::vector<secp256k1_pubkey> c2Vec(setup.recipients.size());
|
||||
std::vector<secp256k1_pubkey> pkVec(setup.recipients.size());
|
||||
for (std::size_t i = 0; i < setup.recipients.size(); ++i)
|
||||
{
|
||||
auto const& r = setup.recipients[i];
|
||||
if (i == 0 &&
|
||||
secp256k1_ec_pubkey_parse(
|
||||
ctx, &c1, r.encryptedAmount.data(), kCompressedEcPointLength) != 1)
|
||||
Throw<std::runtime_error>("Failed to parse C1");
|
||||
if (secp256k1_ec_pubkey_parse(
|
||||
ctx,
|
||||
&c2Vec[i],
|
||||
r.encryptedAmount.data() + kCompressedEcPointLength,
|
||||
kCompressedEcPointLength) != 1)
|
||||
Throw<std::runtime_error>("Failed to parse C2");
|
||||
if (secp256k1_ec_pubkey_parse(
|
||||
ctx, &pkVec[i], r.publicKey.data(), kCompressedEcPointLength) != 1)
|
||||
Throw<std::runtime_error>("Failed to parse recipient pubkey");
|
||||
}
|
||||
|
||||
secp256k1_pubkey pkSender, pcAmount, pcBalance, b1, b2;
|
||||
if (secp256k1_ec_pubkey_parse(
|
||||
ctx, &pkSender, setup.senderPubKey.data(), kCompressedEcPointLength) != 1 ||
|
||||
secp256k1_ec_pubkey_parse(
|
||||
ctx, &pcAmount, setup.amountCommitment.data(), kCompressedEcPointLength) != 1 ||
|
||||
secp256k1_ec_pubkey_parse(
|
||||
ctx, &pcBalance, setup.balanceCommitment.data(), kCompressedEcPointLength) != 1 ||
|
||||
secp256k1_ec_pubkey_parse(
|
||||
ctx, &b1, setup.prevEncryptedSpending.data(), kCompressedEcPointLength) != 1 ||
|
||||
secp256k1_ec_pubkey_parse(
|
||||
ctx,
|
||||
&b2,
|
||||
setup.prevEncryptedSpending.data() + kCompressedEcPointLength,
|
||||
kCompressedEcPointLength) != 1)
|
||||
Throw<std::runtime_error>("Failed to parse commitments/ciphertext");
|
||||
|
||||
Buffer const senderPrivKey =
|
||||
requireOptional(mpt.getPrivKey(sender), "Missing sender privkey");
|
||||
auto const ctxHash = getSendContextHash(
|
||||
sender.id(), mpt.issuanceID(), env.seq(sender), dest.id(), setup.version);
|
||||
|
||||
Buffer sigmaProof(SECP256K1_COMPACT_STANDARD_PROOF_SIZE);
|
||||
if (secp256k1_compact_standard_prove(
|
||||
ctx,
|
||||
sigmaProof.data(),
|
||||
setup.sendAmount,
|
||||
setup.prevSpending,
|
||||
setup.blindingFactor.data(),
|
||||
senderPrivKey.data(),
|
||||
setup.balanceBlindingFactor.data(),
|
||||
setup.recipients.size(),
|
||||
&c1,
|
||||
c2Vec.data(),
|
||||
pkVec.data(),
|
||||
&pcAmount,
|
||||
&pkSender,
|
||||
&pcBalance,
|
||||
&b1,
|
||||
&b2,
|
||||
ctxHash.data()) != 1)
|
||||
Throw<std::runtime_error>("Failed to generate sigma proof");
|
||||
|
||||
// Wraps (mod 2^64) for overdrafts, unlike the ledger's own homomorphic
|
||||
// commitment subtraction (mod the curve order) — that mismatch is
|
||||
// exactly what makes the forged proof fail verification.
|
||||
// Computed without a wrapping `uint64` subtract: Clang UBSan treats
|
||||
// unsigned overflow as fatal (see incrementConfidentialVersion).
|
||||
std::uint64_t const remaining = setup.sendAmount <= setup.prevSpending
|
||||
? setup.prevSpending - setup.sendAmount
|
||||
: ~setup.sendAmount + setup.prevSpending + 1;
|
||||
|
||||
Buffer negAmountBf(kEcBlindingFactorLength);
|
||||
Buffer remainingBf(kEcBlindingFactorLength);
|
||||
secp256k1_mpt_scalar_negate(negAmountBf.data(), setup.amountBlindingFactor.data());
|
||||
secp256k1_mpt_scalar_add(
|
||||
remainingBf.data(), setup.balanceBlindingFactor.data(), negAmountBf.data());
|
||||
|
||||
auto const forgedBulletproof = getForgedBulletproof(
|
||||
{setup.sendAmount, remaining}, {setup.amountBlindingFactor, remainingBf}, ctxHash);
|
||||
|
||||
Buffer combinedProof(kEcSendProofLength);
|
||||
std::memcpy(combinedProof.data(), sigmaProof.data(), SECP256K1_COMPACT_STANDARD_PROOF_SIZE);
|
||||
std::memcpy(
|
||||
combinedProof.data() + SECP256K1_COMPACT_STANDARD_PROOF_SIZE,
|
||||
forgedBulletproof.data(),
|
||||
kEcDoubleBulletproofLength);
|
||||
|
||||
return combinedProof;
|
||||
}
|
||||
|
||||
// Helper that wraps the boilerplate setup: Env + MPT creation, funding, key
|
||||
// generation, and seeding each holder with a confidential balance.
|
||||
// The caller supplies the issuer and any number of holders.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -742,7 +742,7 @@ public:
|
||||
PedersenProofParams const& amountParams,
|
||||
PedersenProofParams const& balanceParams) const;
|
||||
|
||||
[[nodiscard]] Buffer
|
||||
[[nodiscard]] std::optional<Buffer>
|
||||
getConvertBackProof(
|
||||
Account const& holder,
|
||||
std::uint64_t const amount,
|
||||
@@ -841,15 +841,28 @@ private:
|
||||
[[nodiscard]] std::uint32_t
|
||||
getFlags(std::optional<Account> const& holder) const;
|
||||
|
||||
/**
|
||||
* @brief Sets sfMPTokenIssuanceID on jv, falling back to id_ if arg's id is
|
||||
* not set.
|
||||
*
|
||||
* @param jv The JSON object to set the field on.
|
||||
* @param id The explicit issuance ID override from the caller, if any.
|
||||
*/
|
||||
void
|
||||
setIssuanceIdField(json::Value& jv, std::optional<MPTID> const& id) const;
|
||||
|
||||
[[nodiscard]] std::uint32_t
|
||||
ticketOrSeq(
|
||||
std::optional<std::uint32_t> const& ticketSeq,
|
||||
std::optional<Account> const& account) const;
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
fillConversionCiphertexts(
|
||||
T const& arg,
|
||||
json::Value& jv,
|
||||
Buffer& holderCiphertext,
|
||||
Buffer& issuerCiphertext,
|
||||
std::optional<Buffer>& auditorCiphertext,
|
||||
Buffer& blindingFactor) const;
|
||||
Account const& account,
|
||||
std::uint64_t const amount) const;
|
||||
};
|
||||
|
||||
} // namespace xrpl::test::jtx
|
||||
|
||||
Reference in New Issue
Block a user