Search code examples
pythonpython-3.xcryptographybitcoinentropy

Why does btclib.mnemonic_from_raw_entropy claim I passed it less than 128 bits of entropy?


What I am doing is generating 'dummy' wallets, retrieving both a mnemonic passphrase and wallet from raw entropy, which is generated using secrets.randbits(128) – resulting in a valid bip39 pair of keys.

The error I get is along these lines:

ValueError: 125 bits provided; expected: (128, 160, 192, 224, 256)

It can vary from 122 to 127 bits. It usually says the same amount of bits for multiple errors in a row, i.e. 125 bits provided for 3 times, then switching to 122 bits provided for 2 times, then working at the 6th try.

I am using btclib – the full function is

def create_passphrase():
    memo = bip39.mnemonic_from_raw_entropy(secrets.randbits(128) , 'en')

    print(mnemo) 
    return mnemo

Sorry if I am missing something obvious.


Solution

  • This is a bug in btclib.

    The function bip39.mnemonic_from_raw_entropy() calls bip39.entropy_from_raw_entropy(), which calls entropy.str_from_entropy().

    When entropy.str_from_entropy() is called with an integer as its entr argument, it attempts to convert that integer into a string representing the bits, here:

            entr = bin(entr)[2:] # remove '0b'
    

    This is broken: any integer passed in will, if it's truly random, only be converted to the number of bits expected about half the time. To see why, consider these examples of random data (I'll use 8 bits rather than 128 for simplicity's sake, but the principle is the same):

    >>> bin(0b10001011)[2:]
    '10001011'
    >>> bin(0b01010110)[2:]
    '1010110'
    >>> bin(0b00111011)[2:]
    '111011'
    

    As you can see, the conversion method used by btclib strips off any leading zeroes, leading to a string of the wrong length being produced.

    A workaround might be to convert the result of secrets.randbits(128) to an appropriate string yourself, and pass that:

    def create_passphrase(bits=128):
        bitstring = f'{secrets.randbits(bits):0{bits}b}'
        memo = bip39.mnemonic_from_raw_entropy(bitstring , 'en')
        print(memo) 
        return memo
    

    … assuming that there aren't other bugs in btclib waiting to bite you.