// Copyright © Albert Lane. All Rights Reserved. // Proprietary and Confidential — Albert Lane Proprietary Software License. // Unauthorized use, copying, modification, or distribution is prohibited. // License inquiries: https://albertlane.net #include "eth_crypto.hpp" #include #include #include #include #include #include namespace lane_crypto { static Bytes digest(const char* algo, const std::string& data) { EVP_MD* md = EVP_MD_fetch(nullptr, algo, nullptr); if (!md) return {}; Bytes out(EVP_MD_get_size(md)); unsigned int n = 0; EVP_MD_CTX* c = EVP_MD_CTX_new(); bool ok = c && EVP_DigestInit_ex(c, md, nullptr) == 1 && EVP_DigestUpdate(c, data.data(), data.size()) == 1 && EVP_DigestFinal_ex(c, out.data(), &n) == 1; EVP_MD_CTX_free(c); EVP_MD_free(md); if (!ok || n != out.size()) return {}; return out; } Bytes sha256(const std::string& d) { return digest("SHA256", d); } Bytes keccak256(const std::string& d) { return digest("KECCAK-256", d); } std::string to_hex(const Bytes& b) { static const char* H = "0123456789abcdef"; std::string s; s.reserve(b.size() * 2); for (uint8_t x : b) { s.push_back(H[x >> 4]); s.push_back(H[x & 15]); } return s; } std::string normalize_address(const std::string& a) { std::string s = a; if (s.size() == 42 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) s = s.substr(2); if (s.size() != 40) return ""; for (auto& ch : s) { if (!std::isxdigit((unsigned char)ch)) return ""; ch = (char)std::tolower((unsigned char)ch); } if (s == std::string(40, '0')) return ""; return "0x" + s; } Bytes eip191_digest(const std::string& m) { return keccak256(std::string("\x19" "Ethereum Signed Message:\n") + std::to_string(m.size()) + m); } static secp256k1_context* ctx() { static secp256k1_context* c = secp256k1_context_create(SECP256K1_CONTEXT_NONE); return c; } bool recover_address(const Bytes& d, const std::string& sig, std::string& out, std::string& err) { out.clear(); if (d.size() != 32) { err = "digest must be 32 bytes"; return false; } if (sig.size() != 65) { err = "signature must be 65 bytes (got " + std::to_string(sig.size()) + ")"; return false; } int v = (uint8_t)sig[64]; if (v >= 27) v -= 27; if (v != 0 && v != 1) { err = "bad recovery id v"; return false; } secp256k1_ecdsa_recoverable_signature rs; if (!secp256k1_ecdsa_recoverable_signature_parse_compact(ctx(), &rs, (const unsigned char*)sig.data(), v)) { err = "unparsable r||s"; return false; } // Reject high-s (malleable) signatures, as Ethereum (EIP-2) does. secp256k1_ecdsa_signature plain, norm; secp256k1_ecdsa_recoverable_signature_convert(ctx(), &plain, &rs); if (secp256k1_ecdsa_signature_normalize(ctx(), &norm, &plain)) { err = "high-s signature rejected"; return false; } secp256k1_pubkey pk; if (!secp256k1_ecdsa_recover(ctx(), &pk, &rs, d.data())) { err = "ecrecover failed"; return false; } unsigned char ser[65]; size_t len = 65; secp256k1_ec_pubkey_serialize(ctx(), ser, &len, &pk, SECP256K1_EC_UNCOMPRESSED); Bytes h = keccak256(std::string((const char*)ser + 1, 64)); if (h.size() != 32) { err = "keccak unavailable"; return false; } out = "0x" + to_hex(Bytes(h.begin() + 12, h.end())); return true; } bool self_test(std::string& err) { if (to_hex(sha256("abc")) != "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad") { err = "SHA-256 KAT failed"; return false; } if (to_hex(keccak256("")) != "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") { err = "Keccak-256 KAT failed (OpenSSL lacks KECCAK-256?)"; return false; } if (!ctx()) { err = "secp256k1 context"; return false; } return true; } } // namespace lane_crypto