Skip to content

Commit

Permalink
Allow electrum-new to take input entropy of any length from seed and
Browse files Browse the repository at this point in the history
use only what is required.  By default, 132 bits of entropy are used
to match the default for electrum 2.x+.  If more input entropy is
provided and more entropy for the electrum-mnemonic is desired, a
-b,bit-length option is provided to specify how much.
  • Loading branch information
thecodefactory committed Feb 13, 2018
1 parent 54df66a commit b625c63
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 11 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
30 changes: 28 additions & 2 deletions include/bitcoin/explorer/commands/electrum-new.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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."

/**
Expand Down Expand Up @@ -145,6 +147,11 @@ class BCX_API electrum_new
value<boost::filesystem::path>(),
"The path to the configuration settings file."
)
(
"bit_length,b",
value<uint16_t>(&option_.bit_length)->default_value(132),
"The minimum required length of the input seed in bits, defaults to 132."
)
(
"prefix,p",
value<explorer::config::electrum>(&option_.prefix),
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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_;
Expand Down
2 changes: 1 addition & 1 deletion include/bitcoin/explorer/commands/electrum-to-seed.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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."

/**
Expand Down
5 changes: 5 additions & 0 deletions include/bitcoin/explorer/define.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
6 changes: 4 additions & 2 deletions model/generate.xml
Original file line number Diff line number Diff line change
Expand Up @@ -205,17 +205,19 @@
</command>

<command symbol="electrum-new" output="string" multipleX="true" category="WALLET" description="Create a mnemonic seed (Electrum) from entropy. WARNING: mnemonic should be created from properly generated entropy.">
<option name="bit_length" type="uint16_t" default="132" description="The minimum required length of the input seed in bits, defaults to 132." />
<option name="prefix" type="electrum" description="The electrum seed type identifier to use. Options are 'standard', 'witness', and 'dual' (for two factor authentication), defaults to 'standard'." />
<option name="language" type="language" description="The language identifier of the mnemonic dictionary to use. Options are 'en', 'es', 'pt', 'ja', 'zh_Hans' and 'any', defaults to 'en'." />
<argument name="SEED" stdin="true" type="base16" description="The Base16 entropy from which the mnemonic is created. If not specified the entropy is read from STDIN." />
<define name="BX_EC_ELECTRUM_NEW_UNSUPPORTED" value="The electrum-new command requires an ICU build." />
<define name="BX_ELECTRUM_NEW_BIT_LENGTH_UNSUPPORTED" value="The seed size is not supported." />
<define name="BX_ELECTRUM_NEW_UNSUPPORTED" value="The electrum-new command requires an ICU build." />
</command>

<command symbol="electrum-to-seed" output="base16" category="WALLET" description="Convert a mnemonic seed (Electrum) to its numeric representation.">
<option name="language" type="language" description="The language identifier of the dictionary of the mnemonic. Options are 'en', 'es', 'ja', 'pt', 'zh_Hans' and 'any', defaults to 'any'." />
<option name="passphrase" type="string" description="An optional passphrase for converting the mnemonic to a seed." />
<argument name="WORD" stdin="true" limit="-1" type="string" description="The set of words that that make up the mnemonic. If not specified the words are read from STDIN." />
<define name="BX_EC_ELECTRUM_TO_SEED_PASSPHRASE_UNSUPPORTED" value="The passphrase option requires an ICU build." />
<define name="BX_ELECTRUM_TO_SEED_PASSPHRASE_UNSUPPORTED" value="The passphrase option requires an ICU build." />
</command>

<command symbol="ek-address" output="payment_address" category="KEY_ENCRYPTION" description="Create a payment address derived from an intermediate passphrase token (BIP38).">
Expand Down
39 changes: 36 additions & 3 deletions src/commands/electrum-new.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,17 @@
*/
#include <bitcoin/explorer/commands/electrum-new.hpp>

#include <iostream>
#include <sstream>
#include <bitcoin/bitcoin.hpp>
#include <bitcoin/bitcoin/math/hash.hpp>
#include <bitcoin/explorer/define.hpp>
#include <boost/multiprecision/cpp_int.hpp>

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)
Expand All @@ -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<uint32_t>(
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
}
Expand Down
2 changes: 1 addition & 1 deletion src/commands/electrum-to-seed.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
1 change: 0 additions & 1 deletion src/commands/seed.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
*/
#include <bitcoin/explorer/commands/seed.hpp>

#include <iostream>
#include <bitcoin/bitcoin.hpp>
#include <bitcoin/explorer/define.hpp>
#include <bitcoin/explorer/utility.hpp>
Expand Down
14 changes: 13 additions & 1 deletion test/commands/electrum-new.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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" });
Expand All @@ -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
Expand Down

0 comments on commit b625c63

Please sign in to comment.