btclib.mnemonic package¶
Submodules¶
btclib.mnemonic.bip39 module¶
BIP39 entropy / mnemonic / seed functions.
https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki.
Checksummed entropy (ENT+CS) is converted from/to mnemonic.
bits per word = bpw = 11
ENT = raw entropy
CS = checksum = ENT / 32
MS = words in the mnemonic sentence = (ENT+CS) / bpw
ENT |
CS |
ENT+CS |
MS |
|---|---|---|---|
128 |
4 |
132 |
12 |
160 |
5 |
165 |
15 |
192 |
6 |
198 |
18 |
224 |
7 |
231 |
21 |
256 |
8 |
264 |
24 |
Every word-list of the reference implementation is here, twelve of them, and three things follow from a mnemonic being more than English.
the sentence is NFKD-normalized before it is looked up, hashed or stretched, and so is the passphrase. That is what BIP39 asks for and what trezor/python-mnemonic does; a Spanish sentence typed with precomposed accents and the same sentence decomposed are one mnemonic and one seed
a japanese mnemonic is joined with the ideographic space U+3000, as the reference implementation joins it. NFKD maps that space to a plain one, so the seed is the same either way; the sentence is not
the language need not be given: lang_from_mnemonic reads it off the words, which is what makes seed_from_mnemonic work for a mnemonic that is not English
Translating a mnemonic from one word-list to another is not here, and BIP39 says why in its own Shortcomings section: the seed is stretched from the sentence rather than from the entropy it encodes, so the translated sentence is a valid mnemonic of a different wallet. What the entropy round trip below expresses – read in one language, write in another – is a re-spelling of the entropy and nothing more.
- btclib.mnemonic.bip39.entropy_from_mnemonic(mnemonic: str, lang: str | None = None) str[source]¶
Return the entropy from the BIP39 checksummed mnemonic sentence.
The language is read off the words if it is not provided.
The sentence is normalized first, so that the word looked up in the word-list is the NFKD one the word-list holds: a mnemonic typed on a japanese IME arrives in fullwidth latin, and U+FF41 is not “a” until it is decomposed. The language is read off the normalized sentence for the same reason.
- btclib.mnemonic.bip39.lang_from_mnemonic(mnemonic: str) str[source]¶
Return the language of the BIP39 mnemonic sentence.
The word-lists that hold every word of the sentence are the candidates, and where there is more than one the checksum decides. Both steps are needed, and neither settles every sentence.
Simplified and Traditional Chinese share 1275 of their 2048 words, so a Chinese sentence is in both about once in three hundred; english and french share a hundred words, which is the same thing far less often. The two Chinese lists are aligned, a shared word sitting at the same index in each, so an ambiguous chinese sentence usually spells the same entropy either way – the checksum then rules nothing out, and the first candidate is returned because the two are indistinguishable and not because one was picked over the other.
What is refused is the sentence that is valid in two languages and spells a different entropy in each: there the language is a question only the caller can answer. A sentence that is valid in none is not that case, and the first candidate is returned so that the caller hears about the checksum, which is what is actually wrong with it.
Prefixes are not accepted, unlike trezor/python-mnemonic’s detect_language: btclib has no expand, so a four-letter prefix is not a word anywhere else in this module either.
- btclib.mnemonic.bip39.mnemonic_from_entropy(entropy: str | int | bytes | None = None, lang: str = 'en') str[source]¶
Convert input entropy to BIP39 checksummed mnemonic sentence.
Input entropy can be expressed as binary 0/1 string, bytes-like, or integer; it must be 128, 160, 192, 224, or 256 bits.
In the case of binary 0/1 string and bytes-like, leading zeros are not considered redundant padding.
In the case of integer, where leading zeros cannot be represented, if the bit length is not an allowed value, then the binary 0/1 string is padded with leading zeros up to the next allowed bit length; if the integer bit length is longer than the maximum length, then only the leftmost bits are retained.
- btclib.mnemonic.bip39.mxprv_from_mnemonic(mnemonic: str, passphrase: str | None = None, network: str = 'mainnet', verify_checksum: bool = True) str[source]¶
Return BIP32 root master extended private key from BIP39 mnemonic.
- btclib.mnemonic.bip39.seed_from_mnemonic(mnemonic: str, passphrase: str, verify_checksum: bool = True) bytes[source]¶
Return the seed from the provided BIP39 mnemonic sentence.
The mnemonic checksum verification can be skipped if needed.
Both the sentence and the passphrase are normalized NFKD, which is what BIP39 stretches: “a mnemonic sentence (in UTF-8 NFKD) used as the password and the string ‘mnemonic’ + passphrase (again in UTF-8 NFKD) used as the salt”. Without it the twenty-four japanese vectors are twenty-four wrong seeds, and every english one still passes – “TREZOR” and the english word-list being ASCII, which is NFKD already. The two spellings of an accented word are one mnemonic for the same reason, and the ideographic space a japanese sentence is written with decomposes to a plain one, so the seed does not depend on which of the two joined the words.
The whitespace collapse on top of that is btclib’s and not the reference implementation’s, and it is why a sentence with a doubled space stretches to the seed of the sentence without it; normalize_mnemonic says why it is worth the difference.
btclib.mnemonic.dispatch module¶
Which mnemonic scheme claims a sentence, and in what order.
btclib holds a validator for each scheme and had no entry point that tries them, so a caller had to know the answer before it could ask the question. The schemes overlap, too: the same twelve English words can be a pre-2.0 Electrum seed, a versioned Electrum seed and a valid BIP39 mnemonic at once, and the three derive three different wallets.
The answers, one string each:
“slip39” – one SLIP-0039 share: every word in SLIP-0039’s own 1024-word list, a length it defines, and an RS1024 checksum that verifies. One share and not a set of them, a sentence at a time being all this is given; recovering the secret needs the threshold number of them, which is mnemonic.slip39’s business and not this function’s.
“electrum_old”, “electrum_standard”, “electrum_segwit”, “electrum_2fa”, “electrum_2fa_segwit” – the five Electrum reports. Prefixed, because “standard” says nothing once BIP39 is in the running.
“bip39” – every word in the list, a length BIP39 defines, and a checksum that verifies.
“bip39_wordlist” – every word in the list and nothing else true: the checksum failed, or the word count is not one of BIP39’s five. Kept apart from “” rather than folded into it because it is exactly what bip39.mxprv_from_mnemonic reads with verify_checksum=False, and Electrum’s own wizard accepts it as BIP39 too (spesmilo/electrum#8720).
“” – no scheme claims it.
Expect “bip39_wordlist” beside most English Electrum seeds, and read nothing into it: Electrum’s english.txt is BIP39’s, byte for byte, so every word of an English Electrum seed is a BIP39 word and only the checksum says no. The overlap worth noticing is “bip39” beside an Electrum type, which is one sentence that two schemes both read as valid and derive two different wallets from.
The order is Electrum’s where Electrum has one, and btclib’s where it does not, and the two halves are worth telling apart.
Within Electrum it is calc_seed_type’s chain: “old” first, then the version prefixes “01”, “100”, “101” (twelve words, or twenty and up) and “102”. Upstream’s, and implemented by electrum.version_from_mnemonic rather than again here. Old first is the load-bearing part – a pre-2.0 seed carries no version prefix and can match one by chance, and calling it “standard” would hand back the wrong derivation without a word.
Between the schemes it is btclib’s, none of them having an order to copy: Electrum’s wizard asks the user which variant a sentence is, and validate_seed then dispatches on that answer instead of guessing. The reason for the order is the base rate, and the principle is that the rarer signal is the one carrying information about where a sentence came from. A valid BIP39 checksum is present by chance in one twelve-word sentence in sixteen. An Electrum version prefix is rarer, one sentence in 256 for “01” and one in 4096 for the three-nibble prefixes. A SLIP-0039 share is rarer again by orders of magnitude: 30 checksum bits over words that must every one of them be among SLIP-0039’s 1024, which is why it goes first. The choice is not hidden either: all_seed_types_from_mnemonic names every scheme that claims the sentence, so a caller preferring another order has what it needs to take it.
SLIP-0039 first is measured, not assumed, and it is the one place where the order changes an answer. Electrum’s version check is an HMAC over the sentence and consults no word-list at all, so it claims a share whenever the HMAC happens to start “01”: 80 of 20000 random 1-of-1 shares, one in 250, and 14 of the first 4000 were “electrum_standard” with one “electrum_segwit”. The reverse never happened – 0 of 2000 Electrum seeds and 0 of 2000 BIP39 mnemonics read as a share – because 1495 of BIP39’s 2048 English words are absent from SLIP-0039’s list, so a sentence has to be built from that list to pass its checksum at all. Last in the chain would therefore report one share in 250 as an Electrum seed and hand the caller the wrong scheme, which is the failure “old before standard” exists to prevent, one scheme further out.
Normalization is deliberately not done here. Each branch normalizes as its own scheme defines – Electrum’s NFKD, lower-casing, accent dropping and CJK rules inside electrum.py, BIP39’s bare whitespace split inside bip39.py – so an upper-cased sentence is an Electrum seed and not a BIP39 mnemonic. That is a difference between the two schemes as they stand, not a decision taken here; what btclib should normalize, once and for every scheme, is issue 201.
What “slip39” does not say is how many shares are wanted. A share names its group and member thresholds, so the count is there to be read, but reading one sentence cannot tell whether the others are to hand; that is master_secret_from_mnemonics’ answer, and it refuses a set that is short. A restore flow asking this function what it has been handed gets the scheme, and asks slip39 for the rest.
- btclib.mnemonic.dispatch.all_seed_types_from_mnemonic(mnemonic: str, lang: str = 'en') list[str][source]¶
Return every seed type that claims the mnemonic, best first.
The list is empty when nothing claims it, and holds more than one entry when the schemes overlap – which is the case worth seeing, since seed_type_from_mnemonic can only answer with the first of them. Three entries at most: each of the three schemes answers at most once, Electrum resolving its own five against each other before it reports.
The module docstring has the order and the measurements behind it.
- btclib.mnemonic.dispatch.seed_type_from_mnemonic(mnemonic: str, lang: str = 'en') str[source]¶
Return what the mnemonic is, “” if no scheme claims it.
The first of all_seed_types_from_mnemonic, which is the answer the precedence in the module docstring picks. Where a sentence is claimed by more than one scheme this is the one that wins and the others are not mentioned, so a caller that has to know about the collision – a restore flow, say, where the wrong choice is a wallet the user cannot see – wants the plural function instead.
btclib.mnemonic.electrum module¶
Electrum entropy / mnemonic / seed functions.
Electrum mnemonic is versioned, conveying BIP32 derivation rule too.
What is implemented here is Electrum’s scheme, not an approximation of it: the same entropy yields the mnemonic Electrum yields, and a mnemonic Electrum accepts is accepted here. Four consequences, none of them BIP39’s behaviour, so none of them can be guessed from bip39.py:
the words run least-significant first, the reverse of BIP39’s order
the entropy is an integer and the search starts at entropy + 1, so the value supplied is a starting point and never itself the answer
a candidate that is a pre-2.0 Electrum seed, or that is also a valid BIP39 mnemonic, is skipped rather than returned
a mnemonic is normalized before it is hashed or stretched – NFKD, lower-case, accents dropped, whitespace collapsed – so an upper-cased or accented sentence is read, not rejected
Electrum reads five word-lists – en, es, ja, pt, zh – and its own registry here holds all twelve of BIP39’s beside them, so a language Electrum does not read is still a language this module writes: an “electrum” mnemonic in Italian is btclib’s extension, and Electrum cannot read it.
Four of the five are BIP39’s file after NFKD normalization, byte for byte, so nothing above depends on which of the two schemes loaded them. Portuguese is the exception and the reason this module has a registry of its own rather than sharing WORDLISTS: Electrum’s Portuguese is Monero’s word-list, 1626 words rather than 2048, and “pt” therefore names one word-list in bip39.py and another here. Two consequences, both of them visible in the code below: 1626 is not a power of two, so an index into it is not eleven bits and the entropy is a base conversion and nothing more; and a Portuguese sentence carrying Electrum’s default entropy is thirteen words, which is why a “2fa” mnemonic cannot be generated in Portuguese at all – that version wants twelve words or twenty, and Electrum raises there too.
The pre-2.0 scheme is here too, and it is a different thing wearing the same words. A wallet created before Electrum 2.0 has a twelve- or twenty-four-word mnemonic over a word-list of its own, 1626 words long; it decodes to a hex master seed rather than to entropy, three words to each 32-bit group; the master private key is that seed stretched by a hundred thousand rounds of SHA-256 rather than by PBKDF2; and it has no passphrase, so one supplied is refused rather than defaulted away. There is no specification to follow – the scheme predates the BIPs and never had one – so Electrum’s implementation is what correct means, and every vector for it comes from Electrum’s own tests.
- btclib.mnemonic.electrum.entropy_from_mnemonic(mnemonic: str, lang: str | None = None) str[source]¶
Return the entropy from the Electrum versioned mnemonic sentence.
This is the entropy the mnemonic encodes, which is one more than the smallest entropy mnemonic_from_entropy would produce it from.
The language is read off the words if it is not provided.
- btclib.mnemonic.electrum.hex_seed_from_old_mnemonic(mnemonic: str) str[source]¶
Return the hex master seed of a pre-2.0 Electrum mnemonic.
Electrum’s Old_KeyStore.format_seed wrapped around old_mnemonic.mn_decode: the sentence is normalized, a seed already written as hex passes through – pre-2.0 Electrum stored the seed that way, so that is what a user may be holding – and anything else is decoded three words at a time.
The hex test here is the loose one, bytes.fromhex alone, because that is the one format_seed uses; the strict test of _is_hex_str is the one Electrum applies a step later, in stretch_key, and the gap between them is upstream’s rather than something to close here.
Whether the answer is a seed at all is a question this cannot answer, and Electrum cannot either: three words can carry a group above 2**32, so twelve words can decode to 33 or 34 hex characters instead of 32, and neither decoder notices. Deriving is where that fails, if it fails at all – 34 characters are still octets, and one of Electrum’s own published seeds is exactly that.
- btclib.mnemonic.electrum.lang_from_mnemonic(mnemonic: str) str[source]¶
Return the language of the Electrum mnemonic sentence.
The word-lists that hold every word of the sentence are the candidates, and more than one is refused rather than resolved. BIP39’s tie-break is not available here: an Electrum mnemonic has no checksum over its entropy – the version prefix is a hash of the sentence and says nothing about which word-list spelled it – so every candidate decodes to an entropy that is as valid as the others, and only the caller knows which one was meant. It is Chinese that reaches this, Simplified and Traditional sharing 1275 of their 2048 words.
- btclib.mnemonic.electrum.mnemonic_from_entropy(mnemonic_type: str = 'standard', entropy: str | int | bytes | None = None, lang: str = 'en') str[source]¶
Convert input entropy to Electrum versioned mnemonic sentence.
Input entropy can be expressed as binary 0/1 string, bytes-like, or integer.
In the case of binary 0/1 string and bytes-like, leading zeros are considered redundant padding.
The entropy is where the search for a mnemonic of the requested version starts, not what the mnemonic encodes: the first candidate tried is entropy + 1, and candidates that are pre-2.0 Electrum seeds or valid BIP39 mnemonics are passed over. The search is Electrum’s, so the mnemonic is the one Electrum returns for that entropy.
- btclib.mnemonic.electrum.mxprv_from_mnemonic(mnemonic: str, passphrase: str | None = None, network: str = 'mainnet') str[source]¶
Return BIP32 master extended private key from Electrum mnemonic.
The derivation path is “m” for a “standard” mnemonic and “m/0h” for a “segwit” one.
- btclib.mnemonic.electrum.old_master_prv_key_from_mnemonic(mnemonic: str, passphrase: str | None = None) int[source]¶
Return the pre-2.0 Electrum master private key, as an integer.
Electrum’s Old_KeyStore.stretch_key: a hundred thousand rounds of sha256 over the digest so far followed by the seed, where the seed is the hex characters and not the octets they spell, and the last digest read as a big-endian integer. Iterated sha256, not PBKDF2, and not the versioned scheme’s 2048 iterations of it: that is the fact only a vector pins, and the vectors here are Electrum’s own.
The passphrase is refused rather than defaulted. Nothing but the seed enters the stretch, so there is nowhere for one to go, and accepting it silently would hand back the wallet of a seed the caller did not ask for. Electrum refuses it the same way in keystore.from_seed – “‘old’-type electrum seed cannot have passphrase” – and its can_seed_have_passphrase answers False for this scheme alone; None and the empty string are “no passphrase” there and here.
- btclib.mnemonic.electrum.old_master_pub_key_from_mnemonic(mnemonic: str, passphrase: str | None = None) str[source]¶
Return the pre-2.0 Electrum master public key, as Electrum writes it.
Electrum’s Old_KeyStore.mpk_from_seed: the uncompressed SEC point of the stretched key with its 04 prefix cut off, so 128 hex characters of x and then y. That string is what a pre-2.0 wallet file holds under “master_public_key”, which is what makes it the value a vector can be taken from.
- btclib.mnemonic.electrum.old_mnemonic_from_hex_seed(hex_seed: str) str[source]¶
Return the pre-2.0 Electrum mnemonic of a hex master seed.
Electrum’s old_mnemonic.mn_encode. Each group of eight hex characters – one 32-bit word – becomes three words of the 1626-word list, and the second and third are offsets from the one before rather than digits of their own. Electrum’s file names the reason: US patent 5892470 claims a scheme in which a word stands for a fixed digit, and in this one the digit a word carries depends on the word before it.
Three words to a group is also why 1626 need not be a power of two, and so why the list is not a WORDLISTS language.
- btclib.mnemonic.electrum.version_from_mnemonic(mnemonic: str) tuple[str, str][source]¶
Return the (Electrum version, normalized mnemonic) tuple.
The version is one of the four in _MNEMONIC_VERSIONS, or “old” for the pre-2.0 scheme, which is recognized so that it is never mistaken for one of the four; deriving keys from it is not supported.
The normalized mnemonic is the one electrum hashes and stretches: NFKD, lower-case, accents dropped, whitespace collapsed.
btclib.mnemonic.entropy module¶
Entropy conversion functions.
Depending on the function, input entropy can be expressed as raw (i.e. binary 0/1 string), bytes, or integer and their equivalent representations.
Leading zeros in raw or bytes entropy are never considered redundant padding.
Output entropy is always raw.
- btclib.mnemonic.entropy.bin_str_entropy_from_bytes(bytes_entropy: bytes | str | bytearray | memoryview, bits: int | Iterable[int] = (128, 160, 192, 224, 256, 512)) str[source]¶
Return raw entropy from the input Octets entropy.
Input entropy can be expressed as hex-string or bytes; it is never padded to satisfy the bit-size requirement.
If more bits than required are provided, the leftmost ones are retained.
Default bit-sizes are 128, 160, 192, 224, 256, or 512 bits.
- btclib.mnemonic.entropy.bin_str_entropy_from_entropy(entr: str | int | bytes, bits: int | Iterable[int] = (128, 160, 192, 224, 256, 512)) str[source]¶
Return raw entropy from the input entropy.
Input entropy can be expressed as:
raw (i.e. binary 0/1 string) entropy
bytes (no hex-string, as they would conflict with raw entropy representation)
integer (int, no string starting with “0b”/”0x”)
In the case of raw entropy and bytes, entropy is never padded to satisfy the bit-size requirement; instead, integer entropy is front-padded with zeros digits as much as necessary to satisfy the bit-size requirement.
In all cases if more bits than required are provided, the leftmost ones are retained.
Default bit-sizes are 128, 160, 192, 224, 256, or 512 bits.
- btclib.mnemonic.entropy.bin_str_entropy_from_int(int_entropy: int | str, bits: int | Iterable[int] = (128, 160, 192, 224, 256, 512)) str[source]¶
Return raw entropy from the input integer entropy.
Input entropy can be expressed as int or string starting with “0x”/”0b”; it is front-padded with zeros digits as much as necessary to satisfy the bit-size requirement.
If more bits than required are provided, the leftmost ones are retained.
Default bit-sizes are 128, 160, 192, 224, 256, or 512 bits.
- btclib.mnemonic.entropy.bin_str_entropy_from_random(bits: int, entropy: str | None = None, to_be_hashed: bool = True) str[source]¶
Return CSPRNG raw entropy XOR-ed with input raw entropy.
The input raw entropy is used as initialization value; if not provided, then entropy is generated with the system cryptographically strong pseudo-random number generator (CSPRNG).
Then, this entropy is:
XOR-ed with CSPRNG system entropy
possibly hashed (if requested)
- btclib.mnemonic.entropy.bin_str_entropy_from_rolls(bits: int, dice_sides: int, rolls: list[int], shuffle: bool = True) str[source]¶
Return raw entropy from the input dice rolls.
Dice rolls are represented by integers in the [1-dice_sides] range; there must be enough rolls to satisfy the bit-size requirement.
Only rolls having value in the [1-base] range are used, with base being the highest power of 2 that is lower than the dice_sides (e.g. for a traditional D6 dice, only rolls having value in [1-4] are used; for a D20 dice, only rolls having value in [1-16] are used; etc.). Rolls can also be shuffled.
If more bits than required are provided, the leftmost ones are retained.
This reads dice into entropy; bip85.rolls_from_root_key writes rolls out of entropy, which is the opposite direction and not an inverse. It numbers a die’s faces from zero and draws again for a trial the die has no face for, where this reads them from one and keeps only the rolls below the largest power of two – so rolls carried from there to a wallet that reads dice are shifted by one, by whoever carries them.
- btclib.mnemonic.entropy.bin_str_entropy_from_str(str_entropy: str, bits: int | Iterable[int] = (128, 160, 192, 224, 256, 512)) str[source]¶
Return raw entropy from the input raw entropy.
Input entropy must be expressed as raw entropy; it is never padded to satisfy the bit-size requirement.
If more bits than required are provided, the leftmost ones are retained.
Default bit-sizes are 128, 160, 192, 224, 256, or 512 bits.
- btclib.mnemonic.entropy.bin_str_entropy_from_wordlist_indexes(indexes: Sequence[int], base: int) str[source]¶
Return the raw entropy from a list of word-list indexes.
Return the raw (i.e. binary 0/1 string) entropy from the provided list of integer indexes into a given language word-list.
An index the word list has no word for is refused rather than carried: base-base arithmetic accepts any number as a digit, so 2048 in a 2048-word list is not an error but a carry into the digit above it – entropy nothing spells, out of a function whose whole job is to say what a mnemonic means.
- btclib.mnemonic.entropy.bytes_entropy_from_str(bin_str_entropy: str) bytes[source]¶
Return the binary-string entropy as bytes, left-padded to whole ones.
- btclib.mnemonic.entropy.collect_rolls(bits: int) tuple[int, list[int]][source]¶
Prompt for dice rolls until they carry bits of entropy.
Interactive on purpose, input() and print() being its interface: the caller gets (dice sides, the rolls that count). Rolls beyond a power of two are discarded and asked again, carrying no whole bits.
The automated mode rolls with secrets, and must keep doing so. bip85.rolls_from_root_key derives rolls as well, and derives them reproducibly from a root key – which is what the entropy of a seed that does not exist yet must never be.
- btclib.mnemonic.entropy.wordlist_indexes_from_bin_str_entropy(entropy: str, base: int) list[int][source]¶
Return the digit indexes for the provided raw entropy.
Return the list of integer indexes into a digit set, usually a language word-list, for the provided raw (i.e. binary 0/1 string) entropy; leading zeros are not considered redundant padding.
btclib.mnemonic.mnemonic module¶
Mnemonic sentence conversion from/to sequence of integer indexes.
- class btclib.mnemonic.mnemonic.WordLists(language_files: dict[str, str] | None = None, power_of_two: bool = True)[source]¶
Bases:
objectClass for word-lists to be used in entropy/mnemonic conversions.
The word-lists loaded by default are DEFAULT_LANGUAGE_FILES: the twelve of BIP39’s reference implementation, plus slip39’s. More can be added, or an existing language pointed at another file, with the load_lang method; a caller wanting an altogether different set – as electrum.py does, electrum’s Portuguese not being BIP39’s – passes language_files to the constructor.
The thirteen keys above are what alias.MnemonicLang names, and load_lang is why no lang parameter here or in bip39 and electrum is typed with it: the set is open, so a Literal would reject the language a caller has just loaded (issue #216).
Word-lists are loaded only if needed and read only once from disk. Each word is NFKD-normalized as it is read, which is the form BIP39 requires and the form electrum normalizes to, so a word looked up in either form is found; a ‘#’ starts a comment, which is what carries the licence header of electrum’s Portuguese list.
power_of_two says whether the word count must be one. A BIP39 index is eleven bits, so for BIP39 it must; electrum converts to base len(wordlist) instead, and its Portuguese list has 1626 words, so for electrum it must not.
The loading is under a lock, and the reason is not merely that two threads might read the same file twice. load_lang recorded the word count before the words – “self._language_length[lang] = nwords” and then “self._wordlist[lang] = …” – and it treats a non-zero count as “already loaded”. A second thread arriving between those two statements therefore skipped the load and got an empty word-list back, so mnemonic_from_indexes raised IndexError and indexes_from_mnemonic reported every word as unknown. The lock closes that, and the two assignments are also ordered the other way round now, so that the published count is never ahead of the words it counts.
The module-level WORDLISTS is a singleton, and load_lang mutating it affects every caller in the process: adding a language, or pointing an existing one at another file, is a process-wide decision. Callers wanting a private set can build their own WordLists().
- langs_of_words(words: Sequence[str]) list[str][source]¶
Return the languages whose word-list holds every word.
Every language is read from disk, the question being about all of them; a caller that knows the language names it instead of asking.
- btclib.mnemonic.mnemonic.data_file(filename: str) str[source]¶
Return the path of a word-list shipped with btclib.
- btclib.mnemonic.mnemonic.indexes_from_mnemonic(mnemonic: str, lang: str, wordlists: WordLists = <btclib.mnemonic.mnemonic.WordLists object>) list[int][source]¶
Return the word-list integer indexes for a given mnemonic.
Return the list of integer indexes into a language word-list for a given mnemonic. The sentence is split on any whitespace, the ideographic space of a japanese mnemonic included.
- btclib.mnemonic.mnemonic.mnemonic_from_indexes(indexes: ~collections.abc.Sequence[int], lang: str, wordlists: ~btclib.mnemonic.mnemonic.WordLists = <btclib.mnemonic.mnemonic.WordLists object>, separator: str = ' ') str[source]¶
Return the mnemonic from a list of word-list integer indexes.
Return the mnemonic from a list of integer indexes into a given language word-list.
- btclib.mnemonic.mnemonic.normalize_mnemonic(mnemonic: str) str[source]¶
Return the mnemonic as btclib reads it, whatever separates its words.
NFKD first, then every run of unicode whitespace becomes one space, so that “ abandon abandontabandon “ and “abandon abandon abandon” are the same sentence and reach the same seed. That is btclib’s answer for every mnemonic scheme: BIP39 mandates the NFKD and describes words separated by spaces, but says nothing about a doubled space, a tab, or the newline a mnemonic wrapped across two lines of a paper backup carries.
The NFKD comes first, and not merely because BIP39 asks for it. U+3000, the ideographic space BIP39’s own Japanese vectors separate words with, decomposes to U+0020, so the separator question is answered by the normalization the spec already requires rather than by a rule of btclib’s; and normalizing first leaves no run of whitespace for the collapse to miss, which the other order does – U+00A8 decomposes to a space plus a combining diaeresis, so collapsing before normalizing hands PBKDF2 two adjacent spaces.
Refusing anything but a single space is the other defensible position, and it is what the reference implementation reads: trezor’s python-mnemonic splits on “ “, so a doubled space is an empty word and an error. It is not taken here because that implementation is strict only where it checks a mnemonic – its to_seed applies NFKD and nothing else, so a leading space or a trailing newline stretches into a different seed with no complaint, and an unreadable wallet is the one outcome worse than a refusal. Collapsing cannot do that, and it refuses nothing a wallet, a mail client or an editor can plausibly produce.
Zero-width characters stay: U+200B and U+FEFF carry no White_Space property and survive NFKD, so a word holding one is an unknown word rather than two words – which is a refusal, the safe answer, and not a silently different seed.
This is not electrum’s normalization, and cannot be: electrum.py lower-cases, drops the combining characters and joins words that NFKD left either side of a space between two CJK characters. Dropping combining characters undoes the very decomposition BIP39 requires, so the two schemes need two functions, and electrum keeps its own.
btclib.mnemonic.slip39 module¶
SLIP-0039 share / master secret / seed functions.
https://github.com/satoshilabs/slips/blob/master/slip-0039.md.
Shamir backup, the scheme every Trezor since 2019 offers. A master
secret is split into shares, of which a threshold number is required
to put it back together; fewer than that leak nothing about it. The
master secret is the BIP32 seed itself, not entropy to be stretched
into one, so mxprv_from_mnemonics hands it straight to
rootxprv_from_seed.
The split is two-level. The master secret is encrypted under the
passphrase, the result split GT-of-G into group shares, and
each group share split T-of-N into the member shares people
actually hold. A plain T-of-N backup is the degenerate case:
one group, split at the member level.
Four pieces underneath, none of them shared with BIP39:
Shamir’s scheme over GF(256), applied byte by byte, with the secret at
f(255)and a digest of it atf(254)so that a wrong set of shares is caught rather than silently reconstructing rubbishan RS1024 checksum over the 10-bit word indexes, three words long
a two-level group/member threshold structure, four bits per index and per threshold, which is what caps both at 16
a four-round Feistel network with PBKDF2-HMAC-SHA256 as the round function, encrypting the master secret under the passphrase
The word-list is SLIP-0039’s own 1024 words, ten bits each; it is not BIP39’s, and no localization exists.
Security |
Padded share value length |
Total share length |
|---|---|---|
128 bits |
130 bits |
200 bits = 20 words |
256 bits |
260 bits |
330 bits = 33 words |
Both recovery and generation are here. Recovery alone is what a wallet needs and what several implementations stop at; a library is asked the other question too – and generation is also what lets the round trip be tested against something other than itself.
Bases:
objectA single SLIP-0039 share, i.e. one decoded mnemonic.
The thresholds and the group count are the true values, 1 to 16, not the zero-based numbers the mnemonic encodes; the indexes are zero-based, as encoded, because they are x coordinates and nothing else.
Raise if a field is outside what the share format can hold.
- btclib.mnemonic.slip39.master_secret_from_mnemonics(mnemonics: Sequence[str], passphrase: str = '') bytes[source]¶
Return the master secret the SLIP-0039 mnemonics reconstruct.
The mnemonics must be exactly a threshold number of groups, each holding exactly its own member threshold of shares. A wrong passphrase is not an error: SLIP-0039 has no way to tell one from a right one, which is what lets a decoy wallet exist.
Return the SLIP-0039 mnemonic encoding the share.
- btclib.mnemonic.slip39.mnemonics_from_master_secret(master_secret: bytes | str | bytearray | memoryview, groups: ~collections.abc.Sequence[tuple[int, int]] = ((1, 1),), group_threshold: int = 1, passphrase: str = '', iteration_exponent: int = 1, extendable: bool = True, entropy_source: ~collections.abc.Callable[[int], bytes] = <built-in function urandom>) list[list[str]][source]¶
Return the SLIP-0039 shares of a master secret, grouped.
groups is one (member threshold, member count) pair per group, and group_threshold is how many groups are needed; the default is the single 1-of-1 share a wallet starts with. The master secret is the BIP32 seed to back up, at least 128 bits and a multiple of 16.
entropy_source is where every random byte comes from – the identifier, the free coefficients of each polynomial and the digest padding – and it is a parameter so that a caller can hand in a deterministic source and get a reproducible answer: nothing else in this scheme is testable against a fixed expectation, the shares of a 2-of-3 backup being random by construction. The default is os.urandom, the CSPRNG SLIP-0039 requires (secrets.token_bytes is the same bytes through another name); anything weaker substituted here is a secret an attacker can reproduce.
- btclib.mnemonic.slip39.mxprv_from_mnemonics(mnemonics: Sequence[str], passphrase: str | None = None, network: str = 'mainnet') str[source]¶
Return BIP32 root master extended private key from SLIP-0039 shares.
The master secret is the BIP32 seed, so there is no stretching step between the two: SLIP-0039 backs up the seed itself.
Return the Share the SLIP-0039 mnemonic encodes.
The checksum is verified; the share value is not, a single share carrying nothing that could verify it.
Module contents¶
Module btclib.mnemonic.
bip39, electrum and slip39 are the three mnemonic schemes this package is for, and none is exported by importing the package alone: import btclib.mnemonic followed by btclib.mnemonic.bip39.mnemonic_from_entropy(…) raises AttributeError until something else in the process happens to import the submodule, which is why all three are named here. dispatch is exported beside them: it is the entry point that answers which scheme a sentence belongs to, and it is of no use to anyone who has to import it by name after already knowing.
entropy and mnemonic are named too, being the two modules the three schemes are built on rather than schemes themselves: the first turns dice rolls, bytes, an int or a bit string into the entropy a sentence encodes, the second is the word list and the index codec over it. Their functions are also re-exported flat below, so naming the modules adds one thing – what is not flat, WordLists and data_file among it, is reached as btclib.mnemonic.mnemonic, a module spelled like the package that holds it and easy to assume is the package.
- btclib.mnemonic.bin_str_entropy_from_bytes(bytes_entropy: bytes | str | bytearray | memoryview, bits: int | Iterable[int] = (128, 160, 192, 224, 256, 512)) str[source]¶
Return raw entropy from the input Octets entropy.
Input entropy can be expressed as hex-string or bytes; it is never padded to satisfy the bit-size requirement.
If more bits than required are provided, the leftmost ones are retained.
Default bit-sizes are 128, 160, 192, 224, 256, or 512 bits.
- btclib.mnemonic.bin_str_entropy_from_entropy(entr: str | int | bytes, bits: int | Iterable[int] = (128, 160, 192, 224, 256, 512)) str[source]¶
Return raw entropy from the input entropy.
Input entropy can be expressed as:
raw (i.e. binary 0/1 string) entropy
bytes (no hex-string, as they would conflict with raw entropy representation)
integer (int, no string starting with “0b”/”0x”)
In the case of raw entropy and bytes, entropy is never padded to satisfy the bit-size requirement; instead, integer entropy is front-padded with zeros digits as much as necessary to satisfy the bit-size requirement.
In all cases if more bits than required are provided, the leftmost ones are retained.
Default bit-sizes are 128, 160, 192, 224, 256, or 512 bits.
- btclib.mnemonic.bin_str_entropy_from_int(int_entropy: int | str, bits: int | Iterable[int] = (128, 160, 192, 224, 256, 512)) str[source]¶
Return raw entropy from the input integer entropy.
Input entropy can be expressed as int or string starting with “0x”/”0b”; it is front-padded with zeros digits as much as necessary to satisfy the bit-size requirement.
If more bits than required are provided, the leftmost ones are retained.
Default bit-sizes are 128, 160, 192, 224, 256, or 512 bits.
- btclib.mnemonic.bin_str_entropy_from_random(bits: int, entropy: str | None = None, to_be_hashed: bool = True) str[source]¶
Return CSPRNG raw entropy XOR-ed with input raw entropy.
The input raw entropy is used as initialization value; if not provided, then entropy is generated with the system cryptographically strong pseudo-random number generator (CSPRNG).
Then, this entropy is:
XOR-ed with CSPRNG system entropy
possibly hashed (if requested)
- btclib.mnemonic.bin_str_entropy_from_rolls(bits: int, dice_sides: int, rolls: list[int], shuffle: bool = True) str[source]¶
Return raw entropy from the input dice rolls.
Dice rolls are represented by integers in the [1-dice_sides] range; there must be enough rolls to satisfy the bit-size requirement.
Only rolls having value in the [1-base] range are used, with base being the highest power of 2 that is lower than the dice_sides (e.g. for a traditional D6 dice, only rolls having value in [1-4] are used; for a D20 dice, only rolls having value in [1-16] are used; etc.). Rolls can also be shuffled.
If more bits than required are provided, the leftmost ones are retained.
This reads dice into entropy; bip85.rolls_from_root_key writes rolls out of entropy, which is the opposite direction and not an inverse. It numbers a die’s faces from zero and draws again for a trial the die has no face for, where this reads them from one and keeps only the rolls below the largest power of two – so rolls carried from there to a wallet that reads dice are shifted by one, by whoever carries them.
- btclib.mnemonic.bin_str_entropy_from_str(str_entropy: str, bits: int | Iterable[int] = (128, 160, 192, 224, 256, 512)) str[source]¶
Return raw entropy from the input raw entropy.
Input entropy must be expressed as raw entropy; it is never padded to satisfy the bit-size requirement.
If more bits than required are provided, the leftmost ones are retained.
Default bit-sizes are 128, 160, 192, 224, 256, or 512 bits.
- btclib.mnemonic.bin_str_entropy_from_wordlist_indexes(indexes: Sequence[int], base: int) str[source]¶
Return the raw entropy from a list of word-list indexes.
Return the raw (i.e. binary 0/1 string) entropy from the provided list of integer indexes into a given language word-list.
An index the word list has no word for is refused rather than carried: base-base arithmetic accepts any number as a digit, so 2048 in a 2048-word list is not an error but a carry into the digit above it – entropy nothing spells, out of a function whose whole job is to say what a mnemonic means.
- btclib.mnemonic.bytes_entropy_from_str(bin_str_entropy: str) bytes[source]¶
Return the binary-string entropy as bytes, left-padded to whole ones.
- btclib.mnemonic.collect_rolls(bits: int) tuple[int, list[int]][source]¶
Prompt for dice rolls until they carry bits of entropy.
Interactive on purpose, input() and print() being its interface: the caller gets (dice sides, the rolls that count). Rolls beyond a power of two are discarded and asked again, carrying no whole bits.
The automated mode rolls with secrets, and must keep doing so. bip85.rolls_from_root_key derives rolls as well, and derives them reproducibly from a root key – which is what the entropy of a seed that does not exist yet must never be.
- btclib.mnemonic.indexes_from_mnemonic(mnemonic: str, lang: str, wordlists: WordLists = <btclib.mnemonic.mnemonic.WordLists object>) list[int][source]¶
Return the word-list integer indexes for a given mnemonic.
Return the list of integer indexes into a language word-list for a given mnemonic. The sentence is split on any whitespace, the ideographic space of a japanese mnemonic included.
- btclib.mnemonic.mnemonic_from_indexes(indexes: ~collections.abc.Sequence[int], lang: str, wordlists: ~btclib.mnemonic.mnemonic.WordLists = <btclib.mnemonic.mnemonic.WordLists object>, separator: str = ' ') str[source]¶
Return the mnemonic from a list of word-list integer indexes.
Return the mnemonic from a list of integer indexes into a given language word-list.
- btclib.mnemonic.normalize_mnemonic(mnemonic: str) str[source]¶
Return the mnemonic as btclib reads it, whatever separates its words.
NFKD first, then every run of unicode whitespace becomes one space, so that “ abandon abandontabandon “ and “abandon abandon abandon” are the same sentence and reach the same seed. That is btclib’s answer for every mnemonic scheme: BIP39 mandates the NFKD and describes words separated by spaces, but says nothing about a doubled space, a tab, or the newline a mnemonic wrapped across two lines of a paper backup carries.
The NFKD comes first, and not merely because BIP39 asks for it. U+3000, the ideographic space BIP39’s own Japanese vectors separate words with, decomposes to U+0020, so the separator question is answered by the normalization the spec already requires rather than by a rule of btclib’s; and normalizing first leaves no run of whitespace for the collapse to miss, which the other order does – U+00A8 decomposes to a space plus a combining diaeresis, so collapsing before normalizing hands PBKDF2 two adjacent spaces.
Refusing anything but a single space is the other defensible position, and it is what the reference implementation reads: trezor’s python-mnemonic splits on “ “, so a doubled space is an empty word and an error. It is not taken here because that implementation is strict only where it checks a mnemonic – its to_seed applies NFKD and nothing else, so a leading space or a trailing newline stretches into a different seed with no complaint, and an unreadable wallet is the one outcome worse than a refusal. Collapsing cannot do that, and it refuses nothing a wallet, a mail client or an editor can plausibly produce.
Zero-width characters stay: U+200B and U+FEFF carry no White_Space property and survive NFKD, so a word holding one is an unknown word rather than two words – which is a refusal, the safe answer, and not a silently different seed.
This is not electrum’s normalization, and cannot be: electrum.py lower-cases, drops the combining characters and joins words that NFKD left either side of a space between two CJK characters. Dropping combining characters undoes the very decomposition BIP39 requires, so the two schemes need two functions, and electrum keeps its own.
- btclib.mnemonic.wordlist_indexes_from_bin_str_entropy(entropy: str, base: int) list[int][source]¶
Return the digit indexes for the provided raw entropy.
Return the list of integer indexes into a digit set, usually a language word-list, for the provided raw (i.e. binary 0/1 string) entropy; leading zeros are not considered redundant padding.