diff --git a/src/CryptoNoteConfig.h b/src/CryptoNoteConfig.h index cffd254..1a599db 100644 --- a/src/CryptoNoteConfig.h +++ b/src/CryptoNoteConfig.h @@ -173,6 +173,9 @@ const std::initializer_list CHECKPOINTS = { {3, "90e8374d29bc23be19ee69cf5b97dc9551a5d5d8385de6a6443e196c102937d2" }, {10, "15d2d574f2b037bce7dfb0e995ade99f2e618399c42a7acf7326b511377db1e4" }, {100, "2b8cf73215920ce9e8ac6f0fe0106ccb39451e9423da055216e97fd6781003f8" }, + {1000, "8975877f6ab04ba985ef73fba5045cade3d6d437e29a6771b1da10f57b4b2aca" }, + {10000, "48fd53db9bb4885fd530072528e582f7ae652b1cd62e66c57c47ac79d5ec738c" }, + {20000, "0a9908210b03805da6b60c456692b5c8a42382fbef2e2fa8eea317d0bb0aad6c" }, }; } // CryptoNote diff --git a/src/CryptoNoteCore/Account.cpp b/src/CryptoNoteCore/Account.cpp index 4da6381..e860e5d 100644 --- a/src/CryptoNoteCore/Account.cpp +++ b/src/CryptoNoteCore/Account.cpp @@ -76,6 +76,18 @@ Crypto::SecretKey AccountBase::generate_key(const Crypto::SecretKey& recovery_ke return first; } +void AccountBase::generateViewFromSpend(Crypto::SecretKey &spend, Crypto::SecretKey &viewSecret, Crypto::PublicKey &viewPublic) { + Crypto::SecretKey viewKeySeed; + keccak((uint8_t *)&spend, sizeof(spend), (uint8_t *)&viewKeySeed, sizeof(viewKeySeed)); + + Crypto::generate_deterministic_keys(viewPublic, viewSecret, viewKeySeed); +} + +void AccountBase::generateViewFromSpend(Crypto::SecretKey &spend, Crypto::SecretKey &viewSecret) { + /* If we don't need the pub key */ + Crypto::PublicKey unused_dummy_variable; + generateViewFromSpend(spend, viewSecret, unused_dummy_variable); +} //----------------------------------------------------------------- const AccountKeys &AccountBase::getAccountKeys() const { diff --git a/src/CryptoNoteCore/Account.h b/src/CryptoNoteCore/Account.h index 8b8f5a9..2af1c71 100644 --- a/src/CryptoNoteCore/Account.h +++ b/src/CryptoNoteCore/Account.h @@ -33,6 +33,9 @@ namespace CryptoNote { void generate(); void generateDeterministic(); Crypto::SecretKey generate_key(const Crypto::SecretKey& recovery_key = Crypto::SecretKey(), bool recover = false, bool two_random = false); + + static void generateViewFromSpend(Crypto::SecretKey&, Crypto::SecretKey&, Crypto::PublicKey&); + static void generateViewFromSpend(Crypto::SecretKey&, Crypto::SecretKey&); const AccountKeys& getAccountKeys() const; void setAccountKeys(const AccountKeys& keys); diff --git a/src/PaymentGate/PaymentServiceJsonRpcMessages.cpp b/src/PaymentGate/PaymentServiceJsonRpcMessages.cpp index ff2e50e..150f101 100644 --- a/src/PaymentGate/PaymentServiceJsonRpcMessages.cpp +++ b/src/PaymentGate/PaymentServiceJsonRpcMessages.cpp @@ -51,6 +51,16 @@ void GetViewKey::Response::serialize(CryptoNote::ISerializer& serializer) { serializer(viewSecretKey, "viewSecretKey"); } +void GetMnemonicSeed::Request::serialize(CryptoNote::ISerializer& serializer) { + if (!serializer(address, "address")) { + throw RequestSerializationError(); + } +} + +void GetMnemonicSeed::Response::serialize(CryptoNote::ISerializer& serializer) { + serializer(mnemonicSeed, "mnemonicSeed"); +} + void GetStatus::Request::serialize(CryptoNote::ISerializer& serializer) { } diff --git a/src/PaymentGate/PaymentServiceJsonRpcMessages.h b/src/PaymentGate/PaymentServiceJsonRpcMessages.h index 783fd2e..2b71be0 100644 --- a/src/PaymentGate/PaymentServiceJsonRpcMessages.h +++ b/src/PaymentGate/PaymentServiceJsonRpcMessages.h @@ -80,6 +80,20 @@ struct GetViewKey { }; }; +struct GetMnemonicSeed { + struct Request { + std::string address; + + void serialize(CryptoNote::ISerializer& serializer); + }; + + struct Response { + std::string mnemonicSeed; + + void serialize(CryptoNote::ISerializer& serializer); + }; +}; + struct GetStatus { struct Request { void serialize(CryptoNote::ISerializer& serializer); diff --git a/src/PaymentGate/PaymentServiceJsonRpcServer.cpp b/src/PaymentGate/PaymentServiceJsonRpcServer.cpp index f0d9b6a..9284e82 100644 --- a/src/PaymentGate/PaymentServiceJsonRpcServer.cpp +++ b/src/PaymentGate/PaymentServiceJsonRpcServer.cpp @@ -55,6 +55,7 @@ PaymentServiceJsonRpcServer::PaymentServiceJsonRpcServer(System::Dispatcher& sys handlers.emplace("deleteDelayedTransaction", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleDeleteDelayedTransaction, this, std::placeholders::_1, std::placeholders::_2))); handlers.emplace("sendDelayedTransaction", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleSendDelayedTransaction, this, std::placeholders::_1, std::placeholders::_2))); handlers.emplace("getViewKey", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleGetViewKey, this, std::placeholders::_1, std::placeholders::_2))); + handlers.emplace("getMnemonicSeed", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleGetMnemonicSeed, this, std::placeholders::_1, std::placeholders::_2))); handlers.emplace("getStatus", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleGetStatus, this, std::placeholders::_1, std::placeholders::_2))); handlers.emplace("getAddresses", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleGetAddresses, this, std::placeholders::_1, std::placeholders::_2))); handlers.emplace("sendFusionTransaction", jsonHandler(std::bind(&PaymentServiceJsonRpcServer::handleSendFusionTransaction, this, std::placeholders::_1, std::placeholders::_2))); @@ -199,6 +200,10 @@ std::error_code PaymentServiceJsonRpcServer::handleGetViewKey(const GetViewKey:: return service.getViewKey(response.viewSecretKey); } +std::error_code PaymentServiceJsonRpcServer::handleGetMnemonicSeed(const GetMnemonicSeed::Request& request, GetMnemonicSeed::Response& response) { + return service.getMnemonicSeed(request.address, response.mnemonicSeed); +} + std::error_code PaymentServiceJsonRpcServer::handleGetStatus(const GetStatus::Request& request, GetStatus::Response& response) { response.version = PROJECT_VERSION_LONG; return service.getStatus(response.blockCount, response.knownBlockCount, response.localDaemonBlockCount, response.lastBlockHash, response.peerCount, response.minimalFee); diff --git a/src/PaymentGate/PaymentServiceJsonRpcServer.h b/src/PaymentGate/PaymentServiceJsonRpcServer.h index 0555e42..ca98a95 100644 --- a/src/PaymentGate/PaymentServiceJsonRpcServer.h +++ b/src/PaymentGate/PaymentServiceJsonRpcServer.h @@ -92,6 +92,7 @@ class PaymentServiceJsonRpcServer : public CryptoNote::JsonRpcServer { std::error_code handleDeleteDelayedTransaction(const DeleteDelayedTransaction::Request& request, DeleteDelayedTransaction::Response& response); std::error_code handleSendDelayedTransaction(const SendDelayedTransaction::Request& request, SendDelayedTransaction::Response& response); std::error_code handleGetViewKey(const GetViewKey::Request& request, GetViewKey::Response& response); + std::error_code handleGetMnemonicSeed(const GetMnemonicSeed::Request& request, GetMnemonicSeed::Response& response); std::error_code handleGetStatus(const GetStatus::Request& request, GetStatus::Response& response); std::error_code handleGetAddresses(const GetAddresses::Request& request, GetAddresses::Response& response); std::error_code handleValidateAddress(const ValidateAddress::Request& request, ValidateAddress::Response& response); diff --git a/src/PaymentGate/WalletService.cpp b/src/PaymentGate/WalletService.cpp index 5452ba8..3ea595f 100644 --- a/src/PaymentGate/WalletService.cpp +++ b/src/PaymentGate/WalletService.cpp @@ -36,6 +36,7 @@ #include "CryptoNoteCore/CryptoNoteFormatUtils.h" #include "CryptoNoteCore/CryptoNoteBasicImpl.h" #include "CryptoNoteCore/TransactionExtra.h" +#include "CryptoNoteCore/Account.h" #include @@ -48,6 +49,7 @@ #include "Wallet/WalletUtils.h" #include "WalletServiceErrorCategory.h" #include "ITransfersContainer.h" +#include "mnemonics/electrum-words.cpp" using namespace CryptoNote; @@ -331,12 +333,75 @@ void generateNewWallet(const CryptoNote::Currency& currency, const WalletConfigu CryptoNote::IWallet* wallet = new CryptoNote::WalletGreen(dispatcher, currency, *nodeStub, logger); std::unique_ptr walletGuard(wallet); -log(Logging::INFO, Logging::BRIGHT_WHITE) << "Generating new wallet"; +std::string address; + if (conf.secretSpendKey.empty() && conf.secretViewKey.empty() && conf.mnemonicSeed.empty()) + { + if (conf.generateDeterministic) { + log(Logging::INFO, Logging::BRIGHT_WHITE) << "Generating new deterministic wallet"; + + Crypto::SecretKey private_view_key; + CryptoNote::KeyPair spendKey; + + Crypto::generate_keys(spendKey.publicKey, spendKey.secretKey); + CryptoNote::AccountBase::generateViewFromSpend(spendKey.secretKey, private_view_key); + + wallet->initializeWithViewKey(conf.walletFile, conf.walletPassword, private_view_key); + address = wallet->createAddress(spendKey.secretKey); + + log(Logging::INFO, Logging::BRIGHT_WHITE) << "New deterministic wallet is generated. Address: " << address; + } + else { + log(Logging::INFO, Logging::BRIGHT_WHITE) << "Generating new non-deterministic wallet"; wallet->initialize(conf.walletFile, conf.walletPassword); - auto address = wallet->createAddress(); + address = wallet->createAddress(); + log(Logging::INFO, Logging::BRIGHT_WHITE) << "New non-deterministic wallet is generated. Address: " << address; + } + } + else if (!conf.mnemonicSeed.empty()) { + log(Logging::INFO, Logging::BRIGHT_WHITE) << "Importing wallet from mnemonic seed"; + + Crypto::SecretKey private_spend_key; + Crypto::SecretKey private_view_key; + + std::string languageName; + if (!Crypto::ElectrumWords::words_to_bytes(conf.mnemonicSeed, private_spend_key, languageName)) + { + log(Logging::ERROR, Logging::BRIGHT_RED) << "Electrum-style word list failed verification."; + return; + } - log(Logging::INFO, Logging::BRIGHT_WHITE) << "New wallet is generated. Address: " << address; + CryptoNote::AccountBase::generateViewFromSpend(private_spend_key, private_view_key); + wallet->initializeWithViewKey(conf.walletFile, conf.walletPassword, private_view_key); + address = wallet->createAddress(private_spend_key); + log(Logging::INFO, Logging::BRIGHT_WHITE) << "Imported wallet successfully."; + } + else { + if (conf.secretSpendKey.empty() || conf.secretViewKey.empty()) + { + log(Logging::ERROR, Logging::BRIGHT_RED) << "Need both secret spend key and secret view key."; + return; + } else { + log(Logging::INFO, Logging::BRIGHT_WHITE) << "Importing wallet from keys"; + Crypto::Hash private_spend_key_hash; + Crypto::Hash private_view_key_hash; + size_t size; + if (!Common::fromHex(conf.secretSpendKey, &private_spend_key_hash, sizeof(private_spend_key_hash), size) || size != sizeof(private_spend_key_hash)) { + log(Logging::ERROR, Logging::BRIGHT_RED) << "Invalid spend key"; + return; + } + if (!Common::fromHex(conf.secretViewKey, &private_view_key_hash, sizeof(private_view_key_hash), size) || size != sizeof(private_spend_key_hash)) { + log(Logging::ERROR, Logging::BRIGHT_RED) << "Invalid view key"; + return; + } + Crypto::SecretKey private_spend_key = *(struct Crypto::SecretKey *) &private_spend_key_hash; + Crypto::SecretKey private_view_key = *(struct Crypto::SecretKey *) &private_view_key_hash; + + wallet->initializeWithViewKey(conf.walletFile, conf.walletPassword, private_view_key); + address = wallet->createAddress(private_spend_key); + log(Logging::INFO, Logging::BRIGHT_WHITE) << "Wallet imported successfully."; + } + } wallet->save(CryptoNote::WalletSaveLevel::SAVE_KEYS_ONLY); log(Logging::INFO, Logging::BRIGHT_WHITE) << "Wallet is saved"; @@ -688,6 +753,35 @@ std::error_code WalletService::getViewKey(std::string& viewSecretKey) { return std::error_code(); } +std::error_code WalletService::getMnemonicSeed(const std::string& address, std::string& mnemonicSeed) { + try { + System::EventLock lk(readyEvent); + CryptoNote::KeyPair key = wallet.getAddressSpendKey(address); + CryptoNote::KeyPair viewKey = wallet.getViewKey(); + + Crypto::SecretKey deterministic_private_view_key; + + CryptoNote::AccountBase::generateViewFromSpend(key.secretKey, deterministic_private_view_key); + + bool deterministic_private_keys = deterministic_private_view_key == viewKey.secretKey; + + if (deterministic_private_keys) { + Crypto::ElectrumWords::bytes_to_words(key.secretKey, mnemonicSeed, "English"); + } else { + /* Have to be able to derive view key from spend key to create a mnemonic + seed, due to being able to generate multiple addresses we can't do + this in walletd as the default */ + logger(Logging::WARNING, Logging::BRIGHT_YELLOW) << "Your private keys are not deterministic and so a mnemonic seed cannot be generated!"; + return make_error_code(CryptoNote::error::WalletServiceErrorCode::KEYS_NOT_DETERMINISTIC); + } + } catch (std::system_error& x) { + logger(Logging::WARNING, Logging::BRIGHT_YELLOW) << "Error while getting mnemonic seed: " << x.what(); + return x.code(); + } + + return std::error_code(); +} + std::error_code WalletService::getTransactionHashes(const std::vector& addresses, const std::string& blockHashString, uint32_t blockCount, const std::string& paymentId, std::vector& transactionHashes) { try { diff --git a/src/PaymentGate/WalletService.h b/src/PaymentGate/WalletService.h index a86ef56..44c88c7 100644 --- a/src/PaymentGate/WalletService.h +++ b/src/PaymentGate/WalletService.h @@ -44,6 +44,10 @@ namespace PaymentService { struct WalletConfiguration { std::string walletFile; std::string walletPassword; + std::string secretViewKey; + std::string secretSpendKey; + std::string mnemonicSeed; + bool generateDeterministic; }; void generateNewWallet(const CryptoNote::Currency& currency, const WalletConfiguration& conf, Logging::ILogger& logger, System::Dispatcher& dispatcher); @@ -73,6 +77,7 @@ class WalletService { std::error_code getBalance(uint64_t& availableBalance, uint64_t& lockedAmount); std::error_code getBlockHashes(uint32_t firstBlockIndex, uint32_t blockCount, std::vector& blockHashes); std::error_code getViewKey(std::string& viewSecretKey); + std::error_code getMnemonicSeed(const std::string& address, std::string& mnemonicSeed); std::error_code getTransactionHashes(const std::vector& addresses, const std::string& blockHash, uint32_t blockCount, const std::string& paymentId, std::vector& transactionHashes); std::error_code getTransactionHashes(const std::vector& addresses, uint32_t firstBlockIndex, diff --git a/src/PaymentGate/WalletServiceErrorCategory.h b/src/PaymentGate/WalletServiceErrorCategory.h index 7211649..d281697 100644 --- a/src/PaymentGate/WalletServiceErrorCategory.h +++ b/src/PaymentGate/WalletServiceErrorCategory.h @@ -30,7 +30,8 @@ enum class WalletServiceErrorCode { WRONG_PAYMENT_ID_FORMAT, WRONG_HASH_FORMAT, OBJECT_NOT_FOUND, - DUPLICATE_KEY + DUPLICATE_KEY, + KEYS_NOT_DETERMINISTIC }; // custom category: @@ -55,6 +56,7 @@ class WalletServiceErrorCategory : public std::error_category { case WalletServiceErrorCode::WRONG_HASH_FORMAT: return "Wrong block id format"; case WalletServiceErrorCode::OBJECT_NOT_FOUND: return "Requested object not found"; case WalletServiceErrorCode::DUPLICATE_KEY: return "Duplicate key"; + case WalletServiceErrorCode::KEYS_NOT_DETERMINISTIC: return "Keys are non-deterministic"; default: return "Unknown error"; } } diff --git a/src/PaymentGateService/PaymentGateService.cpp b/src/PaymentGateService/PaymentGateService.cpp index 94269f7..a72a9c9 100644 --- a/src/PaymentGateService/PaymentGateService.cpp +++ b/src/PaymentGateService/PaymentGateService.cpp @@ -106,7 +106,11 @@ bool PaymentGateService::init(int argc, char** argv) { WalletConfiguration PaymentGateService::getWalletConfig() const { return WalletConfiguration{ config.gateConfiguration.containerFile, - config.gateConfiguration.containerPassword + config.gateConfiguration.containerPassword, + config.gateConfiguration.secretViewKey, + config.gateConfiguration.secretSpendKey, + config.gateConfiguration.mnemonicSeed, + config.gateConfiguration.generateDeterministic }; } diff --git a/src/PaymentGateService/PaymentServiceConfiguration.cpp b/src/PaymentGateService/PaymentServiceConfiguration.cpp index 41d25e3..9d178aa 100644 --- a/src/PaymentGateService/PaymentServiceConfiguration.cpp +++ b/src/PaymentGateService/PaymentServiceConfiguration.cpp @@ -33,6 +33,7 @@ namespace PaymentService { Configuration::Configuration() { generateNewContainer = false; + generateDeterministic = false; daemonize = false; registerService = false; unregisterService = false; @@ -44,6 +45,9 @@ Configuration::Configuration() { bindPort = 0; m_rpcUser = ""; m_rpcPassword = ""; + secretViewKey = ""; + secretSpendKey = ""; + mnemonicSeed = ""; } void Configuration::initOptions(boost::program_options::options_description& desc) { @@ -55,6 +59,10 @@ void Configuration::initOptions(boost::program_options::options_description& des ("container-file,w", po::value(), "container file") ("container-password,p", po::value(), "container password") ("generate-container,g", "generate new container file with one wallet and exit") + ("view-key", po::value(), "generate a container with this secret key view") + ("spend-key", po::value(), "generate a container with this secret spend key") + ("mnemonic-seed", po::value(), "generate a container with this mnemonic seed") + ("deterministic", "generate a container with deterministic keys. View key is generated from spend key of the first address") ("daemon,d", "run as daemon in Unix or as service in Windows") #ifdef _WIN32 ("register-service", "register service and exit (Windows only)") @@ -133,6 +141,34 @@ void Configuration::init(const boost::program_options::variables_map& options) { generateNewContainer = true; } + if (options.count("deterministic") != 0) { + generateDeterministic = true; + } + + if (options.count("view-key") != 0) { + if (!generateNewContainer) { + throw ConfigurationError("generate-container parameter is required"); + } + secretViewKey = options["view-key"].as(); + } + + if (options.count("spend-key") != 0) { + if (!generateNewContainer) { + throw ConfigurationError("generate-container parameter is required"); + } + secretSpendKey = options["spend-key"].as(); + } + + if (options.count("mnemonic-seed") != 0) { + if (!generateNewContainer) { + throw ConfigurationError("generate-container parameter is required"); + } + else if (options.count("spend-key") != 0 || options.count("view-key") != 0) { + throw ConfigurationError("Cannot specify import via both mnemonic seed and private keys"); + } + mnemonicSeed = options["mnemonic-seed"].as(); + } + if (options.count("address") != 0) { printAddresses = true; } diff --git a/src/PaymentGateService/PaymentServiceConfiguration.h b/src/PaymentGateService/PaymentServiceConfiguration.h index e9f96f6..5d9c2ce 100644 --- a/src/PaymentGateService/PaymentServiceConfiguration.h +++ b/src/PaymentGateService/PaymentServiceConfiguration.h @@ -54,8 +54,12 @@ struct Configuration { std::string containerPassword; std::string logFile; std::string serverRoot; + std::string secretViewKey; + std::string secretSpendKey; + std::string mnemonicSeed; bool generateNewContainer; + bool generateDeterministic; bool daemonize; bool registerService; bool unregisterService; diff --git a/src/Rpc/RpcServer.cpp b/src/Rpc/RpcServer.cpp index 29c9495..3d28cc9 100644 --- a/src/Rpc/RpcServer.cpp +++ b/src/Rpc/RpcServer.cpp @@ -956,6 +956,7 @@ bool RpcServer::f_on_block_json(const F_COMMAND_RPC_GET_BLOCK_DETAILS::request& res.block.nonce = block_header.nonce; res.block.hash = block_header.hash; res.block.depth = block_header.depth; + res.block.orphan_status = block_header.orphan_status; m_core.getBlockDifficulty(static_cast(res.block.height), res.block.difficulty); m_core.getBlockCumulativeDifficulty(static_cast(res.block.height), res.block.cumulativeDifficulty); diff --git a/src/version.h.in b/src/version.h.in index 06e4bf3..a5e44dc 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -1,4 +1,4 @@ #define BUILD_COMMIT_ID "Stable" #define PROJECT_VERSION "3.0" -#define PROJECT_VERSION_BUILD_NO "0" +#define PROJECT_VERSION_BUILD_NO "1" #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO " (" BUILD_COMMIT_ID ")"