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/seed.hpp b/include/bitcoin/explorer/commands/seed.hpp index 81fb0c7ae..9eef68946 100644 --- a/include/bitcoin/explorer/commands/seed.hpp +++ b/include/bitcoin/explorer/commands/seed.hpp @@ -145,7 +145,12 @@ class BCX_API seed ( "bit_length,b", value(&option_.bit_length)->default_value(192), - "The length of the seed in bits. Must be divisible by 8 and must not be less than 128, defaults to 192." + "The length of the seed in bits. Unless electrum option is used, this must be divisible by 8 and must not be less than 128, defaults to 192." + ) + ( + "electrum,e", + value(&option_.electrum)->zero_tokens(), + "Generate electrum compatible seed for electrum mnemonics." ); return options; @@ -187,6 +192,23 @@ class BCX_API seed option_.bit_length = value; } + /** + * Get the value of the electrum option. + */ + virtual bool& get_electrum_option() + { + return option_.electrum; + } + + /** + * Set the value of the electrum option. + */ + virtual void set_electrum_option( + const bool& value) + { + option_.electrum = value; + } + private: /** @@ -210,11 +232,13 @@ class BCX_API seed struct option { option() - : bit_length() + : bit_length(), + electrum() { } uint16_t bit_length; + bool electrum; } option_; }; diff --git a/include/bitcoin/explorer/define.hpp b/include/bitcoin/explorer/define.hpp index 5fa36511f..f2c50b9e7 100644 --- a/include/bitcoin/explorer/define.hpp +++ b/include/bitcoin/explorer/define.hpp @@ -97,6 +97,20 @@ 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; + +/** + * The maximum length of an electrum seed in bits. + * + * This constant defines the arbitrarily chosen maximum allowable seed + * length, as a fixed number is required for this implementation. + * This is safe to increase/decrease as needed. + */ +BC_CONSTEXPR size_t maximum_electrum_seed_bits = 1024; + /** * Suppported output encoding engines. */ diff --git a/include/bitcoin/explorer/utility.hpp b/include/bitcoin/explorer/utility.hpp index ea3b87dde..e903daf1c 100644 --- a/include/bitcoin/explorer/utility.hpp +++ b/include/bitcoin/explorer/utility.hpp @@ -192,6 +192,13 @@ BCX_API ec_secret new_key(const data_chunk& seed); */ BCX_API data_chunk new_seed(size_t bit_length=minimum_seed_bits); +/** + * Generate a new pseudorandom seed suitable for electrum mnemonics. + * @param[in] seed The electrum minimum seed length in bits. + * @return The new key. + */ +BCX_API data_chunk new_electrum_seed(size_t bit_length); + /** * Convert a list of indexes to a list of strings. This could be generalized. * @param[in] indexes The list of indexes to convert. diff --git a/model/generate.xml b/model/generate.xml index d4685a63b..558b63247 100644 --- a/model/generate.xml +++ b/model/generate.xml @@ -471,7 +471,8 @@ - diff --git a/src/commands/seed.cpp b/src/commands/seed.cpp index bd6ec9dea..f7cdd49cd 100644 --- a/src/commands/seed.cpp +++ b/src/commands/seed.cpp @@ -18,7 +18,6 @@ */ #include -#include #include #include #include @@ -31,19 +30,43 @@ using namespace bc::config; console_result seed::invoke(std::ostream& output, std::ostream& error) { const auto bit_length = get_bit_length_option(); + const auto electrum = get_electrum_option(); - // These are soft requirements for security and rationality. - // We use bit vs. byte length input as the more familiar convention. - if (bit_length < minimum_seed_size * byte_bits || - bit_length % byte_bits != 0) + if (electrum) { - error << BX_SEED_BIT_LENGTH_UNSUPPORTED << std::endl; - return console_result::failure; + // We use bits instead of bytes since the input bit length is + // not required to be byte aligned + if (bit_length < minimum_electrum_seed_bits || + bit_length > maximum_electrum_seed_bits) + { + error << BX_SEED_BIT_LENGTH_UNSUPPORTED << std::endl; + return console_result::failure; + } + + static constexpr 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); + + const auto seed = new_electrum_seed(number_of_entropy_bits); + output << base16(seed) << std::endl; } + else + { + // These are soft requirements for security and rationality. + // We use bit vs. byte length input as the more familiar convention. + if (bit_length < minimum_seed_size * byte_bits || + bit_length % byte_bits != 0) + { + error << BX_SEED_BIT_LENGTH_UNSUPPORTED << std::endl; + return console_result::failure; + } - const auto seed = new_seed(bit_length); + const auto seed = new_seed(bit_length); + output << base16(seed) << std::endl; + } - output << base16(seed) << std::endl; return console_result::okay; } diff --git a/src/utility.cpp b/src/utility.cpp index 203f9222a..c640687a5 100644 --- a/src/utility.cpp +++ b/src/utility.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -34,15 +35,23 @@ #include #include #include +#include +#include #include #include #include #include +#include +#include + #include #include #include +using namespace std::chrono; using namespace bc::client; +using namespace boost::random; +using namespace boost::multiprecision; using boost::filesystem::path; namespace libbitcoin { @@ -80,6 +89,26 @@ data_chunk new_seed(size_t bit_length) return seed; } +data_chunk new_electrum_seed(size_t bit_length) +{ + static const auto now = high_resolution_clock::now(); + static mt19937 twister(now.time_since_epoch().count()); + + const cpp_int maximum = boost::multiprecision::pow(cpp_int(2), bit_length); + uniform_int_distribution distribution(cpp_int(1), maximum); + + std::stringstream sstream; + sstream << std::hex << distribution(twister); + + // pre-pend zero if the string conversion length is not even + const auto prefix = ((sstream.str().size() % 2 == 0) ? "" : "0"); + const auto entropy_string = prefix + sstream.str(); + + data_chunk seed; + decode_base16(seed, entropy_string); + return seed; +} + string_list numbers_to_strings(const chain::point::indexes& indexes) { string_list stringlist;