diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index dbcbbdf22a..f8dac444e5 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -22,6 +22,7 @@ * THE SOFTWARE. */ +#include #include #include #include @@ -124,6 +125,9 @@ void_result account_create_evaluator::do_evaluate( const account_create_operatio FC_ASSERT( fee_paying_account->is_lifetime_member(), "Only Lifetime members may register an account." ); FC_ASSERT( op.referrer(d).is_member(d.head_block_time()), "The referrer must be either a lifetime or annual subscriber." ); + FC_ASSERT(graphene::protocol::account_name_validator::is_valid_account_name(op.name), + "Account name '${name}' matches known blockchain address pattern and cannot be registered", + ("name", op.name)); try { diff --git a/libraries/protocol/CMakeLists.txt b/libraries/protocol/CMakeLists.txt index fd0d12e041..2ceb4bdecf 100644 --- a/libraries/protocol/CMakeLists.txt +++ b/libraries/protocol/CMakeLists.txt @@ -1,6 +1,7 @@ file(GLOB HEADERS "include/graphene/protocol/*.hpp") list(APPEND SOURCES account.cpp + account_name_validation.cpp assert.cpp asset_ops.cpp block.cpp diff --git a/libraries/protocol/account.cpp b/libraries/protocol/account.cpp index f290066d38..c342de1690 100644 --- a/libraries/protocol/account.cpp +++ b/libraries/protocol/account.cpp @@ -21,6 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ +#include #include #include @@ -63,6 +64,12 @@ namespace graphene { namespace protocol { */ bool is_valid_name( const string& name ) { try { + + if( not is_valid_account_name(name) ) + { + return false; + } + const size_t len = name.size(); if( len < GRAPHENE_MIN_ACCOUNT_NAME_LENGTH ) diff --git a/libraries/protocol/account_name_validation.cpp b/libraries/protocol/account_name_validation.cpp new file mode 100644 index 0000000000..20d24b5035 --- /dev/null +++ b/libraries/protocol/account_name_validation.cpp @@ -0,0 +1,409 @@ +#include +#include +#include +#include +#include + +namespace graphene { namespace protocol { + +bool account_name_validator::_patterns_initialized = false; +std::vector> account_name_validator::_forbidden_patterns; + +bool account_name_validator::initialize_patterns() +{ + if(_patterns_initialized) return true; + + try { + // ====================== + // ETHEREUM ECOSYSTEM + // ====================== + _forbidden_patterns.emplace_back(std::regex("^0x[a-fA-F0-9]{40}$"), ETHEREUM_ECOSYSTEM); // Ethereum, BSC, Polygon, etc. + _forbidden_patterns.emplace_back(std::regex("^0x[a-fA-F0-9]{64}$"), ETHEREUM_ECOSYSTEM); // Transaction hashes sometimes used as addresses + + // ====================== + // BITCOIN ECOSYSTEM + // ====================== + _forbidden_patterns.emplace_back(std::regex("^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$"), BITCOIN_ECOSYSTEM); // Legacy P2PKH/P2SH + _forbidden_patterns.emplace_back(std::regex("^bc1[a-z0-9]{25,39}$"), BITCOIN_ECOSYSTEM); // Bech32 + _forbidden_patterns.emplace_back(std::regex("^tb1[a-z0-9]{25,39}$"), BITCOIN_ECOSYSTEM); // Testnet Bech32 + _forbidden_patterns.emplace_back(std::regex("^bcrt1[a-z0-9]{25,39}$"), BITCOIN_ECOSYSTEM); // Regtest Bech32 + + // ====================== + // LITECOIN + // ====================== + _forbidden_patterns.emplace_back(std::regex("^[LM3][a-km-zA-HJ-NP-Z1-9]{25,34}$"), LITECOIN); // Legacy L/M/3 prefix + _forbidden_patterns.emplace_back(std::regex("^ltc1[a-z0-9]{25,39}$"), LITECOIN); // Bech32 + + // ====================== + // DOGECOIN + // ====================== + _forbidden_patterns.emplace_back(std::regex("^D{1}[5-9A-HJ-NP-U]{1}[1-9A-HJ-NP-Za-km-z]{32}$"), DOGECOIN); + + // ====================== + // XRP (RIPPLE) + // ====================== + _forbidden_patterns.emplace_back(std::regex("^r[a-zA-Z0-9]{24,34}$"), XRP); // Standard XRP address + _forbidden_patterns.emplace_back(std::regex("^X[a-zA-Z0-9]{49}$"), XRP); // X-address format + + // ====================== + // COSMOS ECOSYSTEM (Bech32 with prefixes) + // ====================== + // Cosmos Hub + _forbidden_patterns.emplace_back(std::regex("^cosmos1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // Osmosis + _forbidden_patterns.emplace_back(std::regex("^osmo1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // Juno + _forbidden_patterns.emplace_back(std::regex("^juno1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // Secret Network + _forbidden_patterns.emplace_back(std::regex("^secret1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // Kava + _forbidden_patterns.emplace_back(std::regex("^kava1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // Persistence + _forbidden_patterns.emplace_back(std::regex("^persistence1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // Sifchain + _forbidden_patterns.emplace_back(std::regex("^sif1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // Akash + _forbidden_patterns.emplace_back(std::regex("^akash1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // Regen + _forbidden_patterns.emplace_back(std::regex("^regen1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // Terra Classic/Luna 2.0 + _forbidden_patterns.emplace_back(std::regex("^terra1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + _forbidden_patterns.emplace_back(std::regex("^luna1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // Thorchain + _forbidden_patterns.emplace_back(std::regex("^thor1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // Injective + _forbidden_patterns.emplace_back(std::regex("^inj1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // Band Protocol + _forbidden_patterns.emplace_back(std::regex("^band1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // Chihuahua + _forbidden_patterns.emplace_back(std::regex("^chihuahua1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // Bitsong + _forbidden_patterns.emplace_back(std::regex("^bitsong1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // Stargaze + _forbidden_patterns.emplace_back(std::regex("^stars1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // Umee + _forbidden_patterns.emplace_back(std::regex("^umee1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // Cudos + _forbidden_patterns.emplace_back(std::regex("^cudos1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // Desmos + _forbidden_patterns.emplace_back(std::regex("^desmos1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // Comdex + _forbidden_patterns.emplace_back(std::regex("^comdex1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // Vidulum + _forbidden_patterns.emplace_back(std::regex("^vidulum1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // Iris Network + _forbidden_patterns.emplace_back(std::regex("^iaa1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // Sentinel + _forbidden_patterns.emplace_back(std::regex("^sent1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // AssetMantle + _forbidden_patterns.emplace_back(std::regex("^mantle1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + // Decentr + _forbidden_patterns.emplace_back(std::regex("^decentr1[a-z0-9]{38}$"), COSMOS_ECOSYSTEM); + + // ====================== + // SOLANA + // ====================== + _forbidden_patterns.emplace_back(std::regex("^[1-9A-HJ-NP-Za-km-z]{32,44}$"), SOLANA); // Base58, 32-44 chars + _forbidden_patterns.emplace_back(std::regex("^[a-z0-9]{32,44}$"), SOLANA); // Sometimes lowercase hex-like + + // ====================== + // POLKADOT/KUSAMA ECOSYSTEM + // ====================== + _forbidden_patterns.emplace_back(std::regex("^1[a-z0-9]{46}$"), POLKADOT_ECOSYSTEM); // Polkadot SS58 + _forbidden_patterns.emplace_back(std::regex("^k[a-z0-9]{46}$"), POLKADOT_ECOSYSTEM); // Kusama SS58 + _forbidden_patterns.emplace_back(std::regex("^5[a-z0-9]{46}$"), POLKADOT_ECOSYSTEM); // Generic Substrate + _forbidden_patterns.emplace_back(std::regex("^2[a-z0-9]{46}$"), POLKADOT_ECOSYSTEM); // Westend + + // ====================== + // CARDANO + // ====================== + _forbidden_patterns.emplace_back(std::regex("^addr1[02-9a-z]{58}$"), CARDANO); // Shelley mainnet + _forbidden_patterns.emplace_back(std::regex("^addr_test1[02-9a-z]{58}$"), CARDANO); // Testnet + + // ====================== + // MONERO (XMR) + // ====================== + _forbidden_patterns.emplace_back(std::regex("^4[0-9AB][1-9A-HJ-NP-Za-km-z]{93}$"), MONERO); // Standard + _forbidden_patterns.emplace_back(std::regex("^8[0-9AB][1-9A-HJ-NP-Za-km-z]{93}$"), MONERO); // Integrated + _forbidden_patterns.emplace_back(std::regex("^4[0-9AB][1-9A-HJ-NP-Za-km-z]{113}$"), MONERO); // Subaddress + + // ====================== + // TRON (TRX) + // ====================== + _forbidden_patterns.emplace_back(std::regex("^T[a-zA-Z0-9]{32,33}$"), TRON); + + // ====================== + // NEAR PROTOCOL + // ====================== + _forbidden_patterns.emplace_back(std::regex("^[a-z0-9_\-\\.]{2,64}([\\.]near)?$", std::regex_constants::icase), NEAR); + + // ====================== + // ALGORAND + // ====================== + _forbidden_patterns.emplace_back(std::regex("^[A-Z2-7]{58}$"), ALGORAND); // Mainnet + _forbidden_patterns.emplace_back(std::regex("^[A-Z2-7]{58}$"), ALGORAND); // Testnet (same format) + + // ====================== + // TEZOS (XTZ) + // ====================== + _forbidden_patterns.emplace_back(std::regex("^tz[123][a-zA-Z0-9]{33}$"), TEZOS); // tz1/tz2/tz3 + _forbidden_patterns.emplace_back(std::regex("^kt1[a-zA-Z0-9]{33}$"), TEZOS); // Smart contract + + // ====================== + // ZCASH (ZEC) + // ====================== + _forbidden_patterns.emplace_back(std::regex("^t[13][a-km-zA-HJ-NP-Z1-9]{33}$"), ZCASH); // Transparent (t1/t3) + _forbidden_patterns.emplace_back(std::regex("^zs[a-z0-9]{74}$"), ZCASH); // Shielded Sapling + _forbidden_patterns.emplace_back(std::regex("^z[a-z0-9]{94}$"), ZCASH); // Shielded Sprout + + // ====================== + // FILECOIN (FIL) + // ====================== + _forbidden_patterns.emplace_back(std::regex("^f[0-4][a-z0-9]{45}$"), FILECOIN); + + // ====================== + // HARMONY (ONE) + // ====================== + _forbidden_patterns.emplace_back(std::regex("^one1[a-z0-9]{38}$"), HARMONY); + + // ====================== + // ELROND (EGLD) + // ====================== + _forbidden_patterns.emplace_back(std::regex("^erd1[a-z0-9]{47}$"), ELROND); + + // ====================== + // FLOW + // ====================== + _forbidden_patterns.emplace_back(std::regex("^0x[a-fA-F0-9]{16}$"), FLOW); // Flow addresses are 8 bytes + + // ====================== + // HEDERA HASHGRAPH (HBAR) + // ====================== + _forbidden_patterns.emplace_back(std::regex("^[0-9]{1,3}\\.[0-9]{1,4}\\.[0-9]{1,10}$"), HEDERA); // 0.0.12345 format + + // ====================== + // STELLAR (XLM) + // ====================== + _forbidden_patterns.emplace_back(std::regex("^G[a-zA-Z0-9]{55}$"), STELLAR); // Public key + _forbidden_patterns.emplace_back(std::regex("^M[a-zA-Z0-9]{55}$"), STELLAR); // Federated address + + // ====================== + // WAVES + // ====================== + _forbidden_patterns.emplace_back(std::regex("^3[a-zA-Z0-9]{34}$"), WAVES); // Mainnet + _forbidden_patterns.emplace_back(std::regex("^2[a-zA-Z0-9]{34}$"), WAVES); // Testnet + + // ====================== + // NANO + // ====================== + _forbidden_patterns.emplace_back(std::regex("^nano_[a-z0-9]{60}$"), NANO); + _forbidden_patterns.emplace_back(std::regex("^xrb_[a-z0-9]{60}$"), NANO); // Legacy + + // ====================== + // IOTA + // ====================== + _forbidden_patterns.emplace_back(std::regex("^[a-zA-Z0-9]{90}$"), IOTA); // Ed25519 address + _forbidden_patterns.emplace_back(std::regex("^iota1[a-z0-9]{59}$"), IOTA); // Bech32 + + // ====================== + // AVALANCHE (AVAX) + // ====================== + _forbidden_patterns.emplace_back(std::regex("^X-[a-zA-Z0-9]{32,35}$"), AVALANCHE); // X-Chain + _forbidden_patterns.emplace_back(std::regex("^P-[a-zA-Z0-9]{32,35}$"), AVALANCHE); // P-Chain + _forbidden_patterns.emplace_back(std::regex("^C-0x[a-fA-F0-9]{40}$"), AVALANCHE); // C-Chain (Ethereum compatible) + + // ====================== + // FANTOM (FTM) + // ====================== + _forbidden_patterns.emplace_back(std::regex("^0x[a-fA-F0-9]{40}$"), FANTOM); // Same as Ethereum + + // ====================== + // CELO + // ====================== + _forbidden_patterns.emplace_back(std::regex("^0x[a-fA-F0-9]{40}$"), CELO); // Same as Ethereum + + // ====================== + // EOS + // ====================== + _forbidden_patterns.emplace_back(std::regex("^[a-z0-9]{12}$"), EOS); // 12 character account names + _forbidden_patterns.emplace_back(std::regex("^[a-z]{1,11}[\\.]{1}[a-z0-9]{1,10}$"), EOS); // EOSIO resource names + + // ====================== + // CHAINLINK (LINK) + // ====================== + _forbidden_patterns.emplace_back(std::regex("^0x[a-fA-F0-9]{40}$"), CHAINLINK); // ERC-20 token on Ethereum + + // ====================== + // GENERIC PATTERNS + // ====================== + // Generic Bech32 pattern (catches most modern address formats) + _forbidden_patterns.emplace_back(std::regex("^[a-z]{1,12}1[a-z0-9]{25,50}$"), GENERIC); + + // Generic Base58Check pattern (covers Bitcoin, Litecoin, DOGE, etc.) + _forbidden_patterns.emplace_back(std::regex("^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{26,44}$"), GENERIC); + + // Generic hex patterns (catches many address formats) + _forbidden_patterns.emplace_back(std::regex("^[a-fA-F0-9]{42}$"), GENERIC); // 42 chars (ETH with 0x) + _forbidden_patterns.emplace_back(std::regex("^[a-fA-F0-9]{64}$"), GENERIC); // 64 chars (SHA256 hash size) + _forbidden_patterns.emplace_back(std::regex("^[a-fA-F0-9]{34,40}$"), GENERIC); // Common address lengths + + // Transaction hash patterns (sometimes used as addresses in scams) + _forbidden_patterns.emplace_back(std::regex("^[a-fA-F0-9]{64}$"), GENERIC); + + _patterns_initialized = true; + return true; + } catch (const std::regex_error& e) { + elog("Regex initialization failed: ${error}", ("error", e.what())); + return false; + } +} + +bool account_name_validator::is_valid_account_name(const std::string& name) +{ + if(!_patterns_initialized) initialize_patterns(); + + + // Must contain at least one non-hex character (most foreign addresses are pure hex) + bool has_non_hex = false; + for (char c : name) { + if (!std::isxdigit(c) && c != 'x' && c != 'X') { + has_non_hex = true; + break; + } + } + if (!has_non_hex && name.size() >= 40) { + return false; + } + + // Prevent accounts that are just transaction hashes + if (name.size() == 64 && std::all_of(name.begin(), name.end(), [](char c) { + return std::isxdigit(c); + })) { + return false; + } + + // Prevent accounts that are just block hashes + if (name.size() == 64 && std::all_of(name.begin(), name.end(), [](char c) { + return std::isxdigit(c); + })) { + return false; + } + + // Check against all forbidden patterns + for (const auto& pattern_pair : _forbidden_patterns) { + if (std::regex_match(name, pattern_pair.first)) { + return false; + } + } + + return true; +} + +std::string account_name_validator::get_validation_error(const std::string& name) +{ + if(!_patterns_initialized) initialize_patterns(); + + // Must contain at least one non-hex character for long names + bool has_non_hex = false; + for (char c : name) { + if (!std::isxdigit(c) && c != 'x' && c != 'X') { + has_non_hex = true; + break; + } + } + if (!has_non_hex && name.size() >= 40) { + return "Account name must contain non-hexadecimal characters to prevent blockchain address confusion"; + } + + // Check against all forbidden patterns + for (const auto& pattern_pair : _forbidden_patterns) { + if (std::regex_match(name, pattern_pair.first)) { + switch(pattern_pair.second) { + case ETHEREUM_ECOSYSTEM: return "Account name matches Ethereum ecosystem address format (ETH, BSC, Polygon, Avalanche C-Chain, etc.)"; + case BITCOIN_ECOSYSTEM: return "Account name matches Bitcoin address format"; + case LITECOIN: return "Account name matches Litecoin address format"; + case DOGECOIN: return "Account name matches Dogecoin address format"; + case XRP: return "Account name matches XRP (Ripple) address format"; + case COSMOS_ECOSYSTEM: return "Account name matches Cosmos ecosystem address format (Cosmos, Osmosis, Juno, Secret, Thorchain, etc.)"; + case SOLANA: return "Account name matches Solana address format"; + case POLKADOT_ECOSYSTEM: return "Account name matches Polkadot/Kusama ecosystem address format"; + case CARDANO: return "Account name matches Cardano address format"; + case MONERO: return "Account name matches Monero address format"; + case TRON: return "Account name matches TRON address format"; + case NEAR: return "Account name matches NEAR Protocol address format"; + case ALGORAND: return "Account name matches Algorand address format"; + case TEZOS: return "Account name matches Tezos address format"; + case ZCASH: return "Account name matches Zcash address format"; + case FILECOIN: return "Account name matches Filecoin address format"; + case HARMONY: return "Account name matches Harmony address format"; + case ELROND: return "Account name matches Elrond address format"; + case FLOW: return "Account name matches Flow address format"; + case HEDERA: return "Account name matches Hedera Hashgraph address format"; + case STELLAR: return "Account name matches Stellar address format"; + case WAVES: return "Account name matches Waves address format"; + case NANO: return "Account name matches Nano address format"; + case IOTA: return "Account name matches IOTA address format"; + case AVALANCHE: return "Account name matches Avalanche address format"; + case FANTOM: return "Account name matches Fantom address format"; + case CELO: return "Account name matches Celo address format"; + case EOS: return "Account name matches EOS address format"; + case CHAINLINK: return "Account name matches Chainlink address format"; + case GENERIC: return "Account name matches generic blockchain address pattern"; + case EXCHANGE_PATTERNS: return "Account name matches cryptocurrency exchange deposit pattern"; + case SCAM_PATTERNS: return "Account name matches known scam pattern"; + default: return "Account name matches forbidden blockchain address pattern"; + } + } + } + + return ""; +} + +std::vector account_name_validator::get_matching_patterns(const std::string& name) +{ + std::vector matches; + if(!_patterns_initialized) initialize_patterns(); + + for (const auto& pattern_pair : _forbidden_patterns) { + if (std::regex_match(name, pattern_pair.first)) { + switch(pattern_pair.second) { + case ETHEREUM_ECOSYSTEM: matches.push_back("Ethereum ecosystem"); break; + case BITCOIN_ECOSYSTEM: matches.push_back("Bitcoin ecosystem"); break; + case LITECOIN: matches.push_back("Litecoin"); break; + case DOGECOIN: matches.push_back("Dogecoin"); break; + case XRP: matches.push_back("XRP"); break; + case COSMOS_ECOSYSTEM: matches.push_back("Cosmos ecosystem"); break; + case SOLANA: matches.push_back("Solana"); break; + case POLKADOT_ECOSYSTEM: matches.push_back("Polkadot ecosystem"); break; + case CARDANO: matches.push_back("Cardano"); break; + case MONERO: matches.push_back("Monero"); break; + case TRON: matches.push_back("TRON"); break; + case NEAR: matches.push_back("NEAR"); break; + case ALGORAND: matches.push_back("Algorand"); break; + case TEZOS: matches.push_back("Tezos"); break; + case ZCASH: matches.push_back("Zcash"); break; + case FILECOIN: matches.push_back("Filecoin"); break; + case HARMONY: matches.push_back("Harmony"); break; + case ELROND: matches.push_back("Elrond"); break; + case FLOW: matches.push_back("Flow"); break; + case HEDERA: matches.push_back("Hedera"); break; + case STELLAR: matches.push_back("Stellar"); break; + case WAVES: matches.push_back("Waves"); break; + case NANO: matches.push_back("Nano"); break; + case IOTA: matches.push_back("IOTA"); break; + case AVALANCHE: matches.push_back("Avalanche"); break; + case FANTOM: matches.push_back("Fantom"); break; + case CELO: matches.push_back("Celo"); break; + case EOS: matches.push_back("EOS"); break; + case CHAINLINK: matches.push_back("Chainlink"); break; + case GENERIC: matches.push_back("Generic blockchain pattern"); break; + case EXCHANGE_PATTERNS: matches.push_back("Exchange deposit pattern"); break; + case SCAM_PATTERNS: matches.push_back("Known scam pattern"); break; + default: matches.push_back("Unknown pattern"); break; + } + } + } + return matches; +} + +}} // namespace graphene::protocol diff --git a/libraries/protocol/include/graphene/protocol/account_name_validation.hpp b/libraries/protocol/include/graphene/protocol/account_name_validation.hpp new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/libraries/protocol/include/graphene/protocol/account_name_validation.hpp @@ -0,0 +1 @@ + diff --git a/tests/tests/account_tests.cpp b/tests/tests/account_tests.cpp new file mode 100644 index 0000000000..205bd7dc71 --- /dev/null +++ b/tests/tests/account_tests.cpp @@ -0,0 +1,204 @@ +#include +#include +#include +#include +#include +#include + +using namespace graphene::protocol; +using namespace std; + +BOOST_AUTO_TEST_SUITE(account_name_validation_tests) + +BOOST_AUTO_TEST_CASE( basic_validation_tests ) +{ + // Test initialization + BOOST_CHECK(account_name_validator::initialize_patterns()); + + // Test valid names + BOOST_CHECK(account_name_validator::is_valid_account_name("alice")); + BOOST_CHECK(account_name_validator::is_valid_account_name("bob-test")); + BOOST_CHECK(account_name_validator::is_valid_account_name("bitshares-user")); + BOOST_CHECK(account_name_validator::is_valid_account_name("test123")); + BOOST_CHECK(account_name_validator::is_valid_account_name("a-b-c")); + BOOST_CHECK(account_name_validator::is_valid_account_name("init0")); + BOOST_CHECK(account_name_validator::is_valid_account_name("committee-account")); +} + +BOOST_AUTO_TEST_CASE( ethereum_address_tests ) +{ + // Standard Ethereum addresses + BOOST_CHECK(!account_name_validator::is_valid_account_name("0x742d35cc6634c0532925a3b844bc454e4438f44e")); + BOOST_CHECK(!account_name_validator::is_valid_account_name("0x742D35Cc6634C0532925a3b844Bc454e4438f44e")); + BOOST_CHECK(!account_name_validator::is_valid_account_name("0x1234567890abcdef1234567890abcdef12345678")); + + // Transaction hash format + BOOST_CHECK(!account_name_validator::is_valid_account_name("0x742d35cc6634c0532925a3b844bc454e4438f44e742d35cc6634c0532925a3b8")); + + // Valid BitShares names that might look similar but are safe + BOOST_CHECK(account_name_validator::is_valid_account_name("0x-test-account")); + BOOST_CHECK(account_name_validator::is_valid_account_name("ethereum-test")); +} + +BOOST_AUTO_TEST_CASE( bitcoin_address_tests ) +{ + // Legacy Bitcoin addresses + BOOST_CHECK(!account_name_validator::is_valid_account_name("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa")); + BOOST_CHECK(!account_name_validator::is_valid_account_name("3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy")); + + // Bech32 Bitcoin addresses + BOOST_CHECK(!account_name_validator::is_valid_account_name("bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq")); + BOOST_CHECK(!account_name_validator::is_valid_account_name("tb1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq")); + + // Valid names that might be confused + BOOST_CHECK(account_name_validator::is_valid_account_name("bitcoin-user")); + BOOST_CHECK(account_name_validator::is_valid_account_name("btc-test-123")); +} + +BOOST_AUTO_TEST_CASE( cosmos_ecosystem_tests ) +{ + // Cosmos Hub + BOOST_CHECK(!account_name_validator::is_valid_account_name("cosmos1hjct6q7npsspsg3dgvzk3s77033h33p584x6k")); + + // Osmosis + BOOST_CHECK(!account_name_validator::is_valid_account_name("osmo1hjct6q7npsspsg3dgvzk3s77033h33p584x6k")); + + // Juno + BOOST_CHECK(!account_name_validator::is_valid_account_name("juno1hjct6q7npsspsg3dgvzk3s77033h33p584x6k")); + + // Secret Network + BOOST_CHECK(!account_name_validator::is_valid_account_name("secret1nl7z7ha5kec4camsqm4yel0tsyz8zgmjqg6myp")); + + // Thorchain + BOOST_CHECK(!account_name_validator::is_valid_account_name("thor1ypgjtk59f0yunfespykuwmd4l6ct7dnarlm2a9")); + + // Persistence + BOOST_CHECK(!account_name_validator::is_valid_account_name("persistence1hjct6q7npsspsg3dgvzk3s77033h33p584x6k")); + + // Kava + BOOST_CHECK(!account_name_validator::is_valid_account_name("kava1hjct6q7npsspsg3dgvzk3s77033h33p584x6k")); + + // Real BitShares accounts that should be allowed + BOOST_CHECK(account_name_validator::is_valid_account_name("cosmos-test")); + BOOST_CHECK(account_name_validator::is_valid_account_name("secret-test-account")); + BOOST_CHECK(account_name_validator::is_valid_account_name("thor-test-123")); +} + +BOOST_AUTO_TEST_CASE( solana_address_tests ) +{ + // Solana addresses (base58 format) + BOOST_CHECK(!account_name_validator::is_valid_account_name("11111111111111111111111111111112")); + BOOST_CHECK(!account_name_validator::is_valid_account_name("So111111111111111111111111111111111111112")); + BOOST_CHECK(!account_name_validator::is_valid_account_name("9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM")); + + // Valid names + BOOST_CHECK(account_name_validator::is_valid_account_name("solana-test")); + BOOST_CHECK(account_name_validator::is_valid_account_name("sol-test-123")); +} + +BOOST_AUTO_TEST_CASE( polkadot_kusama_tests ) +{ + // Polkadot SS58 format + BOOST_CHECK(!account_name_validator::is_valid_account_name("15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5")); + + // Kusama SS58 format + BOOST_CHECK(!account_name_validator::is_valid_account_name("kagc64k5zj9z6q6z6q6z6q6z6q6z6q6z6q6z6q6z6q6z6q6")); + + // Substrate generic + BOOST_CHECK(!account_name_validator::is_valid_account_name("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY")); + + // Valid names + BOOST_CHECK(account_name_validator::is_valid_account_name("polkadot-test")); + BOOST_CHECK(account_name_validator::is_valid_account_name("kusama-user-123")); +} + + +BOOST_AUTO_TEST_CASE( generic_pattern_tests ) +{ + // Generic Bech32 pattern + BOOST_CHECK(!account_name_validator::is_valid_account_name("chain1abcdefghijklmnopqrstuvwxyz0123456789")); + + // Generic Base58Check pattern + BOOST_CHECK(!account_name_validator::is_valid_account_name("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa")); + + // Generic hex patterns + BOOST_CHECK(!account_name_validator::is_valid_account_name("a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4")); + BOOST_CHECK(!account_name_validator::is_valid_account_name("a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890")); + + // Valid names with hex characters + BOOST_CHECK(account_name_validator::is_valid_account_name("test-account-1a2b3c")); + BOOST_CHECK(account_name_validator::is_valid_account_name("hex-test-123")); + BOOST_CHECK(account_name_validator::is_valid_account_name("account-742d35")); +} + +BOOST_AUTO_TEST_CASE( error_message_tests ) +{ + // Test error messages for different categories + auto error = account_name_validator::get_validation_error("0x742d35cc6634c0532925a3b844bc454e4438f44e"); + BOOST_CHECK(!error.empty()); + BOOST_CHECK(error.find("Ethereum") != string::npos); + + error = account_name_validator::get_validation_error("bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"); + BOOST_CHECK(!error.empty()); + BOOST_CHECK(error.find("Bitcoin") != string::npos); + + error = account_name_validator::get_validation_error("cosmos1hjct6q7npsspsg3dgvzk3s77033h33p584x6k"); + BOOST_CHECK(!error.empty()); + BOOST_CHECK(error.find("Cosmos") != string::npos); +} + +BOOST_AUTO_TEST_CASE( edge_case_tests ) +{ + // Case sensitivity tests + BOOST_CHECK(!account_name_validator::is_valid_account_name("COSMOS1HJCT6Q7NPSSPSG3DGVZK3S77033H33P584X6K")); + BOOST_CHECK(!account_name_validator::is_valid_account_name("Cosmos1hjct6q7npsspsg3dgvzk3s77033h33p584x6k")); + + // Mixed case with special characters + BOOST_CHECK(!account_name_validator::is_valid_account_name("0x742d35cc6634c0532925a3b844bc454e4438f44e-test")); + BOOST_CHECK(!account_name_validator::is_valid_account_name("bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq-test")); + + // Names with dots and hyphens + BOOST_CHECK(!account_name_validator::is_valid_account_name("secret1.nl7z7ha5kec4camsqm4yel0tsyz8zgmjqg6myp")); + BOOST_CHECK(!account_name_validator::is_valid_account_name("thor1-ypgjtk59f0yunfespykuwmd4l6ct7dnarlm2a9")); + + // Names with underscores + BOOST_CHECK(account_name_validator::is_valid_account_name("test_account")); + BOOST_CHECK(account_name_validator::is_valid_account_name("user_name_123")); + + // Very short valid names + BOOST_CHECK(account_name_validator::is_valid_account_name("ab")); + BOOST_CHECK(account_name_validator::is_valid_account_name("a-b")); + BOOST_CHECK(account_name_validator::is_valid_account_name("a_b")); + + // Names with numbers but also letters + BOOST_CHECK(account_name_validator::is_valid_account_name("user123")); + BOOST_CHECK(account_name_validator::is_valid_account_name("test-456")); + BOOST_CHECK(account_name_validator::is_valid_account_name("account_789")); +} + +BOOST_AUTO_TEST_CASE( matching_patterns_tests ) +{ + auto patterns = account_name_validator::get_matching_patterns("0x742d35cc6634c0532925a3b844bc454e4438f44e"); + BOOST_CHECK(!patterns.empty()); + BOOST_CHECK(find(patterns.begin(), patterns.end(), "Ethereum ecosystem") != patterns.end()); + + patterns = account_name_validator::get_matching_patterns("bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"); + BOOST_CHECK(!patterns.empty()); + BOOST_CHECK(find(patterns.begin(), patterns.end(), "Bitcoin ecosystem") != patterns.end()); + + patterns = account_name_validator::get_matching_patterns("cosmos1hjct6q7npsspsg3dgvzk3s77033h33p584x6k"); + BOOST_CHECK(!patterns.empty()); + BOOST_CHECK(find(patterns.begin(), patterns.end(), "Cosmos ecosystem") != patterns.end()); + + // Test name that matches multiple patterns + patterns = account_name_validator::get_matching_patterns("secret1nl7z7ha5kec4camsqm4yel0tsyz8zgmjqg6myp"); + BOOST_CHECK(!patterns.empty()); + BOOST_CHECK(find(patterns.begin(), patterns.end(), "Cosmos ecosystem") != patterns.end()); + BOOST_CHECK(patterns.size() >= 1); // Should match at least Cosmos ecosystem + + // Test valid name that shouldn't match any patterns + patterns = account_name_validator::get_matching_patterns("valid-bitshares-account"); + BOOST_CHECK(patterns.empty()); +} + +BOOST_AUTO_TEST_SUITE_END()