diff --git a/.gitignore b/.gitignore index 6e7db3614..4642caf33 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,10 @@ console/bx /release/bx /release/bx.exe *.zpl + +build-libbitcoin-explorer +libbitcoin_explorer_test_runner.sh.log +libbitcoin_explorer_test_runner.sh.trs +test-suite.log +test.log +test/libbitcoin_explorer_test diff --git a/include/bitcoin/explorer/commands/electrum-new.hpp b/include/bitcoin/explorer/commands/electrum-new.hpp index d7b7da227..411d0daca 100644 --- a/include/bitcoin/explorer/commands/electrum-new.hpp +++ b/include/bitcoin/explorer/commands/electrum-new.hpp @@ -58,7 +58,9 @@ namespace commands { /** * Various localizable strings. */ -#define BX_EC_ELECTRUM_NEW_UNSUPPORTED \ +#define BX_ELECTRUM_NEW_BIT_LENGTH_UNSUPPORTED \ + "The seed size is not supported." +#define BX_ELECTRUM_NEW_UNSUPPORTED \ "The electrum-new command requires an ICU build." /** @@ -145,6 +147,11 @@ class BCX_API electrum_new value(), "The path to the configuration settings file." ) + ( + "bit_length,b", + value(&option_.bit_length)->default_value(132), + "The minimum required length of the input seed in bits, defaults to 132." + ) ( "prefix,p", value(&option_.prefix), @@ -200,6 +207,23 @@ class BCX_API electrum_new argument_.seed = value; } + /** + * Get the value of the bit_length option. + */ + virtual uint16_t& get_bit_length_option() + { + return option_.bit_length; + } + + /** + * Set the value of the bit_length option. + */ + virtual void set_bit_length_option( + const uint16_t& value) + { + option_.bit_length = value; + } + /** * Get the value of the prefix option. */ @@ -259,11 +283,13 @@ class BCX_API electrum_new struct option { option() - : prefix(), + : bit_length(), + prefix(), language() { } + uint16_t bit_length; explorer::config::electrum prefix; explorer::config::language language; } option_; diff --git a/include/bitcoin/explorer/commands/electrum-to-seed.hpp b/include/bitcoin/explorer/commands/electrum-to-seed.hpp index 9836cc10e..3f40e9792 100644 --- a/include/bitcoin/explorer/commands/electrum-to-seed.hpp +++ b/include/bitcoin/explorer/commands/electrum-to-seed.hpp @@ -58,7 +58,7 @@ namespace commands { /** * Various localizable strings. */ -#define BX_EC_ELECTRUM_TO_SEED_PASSPHRASE_UNSUPPORTED \ +#define BX_ELECTRUM_TO_SEED_PASSPHRASE_UNSUPPORTED \ "The passphrase option requires an ICU build." /** diff --git a/include/bitcoin/explorer/define.hpp b/include/bitcoin/explorer/define.hpp index 5fa36511f..2ba9e7c24 100644 --- a/include/bitcoin/explorer/define.hpp +++ b/include/bitcoin/explorer/define.hpp @@ -97,6 +97,11 @@ BC_CONSTEXPR size_t minimum_seed_bits = 128; */ BC_CONSTEXPR size_t minimum_seed_size = minimum_seed_bits / bc::byte_bits; +/** + * The minimum safe length of an electrum seed in bits. + */ +BC_CONSTEXPR size_t minimum_electrum_seed_bits = 132; + /** * Suppported output encoding engines. */ diff --git a/model/generate.xml b/model/generate.xml index d4685a63b..6ac7eb6be 100644 --- a/model/generate.xml +++ b/model/generate.xml @@ -205,17 +205,19 @@ + diff --git a/src/commands/electrum-new.cpp b/src/commands/electrum-new.cpp index 341404505..0a29cff43 100644 --- a/src/commands/electrum-new.cpp +++ b/src/commands/electrum-new.cpp @@ -18,15 +18,17 @@ */ #include -#include +#include #include #include #include +#include namespace libbitcoin { namespace explorer { namespace commands { using namespace bc::wallet; +using namespace boost::multiprecision; console_result electrum_new::invoke(std::ostream& output, std::ostream& error) @@ -35,16 +37,47 @@ console_result electrum_new::invoke(std::ostream& output, // Bound parameters. const dictionary_list& language = get_language_option(); const data_chunk& seed = get_seed_argument(); + const auto bit_length = get_bit_length_option(); const auto prefix = get_prefix_option(); + const auto bits_per_word = std::log2(bc::wallet::dictionary_size); + + const auto number_of_entropy_bits = static_cast( + std::ceil(bit_length / bits_per_word) * bits_per_word); + + if ((bit_length < minimum_electrum_seed_bits) || + (seed.size() * byte_bits < number_of_entropy_bits)) + { + error << BX_ELECTRUM_NEW_BIT_LENGTH_UNSUPPORTED << std::endl; + return console_result::failure; + } + // If 'any' default to first ('en'), otherwise the one specified. const auto dictionary = language.front(); - const auto words = electrum::create_mnemonic(seed, *dictionary, prefix); + + // convert the provided entropy to a large number and make sure it + // does not exceed the required entropy bits + const cpp_int entropy_number = cpp_int("0x" + encode_base16(seed)) % + boost::multiprecision::pow(cpp_int(2), number_of_entropy_bits); + + // convert large number back into a base16 decode-able string + std::stringstream sstream; + sstream << std::hex << entropy_number; + + // pre-pend zero if the string conversion length is not even + const auto entropy_prefix = ((sstream.str().size() % 2 == 0) ? "" : "0"); + const auto entropy_string = entropy_prefix + sstream.str(); + + data_chunk electrum_seed; + decode_base16(electrum_seed, entropy_string); + + const auto words = electrum::create_mnemonic( + electrum_seed, *dictionary, prefix); output << join(words) << std::endl; return console_result::okay; #else - error << BX_EC_ELECTRUM_NEW_UNSUPPORTED << std::endl; + error << BX_ELECTRUM_NEW_UNSUPPORTED << std::endl; return console_result::failure; #endif } diff --git a/src/commands/electrum-to-seed.cpp b/src/commands/electrum-to-seed.cpp index eee82cc05..5c53a695d 100644 --- a/src/commands/electrum-to-seed.cpp +++ b/src/commands/electrum-to-seed.cpp @@ -42,7 +42,7 @@ console_result electrum_to_seed::invoke(std::ostream& output, // The passphrase requires ICU normalization. if (!passphrase.empty()) { - error << BX_EC_ELECTRUM_TO_SEED_PASSPHRASE_UNSUPPORTED << std::endl; + error << BX_ELECTRUM_TO_SEED_PASSPHRASE_UNSUPPORTED << std::endl; return console_result::failure; } diff --git a/src/commands/seed.cpp b/src/commands/seed.cpp index bd6ec9dea..8a4ca99af 100644 --- a/src/commands/seed.cpp +++ b/src/commands/seed.cpp @@ -18,7 +18,6 @@ */ #include -#include #include #include #include diff --git a/test/commands/electrum-new.cpp b/test/commands/electrum-new.cpp index 876fd28ed..8e725adcc 100644 --- a/test/commands/electrum-new.cpp +++ b/test/commands/electrum-new.cpp @@ -29,6 +29,7 @@ BOOST_AUTO_TEST_SUITE(electrum_new__invoke) BOOST_AUTO_TEST_CASE(electrum_new__invoke__17_bytes__okay_output) { BX_DECLARE_COMMAND(electrum_new); + command.set_bit_length_option(132); command.set_seed_argument({ "05e669b4270f4e25bce6fc3736170d423c" }); BX_REQUIRE_OKAY(command.invoke(output, error)); BX_REQUIRE_OUTPUT("giggle crush argue inflict wear defy combine evolve tiger spatial crumble fury\n"); @@ -37,6 +38,7 @@ BOOST_AUTO_TEST_CASE(electrum_new__invoke__17_bytes__okay_output) BOOST_AUTO_TEST_CASE(electrum_new__invoke__dictionary_prefix__okay_output) { BX_DECLARE_COMMAND(electrum_new); + command.set_bit_length_option(132); command.set_seed_argument({ "05e669b4270f4e25bce6fc3736170d423c" }); command.set_language_option({ "en" }); command.set_prefix_option({ "standard" }); @@ -47,9 +49,19 @@ BOOST_AUTO_TEST_CASE(electrum_new__invoke__dictionary_prefix__okay_output) BOOST_AUTO_TEST_CASE(electrum_new__invoke__32_bytes__okay_output) { BX_DECLARE_COMMAND(electrum_new); + command.set_bit_length_option(132); command.set_seed_argument({ "b0756302179e800b182514c729f1d6814c377ff06097569ef540e6c1f1950f08" }); BX_REQUIRE_OKAY(command.invoke(output, error)); - BX_REQUIRE_OUTPUT("divide february web hire limb run reject nuclear army zone brick below public ladder deer below again cluster divorce ketchup aerobic flee lonely absent\n"); + BX_REQUIRE_OUTPUT("tube feature web hire limb run reject nuclear army zone brick below\n"); +} + +BOOST_AUTO_TEST_CASE(electrum_new__invoke__32_bytes_220_bit_entropy__okay_output) +{ + BX_DECLARE_COMMAND(electrum_new); + command.set_bit_length_option(220); + command.set_seed_argument({ "b0756302179e800b182514c729f1d6814c377ff06097569ef540e6c1f1950f08" }); + BX_REQUIRE_OKAY(command.invoke(output, error)); + BX_REQUIRE_OUTPUT("cruise february web hire limb run reject nuclear army zone brick below public ladder deer below again cluster divorce ketchup\n"); } #endif