Skip to content

Commit

Permalink
offline-signature version
Browse files Browse the repository at this point in the history
  • Loading branch information
dynexcoin committed Dec 11, 2023
1 parent 98b942b commit 200bbbf
Show file tree
Hide file tree
Showing 9 changed files with 316 additions and 6 deletions.
3 changes: 3 additions & 0 deletions include/IWalletLegacy.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<TransactionOutputInformation> getUnlockedOutputs() = 0;
virtual TransactionId signTransaction(Transaction& tx, Crypto::SecretKey tx_key, uint64_t amount, uint64_t fee) = 0;

virtual TransactionId findTransactionByTransferId(TransferId transferId) = 0;

Expand Down
3 changes: 2 additions & 1 deletion src/DynexCNCore/DynexCNFormatUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down Expand Up @@ -269,6 +269,7 @@ bool constructTransaction(
tx.signatures.push_back(std::vector<Signature>());
std::vector<Signature>& sigs = tx.signatures.back();
sigs.resize(src_entr.outputs.size());

generate_ring_signature(tx_prefix_hash, boost::get<KeyInput>(tx.inputs[i]).keyImage, keys_ptrs,
in_contexts[i].in_ephemeral.secretKey, src_entr.realOutput, sigs.data());
i++;
Expand Down
209 changes: 207 additions & 2 deletions src/SimpleWallet/SimpleWallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 <filename>");
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 <filename>");

}
//----------------------------------------------------------------------------------------------------

Expand Down Expand Up @@ -2056,6 +2061,200 @@ uint64_t simple_wallet::getMinimalFee() {
}
return ret;
}
//----------------------------------------------------------------------------------------------------
// offline signature commands:
bool simple_wallet::export_to_offline(const std::vector<std::string> &args) {
std::string filename = args[0];

std::vector<TransactionOutputInformation> outputs;
outputs = m_wallet->getUnlockedOutputs();

std::ofstream fout(filename);
if (fout.is_open()) {
int num_outs = outputs.size();
fout.write(reinterpret_cast<char*>(&num_outs), sizeof(num_outs));
for (auto& t : outputs) {
//fout.write(reinterpret_cast<char*>(&t.type), sizeof(t.type));
fout.write(reinterpret_cast<char*>(&t.amount), sizeof(t.amount));
fout.write(reinterpret_cast<char*>(&t.globalOutputIndex), sizeof(t.globalOutputIndex));
fout.write(reinterpret_cast<char*>(&t.outputInTransaction), sizeof(t.outputInTransaction));
fout.write(reinterpret_cast<char*>(&t.transactionHash), sizeof(t.transactionHash));
fout.write(reinterpret_cast<char*>(&t.transactionPublicKey), sizeof(t.transactionPublicKey));
fout.write(reinterpret_cast<char*>(&t.outputKey), sizeof(t.outputKey));
fout.write(reinterpret_cast<char*>(&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<std::string> &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<char*>(&tx.version), sizeof(tx.version));
fout.read(reinterpret_cast<char*>(&tx.unlockTime), sizeof(tx.unlockTime));
int extrasize;
fout.read(reinterpret_cast<char*>(&extrasize), sizeof(extrasize));
//std::cout << "LOADING: extrasize = " << extrasize << std::endl;
tx.extra.resize(extrasize);
for (int i=0; i<extrasize; i++)
fout.read(reinterpret_cast<char*>(&tx.extra[i]), sizeof(tx.extra[i]));
//for (int i=0; i<tx.extra.size(); i++)
// std::cout << "0x" << Common::podToHex(tx.extra[i])<<", ";
//std::cout << std::endl;

// inputs:
int num_ins;
fout.read(reinterpret_cast<char*>(&num_ins), sizeof(num_ins));
//std::cout << "LOADING: num_ins = " << num_ins << std::endl;
for (int i=0; i<num_ins; i++) {
DynexCN::KeyInput inp;
fout.read(reinterpret_cast<char*>(&inp.amount), sizeof(inp.amount));
fout.read(reinterpret_cast<char*>(&inp.keyImage), sizeof(inp.keyImage));
int num_indices;
fout.read(reinterpret_cast<char*>(&num_indices), sizeof(num_indices));
for (int j=0; j<num_indices; j++) {
uint32_t idx;
fout.read(reinterpret_cast<char*>(&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<char*>(&num_outs), sizeof(num_outs));
//std::cout << "LOADING: num_outs = " << num_outs << std::endl;
for (int i=0; i<num_outs; i++) {
DynexCN::TransactionOutput output;
fout.read(reinterpret_cast<char*>(&output.amount), sizeof(output.amount));
DynexCN::KeyOutput outt;
fout.read(reinterpret_cast<char*>(&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<char*>(&num_sig), sizeof(num_sig));
//std::cout << "LOADING: num_sig = " << num_sig << std::endl;
for (int i=0; i<num_sig; i++) {
std::vector<Crypto::Signature> signatures;
int num_sig_sub;
fout.read(reinterpret_cast<char*>(&num_sig_sub), sizeof(num_sig_sub));
//std::cout << "LOADING: num_sig_sub = " << num_sig_sub << std::endl;
for (int j=0; j<num_sig_sub; j++) {
Crypto::Signature signature;
fout.read(reinterpret_cast<char*>(&signature), sizeof(signature));
signatures.push_back(signature);
}
tx.signatures.push_back(signatures);
}

//tx_key:
fout.read(reinterpret_cast<char*>(&tx_key), sizeof(tx_key));

// update amounts & fee:
amount = total_inputs;
fee = total_inputs - total_outputs;

//tx_prefix_hash:
getObjectHash(*static_cast<DynexCN::TransactionPrefix*>(&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<KeyInput>(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<KeyOutput>(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<std::string> &args) {
if (m_trackingWallet){
Expand Down Expand Up @@ -2637,8 +2836,14 @@ int main(int argc, char* argv[]) {
}

std::vector<std::string> 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();
Expand Down
3 changes: 3 additions & 0 deletions src/SimpleWallet/SimpleWallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ namespace DynexCN
bool get_reserve_proof(const std::vector<std::string> &args);
bool sign_message(const std::vector<std::string> &args);
bool verify_message(const std::vector<std::string> &args);
// offline signature commands:
bool export_to_offline(const std::vector<std::string> &args);
bool send_offline_tx(const std::vector<std::string> &args);

#ifndef __ANDROID__
std::string resolveAlias(const std::string& aliasUrl);
Expand Down
31 changes: 31 additions & 0 deletions src/WalletLegacy/WalletLegacy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,13 @@ size_t WalletLegacy::getUnlockedOutputsCount() {
return outputs.size();
}

// offline signature:
std::vector<TransactionOutputInformation> WalletLegacy::getUnlockedOutputs() {
std::vector<TransactionOutputInformation> outputs;
m_transferDetails->getOutputs(outputs, ITransfersContainer::IncludeKeyUnlocked);
return outputs;
}

size_t WalletLegacy::estimateFusion(const uint64_t& threshold) {
size_t fusionReadyCount = 0;
std::vector<TransactionOutputInformation> outputs;
Expand Down Expand Up @@ -817,6 +824,30 @@ TransactionId WalletLegacy::sendTransaction(const std::vector<WalletLegacyTransf
return txId;
}

// offline transaction
TransactionId WalletLegacy::signTransaction(Transaction& tx, Crypto::SecretKey tx_key, uint64_t amount, uint64_t fee) {

TransactionId txId = 0;
std::shared_ptr<WalletRequest> request;
std::deque<std::shared_ptr<WalletLegacyEvent>> events;
throwIfNotInitialised();

{
std::unique_lock<std::mutex> 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<WalletLegacyTransfer>& transfers, uint64_t fee, const std::string& extra, uint64_t mixIn, uint64_t unlockTimestamp) {
TransactionId txId = 0;
std::shared_ptr<WalletRequest> request;
Expand Down
3 changes: 3 additions & 0 deletions src/WalletLegacy/WalletLegacy.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<TransactionOutputInformation> getUnlockedOutputs();
virtual TransactionId signTransaction(Transaction& tx, Crypto::SecretKey tx_key, uint64_t amount, uint64_t fee);

virtual TransactionId findTransactionByTransferId(TransferId transferId) override;

Expand Down
Loading

0 comments on commit 200bbbf

Please sign in to comment.