diff --git a/CMakeLists.txt b/CMakeLists.txt index b6a3bb2..221812a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,5 +65,11 @@ set_property(TARGET qubic-cli PROPERTY COMPILE_WARNING_AS_ERROR ON) ADD_LIBRARY(fourq-qubic SHARED fourq_qubic.cpp) set_property(TARGET fourq-qubic PROPERTY SOVERSION 1) target_compile_options(fourq-qubic PRIVATE -DBUILD_4Q_LIB) + +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + target_compile_options(qubic-cli PRIVATE -mrdrnd) + target_compile_options(fourq-qubic PRIVATE -mrdrnd) +endif() + install(TARGETS fourq-qubic LIBRARY) diff --git a/README.md b/README.md index 17a3090..7c451fe 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,10 @@ Commands: [WALLET COMMANDS] -showkeys Generate identity, public key and private key from seed. Seed must be passed either from params or configuration file. + -generate-vanity-address + Generates a vanity address matching the expected characters using multiple threads, with an option to match as a suffix. + Example: ./qubic-cli -generate-vanity-address ABC 1 false (will generate an address starting with ABC) + ./qubic-cli -generate-vanity-address XYZ 4 true (will generate an address ending with XYZ using 4 threads) -getbalance Balance of an identity (amount of qubic, number of in/out txs) -getasset diff --git a/argparser.h b/argparser.h index 58b6c80..69c184b 100644 --- a/argparser.h +++ b/argparser.h @@ -50,6 +50,8 @@ void print_help() printf("\n[WALLET COMMANDS]\n"); printf("\t-showkeys\n"); printf("\t\tGenerate identity, public key and private key from seed. Seed must be passed either from params or configuration file.\n"); + printf("\t-generate-vanity-address \n"); + printf("\t\tGenerates a vanity address matching the expected characters using multiple threads, with an option to match as a suffix.\n"); printf("\t-getbalance \n"); printf("\t\tBalance of an identity (amount of qubic, number of in/out txs)\n"); printf("\t-getasset \n"); @@ -728,6 +730,16 @@ void parseArgument(int argc, char** argv) CHECK_OVER_PARAMETERS break; } + if (strcmp(argv[i], "-generate-vanity-address") == 0) + { + g_cmd = GENERATE_VANITY_ADDRESS; + g_vanityPattern = argv[i + 1]; + g_vanityGenerationThreads = uint32_t(charToNumber(argv[i + 2])); + g_isVanitySuffix = (strcmp(argv[i + 3], "1") == 0 || strcasecmp(argv[i + 3], "true") == 0); + i += 4; + CHECK_OVER_PARAMETERS + break; + } if (strcmp(argv[i], "-getbalance") == 0) { CHECK_NUMBER_OF_PARAMETERS(1) diff --git a/global.h b/global.h index 7a87a9d..932a607 100644 --- a/global.h +++ b/global.h @@ -43,6 +43,9 @@ char g_loggingMode = 0; char* g_compChatString = nullptr; uint64_t g_executionFeeMultiplierNumerator = 1; uint64_t g_executionFeeMultiplierDenominator = 1; +char *g_vanityPattern = nullptr; +unsigned int g_vanityGenerationThreads = 1; +bool g_isVanitySuffix = false; char* g_dumpBinaryFileInput = nullptr; char* g_dumpBinaryFileOutput = nullptr; diff --git a/main.cpp b/main.cpp index 67cdc8f..0a452fe 100644 --- a/main.cpp +++ b/main.cpp @@ -34,6 +34,9 @@ int run(int argc, char* argv[]) sanityCheckSeed(g_seed); printWalletInfo(g_seed); break; + case GENERATE_VANITY_ADDRESS: + generateVanityAddress(g_vanityPattern, g_vanityGenerationThreads, g_isVanitySuffix); + break; case GET_CURRENT_TICK: sanityCheckNode(g_nodeIp, g_nodePort); printTickInfoFromNode(g_nodeIp, g_nodePort); diff --git a/structs.h b/structs.h index 1529b27..8f89d86 100644 --- a/structs.h +++ b/structs.h @@ -9,6 +9,7 @@ enum COMMAND { SHOW_KEYS, + GENERATE_VANITY_ADDRESS, GET_CURRENT_TICK, GET_BALANCE, GET_ASSET, diff --git a/wallet_utils.cpp b/wallet_utils.cpp index edcc664..9d6bee9 100644 --- a/wallet_utils.cpp +++ b/wallet_utils.cpp @@ -1,17 +1,19 @@ #include -#include #include #include #include +#include +#include -#include "utils.h" -#include "node_utils.h" -#include "key_utils.h" -#include "logger.h" -#include "structs.h" +#include "wallet_utils.h" #include "connection.h" #include "k12_and_key_utils.h" +#include "key_utils.h" +#include "logger.h" +#include "node_utils.h" #include "sc_utils.h" +#include "structs.h" +#include "utils.h" void printWalletInfo(const char* seed) { @@ -125,7 +127,7 @@ void printBalance(const char* publicIdentity, const char* nodeIp, int nodePort) LOG("Spectum Digest: %s\n", hex); } -void printReceipt(Transaction& tx, const char* txHash = nullptr, const uint8_t* extraData = nullptr, int moneyFlew = -1) +void printReceipt(Transaction& tx, const char* txHash, const uint8_t* extraData, int moneyFlew) { char sourceIdentity[128] = {0}; char dstIdentity[128] = {0}; @@ -331,7 +333,7 @@ void makeContractTransaction(const char* nodeIp, int nodePort, int extraDataSize, const void* extraData, uint32_t scheduledTickOffset, - QCPtr* qcPtr = nullptr) + QCPtr* qcPtr) { QCPtr qc = (!qcPtr) ? make_qc(nodeIp, nodePort) : *qcPtr; @@ -393,7 +395,7 @@ bool runContractFunction(const char* nodeIp, int nodePort, size_t inputSize, void* outputPtr, size_t outputSize, - QCPtr* qcPtr = nullptr) + QCPtr* qcPtr) { QCPtr qc = (!qcPtr) ? make_qc(nodeIp, nodePort) : *qcPtr; std::vector packet(sizeof(RequestResponseHeader) + sizeof(RequestContractFunction) + inputSize); @@ -703,3 +705,102 @@ void printActiveIPOs(const char* nodeIp, int nodePort) LOG("- contract index: %u, asset name: %s\n", ipo.contractIndex, ipo.assetName); } } + +VanityAddress generateVanityAddress(const char* pattern, unsigned int vanityGenerationThreads, bool isSufix) { + auto isAddressValid = [](const char* identity, const char* pattern, bool isSufix) -> bool { + size_t patternLen = strlen(pattern); + size_t identityLen = strlen(identity); + if (isSufix) { + if (patternLen > identityLen) return false; + return strcmp(identity + (identityLen - patternLen), pattern) == 0; + } else { + if (patternLen > identityLen) return false; + return strncmp(identity, pattern, patternLen) == 0; + } + }; + + auto get_hw_entropy = []() -> unsigned long long { + unsigned long long val = 0; + // Try RDRAND (Intel/AMD hardware RNG) + if (_rdrand64_step(&val)) { + return val; + } + // Fallback to OS entropy if RDRAND fails or isn't supported + std::random_device rd; + return (static_cast(rd()) << 32) | rd(); + }; + + unsigned long long estimatedAttempts = static_cast(std::pow(26, strlen(pattern))); + std::atomic attemptsCounter(0); + + auto startTime = std::chrono::high_resolution_clock::now(); + auto lastPrintTime = startTime; + + // random seed (55 lowercase char) + constexpr char charset[] = "abcdefghijklmnopqrstuvwxyz"; + constexpr size_t charset_len = sizeof(charset) - 1; + auto randomSeed = [&]() -> std::string { + std::string str(55, 0); + for (size_t i = 0; i < 55; ++i) { + // We use a simple rejection sampling to avoid modulo bias + uint64_t rand_val; + do { + rand_val = get_hw_entropy(); + } while (rand_val >= (UINT64_MAX - (UINT64_MAX % charset_len))); + str[i] = charset[rand_val % charset_len]; + } + return str; + }; + + auto generateThread = [&](VanityAddress& result, const char* pattern, bool isSufix, std::atomic& found, int threadId) { + while (!found.load(std::memory_order_acquire)) { + uint8_t publicKey[32] = {0}; + char identity[61] = {0}; + auto seed = randomSeed(); + getPublicKeyFromSeed(seed.c_str(), publicKey); + getIdentityFromPublicKey(publicKey, identity, false); + if (isAddressValid(identity, pattern, isSufix)) { + if (!found.exchange(true)) { + memcpy(result.seed, seed.c_str(), sizeof(result.seed)); + memcpy(result.identity, identity, sizeof(result.identity)); + } + found.store(true, std::memory_order_release); + break; + } + ++attemptsCounter; + auto currentTime = std::chrono::high_resolution_clock::now(); + auto durationSinceLastPrint = std::chrono::duration_cast(currentTime - lastPrintTime).count(); + if (durationSinceLastPrint >= 1 && threadId == 0) { + lastPrintTime = currentTime; + const auto elapsedTime = std::chrono::duration_cast(currentTime - startTime).count(); + const auto attempts = static_cast(attemptsCounter.load()); + const double seconds = static_cast(elapsedTime) > 0.0 + ? static_cast(elapsedTime) + : 1.0; + const double attemptsPerSecond = attempts / seconds; + const double percentTried = static_cast(attemptsCounter.load()) / static_cast(estimatedAttempts) * 100.0; + LOG("Attempts: %llu | Estimated Attempts: %llu (%.6f%%) | Attempts/s: %.2f | Elapsed Time: %llus\n", + attemptsCounter.load(), estimatedAttempts, percentTried, attemptsPerSecond, elapsedTime); + } + } + }; + + // Start multiple threads to speed up the search + const unsigned int numThreads = std::min(vanityGenerationThreads, std::thread::hardware_concurrency()); + std::vector threads; + VanityAddress result{}; + std::atomic found{false}; + for (unsigned int i = 0; i < numThreads; ++i) { + threads.emplace_back(generateThread, std::ref(result), pattern, isSufix, std::ref(found), i); + } + std::string patternStr = isSufix ? "*" + std::string(pattern) : std::string(pattern) + "*"; + printf("-------- Starting vanity address generation with %u threads | Pattern %s --------\n", numThreads, patternStr.c_str()); + for (auto& t : threads) { + t.join(); + } + + printf("Found vanity address!\n"); + printf("Seed: %s\n", result.seed); + printf("Identity: %s\n", result.identity); + return result; +} \ No newline at end of file diff --git a/wallet_utils.h b/wallet_utils.h index 2d6f147..1ebb859 100644 --- a/wallet_utils.h +++ b/wallet_utils.h @@ -3,6 +3,11 @@ #include "structs.h" #include "connection.h" +struct VanityAddress { + char seed[55 + 1]; + char identity[60 + 1]; +}; + void printWalletInfo(const char* seed); void printBalance(const char* publicIdentity, const char* nodeIp, int nodePort); void makeStandardTransaction(const char* nodeIp, int nodePort, const char* seed, @@ -60,4 +65,5 @@ void makeIPOBid(const char* nodeIp, int nodePort, uint16_t numberOfShare, uint32_t scheduledTickOffset); void printIPOStatus(const char* nodeIp, int nodePort, uint32_t contractIndex); -void printActiveIPOs(const char* nodeIp, int nodePort); \ No newline at end of file +void printActiveIPOs(const char* nodeIp, int nodePort); +VanityAddress generateVanityAddress(const char* pattern, unsigned int vanityGenerationThreads, bool isSufix = false);