diff --git a/2pc-compose.cfg b/2pc-compose.cfg index de681116b..61e2277ed 100644 --- a/2pc-compose.cfg +++ b/2pc-compose.cfg @@ -1,4 +1,7 @@ 2pc=1 +minter_count=1 +minter0_private_key="0000000000000002000000000000000000000000000000000000000000000000" +minter0_public_key="3adb9db3beb997eec2623ea5002279ea9e337b5c705f3db453dbc1cc1fc9b0a8" sentinel_count=1 sentinel0_endpoint="sentinel0:5555" sentinel0_loglevel="WARN" diff --git a/2pc.cfg.sample b/2pc.cfg.sample index 4ac01bc76..1f22b4835 100644 --- a/2pc.cfg.sample +++ b/2pc.cfg.sample @@ -1,4 +1,6 @@ 2pc=1 +minter_count=1 +minter0="1f05f6173c4f7bef58f7e912c4cb1389097a38f1a9e24c3674d67a0f142af244" sentinel_count=1 sentinel0_endpoint="127.0.0.1:5557" sentinel0_loglevel="WARN" diff --git a/README.md b/README.md index 479369c91..96e788c5d 100644 --- a/README.md +++ b/README.md @@ -128,11 +128,12 @@ Additionally, you can start the atomizer architecture by passing `--file docker- ## Setup test wallets and test them The following commands are all performed from within the second container we started in the previous step. + In each of the below commands, you should pass `atomizer-compose.cfg` instead of `2pc-compose.cfg` if you started the atomizer architecture. -* Mint new coins (e.g., 10 new UTXOs each with a value of 5 atomic units of currency) +* Mint new coins (e.g., 10 new UTXOs each with a value of 5 atomic units of currency using minter0 key to sign) ```terminal - # ./build/src/uhs/client/client-cli 2pc-compose.cfg mempool0.dat wallet0.dat mint 10 5 + # ./build/src/uhs/client/client-cli 2pc-compose.cfg mempool0.dat wallet0.dat mint 10 5 0 [2021-08-17 15:11:57.686] [WARN ] Existing wallet file not found [2021-08-17 15:11:57.686] [WARN ] Existing mempool not found 4bc23da407c3a8110145c5b6c38199c8ec3b0e35ea66bbfd78f0ed65304ce6fa diff --git a/atomizer-compose.cfg b/atomizer-compose.cfg index 74ef9543c..795a31380 100644 --- a/atomizer-compose.cfg +++ b/atomizer-compose.cfg @@ -1,3 +1,6 @@ +minter_count=1 +minter0_private_key="0000000000000002000000000000000000000000000000000000000000000000" +minter0_public_key="3adb9db3beb997eec2623ea5002279ea9e337b5c705f3db453dbc1cc1fc9b0a8" atomizer_count=1 atomizer0_endpoint="atomizer0:5555" atomizer0_raft_endpoint="atomizer0:6666" diff --git a/multimachine.cfg.sample b/multimachine.cfg.sample index 1c788e52a..b04c39bf0 100644 --- a/multimachine.cfg.sample +++ b/multimachine.cfg.sample @@ -1,3 +1,6 @@ +minter_count=1 +minter0_private_key="0000000000000002000000000000000000000000000000000000000000000000" +minter0_public_key="3adb9db3beb997eec2623ea5002279ea9e337b5c705f3db453dbc1cc1fc9b0a8" atomizer_count=3 atomizer0_endpoint="192.168.1.2:5555" atomizer0_raft_endpoint="192.168.1.2:6666" diff --git a/src/uhs/atomizer/sentinel/controller.cpp b/src/uhs/atomizer/sentinel/controller.cpp index 9dc21cf26..02662c77b 100644 --- a/src/uhs/atomizer/sentinel/controller.cpp +++ b/src/uhs/atomizer/sentinel/controller.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2021 MIT Digital Currency Initiative, // Federal Reserve Bank of Boston +// 2022 MITRE Corporation // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -86,7 +87,9 @@ namespace cbdc::sentinel { auto controller::execute_transaction(transaction::full_tx tx) -> std::optional { - const auto res = transaction::validation::check_tx(tx); + const auto res + = transaction::validation::check_tx(tx, + m_opts.m_minter_public_keys); tx_status status{tx_status::pending}; if(res.has_value()) { status = tx_status::static_invalid; @@ -118,7 +121,9 @@ namespace cbdc::sentinel { auto controller::validate_transaction(transaction::full_tx tx) -> std::optional { - const auto res = transaction::validation::check_tx(tx); + const auto res + = transaction::validation::check_tx(tx, + m_opts.m_minter_public_keys); if(res.has_value()) { return std::nullopt; } @@ -167,7 +172,17 @@ namespace cbdc::sentinel { return; } - send_compact_tx(ctx); + // If the tx has no inputs, it's a mint. Send it directly to one of the + // shards + if(ctx.m_inputs.empty()) { + auto ctx_pkt + = std::make_shared(cbdc::make_buffer(ctx)); + if(!m_shard_network.send_to_one(ctx_pkt)) { + m_logger->error("Failed to send mint tx to shard"); + } + } else { + send_compact_tx(ctx); + } } void controller::send_compact_tx(const transaction::compact_tx& ctx) { diff --git a/src/uhs/atomizer/shard/shard.cpp b/src/uhs/atomizer/shard/shard.cpp index c328acfa0..a8d852537 100644 --- a/src/uhs/atomizer/shard/shard.cpp +++ b/src/uhs/atomizer/shard/shard.cpp @@ -120,10 +120,13 @@ namespace cbdc::shard { cbdc::watchtower::tx_error_sync{}}; } + atomizer::tx_notify_request msg; + + // If the tx has no inputs, it's a mint. if(tx.m_inputs.empty()) { - return cbdc::watchtower::tx_error{ - tx.m_id, - cbdc::watchtower::tx_error_inputs_dne{{}}}; + msg.m_tx = std::move(tx); + msg.m_block_height = snp_height; + return msg; } auto read_options = m_read_options; @@ -158,7 +161,6 @@ namespace cbdc::shard { cbdc::watchtower::tx_error_inputs_dne{dne_inputs}}; } - atomizer::tx_notify_request msg; msg.m_attestations = std::move(attestations); msg.m_tx = std::move(tx); msg.m_block_height = snp_height; diff --git a/src/uhs/client/atomizer_client.cpp b/src/uhs/client/atomizer_client.cpp index 8095d0ccf..c76da1a95 100644 --- a/src/uhs/client/atomizer_client.cpp +++ b/src/uhs/client/atomizer_client.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2021 MIT Digital Currency Initiative, // Federal Reserve Bank of Boston +// 2022 MITRE Corporation // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -87,17 +88,4 @@ namespace cbdc { return success; } - auto atomizer_client::send_mint_tx(const transaction::full_tx& mint_tx) - -> bool { - atomizer::tx_notify_request msg; - auto ctx = transaction::compact_tx(mint_tx); - for(size_t i = 0; i < m_opts.m_attestation_threshold; i++) { - auto att - = ctx.sign(m_secp.get(), m_opts.m_sentinel_private_keys[i]); - ctx.m_attestations.insert(att); - } - msg.m_tx = std::move(ctx); - msg.m_block_height = m_wc.request_best_block_height()->height(); - return m_atomizer_network.send_to_one(atomizer::request{msg}); - } } diff --git a/src/uhs/client/atomizer_client.hpp b/src/uhs/client/atomizer_client.hpp index e85adaf73..d973dfe6d 100644 --- a/src/uhs/client/atomizer_client.hpp +++ b/src/uhs/client/atomizer_client.hpp @@ -1,5 +1,6 @@ // Copyright (c) 2021 MIT Digital Currency Initiative, // Federal Reserve Bank of Boston +// 2022 MITRE Corporation // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -52,12 +53,6 @@ namespace cbdc { /// \return true if the initialization succeeded. auto init_derived() -> bool override; - /// Sends the given transaction directly to the atomizer cluster. - /// \param mint_tx transaction to send. - /// \return true if sending the transaction was successful. - auto send_mint_tx(const transaction::full_tx& mint_tx) - -> bool override; - private: cbdc::network::connection_manager m_atomizer_network; cbdc::watchtower::blocking_client m_wc; diff --git a/src/uhs/client/client-cli.cpp b/src/uhs/client/client-cli.cpp index 0801b79e0..0f68d4ae3 100644 --- a/src/uhs/client/client-cli.cpp +++ b/src/uhs/client/client-cli.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2021 MIT Digital Currency Initiative, // Federal Reserve Bank of Boston +// 2022 MITRE Corporation // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -10,9 +11,11 @@ #include "crypto/sha256.h" #include "twophase_client.hpp" #include "uhs/transaction/messages.hpp" +#include "uhs/transaction/wallet.hpp" #include "util/common/config.hpp" #include "util/serialization/util.hpp" +#include #include #include @@ -21,20 +24,42 @@ static constexpr auto bech32_bits_per_symbol = 5; auto mint_command(cbdc::client& client, const std::vector& args) -> bool { - static constexpr auto min_mint_arg_count = 7; + static constexpr auto min_mint_arg_count = 8; if(args.size() < min_mint_arg_count) { - std::cerr << "Mint requires args " + std::cerr << "Mint requires args " << std::endl; return false; } const auto n_outputs = std::stoull(args[5]); const auto output_val = std::stoul(args[6]); + const auto minter_index = std::stoul(args[7]); - const auto mint_tx - = client.mint(n_outputs, static_cast(output_val)); - std::cout << cbdc::to_string(cbdc::transaction::tx_id(mint_tx)) + const auto [tx, resp] = client.mint(n_outputs, + static_cast(output_val), + minter_index); + if(!tx.has_value()) { + std::cout << "Could not generate valid mint tx." << std::endl; + return false; + } + + std::cout << "tx_id:" << std::endl + << cbdc::to_string(cbdc::transaction::tx_id(tx.value())) << std::endl; + + if(resp.has_value()) { + std::cout << "Sentinel responded: " + << cbdc::sentinel::to_string(resp.value().m_tx_status) + << std::endl; + if(resp.value().m_tx_error.has_value()) { + std::cout << "Validation error: " + << cbdc::transaction::validation::to_string( + resp.value().m_tx_error.value()) + << std::endl; + } + } + return true; } @@ -204,9 +229,41 @@ auto confirmtx_command(cbdc::client& client, return true; } +auto dispatch_command(const std::string& command, + cbdc::client& client, + const std::vector& args) -> bool { + auto result = true; + if(command == "mint") { + result = mint_command(client, args); + } else if(command == "send") { + result = send_command(client, args); + } else if(command == "fan") { + result = fan_command(client, args); + } else if(command == "sync") { + client.sync(); + } else if(command == "newaddress") { + newaddress_command(client); + } else if(command == "info") { + const auto balance = client.balance(); + const auto n_txos = client.utxo_count(); + std::cout << "Balance: " << cbdc::client::print_amount(balance) + << ", UTXOs: " << n_txos + << ", pending TXs: " << client.pending_tx_count() + << std::endl; + } else if(command == "importinput") { + result = importinput_command(client, args); + } else if(command == "confirmtx") { + result = confirmtx_command(client, args); + } else { + std::cerr << "Unknown command" << std::endl; + } + return result; +} + // LCOV_EXCL_START auto main(int argc, char** argv) -> int { auto args = cbdc::config::get_args(argc, argv); + static constexpr auto min_arg_count = 5; if(args.size() < min_arg_count) { std::cerr << "Usage: " << args[0] @@ -215,7 +272,12 @@ auto main(int argc, char** argv) -> int { return 0; } - auto cfg_or_err = cbdc::config::load_options(args[1]); + const auto config_file = args[1]; + const auto client_file = args[2]; + const auto wallet_file = args[3]; + const auto command = args[4]; + + auto cfg_or_err = cbdc::config::load_options(config_file); if(std::holds_alternative(cfg_or_err)) { std::cerr << "Error loading config file: " << std::get(cfg_or_err) << std::endl; @@ -226,9 +288,6 @@ auto main(int argc, char** argv) -> int { SHA256AutoDetect(); - const auto wallet_file = args[3]; - const auto client_file = args[2]; - auto logger = std::make_shared( cbdc::config::defaults::log_level); @@ -249,40 +308,8 @@ auto main(int argc, char** argv) -> int { return -1; } - const auto command = std::string(args[4]); - if(command == "mint") { - if(!mint_command(*client, args)) { - return -1; - } - } else if(command == "send") { - if(!send_command(*client, args)) { - return -1; - } - } else if(command == "fan") { - if(!fan_command(*client, args)) { - return -1; - } - } else if(command == "sync") { - client->sync(); - } else if(command == "newaddress") { - newaddress_command(*client); - } else if(command == "info") { - const auto balance = client->balance(); - const auto n_txos = client->utxo_count(); - std::cout << "Balance: " << cbdc::client::print_amount(balance) - << ", UTXOs: " << n_txos - << ", pending TXs: " << client->pending_tx_count() - << std::endl; - } else if(command == "importinput") { - if(!importinput_command(*client, args)) { - return -1; - } - } else if(command == "confirmtx") { - if(!confirmtx_command(*client, args)) { - return -1; - } - } else { - std::cerr << "Unknown command" << std::endl; + if(!dispatch_command(command, *client, args)) { + return -1; } // TODO: check that the send queue has drained before closing diff --git a/src/uhs/client/client.cpp b/src/uhs/client/client.cpp index 84af37014..e7bb344b0 100644 --- a/src/uhs/client/client.cpp +++ b/src/uhs/client/client.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2021 MIT Digital Currency Initiative, // Federal Reserve Bank of Boston +// 2022 MITRE Corporation // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -54,19 +55,30 @@ namespace cbdc { return ss.str(); } - auto client::mint(size_t n_outputs, uint32_t output_val) - -> transaction::full_tx { - auto mint_tx = m_wallet.mint_new_coins(n_outputs, output_val); - import_transaction(mint_tx); + auto + client::mint(size_t n_outputs, uint32_t output_val, size_t minter_index) + -> std::pair, + std::optional> { + static constexpr auto null_return + = std::make_pair(std::nullopt, std::nullopt); - // TODO: make a formal way of minting. For now bypass the sentinels. - if(!send_mint_tx(mint_tx)) { - m_logger->error("Failed to send mint tx"); + auto mint_tx = m_wallet.mint_new_coins(n_outputs, + output_val, + m_opts, + minter_index); + if(!mint_tx.has_value()) { + m_logger->error("mint failed"); + return null_return; + } + auto tx = mint_tx.value(); + auto res = send_transaction(tx); + if(!res.has_value()) { + return null_return; } save(); - return mint_tx; + return std::make_pair(tx, res.value()); } void client::sign_transaction(transaction::full_tx& tx) { diff --git a/src/uhs/client/client.hpp b/src/uhs/client/client.hpp index 6b505ee7e..61f9e0532 100644 --- a/src/uhs/client/client.hpp +++ b/src/uhs/client/client.hpp @@ -1,5 +1,6 @@ // Copyright (c) 2021 MIT Digital Currency Initiative, // Federal Reserve Bank of Boston +// 2022 MITRE Corporation // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -57,9 +58,11 @@ namespace cbdc { /// transaction to the system via \ref send_mint_tx. /// \param n_outputs number of new spendable outputs to create. /// \param output_val value of the amount to associate with each output in the base unit of the currency. - /// \return the completed transaction. - auto mint(size_t n_outputs, uint32_t output_val) - -> transaction::full_tx; + /// \param minter_index the index value of the minter key to use in the configuration file. + /// \return the transaction and sentinel response. + auto mint(size_t n_outputs, uint32_t output_val, size_t minter_index) + -> std::pair, + std::optional>; /// \brief Send a specified amount from this client's wallet to a /// target address. @@ -234,16 +237,6 @@ namespace cbdc { /// \return true if the initialization succeeded. virtual auto init_derived() -> bool = 0; - /// \brief Sends the given minting transaction to a service that will - /// accept and process it. - /// - /// Called by \ref mint to send the resulting transaction. Subclasses - /// should define custom transmission logic here. - /// \param mint_tx invalid transaction that mints new coins. - /// \return true if the transaction was sent successfully. - virtual auto send_mint_tx(const transaction::full_tx& mint_tx) -> bool - = 0; - /// \brief Returns the set of transactions pending confirmation. /// /// Returns the set of pending transactions sent to the transaction diff --git a/src/uhs/client/twophase_client.cpp b/src/uhs/client/twophase_client.cpp index a7ee51794..04f76794a 100644 --- a/src/uhs/client/twophase_client.cpp +++ b/src/uhs/client/twophase_client.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2021 MIT Digital Currency Initiative, // Federal Reserve Bank of Boston +// 2022 MITRE Corporation // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -75,42 +76,4 @@ namespace cbdc { return m_shard_status_client.check_unspent(uhs_id); } - auto twophase_client::send_mint_tx(const transaction::full_tx& mint_tx) - -> bool { - auto ctx = transaction::compact_tx(mint_tx); - for(size_t i = 0; i < m_opts.m_attestation_threshold; i++) { - auto att - = ctx.sign(m_secp.get(), m_opts.m_sentinel_private_keys[i]); - ctx.m_attestations.insert(att); - } - auto done = std::promise(); - auto done_fut = done.get_future(); - auto res = m_coordinator_client.execute_transaction( - ctx, - [&, tx_id = ctx.m_id](std::optional success) { - if(!success.has_value()) { - m_logger->error( - "Coordinator error processing transaction"); - return; - } - if(!success.value()) { - m_logger->error("Coordinator rejected transaction"); - return; - } - confirm_transaction(tx_id); - m_logger->info("Confirmed mint TX"); - done.set_value(); - }); - if(!res) { - m_logger->error("Failed to send transaction to coordinator"); - return false; - } - constexpr auto timeout = std::chrono::seconds(5); - auto maybe_timeout = done_fut.wait_for(timeout); - if(maybe_timeout == std::future_status::timeout) { - m_logger->error("Timed out waiting for mint response"); - return false; - } - return res; - } } diff --git a/src/uhs/client/twophase_client.hpp b/src/uhs/client/twophase_client.hpp index 967062ee7..9d8cb564d 100644 --- a/src/uhs/client/twophase_client.hpp +++ b/src/uhs/client/twophase_client.hpp @@ -1,5 +1,6 @@ // Copyright (c) 2021 MIT Digital Currency Initiative, // Federal Reserve Bank of Boston +// 2022 MITRE Corporation // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -70,12 +71,6 @@ namespace cbdc { /// \return true if the initialization succeeded. auto init_derived() -> bool override; - /// Sends the given mint transaction directly to a coordinator cluster. - /// \param mint_tx transaction to send. - /// \return true if the transaction was sent successfully. - auto send_mint_tx(const transaction::full_tx& mint_tx) - -> bool override; - private: coordinator::rpc::client m_coordinator_client; cbdc::locking_shard::rpc::status_client m_shard_status_client; diff --git a/src/uhs/transaction/validation.cpp b/src/uhs/transaction/validation.cpp index ce6aa18c6..5ebb95dee 100644 --- a/src/uhs/transaction/validation.cpp +++ b/src/uhs/transaction/validation.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2021 MIT Digital Currency Initiative, // Federal Reserve Bank of Boston +// 2022 MITRE Corporation // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -33,8 +34,13 @@ namespace cbdc::transaction::validation { return std::tie(m_code, m_idx) == std::tie(rhs.m_code, rhs.m_idx); } - auto check_tx(const cbdc::transaction::full_tx& tx) + auto check_tx(const cbdc::transaction::full_tx& tx, + const std::unordered_set& minters) -> std::optional { + if(tx.m_inputs.empty()) { + return check_mint_tx(tx, minters); + } + const auto structure_err = check_tx_structure(tx); if(structure_err) { return structure_err; @@ -72,6 +78,38 @@ namespace cbdc::transaction::validation { return std::nullopt; } + auto + check_mint_tx(const cbdc::transaction::full_tx& tx, + const std::unordered_set& minters) + -> std::optional { + // ensure there are outputs + const auto output_count_err = check_output_count(tx); + if(output_count_err) { + return output_count_err; + } + // ensure the number of outputs match the number of witnesses + if(tx.m_outputs.size() != tx.m_witness.size()) { + return tx_error(tx_error_code::mint_output_witness_mismatch); + } + // ensure each output has a value >= 1 + for(size_t idx = 0; idx < tx.m_outputs.size(); idx++) { + const auto& out = tx.m_outputs[idx]; + const auto output_err = check_output_value(out); + if(output_err) { + return tx_error{output_error{output_err.value(), idx}}; + } + } + + for(size_t idx = 0; idx < tx.m_witness.size(); idx++) { + const auto witness_err = check_mint_witness(tx, idx, minters); + if(witness_err) { + return tx_error{witness_error{witness_err.value(), idx}}; + } + } + + return std::nullopt; + } + auto check_tx_structure(const cbdc::transaction::full_tx& tx) -> std::optional { const auto input_count_err = check_input_count(tx); @@ -419,4 +457,115 @@ namespace cbdc::transaction::validation { && tx.verify(secp_context.get(), att); }); } + + auto check_mint_witness( + const cbdc::transaction::full_tx& tx, + size_t idx, + const std::unordered_set& minters) + -> std::optional { + const auto& witness_program = tx.m_witness[idx]; + if(witness_program.empty()) { + return witness_error_code::missing_witness_program_type; + } + + const auto witness_program_type + = static_cast( + witness_program[0]); + switch(witness_program_type) { + case witness_program_type::p2pk: + return check_mint_p2pk_witness(tx, idx, minters); + default: + return witness_error_code::unknown_witness_program_type; + } + } + + auto check_mint_p2pk_witness( + const cbdc::transaction::full_tx& tx, + size_t idx, + const std::unordered_set& minters) + -> std::optional { + const auto witness_len_err = check_p2pk_witness_len(tx, idx); + if(witness_len_err) { + return witness_len_err; + } + + const auto witness_commitment_err + = check_mint_p2pk_witness_commitment(tx, idx); + if(witness_commitment_err) { + return witness_commitment_err; + } + + const auto witness_sig_err + = check_mint_p2pk_witness_signature(tx, idx, minters); + if(witness_sig_err) { + return witness_sig_err; + } + + return std::nullopt; + } + + auto + check_mint_p2pk_witness_commitment(const cbdc::transaction::full_tx& tx, + size_t idx) + -> std::optional { + const auto& wit = tx.m_witness[idx]; + const auto witness_program_hash + = hash_data(wit.data(), p2pk_witness_prog_len); + + // Differs from normal tx that checks the inputs + // Here we make sure the 'payee' in the output is the minter + const auto& witness_program_commitment + = tx.m_outputs[idx].m_witness_program_commitment; + + if(witness_program_hash != witness_program_commitment) { + return witness_error_code::program_mismatch; + } + + return std::nullopt; + } + + auto check_mint_p2pk_witness_signature( + const cbdc::transaction::full_tx& tx, + size_t idx, + const std::unordered_set& minters) + -> std::optional { + const auto& wit = tx.m_witness[idx]; + secp256k1_xonly_pubkey pubkey{}; + + // TODO: use C++20 std::span to avoid pointer arithmetic in validation + // code + pubkey_t pubkey_arr{}; + std::memcpy(pubkey_arr.data(), + &wit[sizeof(witness_program_type)], + sizeof(pubkey_arr)); + + // check if the signer is an authorized minter identified in the + // configuration + if(minters.count(pubkey_arr) == 0) { + return witness_error_code::invalid_minter_key; + } + + if(secp256k1_xonly_pubkey_parse(secp_context.get(), + &pubkey, + pubkey_arr.data()) + != 1) { + return witness_error_code::invalid_public_key; + } + + const auto sighash = cbdc::transaction::tx_id(tx); + + std::array sig_arr{}; + std::memcpy(sig_arr.data(), + &wit[p2pk_witness_prog_len], + sizeof(sig_arr)); + if(secp256k1_schnorrsig_verify(secp_context.get(), + sig_arr.data(), + sighash.data(), + &pubkey) + != 1) { + return witness_error_code::invalid_signature; + } + + return std::nullopt; + } } diff --git a/src/uhs/transaction/validation.hpp b/src/uhs/transaction/validation.hpp index 03acf20e2..595a2a2f1 100644 --- a/src/uhs/transaction/validation.hpp +++ b/src/uhs/transaction/validation.hpp @@ -1,5 +1,6 @@ // Copyright (c) 2021 MIT Digital Currency Initiative, // Federal Reserve Bank of Boston +// 2022 MITRE Corporation // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -67,6 +68,7 @@ namespace cbdc::transaction::validation { program_mismatch, ///< The witness's specified program doesn't match its commitment invalid_public_key, ///< The witness's public key is invalid + invalid_minter_key, ///< The witness is not a valid minter invalid_signature ///< The witness's signature is invalid }; @@ -80,6 +82,8 @@ namespace cbdc::transaction::validation { ///< The total values of inputs and outputs do not match value_overflow, ///< The total value of inputs/outputs overflows a 64-bit integer + mint_output_witness_mismatch ///< number of outputs don't match + ///< witnesses }; /// An error that may occur when sentinels validate witness commitments @@ -118,8 +122,15 @@ namespace cbdc::transaction::validation { /// \note This function returns immediately on the first-found error. /// /// \param tx transaction to validate + /// \param minters public keys of authorized minters /// \return null if transaction is valid, otherwise error information - auto check_tx(const transaction::full_tx& tx) -> std::optional; + auto check_tx(const transaction::full_tx& tx, + const std::unordered_set& minters) + -> std::optional; + auto + check_mint_tx(const cbdc::transaction::full_tx& tx, + const std::unordered_set& minters) + -> std::optional; auto check_tx_structure(const transaction::full_tx& tx) -> std::optional; auto check_input_structure(const transaction::input& inp) -> std::optional< @@ -164,6 +175,26 @@ namespace cbdc::transaction::validation { const transaction::compact_tx& tx, const std::unordered_set& pubkeys, size_t threshold) -> bool; + + auto check_mint_witness( + const cbdc::transaction::full_tx& tx, + size_t idx, + const std::unordered_set& minters) + -> std::optional; + auto check_mint_p2pk_witness( + const cbdc::transaction::full_tx& tx, + size_t idx, + const std::unordered_set& minters) + -> std::optional; + auto + check_mint_p2pk_witness_commitment(const cbdc::transaction::full_tx& tx, + size_t idx) + -> std::optional; + auto check_mint_p2pk_witness_signature( + const cbdc::transaction::full_tx& tx, + size_t idx, + const std::unordered_set& minters) + -> std::optional; } #endif // OPENCBDC_TX_SRC_TRANSACTION_VALIDATION_H_ diff --git a/src/uhs/transaction/wallet.cpp b/src/uhs/transaction/wallet.cpp index 059bd686f..31f2197a8 100644 --- a/src/uhs/transaction/wallet.cpp +++ b/src/uhs/transaction/wallet.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2021 MIT Digital Currency Initiative, // Federal Reserve Bank of Boston +// 2022 MITRE Corporation // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -23,25 +24,140 @@ namespace cbdc { } auto transaction::wallet::mint_new_coins(const size_t n_outputs, - const uint32_t output_val) - -> transaction::full_tx { - transaction::full_tx ret; + const uint32_t output_val, + cbdc::config::options& opts, + const size_t mintkey_index) + -> std::optional { + transaction::full_tx tx; + + // Load private key from cfg given the index of the key + // in the file + auto skey = opts.m_minter_private_keys.find(mintkey_index); + if(skey == opts.m_minter_private_keys.end()) { + // not found + return std::nullopt; + } + + const privkey_t seckey = skey->second; + const pubkey_t pubkey = pubkey_from_privkey(seckey, m_secp.get()); + // TODO: Check it's not already in the wallet + // NOTE: we only need to save to the wallet to ensure + // m_witness_programs is updated as it's needed by + // confirm_transaction(). As long as minter private keys are in the cfg + // file, there's no other reason to actually save to the wallet + { + std::unique_lock lg(m_keys_mut); + m_pubkeys.push_back(pubkey); + m_keys.insert({pubkey, seckey}); + m_witness_programs.insert( + {transaction::validation::get_p2pk_witness_commitment(pubkey), + pubkey}); + } for(size_t i = 0; i < n_outputs; i++) { transaction::output out; + out.m_witness_program_commitment + = transaction::validation::get_p2pk_witness_commitment(pubkey); + out.m_value = output_val; + tx.m_outputs.push_back(out); + } - const auto pubkey = generate_key(); + // Note: This duplicates sign() logic and slightly alters it as + // there are no tx inputs. sign() expects inputs. + // TODO: refactor + const auto sighash = transaction::tx_id(tx); + tx.m_witness.resize(n_outputs); + // Sign tx + for(size_t i = 0; i < n_outputs; i++) { + auto& sig = tx.m_witness[i]; + sig.resize(transaction::validation::p2pk_witness_len); + sig[0] = std::byte( + transaction::validation::witness_program_type::p2pk); + std::memcpy( + &sig[sizeof(transaction::validation::witness_program_type)], + pubkey.data(), + pubkey.size()); + + secp256k1_keypair keypair{}; + [[maybe_unused]] const auto ret + = secp256k1_keypair_create(m_secp.get(), + &keypair, + seckey.data()); + assert(ret == 1); + + std::array sig_arr{}; + [[maybe_unused]] const auto sign_ret + = secp256k1_schnorrsig_sign(m_secp.get(), + sig_arr.data(), + sighash.data(), + &keypair, + nullptr, + nullptr); + std::memcpy(&sig[transaction::validation::p2pk_witness_prog_len], + sig_arr.data(), + sizeof(sig_arr)); + assert(sign_ret == 1); + } + + return tx; + } + + /* + auto transaction::wallet::mint_old_coins(const size_t n_outputs, + const uint32_t output_val) + -> transaction::full_tx { + transaction::full_tx tx; + + const auto pubkey = generate_minter_key(); + + for(size_t i = 0; i < n_outputs; i++) { + transaction::output out; out.m_witness_program_commitment = transaction::validation::get_p2pk_witness_commitment(pubkey); - out.m_value = output_val; + tx.m_outputs.push_back(out); + } - ret.m_outputs.push_back(out); + const auto sighash = transaction::tx_id(tx); + tx.m_witness.resize(n_outputs); + const privkey_t seckey = m_keys.at(pubkey); + + // Sign each output + for(size_t i = 0; i < n_outputs; i++) { + auto& sig = tx.m_witness[i]; + sig.resize(transaction::validation::p2pk_witness_len); + sig[0] = std::byte( + transaction::validation::witness_program_type::p2pk); + std::memcpy( + &sig[sizeof(transaction::validation::witness_program_type)], + pubkey.data(), + pubkey.size()); + + secp256k1_keypair keypair{}; + [[maybe_unused]] const auto ret + = secp256k1_keypair_create(m_secp.get(), + &keypair, + seckey.data()); + assert(ret == 1); + + std::array sig_arr{}; + [[maybe_unused]] const auto sign_ret + = secp256k1_schnorrsig_sign(m_secp.get(), + sig_arr.data(), + sighash.data(), + &keypair, + nullptr, + nullptr); + std::memcpy(&sig[transaction::validation::p2pk_witness_prog_len], + sig_arr.data(), + sizeof(sig_arr)); + assert(sign_ret == 1); } - return ret; + return tx; } + */ auto transaction::wallet::send_to(const uint32_t amount, const pubkey_t& payee, @@ -129,7 +245,7 @@ namespace cbdc { std::shared_lock lg(m_keys_mut); if(m_keys.size() > max_keys) { std::uniform_int_distribution keyshuffle_dist( - 0, + 1, m_keys.size() - 1); const auto index = keyshuffle_dist(m_shuffle); return m_pubkeys[index]; diff --git a/src/uhs/transaction/wallet.hpp b/src/uhs/transaction/wallet.hpp index 4414f5848..ed7307598 100644 --- a/src/uhs/transaction/wallet.hpp +++ b/src/uhs/transaction/wallet.hpp @@ -1,5 +1,6 @@ // Copyright (c) 2021 MIT Digital Currency Initiative, // Federal Reserve Bank of Boston +// 2022 MITRE Corporation // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -44,8 +45,14 @@ namespace cbdc::transaction { /// \param n_outputs number of new spendable outputs to create. /// \param output_val value of the amount to associate with each output /// in the base unit of the currency. + /// \param opts cfg file options + /// \param minter_index the index of the minter key in the cfg file to use for signing /// \return the transaction which mints the new coins. - auto mint_new_coins(size_t n_outputs, uint32_t output_val) -> full_tx; + auto mint_new_coins(const size_t n_outputs, + const uint32_t output_val, + cbdc::config::options& opts, + const size_t minter_index) + -> std::optional; /// \brief Generates a new send transaction with a set value. /// diff --git a/src/uhs/twophase/sentinel_2pc/controller.cpp b/src/uhs/twophase/sentinel_2pc/controller.cpp index e4728e6e1..c6cb2f7f3 100644 --- a/src/uhs/twophase/sentinel_2pc/controller.cpp +++ b/src/uhs/twophase/sentinel_2pc/controller.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2021 MIT Digital Currency Initiative, // Federal Reserve Bank of Boston +// 2022 MITRE Corporation // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -75,7 +76,9 @@ namespace cbdc::sentinel_2pc { auto controller::execute_transaction( transaction::full_tx tx, execute_result_callback_type result_callback) -> bool { - const auto validation_err = transaction::validation::check_tx(tx); + const auto validation_err + = transaction::validation::check_tx(tx, + m_opts.m_minter_public_keys); if(validation_err.has_value()) { auto tx_id = transaction::tx_id(tx); m_logger->debug( @@ -120,7 +123,9 @@ namespace cbdc::sentinel_2pc { auto controller::validate_transaction( transaction::full_tx tx, validate_result_callback_type result_callback) -> bool { - const auto validation_err = transaction::validation::check_tx(tx); + const auto validation_err + = transaction::validation::check_tx(tx, + m_opts.m_minter_public_keys); if(validation_err.has_value()) { result_callback(std::nullopt); return true; diff --git a/src/util/common/config.cpp b/src/util/common/config.cpp index 6fc5ac616..2c1d9cf14 100644 --- a/src/util/common/config.cpp +++ b/src/util/common/config.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2021 MIT Digital Currency Initiative, // Federal Reserve Bank of Boston +// 2022 MITRE Corporation // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -125,6 +126,10 @@ namespace cbdc::config { ss << sentinel_prefix << sentinel_id << config_separator; } + void get_minter_key_prefix(std::stringstream& ss, size_t minter_id) { + ss << minter_prefix << minter_id << config_separator; + } + auto get_sentinel_loglevel_key(size_t sentinel_id) -> std::string { std::stringstream ss; get_sentinel_key_prefix(ss, sentinel_id); @@ -241,6 +246,26 @@ namespace cbdc::config { return ss.str(); } + auto get_minter_public_key_key(size_t minter_id) -> std::string { + std::stringstream ss; + get_minter_key_prefix(ss, minter_id); + ss << public_key_postfix; + return ss.str(); + } + + auto get_minter_private_key_key(size_t minter_id) -> std::string { + std::stringstream ss; + get_minter_key_prefix(ss, minter_id); + ss << private_key_postfix; + return ss.str(); + } + + // auto get_minter_key(size_t minter_id) -> std::string { + // std::stringstream ss; + // ss << minter_prefix << minter_id; + // return ss.str(); + // } + auto read_shard_endpoints(options& opts, const parser& cfg) -> std::optional { const auto shard_count = cfg.get_ulong(shard_count_key).value_or(0); @@ -590,6 +615,34 @@ namespace cbdc::config { } } + auto read_minter_options(options& opts, const parser& cfg) + -> std::optional { + const auto minter_count = cfg.get_ulong(minter_count_key).value_or(0); + + for(size_t i{0}; i < minter_count; i++) { + // get the private key + const auto mskk = get_minter_private_key_key(i); + const auto skv = cfg.get_string(mskk); + if(!skv.has_value()) { + return "Missing minter private key setting: " + + std::to_string(i) + " (" + mskk + ")"; + } + const auto secretkey = cbdc::hash_from_hex(skv.value()); + opts.m_minter_private_keys[i] = secretkey; + + // get the public key + const auto mpkk = get_minter_public_key_key(i); + const auto pkv = cfg.get_string(mpkk); + if(!pkv.has_value()) { + return "Missing minter public key setting: " + + std::to_string(i) + " (" + mpkk + ")"; + } + const auto pubkey = cbdc::hash_from_hex(pkv.value()); + opts.m_minter_public_keys.insert(pubkey); + } + return std::nullopt; + } + void read_loadgen_options(options& opts, const parser& cfg) { opts.m_input_count = cfg.get_ulong(input_count_key).value_or(opts.m_input_count); @@ -655,6 +708,11 @@ namespace cbdc::config { return err.value(); } + err = read_minter_options(opts, cfg); + if(err.has_value()) { + return err.value(); + } + read_raft_options(opts, cfg); read_loadgen_options(opts, cfg); diff --git a/src/util/common/config.hpp b/src/util/common/config.hpp index 1ae3f72ea..badf065c1 100644 --- a/src/util/common/config.hpp +++ b/src/util/common/config.hpp @@ -1,5 +1,6 @@ // Copyright (c) 2021 MIT Digital Currency Initiative, // Federal Reserve Bank of Boston +// 2022 MITRE Corporation // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -118,6 +119,9 @@ namespace cbdc::config { static constexpr auto coordinator_max_threads = "coordinator_max_threads"; static constexpr auto initial_mint_count_key = "initial_mint_count"; static constexpr auto initial_mint_value_key = "initial_mint_value"; + static constexpr auto minter_count_key = "minter_count"; + static constexpr auto minter_prefix = "minter"; + static constexpr auto loadgen_count_key = "loadgen_count"; static constexpr auto shard_completed_txs_cache_size = "shard_completed_txs_cache_size"; @@ -240,6 +244,13 @@ namespace cbdc::config { /// Value for all outputs in the initial mint transaction. size_t m_initial_mint_value{defaults::initial_mint_value}; + /// Map of private keys for minters keyed by the index value + /// in the configuration file. + std::unordered_map m_minter_private_keys; + + /// Set of public keys belonging to authorized minters + std::unordered_set m_minter_public_keys; + /// Number of blocks to store in watchtower block caches. /// (0=unlimited). Defaults to 1 hour of blocks. size_t m_watchtower_block_cache_size{ diff --git a/tests/integration/atomizer_end_to_end_test.cpp b/tests/integration/atomizer_end_to_end_test.cpp index 46388912d..40e2deea8 100644 --- a/tests/integration/atomizer_end_to_end_test.cpp +++ b/tests/integration/atomizer_end_to_end_test.cpp @@ -62,7 +62,7 @@ class atomizer_end_to_end_test : public ::testing::Test { std::this_thread::sleep_for(m_block_wait_interval); - m_sender->mint(10, 10); + m_sender->mint(10, 10, 0); std::this_thread::sleep_for(m_block_wait_interval); m_sender->sync(); diff --git a/tests/integration/integration_tests.cfg b/tests/integration/integration_tests.cfg index 16c26c5c4..35129fcc3 100644 --- a/tests/integration/integration_tests.cfg +++ b/tests/integration/integration_tests.cfg @@ -1,3 +1,6 @@ +minter_count=1 +minter0_private_key="0000000000000002000000000000000000000000000000000000000000000000" +minter0_public_key="3adb9db3beb997eec2623ea5002279ea9e337b5c705f3db453dbc1cc1fc9b0a8" archiver_count=1 archiver0_endpoint="127.0.0.1:5558" archiver0_db="archiver0_db" diff --git a/tests/integration/integration_tests_2pc.cfg b/tests/integration/integration_tests_2pc.cfg index 6087c9d47..fac3fdd9c 100644 --- a/tests/integration/integration_tests_2pc.cfg +++ b/tests/integration/integration_tests_2pc.cfg @@ -1,4 +1,7 @@ 2pc=1 +minter_count=1 +minter0_private_key="0000000000000002000000000000000000000000000000000000000000000000" +minter0_public_key="3adb9db3beb997eec2623ea5002279ea9e337b5c705f3db453dbc1cc1fc9b0a8" sentinel_count=1 sentinel0_endpoint="127.0.0.1:5557" sentinel0_loglevel="DEBUG" diff --git a/tests/integration/replicated_atomizer.cfg b/tests/integration/replicated_atomizer.cfg index c3a1a83ac..c46c425f0 100644 --- a/tests/integration/replicated_atomizer.cfg +++ b/tests/integration/replicated_atomizer.cfg @@ -1,3 +1,6 @@ +minter_count=1 +minter0_private_key="0000000000000002000000000000000000000000000000000000000000000000" +minter0_public_key="3adb9db3beb997eec2623ea5002279ea9e337b5c705f3db453dbc1cc1fc9b0a8" archiver_count=1 archiver0_endpoint="127.0.0.1:5558" archiver0_db="archiver0_db" diff --git a/tests/integration/replicated_shard.cfg b/tests/integration/replicated_shard.cfg index 7354bec1f..d5c499837 100644 --- a/tests/integration/replicated_shard.cfg +++ b/tests/integration/replicated_shard.cfg @@ -1,3 +1,6 @@ +minter_count=1 +minter0_private_key="0000000000000002000000000000000000000000000000000000000000000000" +minter0_public_key="3adb9db3beb997eec2623ea5002279ea9e337b5c705f3db453dbc1cc1fc9b0a8" archiver_count=1 archiver0_endpoint="127.0.0.1:5558" archiver0_db="archiver0_db" diff --git a/tests/integration/sentinel_2pc_integration_test.cpp b/tests/integration/sentinel_2pc_integration_test.cpp index 0c44be28d..8ddae9272 100644 --- a/tests/integration/sentinel_2pc_integration_test.cpp +++ b/tests/integration/sentinel_2pc_integration_test.cpp @@ -51,8 +51,8 @@ TEST_F(sentinel_2pc_integration_test, valid_signed_tx) { cbdc::transaction::wallet wallet; cbdc::transaction::full_tx m_valid_tx{}; - auto mint_tx1 = wallet.mint_new_coins(2, 100); - wallet.confirm_transaction(mint_tx1); + auto mint_tx1 = wallet.mint_new_coins(2, 100, m_opts, 0); + wallet.confirm_transaction(mint_tx1.value()); auto tx = wallet.send_to(2, 2, wallet.generate_key(), true); ASSERT_TRUE(tx.has_value()); diff --git a/tests/integration/sentinel_integration_test.cpp b/tests/integration/sentinel_integration_test.cpp index 425556966..7c4416e4b 100644 --- a/tests/integration/sentinel_integration_test.cpp +++ b/tests/integration/sentinel_integration_test.cpp @@ -49,8 +49,8 @@ TEST_F(sentinel_integration_test, valid_signed_tx) { cbdc::transaction::wallet wallet; cbdc::transaction::full_tx m_valid_tx{}; - auto mint_tx1 = wallet.mint_new_coins(2, 100); - wallet.confirm_transaction(mint_tx1); + auto mint_tx1 = wallet.mint_new_coins(2, 100, m_opts, 0); + wallet.confirm_transaction(mint_tx1.value()); auto tx = wallet.send_to(2, 2, wallet.generate_key(), true); ASSERT_TRUE(tx.has_value()); diff --git a/tests/integration/two_phase_end_to_end_test.cpp b/tests/integration/two_phase_end_to_end_test.cpp index ae82015fd..88f8ed235 100644 --- a/tests/integration/two_phase_end_to_end_test.cpp +++ b/tests/integration/two_phase_end_to_end_test.cpp @@ -49,7 +49,7 @@ class two_phase_end_to_end_test : public ::testing::Test { std::this_thread::sleep_for(m_wait_interval); - m_sender->mint(10, 10); + m_sender->mint(10, 10, 0); std::this_thread::sleep_for(m_wait_interval); m_sender->sync(); diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 7809273b5..05e6640d8 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -10,6 +10,7 @@ add_executable(run_unit_tests archiver_test.cpp locking_shard/format_test.cpp network_test.cpp message_test.cpp + minter_validation_test.cpp raft_test.cpp rpc/tcp_test.cpp sentinel_2pc/controller_test.cpp diff --git a/tests/unit/config_test.cpp b/tests/unit/config_test.cpp index 1c741d0fa..e45eea8ef 100644 --- a/tests/unit/config_test.cpp +++ b/tests/unit/config_test.cpp @@ -3,6 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include "util.hpp" #include "util/common/config.hpp" #include diff --git a/tests/unit/minter_validation_test.cpp b/tests/unit/minter_validation_test.cpp new file mode 100644 index 000000000..01ace2b29 --- /dev/null +++ b/tests/unit/minter_validation_test.cpp @@ -0,0 +1,103 @@ +// Copyright (c) 2021 MIT Digital Currency Initiative, +// Federal Reserve Bank of Boston +// 2022 MITRE Corporation +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "uhs/transaction/validation.hpp" +#include "uhs/transaction/wallet.hpp" + +#include +#include + +class MinterValidationTest : public ::testing::Test { + protected: + void SetUp() override { + const auto minter_private_key = "000000000000000200000000000000000" + "0000000000000000000000000000000"; + const auto minter_public_key = "3adb9db3beb997eec2623ea5002279ea9e" + "337b5c705f3db453dbc1cc1fc9b0a8"; + + m_opts.m_minter_private_keys[0] + = cbdc::hash_from_hex(minter_private_key); + m_opts.m_minter_public_keys.insert( + cbdc::hash_from_hex(minter_public_key)); + } + + cbdc::transaction::wallet m_minter{}; + cbdc::config::options m_opts{}; +}; + +TEST_F(MinterValidationTest, valid_mint) { + auto otx = m_minter.mint_new_coins(5, 10, m_opts, 0); + ASSERT_TRUE(otx.has_value()); + auto tx = otx.value(); + + ASSERT_EQ(tx.m_outputs.size(), 5); + ASSERT_EQ(tx.m_inputs.size(), 0); + m_minter.confirm_transaction(tx); + ASSERT_EQ(m_minter.balance(), 50); + + auto err + = cbdc::transaction::validation::check_tx(tx, + m_opts.m_minter_public_keys); + ASSERT_FALSE(err.has_value()); +} + +TEST_F(MinterValidationTest, invalid_minter_key) { + // Bad minter key index + auto tx = m_minter.mint_new_coins(5, 10, m_opts, 5); + ASSERT_FALSE(tx.has_value()); +} + +TEST_F(MinterValidationTest, no_outputs) { + auto otx = m_minter.mint_new_coins(5, 10, m_opts, 0); + ASSERT_TRUE(otx.has_value()); + auto tx = otx.value(); + + tx.m_outputs.clear(); + + auto err + = cbdc::transaction::validation::check_tx(tx, + m_opts.m_minter_public_keys); + ASSERT_TRUE(err.has_value()); +} + +TEST_F(MinterValidationTest, no_output_value) { + auto otx = m_minter.mint_new_coins(5, 10, m_opts, 0); + ASSERT_TRUE(otx.has_value()); + auto tx = otx.value(); + + tx.m_outputs[0].m_value = 0; + + auto err + = cbdc::transaction::validation::check_tx(tx, + m_opts.m_minter_public_keys); + ASSERT_TRUE(err.has_value()); +} + +TEST_F(MinterValidationTest, missing_witness) { + auto otx = m_minter.mint_new_coins(5, 10, m_opts, 0); + ASSERT_TRUE(otx.has_value()); + auto tx = otx.value(); + + tx.m_witness.clear(); + + auto err + = cbdc::transaction::validation::check_tx(tx, + m_opts.m_minter_public_keys); + ASSERT_TRUE(err.has_value()); +} + +TEST_F(MinterValidationTest, bad_witness_committment) { + auto otx = m_minter.mint_new_coins(5, 10, m_opts, 0); + ASSERT_TRUE(otx.has_value()); + auto tx = otx.value(); + + tx.m_outputs[0].m_witness_program_commitment = cbdc::hash_t{0}; + + auto err + = cbdc::transaction::validation::check_tx(tx, + m_opts.m_minter_public_keys); + ASSERT_TRUE(err.has_value()); +} diff --git a/tests/unit/sentinel_2pc/controller_test.cpp b/tests/unit/sentinel_2pc/controller_test.cpp index da2ed4a89..5038a778d 100644 --- a/tests/unit/sentinel_2pc/controller_test.cpp +++ b/tests/unit/sentinel_2pc/controller_test.cpp @@ -15,6 +15,20 @@ class sentinel_2pc_test : public ::testing::Test { protected: void SetUp() override { + const auto minter_sk0 = "000000000000000200000000000000000" + "0000000000000000000000000000000"; + const auto minter_pk0 = "3adb9db3beb997eec2623ea5002279ea9e" + "337b5c705f3db453dbc1cc1fc9b0a8"; + const auto minter_sk1 = "000000000000000300000000000000000000000000000" + "0000000000000000000"; + const auto minter_pk1 = "4b72a5e9042f4abff48731c3b85047e229aab71cc52a6" + "a98f583fd3a3f2e070d"; + + m_opts.m_minter_private_keys[0] = cbdc::hash_from_hex(minter_sk0); + m_opts.m_minter_public_keys.insert(cbdc::hash_from_hex(minter_pk0)); + m_opts.m_minter_private_keys[1] = cbdc::hash_from_hex(minter_sk1); + m_opts.m_minter_public_keys.insert(cbdc::hash_from_hex(minter_pk1)); + m_dummy_coordinator_net = std::make_unique< decltype(m_dummy_coordinator_net)::element_type>(); @@ -56,10 +70,11 @@ class sentinel_2pc_test : public ::testing::Test { cbdc::transaction::wallet wallet1; cbdc::transaction::wallet wallet2; - auto mint_tx1 = wallet1.mint_new_coins(3, 100); - wallet1.confirm_transaction(mint_tx1); - auto mint_tx2 = wallet2.mint_new_coins(1, 100); - wallet2.confirm_transaction(mint_tx2); + auto mint_tx1 = wallet1.mint_new_coins(3, 100, m_opts, 0); + wallet1.confirm_transaction(mint_tx1.value()); + auto mint_tx2 = wallet2.mint_new_coins(1, 100, m_opts, 1); + wallet2.confirm_transaction(mint_tx2.value()); + m_logger = std::make_shared( cbdc::logging::log_level::debug); m_ctl = std::make_unique(0, diff --git a/tests/unit/shard_test.cpp b/tests/unit/shard_test.cpp index e52fbac92..f8c2f265f 100644 --- a/tests/unit/shard_test.cpp +++ b/tests/unit/shard_test.cpp @@ -81,14 +81,10 @@ TEST_F(shard_test, digest_tx_empty_inputs) { ctx.m_inputs = {}; ctx.m_uhs_outputs = {{'x'}, {'y'}}; + // Txs without inputs are valid mint txs auto res = m_shard.digest_transaction(ctx); - ASSERT_TRUE(std::holds_alternative(res)); - auto got = std::get(res); - - cbdc::watchtower::tx_error want{{'a'}, - cbdc::watchtower::tx_error_inputs_dne{{}}}; - - ASSERT_EQ(got, want); + ASSERT_TRUE( + std::holds_alternative(res)); } TEST_F(shard_test, digest_tx_inputs_dne) { diff --git a/tests/unit/validation_test.cpp b/tests/unit/validation_test.cpp index df5655726..5dd6e6a4b 100644 --- a/tests/unit/validation_test.cpp +++ b/tests/unit/validation_test.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2021 MIT Digital Currency Initiative, // Federal Reserve Bank of Boston +// 2022 MITRE Corporation // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -9,23 +10,41 @@ #include #include +// S: 0000000000000003000000000000000000000000000000000000000000000000 +// P: 4b72a5e9042f4abff48731c3b85047e229aab71cc52a6a98f583fd3a3f2e070d + // TODO: Redo, removing dependence on Wallet. class WalletTxValidationTest : public ::testing::Test { protected: void SetUp() override { + const auto minter_sk0 = "000000000000000200000000000000000" + "0000000000000000000000000000000"; + const auto minter_pk0 = "3adb9db3beb997eec2623ea5002279ea9e" + "337b5c705f3db453dbc1cc1fc9b0a8"; + const auto minter_sk1 = "000000000000000300000000000000000000000000000" + "0000000000000000000"; + const auto minter_pk1 = "4b72a5e9042f4abff48731c3b85047e229aab71cc52a6" + "a98f583fd3a3f2e070d"; + + m_opts.m_minter_private_keys[0] = cbdc::hash_from_hex(minter_sk0); + m_opts.m_minter_public_keys.insert(cbdc::hash_from_hex(minter_pk0)); + m_opts.m_minter_private_keys[1] = cbdc::hash_from_hex(minter_sk1); + m_opts.m_minter_public_keys.insert(cbdc::hash_from_hex(minter_pk1)); + cbdc::transaction::wallet wallet1; cbdc::transaction::wallet wallet2; - auto mint_tx1 = wallet1.mint_new_coins(3, 100); - wallet1.confirm_transaction(mint_tx1); - auto mint_tx2 = wallet2.mint_new_coins(1, 100); - wallet2.confirm_transaction(mint_tx2); + auto mint_tx1 = wallet1.mint_new_coins(3, 100, m_opts, 0); + wallet1.confirm_transaction(mint_tx1.value()); + auto mint_tx2 = wallet2.mint_new_coins(1, 100, m_opts, 1); + wallet2.confirm_transaction(mint_tx2.value()); m_valid_tx = wallet1.send_to(20, wallet2.generate_key(), true).value(); m_valid_tx_multi_inp = wallet1.send_to(200, wallet2.generate_key(), true).value(); } + cbdc::config::options m_opts{}; cbdc::transaction::full_tx m_valid_tx{}; cbdc::transaction::full_tx m_valid_tx_multi_inp{}; @@ -44,14 +63,18 @@ class WalletTxValidationTest : public ::testing::Test { }; TEST_F(WalletTxValidationTest, valid) { - auto err = cbdc::transaction::validation::check_tx(m_valid_tx); + auto err + = cbdc::transaction::validation::check_tx(m_valid_tx, + m_opts.m_minter_public_keys); ASSERT_FALSE(err.has_value()); } TEST_F(WalletTxValidationTest, no_inputs) { m_valid_tx.m_inputs.clear(); - auto err = cbdc::transaction::validation::check_tx(m_valid_tx); + auto err + = cbdc::transaction::validation::check_tx(m_valid_tx, + m_opts.m_minter_public_keys); ASSERT_TRUE(err.has_value()); ASSERT_TRUE( std::holds_alternative( @@ -60,13 +83,18 @@ TEST_F(WalletTxValidationTest, no_inputs) { auto tx_err = std::get(err.value()); - ASSERT_EQ(tx_err, cbdc::transaction::validation::tx_error_code::no_inputs); + // We have this err because 'no inputs' runs the check_mint_tx + ASSERT_EQ(tx_err, + cbdc::transaction::validation::tx_error_code:: + mint_output_witness_mismatch); } TEST_F(WalletTxValidationTest, no_outputs) { m_valid_tx.m_outputs.clear(); - auto err = cbdc::transaction::validation::check_tx(m_valid_tx); + auto err + = cbdc::transaction::validation::check_tx(m_valid_tx, + m_opts.m_minter_public_keys); ASSERT_TRUE(err.has_value()); ASSERT_TRUE( std::holds_alternative( @@ -82,7 +110,9 @@ TEST_F(WalletTxValidationTest, no_outputs) { TEST_F(WalletTxValidationTest, missing_witness) { m_valid_tx.m_witness.clear(); - auto err = cbdc::transaction::validation::check_tx(m_valid_tx); + auto err + = cbdc::transaction::validation::check_tx(m_valid_tx, + m_opts.m_minter_public_keys); ASSERT_TRUE(err.has_value()); ASSERT_TRUE( std::holds_alternative( @@ -98,7 +128,9 @@ TEST_F(WalletTxValidationTest, missing_witness) { TEST_F(WalletTxValidationTest, zero_output) { m_valid_tx.m_outputs[0].m_value = 0; - auto err = cbdc::transaction::validation::check_tx(m_valid_tx); + auto err + = cbdc::transaction::validation::check_tx(m_valid_tx, + m_opts.m_minter_public_keys); ASSERT_TRUE(err.has_value()); ASSERT_TRUE( @@ -118,7 +150,9 @@ TEST_F(WalletTxValidationTest, duplicate_input) { m_valid_tx.m_witness.emplace_back(m_valid_tx.m_witness[0]); m_valid_tx.m_outputs[0].m_value *= 2; - auto err = cbdc::transaction::validation::check_tx(m_valid_tx); + auto err + = cbdc::transaction::validation::check_tx(m_valid_tx, + m_opts.m_minter_public_keys); ASSERT_TRUE(err.has_value()); ASSERT_TRUE( @@ -135,7 +169,9 @@ TEST_F(WalletTxValidationTest, duplicate_input) { TEST_F(WalletTxValidationTest, invalid_input_prevout) { m_valid_tx.m_inputs[0].m_prevout_data.m_value = 0; - auto err = cbdc::transaction::validation::check_tx(m_valid_tx); + auto err + = cbdc::transaction::validation::check_tx(m_valid_tx, + m_opts.m_minter_public_keys); ASSERT_TRUE(err.has_value()); ASSERT_TRUE( @@ -152,7 +188,9 @@ TEST_F(WalletTxValidationTest, invalid_input_prevout) { TEST_F(WalletTxValidationTest, asymmetric_inout_set) { m_valid_tx.m_outputs[0].m_value--; - auto err = cbdc::transaction::validation::check_tx(m_valid_tx); + auto err + = cbdc::transaction::validation::check_tx(m_valid_tx, + m_opts.m_minter_public_keys); ASSERT_TRUE(err.has_value()); ASSERT_TRUE( @@ -249,7 +287,9 @@ TEST_F(WalletTxValidationTest, witness_p2pk_invalid_signature) { TEST_F(WalletTxValidationTest, check_transaction_with_unknown_witness_program_type) { m_valid_tx.m_witness[0][0] = std::byte(0xFF); - auto err = cbdc::transaction::validation::check_tx(m_valid_tx); + auto err + = cbdc::transaction::validation::check_tx(m_valid_tx, + m_opts.m_minter_public_keys); ASSERT_TRUE(err.has_value()); ASSERT_TRUE( std::holds_alternative( diff --git a/tests/unit/wallet_test.cpp b/tests/unit/wallet_test.cpp index a1ff2c7b6..6eedb32ec 100644 --- a/tests/unit/wallet_test.cpp +++ b/tests/unit/wallet_test.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2021 MIT Digital Currency Initiative, // Federal Reserve Bank of Boston +// 2022 MITRE Corporation // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -12,14 +13,22 @@ class WalletTest : public ::testing::Test { protected: void SetUp() override { - auto mint_tx = m_wallet.mint_new_coins(1, 100); - m_wallet.confirm_transaction(mint_tx); + const auto minter_sk0 = "000000000000000200000000000000000" + "0000000000000000000000000000000"; + const auto minter_pk0 = "3adb9db3beb997eec2623ea5002279ea9e" + "337b5c705f3db453dbc1cc1fc9b0a8"; + m_opts.m_minter_private_keys[0] = cbdc::hash_from_hex(minter_sk0); + m_opts.m_minter_public_keys.insert(cbdc::hash_from_hex(minter_pk0)); + + auto mint_tx = m_wallet.mint_new_coins(1, 100, m_opts, 0); + m_wallet.confirm_transaction(mint_tx.value()); } void TearDown() override { std::filesystem::remove(m_wallet_file); } + cbdc::config::options m_opts{}; cbdc::transaction::wallet m_wallet{}; static constexpr auto m_wallet_file = "test_wallet.dat"; }; @@ -100,10 +109,18 @@ TEST_F(WalletTest, fan_out_change) { class WalletTxTest : public ::testing::Test { protected: void SetUp() override { - auto mint_tx = m_sender.mint_new_coins(1, 100); - m_sender.confirm_transaction(mint_tx); + const auto minter_sk0 = "000000000000000200000000000000000" + "0000000000000000000000000000000"; + const auto minter_pk0 = "3adb9db3beb997eec2623ea5002279ea9e" + "337b5c705f3db453dbc1cc1fc9b0a8"; + m_opts.m_minter_private_keys[0] = cbdc::hash_from_hex(minter_sk0); + m_opts.m_minter_public_keys.insert(cbdc::hash_from_hex(minter_pk0)); + + auto mint_tx = m_sender.mint_new_coins(1, 100, m_opts, 0); + m_sender.confirm_transaction(mint_tx.value()); } + cbdc::config::options m_opts{}; cbdc::transaction::wallet m_sender{}; cbdc::transaction::wallet m_receiver{}; }; @@ -122,10 +139,18 @@ TEST_F(WalletTxTest, basic) { class WalletMultiTxTest : public ::testing::Test { protected: void SetUp() override { - auto mint_tx = m_sender.mint_new_coins(100, 100); - m_sender.confirm_transaction(mint_tx); + const auto minter_sk0 = "000000000000000200000000000000000" + "0000000000000000000000000000000"; + const auto minter_pk0 = "3adb9db3beb997eec2623ea5002279ea9e" + "337b5c705f3db453dbc1cc1fc9b0a8"; + m_opts.m_minter_private_keys[0] = cbdc::hash_from_hex(minter_sk0); + m_opts.m_minter_public_keys.insert(cbdc::hash_from_hex(minter_pk0)); + + auto mint_tx = m_sender.mint_new_coins(100, 100, m_opts, 0); + m_sender.confirm_transaction(mint_tx.value()); } + cbdc::config::options m_opts{}; cbdc::transaction::wallet m_sender{}; }; diff --git a/tools/bench/atomizer-cli-watchtower.cpp b/tools/bench/atomizer-cli-watchtower.cpp index 5d8e18250..f51315923 100644 --- a/tools/bench/atomizer-cli-watchtower.cpp +++ b/tools/bench/atomizer-cli-watchtower.cpp @@ -304,8 +304,15 @@ auto main(int argc, char** argv) -> int { // Only mint when not using pre-seeded wallets if(cfg.m_seed_from == cfg.m_seed_to) { - const auto& mint_tx = wal.mint_new_coins(cfg.m_initial_mint_count, - cfg.m_initial_mint_value); + const auto& omint_tx = wal.mint_new_coins(cfg.m_initial_mint_count, + cfg.m_initial_mint_value, + cfg, + 0); + if(!omint_tx.has_value()) { + log->error("Failed to create mint tx"); + return -1; + } + auto mint_tx = omint_tx.value(); cbdc::atomizer::tx_notify_request msg; diff --git a/tools/bench/twophase_gen.cpp b/tools/bench/twophase_gen.cpp index 32c68f234..7c3e79f93 100644 --- a/tools/bench/twophase_gen.cpp +++ b/tools/bench/twophase_gen.cpp @@ -74,8 +74,15 @@ auto main(int argc, char** argv) -> int { return -1; } - auto mint_tx = wallet.mint_new_coins(cfg.m_initial_mint_count, - cfg.m_initial_mint_value); + auto omint_tx = wallet.mint_new_coins(cfg.m_initial_mint_count, + cfg.m_initial_mint_value, + cfg, + 0); + if(!omint_tx.has_value()) { + logger->error("Failed to create mint tx"); + return -1; + } + auto mint_tx = omint_tx.value(); auto compact_mint_tx = cbdc::transaction::compact_tx(mint_tx); auto secp = std::unique_ptr