Skip to content

CVE 2023 39910

Eric Voskuil edited this page Oct 3, 2023 · 4 revisions

Background

This document provides a detailed review of circumstances pertaining to CVE-2023-39910.

Libbitcoin

Libbitcoin ("lib Bitcoin") is a Bitcoin software development toolkit AGPLv3 community project, started in 2011 by Amir Taaki. The current release is version 3.8.0, with v4 work ongoing for several years. Libbitcoin produced the first Bitcoin node implementation independent of Satoshi’s bitcoind and has been continuously maintained to date.

The project consists of ten C++ libraries each within their own GitHub repository, spanning on the order of 500,000 lines of code. The libraries provide many functions that would typically be required in the development of Bitcoin applications in C++. Three libraries provide command line tools that demonstrate the use of these functions – specifically BN (Bitcoin Node), BS (Bitcoin Server) and BX (Bitcoin eXplorer). All libraries are designed in a manner that allows their functionality to be easily extended by developers without having to fork and maintain them.

SubvertX (SX)

SX (sometimes referred to as Spesmilo eXpander) was an early set of ad-hoc tools initially developed for Libbitcoin maintainer use. There was never a versioned release. Among other capabilities, SX explored implementation of a command line wallet, which was discontinued with the release of BX (link).

<command symbol="wallet" category="WALLET" obsolete="true" description="Experimental command line wallet.">
  <define name="BX_WALLET_OBSOLETE" value="This experimental command is no longer supported." />
</command>

Bitcoin Explorer (BX)

BX (Bitcoin eXplorer) is a thin command line interface around the libbitcoin-explorer developer library. This serves the initial objective of maintainer tooling while also providing a demonstration of Libbitcoin functions tied to common scenarios. The initial release of BX was 2.0, reserving the 1.0 designation for SX. The 2.x releases focused on reduction of dependencies, code generation, comprehensive unit testing, continuous integration, Windows support, and rationalization of the command line interface.

Neither Libbitcoin nor BX is a wallet, however many primitive functions used in wallet development are provided. As the name implies, BX is intended for exploration of Bitcoin. Commands include elliptic curve math, hashing, base encoding, HD key derivation, BS query, etc. It has often been used to publicly demonstrate how Bitcoin works at lower levels than typically accessible in consumer-oriented applications. For example: Wright Decoded and Bitcoin Edge Dev++ 2018. It should be quite evident from these examples that BX is an advanced developer tool.

SX Seeding

sx hd-seed was the initial seeding command. It accepted entropy via the command line and if not provided, defaulted to internal entropy generation (link).

data_chunk random_bytes(size_t size)
{
    std::random_device rd;
    std::default_random_engine engine(rd());
    std::uniform_int_distribution<uint8_t> uniform_dist(0, 255);

    data_chunk result;
    for (size_t i = 0; i < size; ++i)
        result.push_back(uniform_dist(engine));
    return result;
}

int main(int argc, char** argv)
{
    if (argc > 2)
    {
        std::cerr << "Usage: sx hd-seed [ENTROPY]" << std::endl;
        return -1;
    }
    data_chunk entropy;
    if (argc == 2)
        entropy = decode_hex(argv[1]);
    else
        entropy = random_bytes(32);
    hd_private_key hd_key(entropy);
    std::cout << hd_key.serialize() << std::endl;
    return 0;
}

Entropy was later incorporated into other commands, such as sx stealth-newkey. Note that this did not provide the command line option (link).

int main()
{
    ec_secret scan_secret = generate_random_secret();
    ec_secret spend_secret = generate_random_secret();
    ec_point spend_pubkey = secret_to_public_key(spend_secret);

    stealth_address addr;
    addr.options |= stealth_address::reuse_key_option;
    addr.scan_pubkey = secret_to_public_key(scan_secret);
    addr.spend_pubkeys.push_back(spend_pubkey);
    addr.number_signatures = 1;

    std::cout << "Stealth address: " << addr.encoded() << std::endl;
    std::cout << "Scan secret: " << scan_secret << std::endl;
    std::cout << "Spend secret: " << spend_secret << std::endl;
    return 0;
}

The sx newkey command was added to provide generation of WIF keys. This also did not provide the command line option (link).

int main()
{
    elliptic_curve_key key;
    key.new_keypair();
    secret_parameter secret = key.secret();
    std::cout << libwallet::secret_to_wif(secret) << std::endl;
    return 0;
}

The sx hd-new command was reworked and renamed to sx newseed, with the command line option removed (link).

int main()
{
    libwallet::deterministic_wallet wallet;
    wallet.new_seed();
    std::cout << wallet.seed() << std::endl;
    return 0;
}

Internal entropy generation was thus centralized in util.hpp.

ec_secret generate_random_secret()
{
    std::random_device random;
    std::default_random_engine engine(random());
    ec_secret secret;
    for (uint8_t& byte: secret)
        byte = engine() % std::numeric_limits<uint8_t>::max();
    return secret;
}

BX Seeding

One of the many interface normalizations implemented by BX was the requirement that entropy only be provided via the command line, for commands that consume it, affecting bx hd-new, bx ec-new, bx mnemonic-encode and the stealth feature of bx tx-encode. The sx newseed command was renamed to bx seed which remained based on std::random_device (link).

// Not testable due to lack of random engine injection.
data_chunk new_seed(size_t bitlength)
{
    size_t fill_seed_size = bitlength / byte_bits;
    data_chunk seed(fill_seed_size);
    random_fill(seed);
    return seed;
}

...

// Not testable due to lack of random engine injection.
void random_fill(data_chunk& chunk)
{
    std::random_device random;
    std::default_random_engine engine(random());

    for (uint8_t& byte: chunk)
        byte = engine() % std::numeric_limits<uint8_t>::max();
}

This set of changes made it necessary for the user to provide entropy via the command line for all commands that consumed it, with length constraints. The option to obtain internally-generated entropy was thus retained, though it became explicit.

SX did not incorporate a consistent help system and did not offer help for the sx newseed command. In recognition of platform generated entropy limitations (see below), the integrated help for bx seed was set to inform the user that the seed was not truly random. The bx seed documentation has always contained the following text and explicit warning (link).

bx-seed-doc

Before the BX 2.1 release, the bx cert-new command had been added (on Feb 12, 2015). This command pertains to client-server communications security. This is a pass through to the external ZeroMQ library for generation of CurveCP certificates. This command has always been documented with the following explicit warning against platform entropy (link).

bx-cert-new-doc

Versions

As a software development toolkit, Libbitcoin endeavors to isolate all breaking changes to major version increments (e.g. 1.x, 2.x, 3.x). Cross-library dependencies are also restricted to the same major version. In other words, libbitcoin-explorer 3.x must depend on libbitcoin-system 3.x and cannot depend on 1.x or 2.x. Applying breaking changes is the purpose of the major increment. As such, BX version 3 depends upon libbitcoin-system version 3. Each major version is staged in the master branch and moved to a release branch (e.g. version3) when tagged for its initial release.

system/version1    10 May 2013
system/version2    05 Jun 2014
system/version3    09 Feb 2017

subvertx/version1  -----------
explorer/version2  13 Dec 2014
explorer/version3  08 Mar 2017

BX v2.0.0 took its initial system dependency upon system v2.7.0, both dated Dec 13, 2014.
BX v2.3.0 (the last update of BX version 2) was tagged for release on Feb 9, 2017.
BX v3.0.0 took its initial system dependency upon system v3.0.0, dated Feb/Mar 2017.

Despite being released at about the same time as BX v3.0.0, BX v2.3.0 continued to depend upon the system 2.x pseudo_random_fill() function which was based on std::random_device.

System 2.x

Versions of BX 2.x depend upon libbitcoin-system (formerly just libbitcoin) v2.7.0 through v2.12.0, the one library used by all others. System v2.7.0 did not provide random number generation. By the final 2.x releases the random number generation from BX had been moved into system, a consequence of pseudo-randomness being required by other libraries (link).

using namespace boost::posix_time;

// DO NOT USE srand() and rand() on MSVC as srand must be called per thread.
// As a result it is difficult to use safely.

// Not fully testable due to lack of random engine injection.
// This may be truly random depending on the underlying device.
uint64_t pseudo_random()
{
    std::random_device device;
    std::uniform_int_distribution<uint64_t> distribution;
    return distribution(device);
}

// Not fully testable due to lack of random engine injection.
// This may be truly random depending on the underlying device.
void pseudo_random_fill(data_chunk& chunk)
{
    std::random_device device;
    std::uniform_int_distribution<uint16_t> distribution;
    for (uint8_t& byte: chunk)
    {
        // uniform_int_distribution is undefined for sizes < 16 bits,
        // so we generate a 16 bit value and reduce it to 8 bits.
        byte = distribution(device) % std::numeric_limits<uint8_t>::max();
    }
}

// Not fully testable due to lack of random engine injection.
// Randomly select a time duration in the range [expiration/ratio, expiration].
time_duration pseudo_randomize(const time_duration& expiration, uint8_t ratio)
{
    if (ratio == 0)
        return expiration;

    const auto max_expire = expiration.total_seconds();
    if (max_expire == 0)
        return expiration;

    const auto offset = max_expire / ratio;
    const auto random_offset = static_cast<int>(bc::pseudo_random() % offset);
    const auto expire = max_expire - random_offset;
    return seconds(expire);
}

Several observations on the above code and comments are in order.

  • Comments show that true randomness is not expected.
  • The fill function is based on std::random_device for all 2.x versions of BX.
  • The formerly BX functions have been renamed from "random" to explicitly "pseudo_random".
  • New 64 bit and time duration functions have been added (unrelated to key seeding).
  • Potential thread safety issues have been commented.

System 3.x

System v3 incorporated an implementation change to the system v2 pseudo_random functions. This was precipitated by a message posted to the Libbitcoin mailing list. It was determined that the lack of thread safety of the pseudo_random functions was causing a failure in the p2p protocol networking stack for pending version 3. The network stack used the pseudo_random() function to select peer network addresses.

The corresponding issue links the Stack Overflow topic C++11 Thread safety of Random number generators.

Just as containers need locks to make them safe to share, you would have to lock the PRNG object. This would make it slow and nondeterministic. One object per thread would be better.

Based on this recommendation, the pseudo_random functions were modified to Make random number generation thread safe and optimal. The "thread safe and optimal" annotation is specifically a reference to:

There was no objective to make the pseudo_random functions cryptographically secure, nor were they previously.

using namespace bc::asio;
using namespace std::chrono;

// DO NOT USE srand() and rand() on MSVC as srand must be called per thread.
// Values may be truly random depending on the underlying device.

static uint32_t get_clock_seed()
{
    const auto now = high_resolution_clock::now();
    return static_cast<uint32_t>(now.time_since_epoch().count());
}

static std::mt19937& get_twister()
{
    // Boost.thread will clean up the thread statics using this function.
    const auto deleter = [](std::mt19937* twister)
    {
        delete twister;
    };

    // Maintain thread static state space.
    static boost::thread_specific_ptr<std::mt19937> twister(deleter);

    // This is thread safe because the instance is static.
    if (twister.get() == nullptr)
    {
        // Seed with high resolution clock.
        twister.reset(new std::mt19937(get_clock_seed()));
    }

    return *twister;
}

uint64_t pseudo_random()
{
    return pseudo_random(0, max_uint64);
}

uint64_t pseudo_random(uint64_t begin, uint64_t end)
{
    std::uniform_int_distribution<uint64_t> distribution(begin, end);
    return distribution(get_twister());
}

void pseudo_random_fill(data_chunk& chunk)
{
    // uniform_int_distribution is undefined for sizes < 16 bits.
    std::uniform_int_distribution<uint16_t> distribution(0, max_uint8);

    for (auto& byte: chunk)
        byte = static_cast<uint8_t>(distribution(get_twister()));
}

// Randomly select a time duration in the range:
// [(expiration - expiration / ratio) .. expiration]
// Not fully testable due to lack of random engine injection.
asio::duration pseudo_randomize(const asio::duration& expiration,
    uint8_t ratio)
{
    if (ratio == 0)
        return expiration;

    // Uses milliseconds level resolution.
    const auto max_expire = duration_cast<milliseconds>(expiration).count();

    // [10 secs, 4] => 10000 / 4 => 2500
    const auto limit = max_expire / ratio;

    if (limit == 0)
        return expiration;

    // [0..2^64) % 2500 => [0..2500]
    const auto random_offset = static_cast<int>(pseudo_random(0, limit));

    // (10000 - [0..2500]) => [7500..10000]
    const auto expires = max_expire - random_offset;

    // [7.5..10] second duration.
    return milliseconds(expires);
}

Note that the following comment had evolved to become obsolete, but was not removed. Despite no longer being correct it continued to show (correctly) that true randomness cannot be relied upon.

// Values may be truly random depending on the underlying device.

Entropy

A pseudorandom number generator (PRNG) can be cryptographically secure (CSPRNG) or not. However a CSPRNG is deterministic, it cannot produce a sequence of cryptographically secure numbers unless it has been seeded by a true random number generator (TRNG). No amount of sophistication in a CSPRNG can compensate for a seed that is not truly random.

C++11 Random

None of the random number engines provided by the standard library are cryptographically secure.

https://en.cppreference.com/w/cpp/numeric/random

std::random_device may be implemented in terms of an implementation-defined pseudo-random number engine if a non-deterministic source (e.g. a hardware device) is not available to the implementation. In this case each std::random_device object may generate the same number sequence.

https://en.cppreference.com/w/cpp/numeric/random/random_device

std::random_device cannot be relied upon as a TRNG, and neither std::default_random_engine nor std::uniform_int_distribution are CSPRNGs. Quite clearly neither BX nor SX ever provided cryptographically secure entropy. This is why BX requires that entropy be provided via the command line, with no default. The bx seed command was retained as a "convenience" for the vast majority of BX use cases - where true randomness is not required. In documentation of some other commands, bx seed was sometimes presented as a stand-in for user-provided entropy, as that is one reason it existed.

So while std::mt19937 is not a CSPRNG, std::random_device is not a TRNG. If one does not have a TRNG, there is no point in a CSPRNG and one might as well seed the sequence with the system clock.

Platform Unsuitability

Even setting aside these considerations, there is the simple fact that no software application can assure that it is being provided true randomness from the operating system. Nor can the operating system offer any assurance that it is being provided true randomness from the underlying hardware/firmware platform. And finally, there is no effective way for a user to audit platform RNG output.

Below nullc describes how Bitcoin Core attempts to mitigate this well known weakness. Note that this mitigation is obscurity, not cryptographic security. It is described as a "hail mary so the user might have some chance to move their funds if they learn about vulnerabilities in their OS/hardware RNGs before an attacker can brute force out the weak sources."

hail-mary

Below gmaxwell describes how operating system secure seeding of an RNG "isn't guaranteed." Yet this is followed by, "Basically it seems they changed from code that had security problems in theory but usually not (and maybe never) in practice to code that was certainly broken all the time for everyone in both theory and practice."

os-no-guarantee

Security problems "in theory", "usually not", "maybe never" seems to imply that trusting the platform is sufficient. Nearly a decade of history shows that Libbitcoin developers have not accepted this weak assumption. While the change to implementation for version 3.0 was initiated due to a thread safety bug in the pre-release code, the removal of the (possible) platform RNG dependency was also expected to demonstrate clearly in the code as well, that there was no expectation of true randomness.

It is also demonstrably false that platform seeding is rarely if ever insecure. The MinGW platform, used by Bitcoin Core and other applications to produce Windows builds, implemented a wholly insecure std::random_device. In fact the internal implementation used the same std::mt19937 engine later explicitly used in BX version 3, and seeded the engine with a fixed value. In other words, the platform implementation was actually worse than the intentional function in BX 3.

mingw

This behavior was observed at least as early as 2013, reported in 2018, and finally patched in 2021.

Similarly, in 2013 the Bitcoin Foundation made the following announcement.

android-rng

We recently learned that a component of Android responsible for generating secure random numbers contains critical weaknesses, that render all Android wallets generated to date vulnerable to theft.

Apart from implementation flaws and limitations, platform RNGs present an obvious attack vector, and are known to have been exploited by state-level actors. Trust in the platform as a TRNG is not safe, not necessary, and not reasonably auditable. It is not possible for a user to know if their computer, even if entirely air-gapped, has a subverted RNG. The random output of two or more independent computers cannot be compared.

Best Practices

There are well-documented best practices for entropy generation, which can be as simple as dice rolls.

glacier

Libbitcoin leaves live wallet entropy generation to the developer, who may instruct their end user on such best practices. SX did not document the expected behavior of its various seeded commands, which hardwired insecure platform entropy generation. However from the first release of BX, all commands/functions that use entropy for wallet seeding require it be passed as an argument.

Security Guarantee

Despite its focus on Mersenne Twister, the Technical write-up does not claim that the above thread safety change for System 3.x turned a cryptographically secure implementation into an insecure one.

ms-old-versions

Specifically, the std::random_device entropy source in combination with std::default_random_engine may not behave securely enough if the random engine uses insufficient seeding and acts as a non-CSPRNG similar to Mersenne Twister.

Actually, as explained above, limitations apply to both std::random_device and std::default_random_engine. The former provides a seed for the latter. This seed is not guaranteed to be truly random and the latter is explicitly not cryptographically secure. In fact the MinGW implementation of std::random_device seeds std::mt19937 with a fixed value and is consistent with the C++ specification ("A notable implementation where std::random_device is deterministic.").

Furthermore, even if the std::random_device happens to provide true randomness for a given compiler, operating system, and hardware instance, none of the random number engines provided by the standard library are cryptographically secure. It cannot therefore be the case that there was a change to the security guarantee supposedly offered by the libbitcoin-system pseudo_random functions from version 2 to 3.

As this incident has shown, people reviewing sources may not be familiar with or consult C++ documentation. Some have assumed that version 2 utilized a TRNG and a CSPRNG. It is less likely for a developer to make this erroneous assumption given an explicitly clock-seeded Mersenne Twister.

Mastering Bitcoin

Misleading Generalization

It is stated in the Disclosure that, "Popular documentation like 'Mastering Bitcoin' suggests the usage of bx seed for wallet generation." The example of such a supposed "suggestion" is contained within the included image (unrelated bullets removed).

ms-mastering-examples

It's not clear how the Disclosure authors consider "examples of using Bitcoin Explorer commands to experiment with keys and addresses" a suggestion to use bx seed for wallet generation. The statement explicitly describes experimentation, which is the primary use case for BX (Bitcoin Explorer).

Content Misattribution

It is stated in the Technical write-up that, "A Libbitcoin team member adds and updates bx seed usage suggestions to 'Mastering Bitcoin'" and "When adding bx seed related workflows to the 'Mastering Bitcoin' book appendix, Libbitcoin team members described it as follows" (unrelated context removed from image below).

ms-mastering-author

The above link to the supposed addition of BX by a Libbitcoin team member shows addition of a new page named 'appdx-bx-asciidoc'. However this is actually a rename of the pre-existing page 'appdx-sx-asciidoc'. The history of the preexisting SX page shows addition by the author and no inputs from Libbitcoin team members. It's not clear why the technical write-up doesn't establish this clear connection and instead states that a Libbitcoin team member created the initial content, especially given the following reference in the write-up to SX.

Libbitcoin Explorer predecessor tool adds a newseed command for entropy generation

The above link is to a repository called BWallet. This is not the SX origin repo and is not associated with any Libbitcoin team members. It is possible that the technical write-up authors did not understand that BX is the rebranded 2.0 version of SX (even sharing the same repo and history). Otherwise it may be that the authors are hair-splitting by not considering the BX content an edit to the author's SX content, given the distinct names. In any case it is very clear from a comparison of the book sources that the edits provided by the Libbitcoin team were made strictly from the standpoint of updating content from SX to the BX equivalent.

Mastering Bitcoin's authors initially incorporated the following statement:

Generate a new private key with the operating system's random number generator by using the newkey command. We save the standard output into the file private_key.

This was revised by a Libbitcoin maintainer to the following:

Generate a random "seed" value using the seed command, which uses the operating system's random number generator. Pass the seed to the ec-new command to generate a new private key. We save the standard output into the file private_key.

The two step process is a consequence of BX requiring entropy on the command line for ec-new as opposed to embedding it within the newkey command. This is strictly the equivalent of the original SX content. Furthermore the reference to the "operating system's random number generator" is correct in both instances, for SX and for all versions of BX 2.x (as shown above), to the extent that std::random_device passes to the OS. Finally, as has been shown above in relation to the cert-new command documentation, it is clear that the Libbitcoin team considered this reference a warning (from the book's author).

On Apr 15, 2014 the Libbitcoin team was notified that libbitcoin (system) and SX were to be included in a new Bitcoin book (Mastering Bitcoin).

Author: Amir Taaki
Date: 2014-04-15 13:43 -400
To: Libbitcoin
Subject: Re: [Libbitcoin] config file stuff @evoskuil

btw andreas wants to have a libbitcoin section + use the sx tools for
examples throughout the book about bitcoin.

if we can make a single binary it can be distributed with the cd.

http://shop.oreilly.com/product/0636920032281.do

In the interim SX was incorporated into Mastering Bitcoin by the book's author. Once BX was tagged for release (on Dec, 13 2014) the Libbitcoin team created a pull request that included the BX updates (dated Jan 19, 2015) and contacted the author, who merged the PR (on Feb 22, 2015). These updates were provided as a courtesy for the author, who had been in touch with the Libbitcoin team since deciding to incorporate SX. As far as we know there have been no contributions to Mastering Bitcoin by Libbitcoin maintainers since.

Incompatibility

BX 3.0 was tagged for release on Mar 8, 2017, more than two years after the BX 2.0 updates were merged into Mastering Bitcoin. Libbitcoin system and BX 3.0 both implemented breaking interface changes, as well as the change to the pseudo_random implementation described in detail above. The Libbitcoin documentation for the bx seed command did not change with the release of version 3.0, as the text and warning remained correct. However the comment regarding operating system sourcing of entropy, that originated with the author's description of sx newkey, was not accurate in relation to BX version 3 - at least to the extent it was previously accurate (see above). However, Mastering Bitcoin has never been compatible with libbitcoin/bx version 3.

Download Warning

In lieu of the aforementioned CD distribution, a landing page with binary downloads for testnet and mainnet was created for the book. This included a substantial warning. BX is a large set of very powerful tools and exposing these to non-developers (such as those who may not even understand how to build the tools) carries risk.

bx-download-page

Documentation

Easily Discoverable

A link to BX documentation is the first line of the BX readme (the home page for BX generally). There is one sidebar menu displayed for the documentation, including one entry for each command, linking to one simple page for the command. It has been suggested that the documentation is "buried" in a wiki. The fact that these pages are implemented in a GitHub wiki does not make them less relevant or harder to find, it makes them easier to maintain and provides a complete public change history. This is the documentation that the team maintains, which could only be missed in the case where one does not care to read it.

Warning in Context

ms-documentation

The above clip from the Technical write-up states:

We're aware of a single warning note in the bx seed documentation page in the wiki...

There has always been one page per command, one bx seed command, and one warning on that page. BX is not a consumer wallet, it is a developer tool. It is in fact expected that developers read the relevant documentation, especially if they intend to put their own or others' coin at risk.

The write-up then shows the bx seed documentation warning, but does not present it in context. The full context is:

bx-seed-warning

The command is documented to "Generate a pseudorandom seed" and directly follows with "WARNING: Pseudorandom seeding can introduce cryptographic weakness into your keys." There can be no doubt that using the command is what is being warned against.

The write-up then states:

The wording "can introduce" is quite weak and a user may not be aware that this produces a seed that is completely insecure...

Whether it "can" (version 2.x) or "will" (version 3.x) is immaterial to the warning. The documentation is provided for all versions. The equivalent statement, "doing this can cause you to lose all of your coin" would not be "quite weak". Focus on the word "can" in this context is nitpicking, and there is a link provided in case the developer does not understand the implications of "cryptographic weakness".

...and should not be used to store anything of value.

The documentation exists to describe how the commands work. As a matter of convention Libbitcoin avoids telling developers what they "should" do.

The addendum "provided as a convenience" admittedly lacks context, but does not soften the preceding warning.

Some have argued that the term "pseudorandom", as used in the bx seed warning above, implies the use of a "cryptographically secure pseudorandom number generator." This ignores the actual distinction between the definitions of PRNG and CSPRNG. A PRNG implies pseudorandomness, not cryptographically secure pseudorandomness. Rectangle does not imply square. The additional warning regarding "cryptographic insecurity" exists specifically to remove any doubt. While it is possible that upon reading this warning a developer might still choose to seed a live wallet with bx seed, this seems unlikely.

Random Numbers Topic

Software CANNOT provide true randomness and CANNOT ensure that underlying hardware does so. From the first release of BX (there was no actual "release" of SX) there was a clear objective to make entropy generation the responsibility of the user. This is evident from the move of seeding in each SX command from hard-wired internal to command line arguments in BX, explicit warnings in both the bx seed and bx cert-new command documentation, source comments ("may be truly random...") which indicate that true randomness cannot be expected, and a documentation topic entitled Random Numbers.

random-numbers

This topic, in place since Dec 11, 2014, makes it abundantly clear that entropy was moved to the command line because, "a weak random number generator can introduce cryptographic weakness." It further explains that this move, "places the responsibility of ensuring random number strength on end-users and also helps them understand the potential for problems." It also cautions that bx cert-new does not allow user-provided entropy, and explains why. And finally, it states that "The 'seed' command is provided as a convenience." In this context it should be evident what was intended by "convenience", a phrase reproduced in the bx seed documentation.

Documentation not Redundant

The write-up lists a number of examples where bx seed is used and no warning is provided. The bx seed documentation is on the bx seed documentation page, it is not repeated on documentation for other commands.

ms-documentation-commands

Consistency Seems Strange

The write-up offers the idle speculation that the bx seed length constraint "seems strange" given that the command is not intended for live wallet seeding (yet does not speculate about why its documentation refers to seed and length as opposed to entropy and strength).

ms-notable-characteristics

It seems strange that the design explicitly prevents the user from creating a seed that is too short, but does not prevent him from creating a seed that has not enough randomness.

It is not clear what "seems strange" with a seeding command, in a suite of commands, requiring sufficient length to not cause error propagation to those other commands. All BX commands validate (and document) their parameters to the extent practical. It would only be strange if this one command did not.

Feature or Bug

It seems certain, and the Libbitcoin team accepts, that the documentation was insufficient to prevent unintended use. However this is a far cry from "bad cryptography", which the Technical write-up asserts based entirely on observations that the documentation is not redundant.

ms-disagreement

By our understanding, they consider bx seed a command that should never be used productively by any bx user since it is sufficiently documented as unsuited for safe wallet generation.

It is the case that the bx seed command is not intended to be used "productively". The Libbitcoin team does not consider this the case merely because "it is sufficiently documented" as unsuitable for productive use. It is the case because that is the intent, as expressed in documentation and associated code/comment history. Whether the documentation is "sufficient" is an independent and entirely subjective question.

This is the difference between "bad cryptography" and some combination of user error and poor documentation. The write-up authors chose to omit their knowledge that, "neither party consulted the wiki on bx seed before using it" - which certainly seems material given the focus on documentation quality. No warnings in the documentation for other commands, or refinements of the bx seed command documentation could have prevented such failures. Comments in the write-up about sufficiency are both subjective and speculative.

Given the ample evidence above, all of which was referenced in one way or another by the write-up authors, it is not clear why they have rejected that bx seed was never intended for "productive" seeding.

Communication

ms-contacts

On 7/22/2023 at 11:43 Phillip Mienk's wife gives premature birth to their first child. 13 minutes later he receives the first contact, addressed to him personally. On 7/26/2030 Eric Voskuil departs the Seattle area for the Boston area in full size U-Haul, unloaded on 8/5/2023.

The technical write-up states, "First Libbitcoin team response, indicating team is too busy for contact." Despite the extraordinary circumstances, Phillip actually responded, "I apologize, as unrelated circumstances may have both of us slow to respond in the immediate future. I'm happy to look into and relay any concerns." As the write-up mischaracterizes the communication, it is reproduced below.

On 7/22/23 11:56 Lance Vick wrote:

I have a libbitcoin security issue I need to discuss to help understand the impact of.

It seems you are one of the few recently active people. Are you the right person to talk to or are there others I should loop in?

Also reached out in the IRC room.

On 7/25/23 01:43, Phillip Mienk wrote:

Hi Lance,

Myself and Eric Voskuil would be your best bets. I apologize, as unrelated circumstances may have both of us slow to respond in the immediate future. I'm happy to look into and relay any concerns.

-- Phillip

On 7/25/23 01:55, Lance Vick wrote:

My team and I have been investigating a sweep of thousands of wallets recently, and in at least two cases so far the root cause was confirmed to be a flaw in libbitcoin, present in all 3x releases.

We are currently investigating the scope of the impact and preparing a public disclosure strategy and as such we are hesitant to reveal the exact details of the vulnerability just yet as our adversaries are still actively sweeping wallets.

At this stage we are seeking to identify victims and potential victims.

If you or anyone on your team are aware of anyone that has used libbitcoin to build any wallet software or to generate private keys of any kind, even for cold wallets, we would like to get in touch.

The contact is "without vulnerability details" and requests only that team members identify Libbitcoin users. As the team does not track usage there is no such knowledge, and in this case no response is expected ("If you or anyone on your team are aware...").

The next response is that, "We haven't heard back from you, but have to move forward with the disclosure for reasons listed below." This is a bit confusing as it implies there was some other expectation than the request to identify Libbitcoin users. At this point, "technical vulnerability details and detailed disclosure context," are finally disclosed to the team.

On 8/3/2023 07:08, Lance Vick wrote:

Hello again,

We haven't heard back from you, but have to move forward with the disclosure for reasons listed below.

In summary, the "bx seed" (now "bx entropy") wallet entropy generation in libbitcoin-explorer/libbitcoin-system has effectively the same fatal flaw as Trust Wallet. The use of 32 bit Mersenne Twister as the PRNG source for long-lived cryptographic key material is highly problematic, and the use of time for seeding is a secondary problem.

Please see CVE-2023-31290 and https://blog.ledger.com/Funds-of-every-wallet-created-with-the-Trust-Wallet-browser-extension-could-have-been-stolen/ for technical information.

As a result of this flaw, all funds stored on these wallets are open to theft as the private keys can be recovered with moderate amounts of computation (a few days on a single server). Note that we were not involved in CVE-2023-31290 and have only learned of it recently as part of our own research.

After the disclosure of CVE-2023-31290, malicious actors discovered that bx has a similar flaw, and have drained at least $0.8M worth of BTC plus other funds in June/July 2023 from wallets originally created with bx. We discovered this after being affected through more than one of these wallets, with funds lost on our end as well. We've comprehensively reproduced this issue over the last days to understand the attack that happened on us and other users.

Unfortunately, no patch to the software will help the already impacted users. Considering the active exploitation, monetary loss and this patch context, we feel a rapid disclosure is needed. Our current public disclosure is planned for sometime next week, exact date to be determined. We're planning to file a CVE for the issue in bx this week.

That said, if you or anyone on your side feels there is any strong reason to slow down our timeline, please do let us know as soon as possible so that we can take it into consideration.

We strongly ask you to keep this advance notice confidential so that we can make a comprehensive public disclosure with all relevant details. Our goal is to hopefully avoid panic for the -majority- of cryptocurrency users that are not impacted.

Less than two hours later, while in transit, Eric responds with a link to the bx seed documentation page. Later, after arriving at his destination, he responds in greater detail that the issue is not a bug in Libbitcoin, again citing the warning against such use.

On 8/3/23 08:51, Eric Voskuil wrote:

https://github.com/libbitcoin/libbitcoin-explorer/wiki/bx-seed

On 8/5/23 06:22, Eric Voskuil wrote:

Hello,

I’ve been traveling and moving across country, but sent you a quick note a couple days ago.

I did not respond to your first query because you did not provide any indication of what the supposed issue was. We do not track Libbitcoin users.

BX does not create a wallet and strongly warns against using its explicitly pseudorandom entropy generator for wallet entropy. The command is documented as a convenience tool only, and exists to demonstrate behavior.

Libbitcoin (bx or otherwise) does not internally provide entropy to wallet generation. Entropy must be explicitly passed to key generating functions. Passage of the PRNG output is explicitly warned against, and any competent wallet producer would never consider doing this.

This is not a bug in Libbitcoin, it is a bug in wallets. As a developer library Libbitcoin cannot prevent insecure use of its code in clear violation of its documentation. If you choose to submit a CVE report we will of course provide this information and publicly dispute the assertion that this is a Libbitcoin flaw.

We thank you for bringing this to us first. Hopefully this helps.

Best, Eric

The next day the authors respond without explanation that, "we do not fully agree with your assessment... we have already filed for a CVE."

On 8/6/2023 18:57, Lance Vick wrote:

Hello Eric,

Thank you for letting us know your thoughts.

Given the circumstances, we do not fully agree with your assessment.

We want to let you know that we have already filed for a CVE for the problems in bx 3.x, and are currently preparing a disclosure for publication in the next few days.

Additionally, we would like to let you know that we became aware today of separate RNG related entropy problems in bx versions before 3.0.0., which affect the secure operation of bx seed in some system environments. Given your stated view on the issue, and our current initial technical understanding of the comparable characteristics and implications, we see this as a variant of the previously explained vulnerability.

On 8/6/23 16:42, Eric Voskuil wrote:

Hi Lance,

You have not explained how exactly you consider this a "vulnerability" given use against explicit warnings in documentation.

Having filed without apparently having read the documented use seems a bit careless.

Best, Eric

Note the earlier comment, "We discovered this after being affected through more than one of these wallets, with funds lost on our end as well." We were later informed that their loss constituted a substantial part of the whole and that they had not read the bx seed documentation. In other words, the authors failed to disclose that they were personally and materially affected, and faulted the implementation of Libbitcoin despite documentation to the contrary which they did not actually read.

This conflict of interest may explain why they were quick to dismiss the possibility of user error and/or poor documentation despite the evidence that the behavior was intended. Additionally several members of the write-up team are advertising their commercial consulting services on the back of the disclosure.

ms-faq-for-hire

Certain of these same experts failed to consult the only documentation for the only BX command they used to establish wallets, "because it seemed like a dead simple OS entropy sampling tool." They used these wallets for a substantial amount of coin, "for years." They concluded that, "After the disclosure of CVE-2023-31290, malicious actors discovered that bx has a similar flaw..." and proceeded to pin their lapses entirely on "bad cryptography". In doing so they then failed to publicly disclose both their conflict of interest and their failure to consult documentation. It's not known to us whether their customer coin was placed at risk, yet even if the loss was limited to their own there would presumably be reputational damage to their business(es) in accepting responsibility.

Exposure

The Technical write-up authors "identified less than 50 wallets with more individual usage patterns that we associate with likely use by human wallet owners", an amount that they assume may be incomplete. They add the observation that, "A decent portion of the discovered individually used wallets did not have any BTC funds on them since before 2023, so no money could be moved away from them by the attackers."

ms-usage

In other words, over the more than five years since the release of BX 3.0, a "decent portion" less than some amount "less than 50" individually-used wallets are known to have been vulnerable in 2023. This includes an undisclosed number ("more than one") of the authors' own wallets. Unfortunately it is impossible to know how many people other than the write-up authors themselves used bx seed as a live wallet entropy source (and of those how many read its documentation). But by this current count, it does not appear to have been very common. This observation is of course not intended to minimize the loss of coin, by any party. However it does speak to the speculative nature of the write-up.

Response

As there is no way to ensure that a seeding command would not be misused, and one cannot be made cryptographically secure in software, the command has been removed. The convenience of a command that provides a properly formatted seed for the most common scenarios is lost, but there is no longer opportunity for its misuse.

BX Menu

Clone this wiki locally