diff --git a/include/IWalletLegacy.h b/include/IWalletLegacy.h index c8ff1d03..2b00f1e4 100644 --- a/include/IWalletLegacy.h +++ b/include/IWalletLegacy.h @@ -143,6 +143,9 @@ class IWalletLegacy { virtual size_t getTransactionCount() = 0; virtual size_t getTransferCount() = 0; virtual size_t getUnlockedOutputsCount() = 0; + // offline-signature: + virtual std::vector getUnlockedOutputs() = 0; + virtual TransactionId signTransaction(Transaction& tx, Crypto::SecretKey tx_key, uint64_t amount, uint64_t fee) = 0; virtual TransactionId findTransactionByTransferId(TransferId transferId) = 0; diff --git a/src/DynexCNCore/DynexCNFormatUtils.cpp b/src/DynexCNCore/DynexCNFormatUtils.cpp index 85872c16..2caa17ce 100644 --- a/src/DynexCNCore/DynexCNFormatUtils.cpp +++ b/src/DynexCNCore/DynexCNFormatUtils.cpp @@ -161,7 +161,7 @@ bool constructTransaction( tx_key = txkey.secretKey; //add tx_key to extra: addTxkeyToExtra(tx.extra, tx_key); - + struct input_generation_context_data { KeyPair in_ephemeral; }; @@ -269,6 +269,7 @@ bool constructTransaction( tx.signatures.push_back(std::vector()); std::vector& sigs = tx.signatures.back(); sigs.resize(src_entr.outputs.size()); + generate_ring_signature(tx_prefix_hash, boost::get(tx.inputs[i]).keyImage, keys_ptrs, in_contexts[i].in_ephemeral.secretKey, src_entr.realOutput, sigs.data()); i++; diff --git a/src/SimpleWallet/SimpleWallet.cpp b/src/SimpleWallet/SimpleWallet.cpp index b6ad48bd..92417caf 100644 --- a/src/SimpleWallet/SimpleWallet.cpp +++ b/src/SimpleWallet/SimpleWallet.cpp @@ -75,6 +75,7 @@ #include "Common/UrlTools.h" #include "Common/Util.h" #include "DynexCNCore/DynexCNFormatUtils.h" +#include "DynexCNCore/DynexCNTools.h" #include "DynexCNProtocol/DynexCNProtocolHandler.h" #include "NodeRpcProxy/NodeRpcProxy.h" #include "Rpc/CoreRpcServerCommandsDefinitions.h" @@ -707,6 +708,10 @@ simple_wallet::simple_wallet(System::Dispatcher& dispatcher, const DynexCN::Curr m_consoleHandler.setHandler("verify_message", boost::bind(&simple_wallet::verify_message, this, boost::placeholders::_1), "Verify a signature of the message"); m_consoleHandler.setHandler("help", boost::bind(&simple_wallet::help, this, boost::placeholders::_1), "Show this help"); m_consoleHandler.setHandler("exit", boost::bind(&simple_wallet::exit, this, boost::placeholders::_1), "Close wallet"); + // offline-signature commands + m_consoleHandler.setHandler("export_to_offline", boost::bind(&simple_wallet::export_to_offline, this, boost::placeholders::_1), "Exports spendable outputs to be used with offline signature client to file "); + m_consoleHandler.setHandler("send_offline_tx", boost::bind(&simple_wallet::send_offline_tx, this, boost::placeholders::_1), "Send a transaction generated by the offline signature client by using file "); + } //---------------------------------------------------------------------------------------------------- @@ -2056,6 +2061,200 @@ uint64_t simple_wallet::getMinimalFee() { } return ret; } +//---------------------------------------------------------------------------------------------------- +// offline signature commands: +bool simple_wallet::export_to_offline(const std::vector &args) { + std::string filename = args[0]; + + std::vector outputs; + outputs = m_wallet->getUnlockedOutputs(); + + std::ofstream fout(filename); + if (fout.is_open()) { + int num_outs = outputs.size(); + fout.write(reinterpret_cast(&num_outs), sizeof(num_outs)); + for (auto& t : outputs) { + //fout.write(reinterpret_cast(&t.type), sizeof(t.type)); + fout.write(reinterpret_cast(&t.amount), sizeof(t.amount)); + fout.write(reinterpret_cast(&t.globalOutputIndex), sizeof(t.globalOutputIndex)); + fout.write(reinterpret_cast(&t.outputInTransaction), sizeof(t.outputInTransaction)); + fout.write(reinterpret_cast(&t.transactionHash), sizeof(t.transactionHash)); + fout.write(reinterpret_cast(&t.transactionPublicKey), sizeof(t.transactionPublicKey)); + fout.write(reinterpret_cast(&t.outputKey), sizeof(t.outputKey)); + fout.write(reinterpret_cast(&t.requiredSignatures), sizeof(t.requiredSignatures)); + } + + fout.close(); + } + + //print outputs: + for (const auto& t : outputs) { + //std::cout << "type : " << t.type << std::endl; + std::cout << "amount : " << t.amount << std::endl; + std::cout << "globalOutputIndex : " << t.globalOutputIndex << std::endl; + std::cout << "outputInTransaction : " << t.outputInTransaction << std::endl; + std::cout << "transactionHash : " << Common::podToHex(t.transactionHash) << std::endl; + std::cout << "transactionPublicKey : " << Common::podToHex(t.transactionPublicKey) << std::endl; + std::cout << "outputKey : " << Common::podToHex(t.outputKey) << std::endl; + std::cout << "requiredSignatures : " << t.requiredSignatures << std::endl; + std::cout << std::endl; + } + + success_msg_writer() << "Output keys have been exported to " << filename; + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::send_offline_tx(const std::vector &args) { + std::string filename = args[0]; + + try { + // read tx file: + std::ifstream fout(filename, std::ios::binary); + Transaction tx; + Crypto::SecretKey tx_key; + Crypto::Hash tx_prefix_hash; + uint64_t total_inputs = 0; + uint64_t total_outputs = 0; + uint64_t amount = 0; + uint64_t fee = 0; + + if (fout.is_open()) { + fout.read(reinterpret_cast(&tx.version), sizeof(tx.version)); + fout.read(reinterpret_cast(&tx.unlockTime), sizeof(tx.unlockTime)); + int extrasize; + fout.read(reinterpret_cast(&extrasize), sizeof(extrasize)); + //std::cout << "LOADING: extrasize = " << extrasize << std::endl; + tx.extra.resize(extrasize); + for (int i=0; i(&tx.extra[i]), sizeof(tx.extra[i])); + //for (int i=0; i(&num_ins), sizeof(num_ins)); + //std::cout << "LOADING: num_ins = " << num_ins << std::endl; + for (int i=0; i(&inp.amount), sizeof(inp.amount)); + fout.read(reinterpret_cast(&inp.keyImage), sizeof(inp.keyImage)); + int num_indices; + fout.read(reinterpret_cast(&num_indices), sizeof(num_indices)); + for (int j=0; j(&idx), sizeof(idx)); + inp.outputIndexes.push_back(idx); + } + tx.inputs.push_back(inp); + total_inputs += inp.amount; + } + // outputs: + int num_outs; + fout.read(reinterpret_cast(&num_outs), sizeof(num_outs)); + //std::cout << "LOADING: num_outs = " << num_outs << std::endl; + for (int i=0; i(&output.amount), sizeof(output.amount)); + DynexCN::KeyOutput outt; + fout.read(reinterpret_cast(&outt.key), sizeof(outt.key)); + output.target = outt; + tx.outputs.push_back(output); + total_outputs += output.amount; + } + // signatures: + int num_sig; + fout.read(reinterpret_cast(&num_sig), sizeof(num_sig)); + //std::cout << "LOADING: num_sig = " << num_sig << std::endl; + for (int i=0; i signatures; + int num_sig_sub; + fout.read(reinterpret_cast(&num_sig_sub), sizeof(num_sig_sub)); + //std::cout << "LOADING: num_sig_sub = " << num_sig_sub << std::endl; + for (int j=0; j(&signature), sizeof(signature)); + signatures.push_back(signature); + } + tx.signatures.push_back(signatures); + } + + //tx_key: + fout.read(reinterpret_cast(&tx_key), sizeof(tx_key)); + + // update amounts & fee: + amount = total_inputs; + fee = total_inputs - total_outputs; + + //tx_prefix_hash: + getObjectHash(*static_cast(&tx), tx_prefix_hash); + + fout.close(); + + } else { + fail_msg_writer() << "file " << filename << " not found."; + return true; + } + + /// print tx: + /* + std::cout << "tx_key : " << Common::podToHex(tx_key) << std::endl; + std::cout << "tx_prefix_hash : " << Common::podToHex(tx_prefix_hash) << std::endl; + std::cout << "amount : " << amount << std::endl; + std::cout << "fee : " << fee << std::endl; + std::cout << "version : " << tx.version << std::endl; + std::cout << "unlockTime : " << tx.unlockTime << std::endl; + std::cout << "extra : " << Common::podToHex(tx.extra) << std::endl; + std::cout << "extra size : " << tx.extra.size() << std::endl; + + for (auto input: tx.inputs) { + DynexCN::KeyInput inp = boost::get(input); + std::cout << "input - amount : " << inp.amount << std::endl; + std::cout << "input - keyimage : " << Common::podToHex(inp.keyImage) << std::endl; + for (auto index : inp.outputIndexes) std::cout << "input outputIndex " << index << std::endl; + } + for (auto output : tx.outputs) { + std::cout << "output - amount : " << output.amount << std::endl; + DynexCN::KeyOutput outt = boost::get(output.target); + std::cout << "output - target key : " << Common::podToHex(outt) << std::endl; + } + for (auto signatures : tx.signatures) { + std::cout << "signature(s) : "; + for (auto signature : signatures) { + std::cout << Common::podToHex(signature) << " "; + } + std::cout << std::endl; + } + */ + + std::cout << "Transaction loaded, sending..." << std::endl; + + DynexCN::WalletHelper::SendCompleteResultObserver sent; + WalletHelper::IWalletRemoveObserverGuard removeGuard(*m_wallet, sent); + + // tx reconstructed, now execute: + DynexCN::TransactionId txid = m_wallet->signTransaction(tx, tx_key, amount, fee); + if (txid == WALLET_LEGACY_INVALID_TRANSACTION_ID) { + fail_msg_writer() << "Can't send money"; + return true; + } + + removeGuard.removeObserver(); + + } catch (const std::system_error& e) { + fail_msg_writer() << e.what(); + } catch (const std::exception& e) { + fail_msg_writer() << e.what(); + } catch (...) { + fail_msg_writer() << "unknown error"; + } + + success_msg_writer() << "Transaction file " << filename << " has been sent"; + return true; +} + + + //---------------------------------------------------------------------------------------------------- bool simple_wallet::transfer(const std::vector &args) { if (m_trackingWallet){ @@ -2637,8 +2836,14 @@ int main(int argc, char* argv[]) { } std::vector command = command_line::get_arg(vm, arg_command); - if (!command.empty()) - wal.process_command(command); + if (!command.empty()) { + try { + wal.process_command(command); + } catch (const std::exception& e) { + logger(ERROR, BRIGHT_RED) << "Error: " << e.what(); + return 1; + } + } Tools::SignalHandler::install([&wal] { wal.stop(); diff --git a/src/SimpleWallet/SimpleWallet.h b/src/SimpleWallet/SimpleWallet.h index 0c24ab22..b6b29878 100644 --- a/src/SimpleWallet/SimpleWallet.h +++ b/src/SimpleWallet/SimpleWallet.h @@ -133,6 +133,9 @@ namespace DynexCN bool get_reserve_proof(const std::vector &args); bool sign_message(const std::vector &args); bool verify_message(const std::vector &args); + // offline signature commands: + bool export_to_offline(const std::vector &args); + bool send_offline_tx(const std::vector &args); #ifndef __ANDROID__ std::string resolveAlias(const std::string& aliasUrl); diff --git a/src/WalletLegacy/WalletLegacy.cpp b/src/WalletLegacy/WalletLegacy.cpp index 5a3cbf2e..f5324ee5 100644 --- a/src/WalletLegacy/WalletLegacy.cpp +++ b/src/WalletLegacy/WalletLegacy.cpp @@ -687,6 +687,13 @@ size_t WalletLegacy::getUnlockedOutputsCount() { return outputs.size(); } +// offline signature: +std::vector WalletLegacy::getUnlockedOutputs() { + std::vector outputs; + m_transferDetails->getOutputs(outputs, ITransfersContainer::IncludeKeyUnlocked); + return outputs; +} + size_t WalletLegacy::estimateFusion(const uint64_t& threshold) { size_t fusionReadyCount = 0; std::vector outputs; @@ -817,6 +824,30 @@ TransactionId WalletLegacy::sendTransaction(const std::vector request; + std::deque> events; + throwIfNotInitialised(); + + { + std::unique_lock lock(m_cacheMutex); + request = m_sender->makeSignRequest(txId, events, tx, tx_key, amount, fee); + } + + notifyClients(events); + + if (request) { + m_asyncContextCounter.addAsyncContext(); + request->perform(m_node, std::bind(&WalletLegacy::sendTransactionCallback, this, std::placeholders::_1, std::placeholders::_2)); + } + + return txId; + +} + TransactionId WalletLegacy::sendDustTransaction(const std::vector& transfers, uint64_t fee, const std::string& extra, uint64_t mixIn, uint64_t unlockTimestamp) { TransactionId txId = 0; std::shared_ptr request; diff --git a/src/WalletLegacy/WalletLegacy.h b/src/WalletLegacy/WalletLegacy.h index 75323721..cec7434a 100644 --- a/src/WalletLegacy/WalletLegacy.h +++ b/src/WalletLegacy/WalletLegacy.h @@ -101,6 +101,9 @@ class WalletLegacy : virtual size_t getTransactionCount() override; virtual size_t getTransferCount() override; virtual size_t getUnlockedOutputsCount() override; + // offline-signature: + virtual std::vector getUnlockedOutputs(); + virtual TransactionId signTransaction(Transaction& tx, Crypto::SecretKey tx_key, uint64_t amount, uint64_t fee); virtual TransactionId findTransactionByTransferId(TransferId transferId) override; diff --git a/src/WalletLegacy/WalletTransactionSender.cpp b/src/WalletLegacy/WalletTransactionSender.cpp index e40f94f3..eadccd30 100644 --- a/src/WalletLegacy/WalletTransactionSender.cpp +++ b/src/WalletLegacy/WalletTransactionSender.cpp @@ -27,7 +27,7 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // Parts of this project are originally copyright by: -// Copyright (c) 2012-2016, The CN developers, The Bytecoin developers +// Copyright (c) 2012-2016, The DynexCN developers, The Bytecoin developers // Copyright (c) 2014-2018, The Monero project // Copyright (c) 2014-2018, The Forknote developers // Copyright (c) 2018, The TurtleCoin developers @@ -48,6 +48,7 @@ #include #include +#include using namespace Crypto; @@ -164,6 +165,63 @@ std::shared_ptr WalletTransactionSender::makeSendRequest(Transact return doSendTransaction(context, events); } +// offline transaction +std::shared_ptr WalletTransactionSender::makeSignRequest(TransactionId& transactionId, std::deque>& events, + Transaction& tx, Crypto::SecretKey tx_key, uint64_t amount, uint64_t fee) { + + std::shared_ptr context = std::make_shared(); + + context->tx_key = tx_key; + context->foundMoney = amount; + context->mixIn = 0; + uint64_t neededMoney = (uint64_t)(amount/1000000000); + + std::string extra = ""; + std::vector transfers; + uint64_t unlockTimestamp = 0; + + transactionId = m_transactionsCache.addNewTransaction(neededMoney, fee, extra, transfers, unlockTimestamp); + + /// print tx: + std::cout << "-----------------------------------------------------------------------------------------" << std::endl; + std::cout << "SIGNING OFFLINE TRANSACTION" << std::endl; + std::cout << "-----------------------------------------------------------------------------------------" << std::endl; + //std::cout << "tx_key : " << Common::podToHex(tx_key) << std::endl; + std::cout << "amount : " << amount << std::endl; + std::cout << "fee : " << fee << std::endl; + std::cout << "version : " << tx.version << std::endl; + std::cout << "unlockTime : " << tx.unlockTime << std::endl; + std::cout << "extra : " << Common::podToHex(tx.extra) << std::endl; + std::cout << "extra size : " << tx.extra.size() << std::endl; + + for (auto input: tx.inputs) { + DynexCN::KeyInput inp = boost::get(input); + std::cout << "input - amount : " << inp.amount << std::endl; + std::cout << "input - keyimage : " << Common::podToHex(inp.keyImage) << std::endl; + for (auto index : inp.outputIndexes) std::cout << "input outputIndex " << index << std::endl; + } + for (auto output : tx.outputs) { + std::cout << "output - amount : " << output.amount << std::endl; + DynexCN::KeyOutput outt = boost::get(output.target); + std::cout << "output - target key : " << Common::podToHex(outt) << std::endl; + } + for (auto signatures : tx.signatures) { + std::cout << "signature(s) : "; + for (auto signature : signatures) { + std::cout << Common::podToHex(signature) << " "; + } + std::cout << std::endl; + } + std::cout << "-----------------------------------------------------------------------------------------" << std::endl; + + m_transactionsCache.updateTransaction(context->transactionId, tx, amount, context->selectedTransfers, context->tx_key); + std::error_code ec; + events.push_back(makeCompleteEvent(m_transactionsCache, context->transactionId, ec)); + + return std::make_shared(tx, std::bind(&WalletTransactionSender::relayTransactionCallback, this, context, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); +} + std::shared_ptr WalletTransactionSender::makeSendDustRequest(TransactionId& transactionId, std::deque>& events, const std::vector& transfers, uint64_t fee, const std::string& extra, uint64_t mixIn, uint64_t unlockTimestamp) { diff --git a/src/WalletLegacy/WalletTransactionSender.h b/src/WalletLegacy/WalletTransactionSender.h index dd7d3157..aa64af10 100644 --- a/src/WalletLegacy/WalletTransactionSender.h +++ b/src/WalletLegacy/WalletTransactionSender.h @@ -66,6 +66,10 @@ class WalletTransactionSender std::shared_ptr makeSendFusionRequest(TransactionId& transactionId, std::deque>& events, const std::vector& transfers, const std::list& fusionInputs, uint64_t fee, const std::string& extra = "", uint64_t mixIn = 0, uint64_t unlockTimestamp = 0); + // offline transaction + std::shared_ptr makeSignRequest(TransactionId& transactionId, std::deque>& events, + Transaction& tx, Crypto::SecretKey tx_key, uint64_t amount, uint64_t fee); + private: std::shared_ptr makeGetRandomOutsRequest(std::shared_ptr context); std::shared_ptr doSendTransaction(std::shared_ptr context, std::deque>& events); diff --git a/src/WalletLegacy/WalletUserTransactionsCache.cpp b/src/WalletLegacy/WalletUserTransactionsCache.cpp index 9c20e420..ac78b4e9 100644 --- a/src/WalletLegacy/WalletUserTransactionsCache.cpp +++ b/src/WalletLegacy/WalletUserTransactionsCache.cpp @@ -61,7 +61,7 @@ bool WalletUserTransactionsCache::serialize(DynexCN::ISerializer& s) { updateUnconfirmedTransactions(); deleteOutdatedTransactions(); - rebuildPaymentsIndex(); + rebuildPaymentsIndex(); } else { UserTransactions txsToSave; UserTransfers transfersToSave; @@ -136,7 +136,7 @@ TransactionId WalletUserTransactionsCache::addNewTransaction( uint64_t amount, uint64_t fee, const std::string& extra, const std::vector& transfers, uint64_t unlockTime) { WalletLegacyTransaction transaction; - + transaction.firstTransferId = insertTransfers(transfers); transaction.transferCount = transfers.size(); transaction.totalAmount = -static_cast(amount); @@ -155,10 +155,12 @@ TransactionId WalletUserTransactionsCache::addNewTransaction( void WalletUserTransactionsCache::updateTransaction( TransactionId transactionId, const DynexCN::Transaction& tx, uint64_t amount, const std::list& usedOutputs, Crypto::SecretKey& tx_key) { + // update extra field from created transaction auto& txInfo = m_transactions.at(transactionId); txInfo.extra.assign(tx.extra.begin(), tx.extra.end()); txInfo.secretKey = tx_key; + m_unconfirmedTransactions.add(tx, transactionId, amount, usedOutputs, tx_key); }