//------------------------------------------------------------------------------ /* 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 #include #include #include #include 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(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(proof.data()), // proof.length(), // Length of the proof byte array (98 bytes) // &c1, // &c2, // reinterpret_cast(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("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("Failed to encrypt amount"); // Serialize the ciphertext pair into the buffer if (!serializeEcPair(c1, c2, buf)) Throw( "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("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("Failed to encrypt amount"); // Serialize the ciphertext pair into the buffer if (!serializeEcPair(c1, c2, buf)) Throw( "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(proof.data()), // proof.length(), // txContextId.data(), // &bal_c1, // &bal_c2, // &sender_c1, // &sender_c2, // reinterpret_cast(senderPubKey.data()), // &dest_c1, // &dest_c2, // reinterpret_cast(destPubKey.data()), // &issuer_c1, // &issuer_c2, // reinterpret_cast(issuerPubKey.data()), // txContextId.data(), // txContextId.bytes) != 1) // return tecBAD_PROOF; return tesSUCCESS; } } // namespace ripple