14#include <secp256k1_schnorrsig.h>
18 auto seed = std::chrono::high_resolution_clock::now()
21 seed %= std::numeric_limits<uint32_t>::max();
22 m_shuffle.seed(
static_cast<uint32_t
>(
seed));
26 const uint32_t output_val)
30 for(
size_t i = 0; i < n_outputs; i++) {
33 const auto pubkey = generate_key();
49 -> std::optional<transaction::full_tx> {
50 auto maybe_tx = accumulate_inputs(amount);
51 if(!maybe_tx.has_value()) {
55 auto& ret = maybe_tx.value().first;
56 auto total_amount = maybe_tx.value().second;
59 destination_out.
m_value = amount;
63 ret.m_outputs.push_back(destination_out);
65 if(total_amount > amount) {
68 change_out.
m_value = total_amount - amount;
69 const auto pubkey = generate_key();
72 ret.m_outputs.push_back(change_out);
83 -> std::optional<transaction::full_tx> {
84 if(m_seed_from == m_seed_to) {
90 tx.
m_inputs[0].m_prevout.m_tx_id = {0};
91 tx.
m_inputs[0].m_prevout_data.m_value = m_seed_value;
92 tx.
m_inputs[0].m_prevout_data.m_witness_program_commitment = {0};
93 tx.
m_outputs[0].m_witness_program_commitment
94 = m_seed_witness_commitment;
96 tx.
m_inputs[0].m_prevout.m_index = seed_idx;
100 auto transaction::wallet::create_seeded_input(
size_t seed_idx)
101 -> std::optional<transaction::input> {
102 if(
auto tx = create_seeded_transaction(seed_idx)) {
111 const pubkey_t& payee) -> std::vector<transaction::input> {
114 auto ret = std::vector<input>();
115 for(uint32_t i = 0; i < send_tx.m_outputs.size(); i++) {
116 if(send_tx.m_outputs[i].m_witness_program_commitment == wit_comm) {
128 static constexpr size_t max_keys = 10000;
129 std::shared_lock<std::shared_mutex> lg(m_keys_mut);
130 if(m_keys.size() > max_keys) {
131 std::uniform_int_distribution<size_t> keyshuffle_dist(
134 const auto index = keyshuffle_dist(m_shuffle);
135 return m_pubkeys[index];
139 std::uniform_int_distribution<unsigned char> keygen;
142 for(
auto&& b : seckey) {
143 b = keygen(*m_random_source);
147 std::unique_lock<std::shared_mutex> lg(m_keys_mut);
148 m_pubkeys.push_back(ret);
149 m_keys.insert({ret, seckey});
150 m_witness_programs.insert(
163 for(
size_t i = 0; i < tx.
m_inputs.size(); i++) {
164 const auto& wit_commit
165 = tx.
m_inputs[i].m_prevout_data.m_witness_program_commitment;
169 bool key_ours =
false;
171 std::shared_lock<std::shared_mutex> sl(m_keys_mut);
172 const auto wit_prog = m_witness_programs.find(wit_commit);
173 key_ours = wit_prog != m_witness_programs.end();
175 pubkey = wit_prog->second;
176 seckey = m_keys.at(pubkey);
182 sig.resize(transaction::validation::p2pk_witness_len);
191 secp256k1_keypair keypair{};
192 [[maybe_unused]]
const auto ret
193 = secp256k1_keypair_create(m_secp.get(),
198 std::array<unsigned char, sig_len> sig_arr{};
199 [[maybe_unused]]
const auto sign_ret
200 = secp256k1_schnorrsig_sign(m_secp.get(),
207 &sig[transaction::validation::p2pk_witness_prog_len],
210 assert(sign_ret == 1);
215 void transaction::wallet::update_balance(
216 const std::vector<transaction::input>& credits,
217 const std::vector<transaction::input>& debits) {
218 std::unique_lock<std::shared_mutex> lu(m_utxos_mut);
219 for(
const auto& inp : credits) {
220 const auto added = m_utxos_set.insert(inp);
222 m_balance += inp.m_prevout_data.m_value;
223 m_spend_queue.push_back(inp);
227 for(
const auto& inp : debits) {
228 const auto erased = m_utxos_set.erase(inp) > 0;
230 m_balance -= inp.m_prevout_data.m_value;
233 assert(m_spend_queue.size() == m_utxos_set.size());
239 size_t end_seed) ->
bool {
240 if(end_seed <= begin_seed) {
245 auto witness_commitment
248 std::unique_lock<std::shared_mutex> lg(m_keys_mut);
249 if(!m_keys.empty()) {
252 m_pubkeys.push_back(pubkey);
253 m_keys.insert({pubkey, privkey});
254 m_witness_programs.insert({witness_commitment, pubkey});
256 seed_readonly(witness_commitment, value, begin_seed, end_seed);
264 m_seed_from = begin_seed;
265 m_seed_to = end_seed;
266 m_seed_value = value;
267 m_seed_witness_commitment = witness_commitment;
273 std::vector<transaction::input> new_utxos;
275 std::shared_lock<std::shared_mutex> sl(m_keys_mut);
276 for(uint32_t i = 0; i < tx.
m_outputs.size(); i++) {
278 if(m_witness_programs.find(out.m_witness_program_commitment)
279 != m_witness_programs.end()) {
285 update_balance(new_utxos, tx.
m_inputs);
289 std::shared_lock<std::shared_mutex> lg(m_utxos_mut);
291 auto balance = m_balance;
292 if(m_seed_from != m_seed_to) {
293 balance += (m_seed_to - m_seed_from) * m_seed_value;
299 std::shared_lock<std::shared_mutex> lg(m_utxos_mut);
300 auto size = m_utxos_set.size();
301 if(m_seed_from != m_seed_to) {
302 size += (m_seed_to - m_seed_from);
308 std::ofstream wal_file(wallet_file,
309 std::ios::binary | std::ios::trunc
311 if(!wal_file.good()) {
315 std::exit(EXIT_FAILURE);
319 std::shared_lock<std::shared_mutex> lk(m_keys_mut);
324 std::shared_lock<std::shared_mutex> lu(m_utxos_mut);
330 std::ifstream wal_file(wallet_file, std::ios::binary | std::ios::in);
331 if(wal_file.good()) {
334 std::unique_lock<std::shared_mutex> lk(m_keys_mut);
338 m_witness_programs.clear();
341 for(
const auto& k : m_keys) {
342 m_pubkeys.push_back(k.first);
343 m_witness_programs.insert(
351 std::unique_lock<std::shared_mutex> lu(m_utxos_mut);
354 m_spend_queue.clear();
357 deser >> m_utxos_set;
358 for(
const auto& utxo : m_utxos_set) {
359 m_balance += utxo.m_prevout_data.m_value;
360 m_spend_queue.push_back(utxo);
370 -> std::optional<transaction::full_tx> {
371 assert(input_count > 0);
372 assert(output_count > 0);
375 uint64_t total_amount = 0;
377 uint64_t output_val{};
380 std::unique_lock<std::shared_mutex> ul(m_utxos_mut);
381 if((m_utxos_set.size() + m_seed_to - m_seed_from) < input_count) {
387 size_t seeded_inputs = 0;
388 while(m_seed_from != m_seed_to
389 && ret.
m_inputs.size() < input_count) {
390 auto seed_utxo = create_seeded_input(m_seed_from);
394 ret.
m_inputs.push_back(seed_utxo.value());
395 ret.
m_witness.emplace_back(sig_len, std::byte(0));
396 total_amount += m_seed_value;
401 for(
auto utxo = m_spend_queue.begin();
402 (utxo != m_spend_queue.end())
403 && (ret.
m_inputs.size() < input_count);
406 total_amount += utxo->m_prevout_data.m_value;
409 output_val = total_amount / output_count;
410 if(output_val == 0 && output_count > 1) {
413 m_seed_from -= seeded_inputs;
417 for(
size_t i = seeded_inputs; i < ret.
m_inputs.size(); i++) {
419 m_balance -= inp.m_prevout_data.m_value;
420 m_utxos_set.erase(inp);
421 m_spend_queue.pop_front();
428 for(
size_t i{0}; i < output_count; i++) {
430 if(i == output_count - 1) {
431 send_out.
m_value = total_amount;
435 total_amount -= send_out.
m_value;
440 assert(total_amount == 0);
450 const std::vector<transaction::input>& credits) {
451 update_balance(credits, {});
458 -> std::optional<transaction::full_tx> {
459 const uint64_t amount = output_count * value;
460 auto maybe_tx = accumulate_inputs(amount);
461 if(!maybe_tx.has_value()) {
465 auto& ret = maybe_tx.value().first;
466 auto total_amount = maybe_tx.value().second;
468 if(total_amount > amount) {
471 change_out.
m_value = total_amount - amount;
472 const auto pubkey = generate_key();
475 ret.m_outputs.push_back(change_out);
479 destination_out.
m_value = value;
483 for(
size_t i{0}; i < output_count; i++) {
484 ret.m_outputs.push_back(destination_out);
494 auto transaction::wallet::accumulate_inputs(uint64_t amount)
495 -> std::optional<std::pair<full_tx, uint64_t>> {
496 uint64_t total_amount = 0;
499 std::unique_lock<std::shared_mutex> ul(m_utxos_mut);
500 size_t seeded_inputs = 0;
501 while(m_seed_from != m_seed_to && total_amount < amount) {
502 auto seed_utxo = create_seeded_input(m_seed_from);
506 ret.m_inputs.push_back(seed_utxo.value());
507 ret.m_witness.emplace_back(sig_len, std::byte(0));
508 total_amount += m_seed_value;
513 auto utxo = m_spend_queue.begin();
514 while((total_amount < amount) && (utxo != m_spend_queue.end())) {
515 ret.m_inputs.push_back(*utxo);
516 ret.m_witness.emplace_back(sig_len, std::byte(0));
517 total_amount += utxo->m_prevout_data.m_value;
518 std::advance(utxo, 1);
521 if(total_amount < amount) {
522 m_seed_from -= seeded_inputs;
526 for(
size_t i = seeded_inputs; i < ret.m_inputs.size(); i++) {
527 const auto del_utxo = m_spend_queue.begin();
528 m_balance -= del_utxo->m_prevout_data.m_value;
529 m_utxos_set.erase(*del_utxo);
530 m_spend_queue.pop_front();
533 return {{ret, total_amount}};
538 const auto& in_key = in.m_prevout_data.m_witness_program_commitment;
539 bool key_ours =
false;
541 std::shared_lock<std::shared_mutex> sl(m_keys_mut);
542 const auto wit_prog = m_witness_programs.find(in_key);
543 key_ours = wit_prog != m_witness_programs.end();
Implementation of serializer for reading from a std::istream.
Implementation of serializer for writing to a std::ostream.
void confirm_inputs(const std::vector< input > &credits)
Given a set of credit inputs, add the UTXOs and update the wallet's balance.
void seed_readonly(const hash_t &witness_commitment, uint32_t value, size_t begin_seed, size_t end_seed)
Marks the wallet as having read-only pre-seeded outputs to spend.
auto generate_key() -> pubkey_t
Generates a new public key at which this wallet can receive payments via send_to.
void load(const std::string &wallet_file)
Overwrites the current state of the wallet with data loaded from a file saved via the Wallet::save fu...
void confirm_transaction(const full_tx &tx)
Confirms a transaction.
auto mint_new_coins(size_t n_outputs, uint32_t output_val) -> full_tx
Mints new spendable outputs.
auto count() const -> size_t
Returns the number of UTXOs stored in this wallet.
static auto export_send_inputs(const full_tx &send_tx, const pubkey_t &payee) -> std::vector< input >
Extracts the transaction data that recipients need from senders to confirm pending transfers.
auto send_to(uint32_t amount, const pubkey_t &payee, bool sign_tx) -> std::optional< full_tx >
Generates a new send transaction with a set value.
void sign(full_tx &tx) const
Signs each of the transaction's inputs using Schnorr signatures.
void save(const std::string &wallet_file) const
Save the state of the wallet to a binary data file.
auto is_spendable(const input &in) const -> bool
Checks if the input is spendable by the current wallet.
auto create_seeded_transaction(size_t seed_idx) -> std::optional< full_tx >
Creates a new transaction from seeded outputs.
auto fan(size_t output_count, uint32_t value, const pubkey_t &payee, bool sign_tx) -> std::optional< transaction::full_tx >
Generates a transaction sending multiple outputs of a set value.
auto seed(const privkey_t &privkey, uint32_t value, size_t begin_seed, size_t end_seed) -> bool
Marks the wallet as having pre-seeded outputs to spend.
auto balance() const -> uint64_t
Returns the total balance of the wallet, e.g.
auto get_p2pk_witness_commitment(const pubkey_t &payee) -> hash_t
witness_program_type
Specifies how validators should interpret the witness program.
auto tx_id(const full_tx &tx) noexcept -> hash_t
Calculates the unique hash of a full transaction.
auto input_from_output(const full_tx &tx, size_t i, const hash_t &txid) -> std::optional< input >
Converts the output at the specified index to an input.
std::array< unsigned char, cbdc::hash_size > hash_t
SHA256 hash container.
std::array< unsigned char, pubkey_len > privkey_t
A private key of a public/private keypair.
auto pubkey_from_privkey(const privkey_t &privkey, secp256k1_context *ctx) -> pubkey_t
Generates a public key from the specified private key.
std::array< unsigned char, pubkey_len > pubkey_t
A public key of a public/private keypair.
std::vector< input > m_inputs
The set of inputs for the transaction.
std::vector< output > m_outputs
The set of new outputs created by the transaction.
std::vector< witness_t > m_witness
The set of witnesses.
An output of a transaction.
uint64_t m_value
The integral value of the output, in atomic units of currency.
hash_t m_witness_program_commitment
Hash of the witness program.