// Copyright (c) 2014-2018 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Boolberry developers // Copyright (c) 2017-2025 Lethean (https://lt.hn) // // Licensed under the European Union Public Licence (EUPL) version 1.2. // You may obtain a copy of the licence at: // // https://joinup.ec.europa.eu/software/page/eupl/licence-eupl // // The EUPL is a copyleft licence that is compatible with the MIT/X11 // licence used by the original projects; the MIT terms are therefore // considered “grandfathered” under the EUPL for this code. // // SPDX‑License‑Identifier: EUPL-1.2 // #include "eth_signature.h" #include "crypto.h" #ifndef USE_OPEN_SSL_FOR_ECDSA #include "bitcoin-secp256k1/include/secp256k1.h" #endif #include "random.h" #include "misc_language.h" #include #ifdef USE_OPEN_SSL_FOR_ECDSA #include #include #include #include #include #endif // Function to create EC_KEY from raw 32 - byte private key EC_KEY * create_ec_key_from_private_key(const unsigned char* private_key) { EC_KEY* key = EC_KEY_new_by_curve_name(NID_secp256k1); if (!key) { std::cerr << "Failed to create new EC Key" << std::endl; return nullptr; } BIGNUM* priv_key_bn = BN_bin2bn(private_key, 32, nullptr); if (!priv_key_bn) { std::cerr << "Failed to convert private key to BIGNUM" << std::endl; EC_KEY_free(key); return nullptr; } if (!EC_KEY_set_private_key(key, priv_key_bn)) { std::cerr << "Failed to set private key" << std::endl; EC_KEY_free(key); BN_free(priv_key_bn); return nullptr; } BN_free(priv_key_bn); return key; } void ensure_canonical_s(BIGNUM* s, const EC_GROUP* group) { // Get the order of the curve BIGNUM* order = BN_new(); EC_GROUP_get_order(group, order, nullptr); // Compute half of the order: `n / 2` BIGNUM* half_order = BN_new(); BN_rshift1(half_order, order); // If `s` is greater than `n / 2`, replace `s` with `n - s` if (BN_cmp(s, half_order) > 0) { BN_sub(s, order, s); } BN_free(order); BN_free(half_order); } // Update the function to ensure canonical `s` bool generate_ethereum_signature(const unsigned char* hash, const unsigned char* private_key, crypto::eth_signature& sig_res) { EC_KEY* ec_key = create_ec_key_from_private_key(private_key); if (!ec_key) { throw std::runtime_error("Failed to create EC key from private key"); } // Sign the hash unsigned int sig_len = ECDSA_size(ec_key); std::vector signature(sig_len); if (ECDSA_sign(0, hash, 32, signature.data(), &sig_len, ec_key) == 0) { EC_KEY_free(ec_key); throw std::runtime_error("Failed to create signature"); } signature.resize(sig_len); // The OpenSSL ECDSA signature output is DER encoded, Ethereum expects (r, s, v) const unsigned char* p = signature.data(); ECDSA_SIG* sig = d2i_ECDSA_SIG(nullptr, &p, sig_len); if (!sig) { EC_KEY_free(ec_key); throw std::runtime_error("Failed to parse ECDSA signature"); } const BIGNUM* r = nullptr; const BIGNUM* s = nullptr; ECDSA_SIG_get0(sig, &r, &s); // Ensure canonical `s` BIGNUM* s_canonical = BN_dup(s); ensure_canonical_s(s_canonical, EC_KEY_get0_group(ec_key)); BN_bn2binpad(r, (unsigned char* )&sig_res.data[0], 32); BN_bn2binpad(s_canonical, (unsigned char*)&sig_res.data[32], 32); ECDSA_SIG_free(sig); BN_free(s_canonical); EC_KEY_free(ec_key); return true; } // Convert raw 33-byte compressed public key to EC_KEY object EC_KEY* create_ec_key_from_compressed_public_key(const unsigned char* compressed_pub_key) { EC_KEY* key = EC_KEY_new_by_curve_name(NID_secp256k1); if (!key) { std::cerr << "Failed to create EC_KEY object" << std::endl; return nullptr; } EC_POINT* pub_point = EC_POINT_new(EC_KEY_get0_group(key)); if (!EC_POINT_oct2point(EC_KEY_get0_group(key), pub_point, compressed_pub_key, 33, nullptr)) { std::cerr << "Failed to convert compressed public key" << std::endl; EC_POINT_free(pub_point); EC_KEY_free(key); return nullptr; } if (!EC_KEY_set_public_key(key, pub_point)) { std::cerr << "Failed to set public key" << std::endl; EC_POINT_free(pub_point); EC_KEY_free(key); return nullptr; } EC_POINT_free(pub_point); return key; } // Function to verify Ethereum-compatible signature bool verify_ethereum_signature(const crypto::hash& m, const crypto::eth_signature& sig_res, const crypto::eth_public_key& compressed_pub_key) { EC_KEY* ec_key = create_ec_key_from_compressed_public_key((const unsigned char*)&compressed_pub_key.data[0]); if (!ec_key) { throw std::runtime_error("Failed to create EC key from compressed public key"); } const unsigned char* r = (unsigned char*)&sig_res.data[0]; const unsigned char* s = (unsigned char*)&sig_res.data[32]; const unsigned char* hash = (unsigned char*)&m; // Create ECDSA_SIG from r and s BIGNUM* bn_r = BN_bin2bn(r, 32, nullptr); BIGNUM* bn_s = BN_bin2bn(s, 32, nullptr); if (!bn_r || !bn_s) { EC_KEY_free(ec_key); BN_free(bn_r); BN_free(bn_s); throw std::runtime_error("Failed to convert r or s to BIGNUM"); } ECDSA_SIG* sig = ECDSA_SIG_new(); if (!sig) { EC_KEY_free(ec_key); BN_free(bn_r); BN_free(bn_s); throw std::runtime_error("Failed to create ECDSA_SIG object"); } if (!ECDSA_SIG_set0(sig, bn_r, bn_s)) { EC_KEY_free(ec_key); ECDSA_SIG_free(sig); BN_free(bn_r); BN_free(bn_s); throw std::runtime_error("Failed to set r and s in ECDSA_SIG"); } // Verify the signature int verification_result = ECDSA_do_verify(hash, 32, sig, ec_key); ECDSA_SIG_free(sig); EC_KEY_free(ec_key); return verification_result == 1; } // // struct KeyPair { // std::vector private_key; // 32 bytes // std::vector public_key; // 33 bytes (compressed format) // }; // Function to generate an Ethereum-compatible key pair bool generate_ethereum_key_pair(crypto::eth_secret_key& sec_key, crypto::eth_public_key& pub_key) { /*KeyPair keypair;*/ // Create a new EC_KEY object with the secp256k1 curve EC_KEY* key = EC_KEY_new_by_curve_name(NID_secp256k1); if (!key) { throw std::runtime_error("Failed to create new EC_KEY object"); } // Generate the key pair if (EC_KEY_generate_key(key) == 0) { EC_KEY_free(key); throw std::runtime_error("Failed to generate key pair"); } // Extract the private key const BIGNUM* priv_bn = EC_KEY_get0_private_key(key); if (!priv_bn) { EC_KEY_free(key); throw std::runtime_error("Failed to get private key"); } BN_bn2binpad(priv_bn, (unsigned char*)&sec_key.data[0], 32); // Extract the public key in compressed format const EC_POINT* pub_point = EC_KEY_get0_public_key(key); if (!pub_point) { EC_KEY_free(key); throw std::runtime_error("Failed to get public key"); } //keypair.public_key.resize(33); // Compressed format if (EC_POINT_point2oct(EC_KEY_get0_group(key), pub_point, POINT_CONVERSION_COMPRESSED, (unsigned char*)&pub_key.data[0], sizeof(pub_key.data), nullptr) == 0) { EC_KEY_free(key); throw std::runtime_error("Failed to convert public key to compressed format"); } EC_KEY_free(key); return true; } namespace crypto { bool generate_eth_key_pair(eth_secret_key& sec_key, eth_public_key& pub_key) noexcept { try { #ifndef USE_OPEN_SSL_FOR_ECDSA secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){ secp256k1_context_destroy(ctx); ctx = nullptr; }); uint8_t randomness[32]; crypto::generate_random_bytes(sizeof randomness, randomness); if (!secp256k1_context_randomize(ctx, randomness)) return false; for(size_t i = 1024; i != 0; --i) { crypto::generate_random_bytes(sizeof sec_key, sec_key.data); if (secp256k1_ec_seckey_verify(ctx, sec_key.data)) break; if (i == 1) return false; } secp256k1_pubkey uncompressed_pub_key{}; if (!secp256k1_ec_pubkey_create(ctx, &uncompressed_pub_key, sec_key.data)) return false; size_t output_len = sizeof pub_key; if (!secp256k1_ec_pubkey_serialize(ctx, pub_key.data, &output_len, &uncompressed_pub_key, SECP256K1_EC_COMPRESSED)) return false; return true; #else return generate_ethereum_key_pair(sec_key, pub_key); #endif } catch(...) { return false; } } #ifndef USE_OPEN_SSL_FOR_ECDSA bool eth_secret_key_to_public_key(const eth_secret_key& sec_key, eth_public_key& pub_key) noexcept { try { // TODO: do we need this? consider using static context secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){ secp256k1_context_destroy(ctx); ctx = nullptr; }); secp256k1_pubkey uncompressed_pub_key{}; if (!secp256k1_ec_pubkey_create(ctx, &uncompressed_pub_key, sec_key.data)) return false; size_t output_len = sizeof pub_key; if (!secp256k1_ec_pubkey_serialize(ctx, pub_key.data, &output_len, &uncompressed_pub_key, SECP256K1_EC_COMPRESSED)) return false; return true; } catch(...) { return false; } } #endif // generates secp256k1 ECDSA signature bool generate_eth_signature(const hash& m, const eth_secret_key& sec_key, eth_signature& sig) noexcept { try { #ifndef USE_OPEN_SSL_FOR_ECDSA secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){ secp256k1_context_destroy(ctx); ctx = nullptr; }); uint8_t randomness[32]; crypto::generate_random_bytes(sizeof randomness, randomness); if (!secp256k1_context_randomize(ctx, randomness)) return false; secp256k1_ecdsa_signature secp256k1_ecdsa_sig{}; if (!secp256k1_ecdsa_sign(ctx, &secp256k1_ecdsa_sig, (const unsigned char*)m.data, sec_key.data, NULL, NULL)) return false; if (!secp256k1_ecdsa_signature_serialize_compact(ctx, sig.data, &secp256k1_ecdsa_sig)) return false; return true; #else return generate_ethereum_signature((const unsigned char*)&m.data, (unsigned char*)&sec_key.data, sig); #endif } catch(...) { return false; } } // verifies secp256k1 ECDSA signature bool verify_eth_signature(const hash& m, const eth_public_key& pub_key, const eth_signature& sig) noexcept { try { // TODO (performance) consider using secp256k1_context_static for verification -- sowle #ifndef USE_OPEN_SSL_FOR_ECDSA secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){ secp256k1_context_destroy(ctx); ctx = nullptr; }); uint8_t randomness[32]; crypto::generate_random_bytes(sizeof randomness, randomness); if (!secp256k1_context_randomize(ctx, randomness)) return false; secp256k1_ecdsa_signature secp256k1_ecdsa_sig{}; secp256k1_pubkey uncompressed_pub_key{}; if (!secp256k1_ecdsa_signature_parse_compact(ctx, &secp256k1_ecdsa_sig, sig.data)) return false; if (!secp256k1_ec_pubkey_parse(ctx, &uncompressed_pub_key, pub_key.data, sizeof pub_key)) return false; // verify a signature if (!secp256k1_ecdsa_verify(ctx, &secp256k1_ecdsa_sig, (const unsigned char*)m.data, &uncompressed_pub_key)) return false; return true; #else return verify_ethereum_signature(m, sig, pub_key); #endif } catch(...) { return false; } } std::ostream& operator<<(std::ostream& o, const eth_secret_key& v) { return o << epee::string_tools::pod_to_hex(v); } std::ostream& operator<<(std::ostream& o, const eth_public_key& v) { return o << epee::string_tools::pod_to_hex(v); } std::ostream& operator<<(std::ostream& o, const eth_signature& v) { return o << epee::string_tools::pod_to_hex(v); } } // namespace crypto