mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
609 lines
17 KiB
C++
609 lines
17 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
This file is part of rippled: https://github.com/ripple/rippled
|
|
Copyright (c) 2025 Ripple Labs Inc.
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
copyright notice and this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
//==============================================================================
|
|
|
|
#include <xrpl/protocol/ConfidentialTransfer.h>
|
|
#include <xrpl/protocol/Protocol.h>
|
|
#include <xrpl/protocol/TER.h>
|
|
|
|
#include <openssl/rand.h>
|
|
#include <openssl/sha.h>
|
|
|
|
namespace ripple {
|
|
int
|
|
secp256k1_elgamal_generate_keypair(
|
|
secp256k1_context const* ctx,
|
|
unsigned char* privkey,
|
|
secp256k1_pubkey* pubkey)
|
|
{
|
|
// 1. Generate 32 random bytes for the private key
|
|
do
|
|
{
|
|
if (RAND_bytes(privkey, 32) != 1)
|
|
{
|
|
return 0; // Failure
|
|
}
|
|
// 2. Verify the random data is a valid private key.
|
|
} while (secp256k1_ec_seckey_verify(ctx, privkey) != 1);
|
|
|
|
// 3. Create the corresponding public key.
|
|
if (secp256k1_ec_pubkey_create(ctx, pubkey, privkey) != 1)
|
|
{
|
|
return 0; // Failure
|
|
}
|
|
|
|
return 1; // Success
|
|
}
|
|
|
|
// ... implementation of secp256k1_elgamal_encrypt ...
|
|
|
|
int
|
|
secp256k1_elgamal_encrypt(
|
|
secp256k1_context const* ctx,
|
|
secp256k1_pubkey* c1,
|
|
secp256k1_pubkey* c2,
|
|
secp256k1_pubkey const* pubkey_Q,
|
|
uint64_t amount,
|
|
unsigned char const* blinding_factor)
|
|
{
|
|
secp256k1_pubkey S;
|
|
|
|
// First, calculate C1 = k * G
|
|
if (secp256k1_ec_pubkey_create(ctx, c1, blinding_factor) != 1)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Next, calculate the shared secret S = k * Q
|
|
S = *pubkey_Q;
|
|
if (secp256k1_ec_pubkey_tweak_mul(ctx, &S, blinding_factor) != 1)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// --- Handle the amount ---
|
|
if (amount == 0)
|
|
{
|
|
// For amount = 0, C2 = S.
|
|
*c2 = S;
|
|
}
|
|
else
|
|
{
|
|
// For non-zero amounts, proceed as before.
|
|
unsigned char amount_scalar[32] = {0};
|
|
secp256k1_pubkey M;
|
|
secp256k1_pubkey const* points_to_add[2];
|
|
|
|
// Convert amount to a 32-byte BIG-ENDIAN scalar.
|
|
for (int i = 0; i < 8; ++i)
|
|
{
|
|
amount_scalar[31 - i] = (amount >> (i * 8)) & 0xFF;
|
|
}
|
|
|
|
// Calculate M = amount * G
|
|
if (secp256k1_ec_pubkey_create(ctx, &M, amount_scalar) != 1)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Calculate C2 = M + S
|
|
points_to_add[0] = &M;
|
|
points_to_add[1] = &S;
|
|
if (secp256k1_ec_pubkey_combine(ctx, c2, points_to_add, 2) != 1)
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1; // Success
|
|
}
|
|
|
|
// ... implementation of secp256k1_elgamal_decrypt ...
|
|
int
|
|
secp256k1_elgamal_decrypt(
|
|
secp256k1_context const* ctx,
|
|
uint64_t* amount,
|
|
secp256k1_pubkey const* c1,
|
|
secp256k1_pubkey const* c2,
|
|
unsigned char const* privkey)
|
|
{
|
|
secp256k1_pubkey S, M, G_point, current_M, next_M;
|
|
secp256k1_pubkey const* points_to_add[2];
|
|
unsigned char c2_bytes[33], s_bytes[33], m_bytes[33], current_m_bytes[33];
|
|
size_t len;
|
|
uint64_t i;
|
|
|
|
/* Create the scalar '1' in big-endian format */
|
|
unsigned char one_scalar[32] = {0};
|
|
one_scalar[31] = 1;
|
|
|
|
/* --- Executable Code --- */
|
|
|
|
// 1. Calculate S = privkey * C1
|
|
S = *c1;
|
|
if (secp256k1_ec_pubkey_tweak_mul(ctx, &S, privkey) != 1)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// 2. Check for amount = 0 by comparing serialized points
|
|
len = sizeof(c2_bytes);
|
|
if (secp256k1_ec_pubkey_serialize(
|
|
ctx, c2_bytes, &len, c2, SECP256K1_EC_COMPRESSED) != 1)
|
|
return 0;
|
|
len = sizeof(s_bytes);
|
|
if (secp256k1_ec_pubkey_serialize(
|
|
ctx, s_bytes, &len, &S, SECP256K1_EC_COMPRESSED) != 1)
|
|
return 0;
|
|
if (memcmp(c2_bytes, s_bytes, sizeof(c2_bytes)) == 0)
|
|
{
|
|
*amount = 0;
|
|
return 1;
|
|
}
|
|
|
|
// 3. Recover M = C2 - S
|
|
if (secp256k1_ec_pubkey_negate(ctx, &S) != 1)
|
|
return 0;
|
|
points_to_add[0] = c2;
|
|
points_to_add[1] = &S;
|
|
if (secp256k1_ec_pubkey_combine(ctx, &M, points_to_add, 2) != 1)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// 4. Serialize M once for comparison in the loop
|
|
len = sizeof(m_bytes);
|
|
if (secp256k1_ec_pubkey_serialize(
|
|
ctx, m_bytes, &len, &M, SECP256K1_EC_COMPRESSED) != 1)
|
|
return 0;
|
|
|
|
// 5. Brute-force search loop
|
|
if (secp256k1_ec_pubkey_create(ctx, &G_point, one_scalar) != 1)
|
|
return 0;
|
|
current_M = G_point;
|
|
|
|
for (i = 1; i <= 1000000; ++i)
|
|
{
|
|
len = sizeof(current_m_bytes);
|
|
if (secp256k1_ec_pubkey_serialize(
|
|
ctx,
|
|
current_m_bytes,
|
|
&len,
|
|
¤t_M,
|
|
SECP256K1_EC_COMPRESSED) != 1)
|
|
return 0;
|
|
if (memcmp(m_bytes, current_m_bytes, sizeof(m_bytes)) == 0)
|
|
{
|
|
*amount = i;
|
|
return 1;
|
|
}
|
|
|
|
points_to_add[0] = ¤t_M;
|
|
points_to_add[1] = &G_point;
|
|
if (secp256k1_ec_pubkey_combine(ctx, &next_M, points_to_add, 2) != 1)
|
|
return 0;
|
|
current_M = next_M;
|
|
}
|
|
|
|
return 0; // Not found
|
|
}
|
|
|
|
int
|
|
secp256k1_elgamal_add(
|
|
secp256k1_context const* ctx,
|
|
secp256k1_pubkey* sum_c1,
|
|
secp256k1_pubkey* sum_c2,
|
|
secp256k1_pubkey const* a_c1,
|
|
secp256k1_pubkey const* a_c2,
|
|
secp256k1_pubkey const* b_c1,
|
|
secp256k1_pubkey const* b_c2)
|
|
{
|
|
secp256k1_pubkey const* c1_points[2] = {a_c1, b_c1};
|
|
if (secp256k1_ec_pubkey_combine(ctx, sum_c1, c1_points, 2) != 1)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
secp256k1_pubkey const* c2_points[2] = {a_c2, b_c2};
|
|
if (secp256k1_ec_pubkey_combine(ctx, sum_c2, c2_points, 2) != 1)
|
|
{
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
secp256k1_elgamal_subtract(
|
|
secp256k1_context const* ctx,
|
|
secp256k1_pubkey* diff_c1,
|
|
secp256k1_pubkey* diff_c2,
|
|
secp256k1_pubkey const* a_c1,
|
|
secp256k1_pubkey const* a_c2,
|
|
secp256k1_pubkey const* b_c1,
|
|
secp256k1_pubkey const* b_c2)
|
|
{
|
|
// To subtract, we add the negation: (A - B) is (A + (-B))
|
|
// Make a local, modifiable copy of B's points.
|
|
secp256k1_pubkey neg_b_c1 = *b_c1;
|
|
secp256k1_pubkey neg_b_c2 = *b_c2;
|
|
|
|
// Negate the copies
|
|
if (secp256k1_ec_pubkey_negate(ctx, &neg_b_c1) != 1 ||
|
|
secp256k1_ec_pubkey_negate(ctx, &neg_b_c2) != 1)
|
|
{
|
|
return 0; // Negation failed
|
|
}
|
|
|
|
// Now, add A and the negated copies of B
|
|
secp256k1_pubkey const* c1_points[2] = {a_c1, &neg_b_c1};
|
|
if (secp256k1_ec_pubkey_combine(ctx, diff_c1, c1_points, 2) != 1)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
secp256k1_pubkey const* c2_points[2] = {a_c2, &neg_b_c2};
|
|
if (secp256k1_ec_pubkey_combine(ctx, diff_c2, c2_points, 2) != 1)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return 1; // Success
|
|
}
|
|
|
|
// Helper function to concatenate data for hashing
|
|
static void
|
|
build_hash_input(
|
|
unsigned char* output_buffer,
|
|
size_t buffer_size,
|
|
unsigned char const* account_id, // 20 bytes
|
|
unsigned char const* mpt_issuance_id // 24 bytes
|
|
)
|
|
{
|
|
char const* domain_separator = "EncZero";
|
|
size_t domain_len = strlen(domain_separator);
|
|
size_t offset = 0;
|
|
|
|
// Ensure buffer is large enough (should be checked by caller if necessary)
|
|
// Size = strlen("EncZero") + 20 + 24 = 7 + 20 + 24 = 51 bytes
|
|
|
|
memcpy(output_buffer + offset, domain_separator, domain_len);
|
|
offset += domain_len;
|
|
|
|
memcpy(output_buffer + offset, account_id, 20);
|
|
offset += 20;
|
|
|
|
memcpy(output_buffer + offset, mpt_issuance_id, 24);
|
|
// offset += 24; // Final size is offset + 24
|
|
}
|
|
|
|
// The canonical encrypted zero
|
|
|
|
int
|
|
generate_canonical_encrypted_zero(
|
|
secp256k1_context const* ctx,
|
|
secp256k1_pubkey* enc_zero_c1,
|
|
secp256k1_pubkey* enc_zero_c2,
|
|
secp256k1_pubkey const* pubkey,
|
|
unsigned char const* account_id, // 20 bytes
|
|
unsigned char const* mpt_issuance_id // 24 bytes
|
|
)
|
|
{
|
|
unsigned char deterministic_scalar[32];
|
|
unsigned char hash_input[51]; // Size calculated above
|
|
|
|
/* 1. Create the input buffer for hashing */
|
|
build_hash_input(
|
|
hash_input, sizeof(hash_input), account_id, mpt_issuance_id);
|
|
|
|
/* 2. Hash the buffer to create the deterministic scalar 'r' */
|
|
do
|
|
{
|
|
// Hash the concatenated bytes
|
|
SHA256(hash_input, sizeof(hash_input), deterministic_scalar);
|
|
|
|
/* Note: If the hash output could be invalid (0 or >= n),
|
|
* you might need to add a nonce/counter to hash_input
|
|
* and re-hash in a loop until a valid scalar is produced. */
|
|
} while (secp256k1_ec_seckey_verify(ctx, deterministic_scalar) != 1);
|
|
|
|
/* 3. Encrypt the amount 0 using the deterministic scalar */
|
|
return secp256k1_elgamal_encrypt(
|
|
ctx,
|
|
enc_zero_c1,
|
|
enc_zero_c2,
|
|
pubkey,
|
|
0, /* The amount is zero */
|
|
deterministic_scalar);
|
|
}
|
|
|
|
bool
|
|
makeEcPair(Slice const& buffer, secp256k1_pubkey& out1, secp256k1_pubkey& out2)
|
|
{
|
|
auto parsePubKey = [](Slice const& slice, secp256k1_pubkey& out) {
|
|
return secp256k1_ec_pubkey_parse(
|
|
secp256k1Context(),
|
|
&out,
|
|
reinterpret_cast<unsigned char const*>(slice.data()),
|
|
slice.length());
|
|
};
|
|
|
|
Slice s1{buffer.data(), ecGamalEncryptedLength};
|
|
Slice s2{buffer.data() + ecGamalEncryptedLength, ecGamalEncryptedLength};
|
|
|
|
int const ret1 = parsePubKey(s1, out1);
|
|
int const ret2 = parsePubKey(s2, out2);
|
|
|
|
return ret1 == 1 && ret2 == 1;
|
|
}
|
|
|
|
bool
|
|
serializeEcPair(
|
|
secp256k1_pubkey const& in1,
|
|
secp256k1_pubkey const& in2,
|
|
Buffer& buffer)
|
|
{
|
|
auto serializePubKey = [](secp256k1_pubkey const& pub, unsigned char* out) {
|
|
size_t outLen = ecGamalEncryptedLength; // 33 bytes
|
|
int const ret = secp256k1_ec_pubkey_serialize(
|
|
secp256k1Context(), out, &outLen, &pub, SECP256K1_EC_COMPRESSED);
|
|
return ret == 1 && outLen == ecGamalEncryptedLength;
|
|
};
|
|
|
|
unsigned char* ptr = buffer.data();
|
|
bool const res1 = serializePubKey(in1, ptr);
|
|
bool const res2 = serializePubKey(in2, ptr + ecGamalEncryptedLength);
|
|
|
|
return res1 && res2;
|
|
}
|
|
|
|
TER
|
|
homomorphicAdd(Slice const& a, Slice const& b, Buffer& out)
|
|
{
|
|
if (a.length() != ecGamalEncryptedTotalLength ||
|
|
b.length() != ecGamalEncryptedTotalLength)
|
|
return tecINTERNAL;
|
|
|
|
secp256k1_pubkey a_c1;
|
|
secp256k1_pubkey a_c2;
|
|
secp256k1_pubkey b_c1;
|
|
secp256k1_pubkey b_c2;
|
|
|
|
if (!makeEcPair(a, a_c1, a_c2) || !makeEcPair(b, b_c1, b_c2))
|
|
return tecINTERNAL;
|
|
|
|
secp256k1_pubkey sum_c1;
|
|
secp256k1_pubkey sum_c2;
|
|
|
|
// todo:: support addition after it's supported
|
|
if (secp256k1_elgamal_add(
|
|
secp256k1Context(), &sum_c1, &sum_c2, &a_c1, &a_c2, &b_c1, &b_c2) !=
|
|
1)
|
|
return tecINTERNAL;
|
|
|
|
if (!serializeEcPair(sum_c1, sum_c2, out))
|
|
return tecINTERNAL;
|
|
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
TER
|
|
homomorphicSubtract(Slice const& a, Slice const& b, Buffer& out)
|
|
{
|
|
if (a.length() != ecGamalEncryptedTotalLength ||
|
|
b.length() != ecGamalEncryptedTotalLength)
|
|
return tecINTERNAL;
|
|
|
|
secp256k1_pubkey a_c1;
|
|
secp256k1_pubkey a_c2;
|
|
secp256k1_pubkey b_c1;
|
|
secp256k1_pubkey b_c2;
|
|
|
|
if (!makeEcPair(a, a_c1, a_c2) || !makeEcPair(b, b_c1, b_c2))
|
|
return tecINTERNAL;
|
|
|
|
secp256k1_pubkey diff_c1;
|
|
secp256k1_pubkey diff_c2;
|
|
|
|
if (secp256k1_elgamal_subtract(
|
|
secp256k1Context(),
|
|
&diff_c1,
|
|
&diff_c2,
|
|
&a_c1,
|
|
&a_c2,
|
|
&b_c1,
|
|
&b_c2) != 1)
|
|
return tecINTERNAL;
|
|
|
|
if (!serializeEcPair(diff_c1, diff_c2, out))
|
|
return tecINTERNAL;
|
|
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
TER
|
|
proveEquality(
|
|
Slice const& proof,
|
|
Slice const& encAmt, // encrypted amount
|
|
Slice const& pubkey,
|
|
uint64_t const amount,
|
|
uint256 const& txHash, // Transaction context data
|
|
std::uint32_t const spendVersion)
|
|
{
|
|
if (proof.length() != ecEqualityProofLength)
|
|
return tecINTERNAL;
|
|
|
|
secp256k1_pubkey c1;
|
|
secp256k1_pubkey c2;
|
|
|
|
if (!makeEcPair(encAmt, c1, c2))
|
|
return tecINTERNAL;
|
|
|
|
// todo: might need to change how its hashed
|
|
Serializer s;
|
|
s.addRaw(txHash.data(), txHash.bytes);
|
|
s.add32(spendVersion);
|
|
auto const txContextId = s.getSHA512Half();
|
|
|
|
// todo: support equality
|
|
// if (secp256k1_equality_verify(
|
|
// secp256k1Context(),
|
|
// reinterpret_cast<unsigned char const*>(proof.data()),
|
|
// proof.length(), // Length of the proof byte array (98 bytes)
|
|
// &c1,
|
|
// &c2,
|
|
// reinterpret_cast<unsigned char const*>(pubkey.data()),
|
|
// amount,
|
|
// txContextId.data(), // Transaction context data
|
|
// txContextId.bytes // Length of context data
|
|
// ) != 1)
|
|
// return tecBAD_PROOF;
|
|
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
Buffer
|
|
encryptAmount(uint64_t amt, Slice const& pubKeySlice)
|
|
{
|
|
Buffer buf(ecGamalEncryptedTotalLength);
|
|
|
|
// Allocate ciphertext placeholders
|
|
secp256k1_pubkey c1, c2;
|
|
|
|
// Prepare a random blinding factor
|
|
unsigned char blinding_factor[32];
|
|
if (RAND_bytes(blinding_factor, 32) != 1)
|
|
Throw<std::runtime_error>("Failed to generate random number");
|
|
|
|
secp256k1_pubkey pubKey;
|
|
|
|
std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength);
|
|
|
|
// Encrypt the amount
|
|
if (!secp256k1_elgamal_encrypt(
|
|
secp256k1Context(), &c1, &c2, &pubKey, amt, blinding_factor))
|
|
Throw<std::runtime_error>("Failed to encrypt amount");
|
|
|
|
// Serialize the ciphertext pair into the buffer
|
|
if (!serializeEcPair(c1, c2, buf))
|
|
Throw<std::runtime_error>(
|
|
"Failed to serialize into 66 byte compressed format");
|
|
|
|
return buf;
|
|
}
|
|
|
|
Buffer
|
|
encryptCanonicalZeroAmount(
|
|
Slice const& pubKeySlice,
|
|
AccountID const& account,
|
|
MPTID const& mptId)
|
|
{
|
|
Buffer buf(ecGamalEncryptedTotalLength);
|
|
|
|
// Allocate ciphertext placeholders
|
|
secp256k1_pubkey c1, c2;
|
|
|
|
// Prepare a random blinding factor
|
|
unsigned char blinding_factor[32];
|
|
if (RAND_bytes(blinding_factor, 32) != 1)
|
|
Throw<std::runtime_error>("Failed to generate random number");
|
|
|
|
secp256k1_pubkey pubKey;
|
|
|
|
std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength);
|
|
|
|
// Encrypt the amount
|
|
if (!generate_canonical_encrypted_zero(
|
|
secp256k1Context(),
|
|
&c1,
|
|
&c2,
|
|
&pubKey,
|
|
account.data(),
|
|
mptId.data()))
|
|
Throw<std::runtime_error>("Failed to encrypt amount");
|
|
|
|
// Serialize the ciphertext pair into the buffer
|
|
if (!serializeEcPair(c1, c2, buf))
|
|
Throw<std::runtime_error>(
|
|
"Failed to serialize into 66 byte compressed format");
|
|
|
|
return buf;
|
|
}
|
|
|
|
TER
|
|
verifyConfidentialSendProof(
|
|
Slice const& proof,
|
|
Slice const& encSenderBalance,
|
|
Slice const& encSenderAmt,
|
|
Slice const& encDestAmt,
|
|
Slice const& encIssuerAmt,
|
|
Slice const& senderPubKey,
|
|
Slice const& destPubKey,
|
|
Slice const& issuerPubKey,
|
|
std::uint32_t const version,
|
|
uint256 const& txHash)
|
|
{
|
|
// if (proof.length() != ecConfidentialSendProofLength)
|
|
// return tecINTERNAL;
|
|
|
|
secp256k1_pubkey bal_c1, bal_c2;
|
|
if (!makeEcPair(encSenderBalance, bal_c1, bal_c2))
|
|
return tecINTERNAL;
|
|
|
|
secp256k1_pubkey sender_c1, sender_c2;
|
|
if (!makeEcPair(encSenderAmt, sender_c1, sender_c2))
|
|
return tecINTERNAL;
|
|
|
|
secp256k1_pubkey dest_c1, dest_c2;
|
|
if (!makeEcPair(encDestAmt, dest_c1, dest_c2))
|
|
return tecINTERNAL;
|
|
|
|
secp256k1_pubkey issuer_c1, issuer_c2;
|
|
if (!makeEcPair(encIssuerAmt, issuer_c1, issuer_c2))
|
|
return tecINTERNAL;
|
|
|
|
Serializer s;
|
|
s.addRaw(txHash.data(), txHash.bytes);
|
|
s.add32(version);
|
|
auto const txContextId = s.getSHA512Half();
|
|
|
|
// todo: equality and range proof verification
|
|
// if (secp256k1_equal_range_verify(
|
|
// secp256k1Context(),
|
|
// reinterpret_cast<unsigned char const*>(proof.data()),
|
|
// proof.length(),
|
|
// txContextId.data(),
|
|
// &bal_c1,
|
|
// &bal_c2,
|
|
// &sender_c1,
|
|
// &sender_c2,
|
|
// reinterpret_cast<unsigned char const*>(senderPubKey.data()),
|
|
// &dest_c1,
|
|
// &dest_c2,
|
|
// reinterpret_cast<unsigned char const*>(destPubKey.data()),
|
|
// &issuer_c1,
|
|
// &issuer_c2,
|
|
// reinterpret_cast<unsigned char const*>(issuerPubKey.data()),
|
|
// txContextId.data(),
|
|
// txContextId.bytes) != 1)
|
|
// return tecBAD_PROOF;
|
|
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
} // namespace ripple
|