New Upstream Release - python-mnemonic
Ready changes
Summary
Merged new upstream version: 0.20 (was: 0.19).
Resulting package
Built on 2023-07-13T14:53 (took 5m37s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases python3-mnemonic
Lintian Result
Diff
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..34ed344
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,2 @@
+Marek Palatinus <marek@satoshilabs.com>
+Pavol Rusnak <stick@satoshilabs.com>
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
new file mode 100644
index 0000000..8bca025
--- /dev/null
+++ b/CHANGELOG.rst
@@ -0,0 +1,45 @@
+Changelog
+=========
+
+.. default-role:: code
+
+All notable changes to this project will be documented in this file.
+
+The format is based on `Keep a Changelog`_, and this project adheres to
+`Semantic Versioning`_.
+
+`0.20`_ - 2021-07-27
+---------------------
+
+.. _0.20: https://github.com/trezor/python-mnemonic/compare/v0.19...v0.20
+
+Added
+~~~~~
+
+- Type annotations
+- Support for testnet private keys
+
+Changed
+~~~~~~~
+
+- Project directory structure was cleaned up
+- Language on the `Mnemonic` object is remembered instead of repeatedly detecting
+
+Removed
+~~~~~~~
+
+- Support for Python 2.7 and 3.4 was dropped
+
+
+
+0.19 - 2019-10-01
+------------------
+
+Added
+~~~~~
+
+- Start of changelog
+
+
+.. _Keep a Changelog: https://keepachangelog.com/en/1.0.0/
+.. _Semantic Versioning: https://semver.org/spec/v2.0.0.html
diff --git a/MANIFEST.in b/MANIFEST.in
index 1aba38f..cc983b9 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1 +1,6 @@
-include LICENSE
+include tools/*
+graft src
+graft tests
+
+include AUTHORS LICENSE README.rst CHANGELOG.rst
+include tox.ini
diff --git a/PKG-INFO b/PKG-INFO
index a8c30ba..bae1ad0 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
-Metadata-Version: 1.1
+Metadata-Version: 1.2
Name: mnemonic
-Version: 0.19
+Version: 0.20
Summary: Implementation of Bitcoin BIP-0039
Home-page: https://github.com/trezor/python-mnemonic
Author: Trezor
@@ -9,8 +9,8 @@ License: UNKNOWN
Description: python-mnemonic
===============
- .. image:: https://travis-ci.org/trezor/python-mnemonic.svg?branch=master
- :target: https://travis-ci.org/trezor/python-mnemonic
+ .. image:: https://badge.fury.io/py/mnemonic.svg
+ :target: https://badge.fury.io/py/mnemonic
Reference implementation of BIP-0039: Mnemonic code for generating
deterministic keys
@@ -31,10 +31,106 @@ Description: python-mnemonic
See https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
for full specification
+ Installation
+ ------------
+
+ To install this library and its dependencies use:
+
+ ``pip install mnemonic``
+
+ Usage examples
+ --------------
+
+ Import library into python project via:
+
+ .. code-block:: python
+
+ from mnemonic import Mnemonic
+
+ Initialize class instance, picking from available dictionaries:
+
+ - english
+ - chinese_simplified
+ - chinese_traditional
+ - french
+ - italian
+ - japanese
+ - korean
+ - spanish
+
+ .. code-block:: python
+
+ mnemo = Mnemonic(language)
+ mnemo = Mnemonic("english")
+
+ Generate word list given the strength (128 - 256):
+
+ .. code-block:: python
+
+ words = mnemo.generate(strength=256)
+
+ Given the word list and custom passphrase (empty in example), generate seed:
+
+ .. code-block:: python
+
+ seed = mnemo.to_seed(words, passphrase="")
+
+ Given the word list, calculate original entropy:
+
+ .. code-block:: python
+
+ entropy = mnemo.to_entropy(words)
+
+ Changelog
+ =========
+
+ .. default-role:: code
+
+ All notable changes to this project will be documented in this file.
+
+ The format is based on `Keep a Changelog`_, and this project adheres to
+ `Semantic Versioning`_.
+
+ `0.20`_ - 2021-07-27
+ ---------------------
+
+ .. _0.20: https://github.com/trezor/python-mnemonic/compare/v0.19...v0.20
+
+ Added
+ ~~~~~
+
+ - Type annotations
+ - Support for testnet private keys
+
+ Changed
+ ~~~~~~~
+
+ - Project directory structure was cleaned up
+ - Language on the `Mnemonic` object is remembered instead of repeatedly detecting
+
+ Removed
+ ~~~~~~~
+
+ - Support for Python 2.7 and 3.4 was dropped
+
+
+
+ 0.19 - 2019-10-01
+ ------------------
+
+ Added
+ ~~~~~
+
+ - Start of changelog
+
+
+ .. _Keep a Changelog: https://keepachangelog.com/en/1.0.0/
+ .. _Semantic Versioning: https://semver.org/spec/v2.0.0.html
+
Platform: UNKNOWN
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: MacOS :: MacOS X
-Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 3
+Requires-Python: >=3.5
diff --git a/README.rst b/README.rst
index 975343e..ec3c074 100644
--- a/README.rst
+++ b/README.rst
@@ -1,8 +1,8 @@
python-mnemonic
===============
-.. image:: https://travis-ci.org/trezor/python-mnemonic.svg?branch=master
- :target: https://travis-ci.org/trezor/python-mnemonic
+.. image:: https://badge.fury.io/py/mnemonic.svg
+ :target: https://badge.fury.io/py/mnemonic
Reference implementation of BIP-0039: Mnemonic code for generating
deterministic keys
@@ -22,3 +22,53 @@ BIP Paper
See https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
for full specification
+
+Installation
+------------
+
+To install this library and its dependencies use:
+
+ ``pip install mnemonic``
+
+Usage examples
+--------------
+
+Import library into python project via:
+
+.. code-block:: python
+
+ from mnemonic import Mnemonic
+
+Initialize class instance, picking from available dictionaries:
+
+- english
+- chinese_simplified
+- chinese_traditional
+- french
+- italian
+- japanese
+- korean
+- spanish
+
+.. code-block:: python
+
+ mnemo = Mnemonic(language)
+ mnemo = Mnemonic("english")
+
+Generate word list given the strength (128 - 256):
+
+.. code-block:: python
+
+ words = mnemo.generate(strength=256)
+
+Given the word list and custom passphrase (empty in example), generate seed:
+
+.. code-block:: python
+
+ seed = mnemo.to_seed(words, passphrase="")
+
+Given the word list, calculate original entropy:
+
+.. code-block:: python
+
+ entropy = mnemo.to_entropy(words)
diff --git a/debian/changelog b/debian/changelog
index 902968f..da740e0 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+python-mnemonic (0.20-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk> Thu, 13 Jul 2023 14:48:14 -0000
+
python-mnemonic (0.19-3) unstable; urgency=medium
* Team upload.
diff --git a/mnemonic.egg-info/PKG-INFO b/mnemonic.egg-info/PKG-INFO
deleted file mode 100644
index a8c30ba..0000000
--- a/mnemonic.egg-info/PKG-INFO
+++ /dev/null
@@ -1,40 +0,0 @@
-Metadata-Version: 1.1
-Name: mnemonic
-Version: 0.19
-Summary: Implementation of Bitcoin BIP-0039
-Home-page: https://github.com/trezor/python-mnemonic
-Author: Trezor
-Author-email: info@trezor.io
-License: UNKNOWN
-Description: python-mnemonic
- ===============
-
- .. image:: https://travis-ci.org/trezor/python-mnemonic.svg?branch=master
- :target: https://travis-ci.org/trezor/python-mnemonic
-
- Reference implementation of BIP-0039: Mnemonic code for generating
- deterministic keys
-
- Abstract
- --------
-
- This BIP describes the implementation of a mnemonic code or mnemonic sentence --
- a group of easy to remember words -- for the generation of deterministic wallets.
-
- It consists of two parts: generating the mnenomic, and converting it into a
- binary seed. This seed can be later used to generate deterministic wallets using
- BIP-0032 or similar methods.
-
- BIP Paper
- ---------
-
- See https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
- for full specification
-
-Platform: UNKNOWN
-Classifier: License :: OSI Approved :: MIT License
-Classifier: Operating System :: POSIX :: Linux
-Classifier: Operating System :: Microsoft :: Windows
-Classifier: Operating System :: MacOS :: MacOS X
-Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 3
diff --git a/mnemonic.egg-info/SOURCES.txt b/mnemonic.egg-info/SOURCES.txt
deleted file mode 100644
index ddf7981..0000000
--- a/mnemonic.egg-info/SOURCES.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-LICENSE
-MANIFEST.in
-README.rst
-setup.cfg
-setup.py
-mnemonic/__init__.py
-mnemonic/mnemonic.py
-mnemonic.egg-info/PKG-INFO
-mnemonic.egg-info/SOURCES.txt
-mnemonic.egg-info/dependency_links.txt
-mnemonic.egg-info/not-zip-safe
-mnemonic.egg-info/top_level.txt
-mnemonic/wordlist/chinese_simplified.txt
-mnemonic/wordlist/chinese_traditional.txt
-mnemonic/wordlist/english.txt
-mnemonic/wordlist/french.txt
-mnemonic/wordlist/italian.txt
-mnemonic/wordlist/japanese.txt
-mnemonic/wordlist/korean.txt
-mnemonic/wordlist/spanish.txt
\ No newline at end of file
diff --git a/mnemonic/__init__.py b/mnemonic/__init__.py
deleted file mode 100644
index 47e293d..0000000
--- a/mnemonic/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from .mnemonic import Mnemonic # noqa: F401
diff --git a/setup.cfg b/setup.cfg
index adf5ed7..1aa827f 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,29 @@
-[bdist_wheel]
-universal = 1
+[flake8]
+extend-ignore =
+ E203,
+ E501,
+ I900,
+
+[mypy]
+warn_unreachable = True
+warn_unused_ignores = True
+warn_redundant_casts = True
+warn_unused_configs = True
+incremental = False
+disallow_untyped_defs = True
+check_untyped_defs = True
+strict_equality = True
+implicit_reexport = False
+no_implicit_optional = True
+
+[mypy-generate_vectors]
+ignore_errors = True
+
+[mypy-setuptools.*]
+ignore_missing_imports = True
+
+[mypy-bip32utils.*]
+ignore_missing_imports = True
[egg_info]
tag_build =
diff --git a/setup.py b/setup.py
index 47ee0c2..9cc27d7 100755
--- a/setup.py
+++ b/setup.py
@@ -1,33 +1,33 @@
-#!/usr/bin/env python
-import os
+#!/usr/bin/env python3
+from pathlib import Path
from setuptools import setup
-CWD = os.path.dirname(os.path.realpath(__file__))
-
-
-def read(*path):
- filename = os.path.join(CWD, *path)
- with open(filename, "r") as f:
- return f.read()
+CWD = Path(__file__).resolve().parent
setup(
name="mnemonic",
- version="0.19",
+ version="0.20",
author="Trezor",
author_email="info@trezor.io",
description="Implementation of Bitcoin BIP-0039",
- long_description=read("README.rst"),
+ long_description="\n".join(
+ (
+ (CWD / "README.rst").read_text(),
+ (CWD / "CHANGELOG.rst").read_text(),
+ )
+ ),
url="https://github.com/trezor/python-mnemonic",
packages=["mnemonic"],
- package_data={"mnemonic": ["wordlist/*.txt"]},
+ package_dir={"": "src"},
+ include_package_data=True,
zip_safe=False,
+ python_requires=">=3.5",
classifiers=[
"License :: OSI Approved :: MIT License",
"Operating System :: POSIX :: Linux",
"Operating System :: Microsoft :: Windows",
"Operating System :: MacOS :: MacOS X",
- "Programming Language :: Python :: 2",
"Programming Language :: Python :: 3",
],
)
diff --git a/src/mnemonic.egg-info/PKG-INFO b/src/mnemonic.egg-info/PKG-INFO
new file mode 100644
index 0000000..bae1ad0
--- /dev/null
+++ b/src/mnemonic.egg-info/PKG-INFO
@@ -0,0 +1,136 @@
+Metadata-Version: 1.2
+Name: mnemonic
+Version: 0.20
+Summary: Implementation of Bitcoin BIP-0039
+Home-page: https://github.com/trezor/python-mnemonic
+Author: Trezor
+Author-email: info@trezor.io
+License: UNKNOWN
+Description: python-mnemonic
+ ===============
+
+ .. image:: https://badge.fury.io/py/mnemonic.svg
+ :target: https://badge.fury.io/py/mnemonic
+
+ Reference implementation of BIP-0039: Mnemonic code for generating
+ deterministic keys
+
+ Abstract
+ --------
+
+ This BIP describes the implementation of a mnemonic code or mnemonic sentence --
+ a group of easy to remember words -- for the generation of deterministic wallets.
+
+ It consists of two parts: generating the mnenomic, and converting it into a
+ binary seed. This seed can be later used to generate deterministic wallets using
+ BIP-0032 or similar methods.
+
+ BIP Paper
+ ---------
+
+ See https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
+ for full specification
+
+ Installation
+ ------------
+
+ To install this library and its dependencies use:
+
+ ``pip install mnemonic``
+
+ Usage examples
+ --------------
+
+ Import library into python project via:
+
+ .. code-block:: python
+
+ from mnemonic import Mnemonic
+
+ Initialize class instance, picking from available dictionaries:
+
+ - english
+ - chinese_simplified
+ - chinese_traditional
+ - french
+ - italian
+ - japanese
+ - korean
+ - spanish
+
+ .. code-block:: python
+
+ mnemo = Mnemonic(language)
+ mnemo = Mnemonic("english")
+
+ Generate word list given the strength (128 - 256):
+
+ .. code-block:: python
+
+ words = mnemo.generate(strength=256)
+
+ Given the word list and custom passphrase (empty in example), generate seed:
+
+ .. code-block:: python
+
+ seed = mnemo.to_seed(words, passphrase="")
+
+ Given the word list, calculate original entropy:
+
+ .. code-block:: python
+
+ entropy = mnemo.to_entropy(words)
+
+ Changelog
+ =========
+
+ .. default-role:: code
+
+ All notable changes to this project will be documented in this file.
+
+ The format is based on `Keep a Changelog`_, and this project adheres to
+ `Semantic Versioning`_.
+
+ `0.20`_ - 2021-07-27
+ ---------------------
+
+ .. _0.20: https://github.com/trezor/python-mnemonic/compare/v0.19...v0.20
+
+ Added
+ ~~~~~
+
+ - Type annotations
+ - Support for testnet private keys
+
+ Changed
+ ~~~~~~~
+
+ - Project directory structure was cleaned up
+ - Language on the `Mnemonic` object is remembered instead of repeatedly detecting
+
+ Removed
+ ~~~~~~~
+
+ - Support for Python 2.7 and 3.4 was dropped
+
+
+
+ 0.19 - 2019-10-01
+ ------------------
+
+ Added
+ ~~~~~
+
+ - Start of changelog
+
+
+ .. _Keep a Changelog: https://keepachangelog.com/en/1.0.0/
+ .. _Semantic Versioning: https://semver.org/spec/v2.0.0.html
+
+Platform: UNKNOWN
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: POSIX :: Linux
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Operating System :: MacOS :: MacOS X
+Classifier: Programming Language :: Python :: 3
+Requires-Python: >=3.5
diff --git a/src/mnemonic.egg-info/SOURCES.txt b/src/mnemonic.egg-info/SOURCES.txt
new file mode 100644
index 0000000..fe1b166
--- /dev/null
+++ b/src/mnemonic.egg-info/SOURCES.txt
@@ -0,0 +1,26 @@
+AUTHORS
+CHANGELOG.rst
+LICENSE
+MANIFEST.in
+README.rst
+setup.cfg
+setup.py
+tox.ini
+src/mnemonic/__init__.py
+src/mnemonic/mnemonic.py
+src/mnemonic/py.typed
+src/mnemonic.egg-info/PKG-INFO
+src/mnemonic.egg-info/SOURCES.txt
+src/mnemonic.egg-info/dependency_links.txt
+src/mnemonic.egg-info/not-zip-safe
+src/mnemonic.egg-info/top_level.txt
+src/mnemonic/wordlist/chinese_simplified.txt
+src/mnemonic/wordlist/chinese_traditional.txt
+src/mnemonic/wordlist/english.txt
+src/mnemonic/wordlist/french.txt
+src/mnemonic/wordlist/italian.txt
+src/mnemonic/wordlist/japanese.txt
+src/mnemonic/wordlist/korean.txt
+src/mnemonic/wordlist/spanish.txt
+tests/test_mnemonic.py
+tools/generate_vectors.py
\ No newline at end of file
diff --git a/mnemonic.egg-info/dependency_links.txt b/src/mnemonic.egg-info/dependency_links.txt
similarity index 100%
rename from mnemonic.egg-info/dependency_links.txt
rename to src/mnemonic.egg-info/dependency_links.txt
diff --git a/mnemonic.egg-info/not-zip-safe b/src/mnemonic.egg-info/not-zip-safe
similarity index 100%
rename from mnemonic.egg-info/not-zip-safe
rename to src/mnemonic.egg-info/not-zip-safe
diff --git a/mnemonic.egg-info/top_level.txt b/src/mnemonic.egg-info/top_level.txt
similarity index 100%
rename from mnemonic.egg-info/top_level.txt
rename to src/mnemonic.egg-info/top_level.txt
diff --git a/src/mnemonic/__init__.py b/src/mnemonic/__init__.py
new file mode 100644
index 0000000..f8c9e1f
--- /dev/null
+++ b/src/mnemonic/__init__.py
@@ -0,0 +1,3 @@
+from .mnemonic import Mnemonic
+
+__all__ = ["Mnemonic"]
diff --git a/mnemonic/mnemonic.py b/src/mnemonic/mnemonic.py
similarity index 75%
rename from mnemonic/mnemonic.py
rename to src/mnemonic/mnemonic.py
index 935620a..e45111b 100644
--- a/mnemonic/mnemonic.py
+++ b/src/mnemonic/mnemonic.py
@@ -20,15 +20,15 @@
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
-import binascii
import bisect
import hashlib
import hmac
import itertools
import os
-import sys
+from typing import AnyStr, List, Optional, Sequence, TypeVar, Union
import unicodedata
+_T = TypeVar("_T")
PBKDF2_ROUNDS = 2048
@@ -37,20 +37,23 @@ class ConfigurationError(Exception):
# From <https://stackoverflow.com/questions/212358/binary-search-bisection-in-python/2233940#2233940>
-def binary_search(a, x, lo=0, hi=None): # can't use a to specify default for hi
+def binary_search(
+ a: Sequence[_T],
+ x: _T,
+ lo: int = 0,
+ hi: Optional[int] = None, # can't use a to specify default for hi
+) -> int:
hi = hi if hi is not None else len(a) # hi defaults to len(a)
pos = bisect.bisect_left(a, x, lo, hi) # find insertion position
return pos if pos != hi and a[pos] == x else -1 # don't walk off the end
# Refactored code segments from <https://github.com/keis/base58>
-def b58encode(v):
+def b58encode(v: bytes) -> str:
alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
p, acc = 1, 0
for c in reversed(v):
- if sys.version < "3":
- c = ord(c)
acc += p * c
p = p << 8
@@ -62,39 +65,36 @@ def b58encode(v):
class Mnemonic(object):
- def __init__(self, language):
+ def __init__(self, language: str):
+ self.language = language
self.radix = 2048
- if sys.version < "3":
- with open("%s/%s.txt" % (self._get_directory(), language), "r") as f:
- self.wordlist = [w.strip().decode("utf8") for w in f.readlines()]
- else:
- with open(
- "%s/%s.txt" % (self._get_directory(), language), "r", encoding="utf-8"
- ) as f:
- self.wordlist = [w.strip() for w in f.readlines()]
+ with open(
+ "%s/%s.txt" % (self._get_directory(), language), "r", encoding="utf-8"
+ ) as f:
+ self.wordlist = [w.strip() for w in f.readlines()]
if len(self.wordlist) != self.radix:
raise ConfigurationError(
"Wordlist should contain %d words, but it contains %d words."
% (self.radix, len(self.wordlist))
)
- @classmethod
- def _get_directory(cls):
+ @staticmethod
+ def _get_directory() -> str:
return os.path.join(os.path.dirname(__file__), "wordlist")
@classmethod
- def list_languages(cls):
+ def list_languages(cls) -> List[str]:
return [
f.split(".")[0]
for f in os.listdir(cls._get_directory())
if f.endswith(".txt")
]
- @classmethod
- def normalize_string(cls, txt):
- if isinstance(txt, str if sys.version < "3" else bytes):
+ @staticmethod
+ def normalize_string(txt: AnyStr) -> str:
+ if isinstance(txt, bytes):
utxt = txt.decode("utf8")
- elif isinstance(txt, unicode if sys.version < "3" else str): # noqa: F821
+ elif isinstance(txt, str):
utxt = txt
else:
raise TypeError("String value expected")
@@ -102,7 +102,7 @@ class Mnemonic(object):
return unicodedata.normalize("NFKD", utxt)
@classmethod
- def detect_language(cls, code):
+ def detect_language(cls, code: str) -> str:
code = cls.normalize_string(code)
first = code.split(" ")[0]
languages = cls.list_languages()
@@ -114,7 +114,7 @@ class Mnemonic(object):
raise ConfigurationError("Language not detected")
- def generate(self, strength=128):
+ def generate(self, strength: int = 128) -> str:
if strength not in [128, 160, 192, 224, 256]:
raise ValueError(
"Strength should be one of the following [128, 160, 192, 224, 256], but it is not (%d)."
@@ -123,7 +123,7 @@ class Mnemonic(object):
return self.to_mnemonic(os.urandom(strength // 8))
# Adapted from <http://tinyurl.com/oxmn476>
- def to_entropy(self, words):
+ def to_entropy(self, words: Union[List[str], str]) -> bytearray:
if not isinstance(words, list):
words = words.split(" ")
if len(words) not in [12, 15, 18, 21, 24]:
@@ -136,7 +136,7 @@ class Mnemonic(object):
concatLenBits = len(words) * 11
concatBits = [False] * concatLenBits
wordindex = 0
- if self.detect_language(" ".join(words)) == "english":
+ if self.language == "english":
use_binary_search = True
else:
use_binary_search = False
@@ -163,28 +163,18 @@ class Mnemonic(object):
entropy[ii] |= 1 << (7 - jj)
# Take the digest of the entropy.
hashBytes = hashlib.sha256(entropy).digest()
- if sys.version < "3":
- hashBits = list(
- itertools.chain.from_iterable(
- (
- [ord(c) & (1 << (7 - i)) != 0 for i in range(8)]
- for c in hashBytes
- )
- )
- )
- else:
- hashBits = list(
- itertools.chain.from_iterable(
- ([c & (1 << (7 - i)) != 0 for i in range(8)] for c in hashBytes)
- )
+ hashBits = list(
+ itertools.chain.from_iterable(
+ [c & (1 << (7 - i)) != 0 for i in range(8)] for c in hashBytes
)
+ )
# Check all the checksum bits.
for i in range(checksumLengthBits):
if concatBits[entropyLengthBits + i] != hashBits[i]:
raise ValueError("Failed checksum.")
return entropy
- def to_mnemonic(self, data):
+ def to_mnemonic(self, data: bytes) -> str:
if len(data) not in [16, 20, 24, 28, 32]:
raise ValueError(
"Data length should be one of the following: [16, 20, 24, 28, 32], but it is not (%d)."
@@ -192,39 +182,39 @@ class Mnemonic(object):
)
h = hashlib.sha256(data).hexdigest()
b = (
- bin(int(binascii.hexlify(data), 16))[2:].zfill(len(data) * 8)
+ bin(int.from_bytes(data, byteorder="big"))[2:].zfill(len(data) * 8)
+ bin(int(h, 16))[2:].zfill(256)[: len(data) * 8 // 32]
)
result = []
for i in range(len(b) // 11):
idx = int(b[i * 11 : (i + 1) * 11], 2)
result.append(self.wordlist[idx])
- if (
- self.detect_language(" ".join(result)) == "japanese"
- ): # Japanese must be joined by ideographic space.
+ if self.language == "japanese": # Japanese must be joined by ideographic space.
result_phrase = u"\u3000".join(result)
else:
result_phrase = " ".join(result)
return result_phrase
- def check(self, mnemonic):
- mnemonic = self.normalize_string(mnemonic).split(" ")
+ def check(self, mnemonic: str) -> bool:
+ mnemonic_list = self.normalize_string(mnemonic).split(" ")
# list of valid mnemonic lengths
- if len(mnemonic) not in [12, 15, 18, 21, 24]:
+ if len(mnemonic_list) not in [12, 15, 18, 21, 24]:
return False
try:
- idx = map(lambda x: bin(self.wordlist.index(x))[2:].zfill(11), mnemonic)
+ idx = map(
+ lambda x: bin(self.wordlist.index(x))[2:].zfill(11), mnemonic_list
+ )
b = "".join(idx)
except ValueError:
return False
l = len(b) # noqa: E741
d = b[: l // 33 * 32]
h = b[-l // 33 :]
- nd = binascii.unhexlify(hex(int(d, 2))[2:].rstrip("L").zfill(l // 33 * 8))
+ nd = int(d, 2).to_bytes(l // 33 * 4, byteorder="big")
nh = bin(int(hashlib.sha256(nd).hexdigest(), 16))[2:].zfill(256)[: l // 33]
return h == nh
- def expand_word(self, prefix):
+ def expand_word(self, prefix: str) -> str:
if prefix in self.wordlist:
return prefix
else:
@@ -236,21 +226,23 @@ class Mnemonic(object):
# this is not a validation routine, just return the input
return prefix
- def expand(self, mnemonic):
+ def expand(self, mnemonic: str) -> str:
return " ".join(map(self.expand_word, mnemonic.split(" ")))
@classmethod
- def to_seed(cls, mnemonic, passphrase=""):
+ def to_seed(cls, mnemonic: str, passphrase: str = "") -> bytes:
mnemonic = cls.normalize_string(mnemonic)
passphrase = cls.normalize_string(passphrase)
passphrase = "mnemonic" + passphrase
- mnemonic = mnemonic.encode("utf-8")
- passphrase = passphrase.encode("utf-8")
- stretched = hashlib.pbkdf2_hmac("sha512", mnemonic, passphrase, PBKDF2_ROUNDS)
+ mnemonic_bytes = mnemonic.encode("utf-8")
+ passphrase_bytes = passphrase.encode("utf-8")
+ stretched = hashlib.pbkdf2_hmac(
+ "sha512", mnemonic_bytes, passphrase_bytes, PBKDF2_ROUNDS
+ )
return stretched[:64]
- @classmethod
- def to_hd_master_key(cls, seed):
+ @staticmethod
+ def to_hd_master_key(seed: bytes, testnet: bool = False) -> str:
if len(seed) != 64:
raise ValueError("Provided seed should have length of 64")
@@ -259,6 +251,8 @@ class Mnemonic(object):
# Serialization format can be found at: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#Serialization_format
xprv = b"\x04\x88\xad\xe4" # Version for private mainnet
+ if testnet:
+ xprv = b"\x04\x35\x83\x94" # Version for private testnet
xprv += b"\x00" * 9 # Depth, parent fingerprint, and child number
xprv += seed[32:] # Chain code
xprv += b"\x00" + seed[:32] # Master key
@@ -274,15 +268,14 @@ class Mnemonic(object):
return b58encode(xprv)
-def main():
- import binascii
+def main() -> None:
import sys
if len(sys.argv) > 1:
- data = sys.argv[1]
+ hex_data = sys.argv[1]
else:
- data = sys.stdin.readline().strip()
- data = binascii.unhexlify(data)
+ hex_data = sys.stdin.readline().strip()
+ data = bytes.fromhex(hex_data)
m = Mnemonic("english")
print(m.to_mnemonic(data))
diff --git a/src/mnemonic/py.typed b/src/mnemonic/py.typed
new file mode 100644
index 0000000..1242d43
--- /dev/null
+++ b/src/mnemonic/py.typed
@@ -0,0 +1 @@
+# Marker file for PEP 561.
diff --git a/mnemonic/wordlist/chinese_simplified.txt b/src/mnemonic/wordlist/chinese_simplified.txt
similarity index 100%
rename from mnemonic/wordlist/chinese_simplified.txt
rename to src/mnemonic/wordlist/chinese_simplified.txt
diff --git a/mnemonic/wordlist/chinese_traditional.txt b/src/mnemonic/wordlist/chinese_traditional.txt
similarity index 100%
rename from mnemonic/wordlist/chinese_traditional.txt
rename to src/mnemonic/wordlist/chinese_traditional.txt
diff --git a/mnemonic/wordlist/english.txt b/src/mnemonic/wordlist/english.txt
similarity index 100%
rename from mnemonic/wordlist/english.txt
rename to src/mnemonic/wordlist/english.txt
diff --git a/mnemonic/wordlist/french.txt b/src/mnemonic/wordlist/french.txt
similarity index 99%
rename from mnemonic/wordlist/french.txt
rename to src/mnemonic/wordlist/french.txt
index 8600949..bcf7942 100644
--- a/mnemonic/wordlist/french.txt
+++ b/src/mnemonic/wordlist/french.txt
@@ -1,4 +1,4 @@
-abaisser
+abaisser
abandon
abdiquer
abeille
diff --git a/mnemonic/wordlist/italian.txt b/src/mnemonic/wordlist/italian.txt
similarity index 100%
rename from mnemonic/wordlist/italian.txt
rename to src/mnemonic/wordlist/italian.txt
diff --git a/mnemonic/wordlist/japanese.txt b/src/mnemonic/wordlist/japanese.txt
similarity index 100%
rename from mnemonic/wordlist/japanese.txt
rename to src/mnemonic/wordlist/japanese.txt
diff --git a/mnemonic/wordlist/korean.txt b/src/mnemonic/wordlist/korean.txt
similarity index 100%
rename from mnemonic/wordlist/korean.txt
rename to src/mnemonic/wordlist/korean.txt
diff --git a/mnemonic/wordlist/spanish.txt b/src/mnemonic/wordlist/spanish.txt
similarity index 100%
rename from mnemonic/wordlist/spanish.txt
rename to src/mnemonic/wordlist/spanish.txt
diff --git a/tests/test_mnemonic.py b/tests/test_mnemonic.py
new file mode 100755
index 0000000..959c238
--- /dev/null
+++ b/tests/test_mnemonic.py
@@ -0,0 +1,127 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2013 Pavol Rusnak
+# Copyright (c) 2017 mruddy
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to
+# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is furnished to do
+# so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+import json
+import random
+import unittest
+from typing import List
+
+from mnemonic import Mnemonic
+
+
+class MnemonicTest(unittest.TestCase):
+ def _check_list(self, language: str, vectors: List[str]) -> None:
+ mnemo = Mnemonic(language)
+ for v in vectors:
+ code = mnemo.to_mnemonic(bytes.fromhex(v[0]))
+ seed = Mnemonic.to_seed(code, passphrase="TREZOR")
+ xprv = Mnemonic.to_hd_master_key(seed)
+ self.assertIs(mnemo.check(v[1]), True)
+ self.assertEqual(v[1], code)
+ self.assertEqual(v[2], seed.hex())
+ self.assertEqual(v[3], xprv)
+
+ def test_vectors(self) -> None:
+ with open("vectors.json", "r") as f:
+ vectors = json.load(f)
+ for lang in vectors.keys():
+ self._check_list(lang, vectors[lang])
+
+ def test_failed_checksum(self) -> None:
+ code = (
+ "bless cloud wheel regular tiny venue bird web grief security dignity zoo"
+ )
+ mnemo = Mnemonic("english")
+ self.assertFalse(mnemo.check(code))
+
+ def test_detection(self) -> None:
+ self.assertEqual("english", Mnemonic.detect_language("security"))
+
+ with self.assertRaises(Exception):
+ Mnemonic.detect_language("xxxxxxx")
+
+ def test_utf8_nfkd(self) -> None:
+ # The same sentence in various UTF-8 forms
+ words_nfkd = u"Pr\u030ci\u0301s\u030cerne\u030c z\u030clut\u030couc\u030cky\u0301 ku\u030an\u030c u\u0301pe\u030cl d\u030ca\u0301belske\u0301 o\u0301dy za\u0301ker\u030cny\u0301 uc\u030cen\u030c be\u030cz\u030ci\u0301 pode\u0301l zo\u0301ny u\u0301lu\u030a"
+ words_nfc = u"P\u0159\xed\u0161ern\u011b \u017elu\u0165ou\u010dk\xfd k\u016f\u0148 \xfap\u011bl \u010f\xe1belsk\xe9 \xf3dy z\xe1ke\u0159n\xfd u\u010de\u0148 b\u011b\u017e\xed pod\xe9l z\xf3ny \xfal\u016f"
+ words_nfkc = u"P\u0159\xed\u0161ern\u011b \u017elu\u0165ou\u010dk\xfd k\u016f\u0148 \xfap\u011bl \u010f\xe1belsk\xe9 \xf3dy z\xe1ke\u0159n\xfd u\u010de\u0148 b\u011b\u017e\xed pod\xe9l z\xf3ny \xfal\u016f"
+ words_nfd = u"Pr\u030ci\u0301s\u030cerne\u030c z\u030clut\u030couc\u030cky\u0301 ku\u030an\u030c u\u0301pe\u030cl d\u030ca\u0301belske\u0301 o\u0301dy za\u0301ker\u030cny\u0301 uc\u030cen\u030c be\u030cz\u030ci\u0301 pode\u0301l zo\u0301ny u\u0301lu\u030a"
+
+ passphrase_nfkd = (
+ u"Neuve\u030cr\u030citelne\u030c bezpec\u030cne\u0301 hesli\u0301c\u030cko"
+ )
+ passphrase_nfc = (
+ u"Neuv\u011b\u0159iteln\u011b bezpe\u010dn\xe9 hesl\xed\u010dko"
+ )
+ passphrase_nfkc = (
+ u"Neuv\u011b\u0159iteln\u011b bezpe\u010dn\xe9 hesl\xed\u010dko"
+ )
+ passphrase_nfd = (
+ u"Neuve\u030cr\u030citelne\u030c bezpec\u030cne\u0301 hesli\u0301c\u030cko"
+ )
+
+ seed_nfkd = Mnemonic.to_seed(words_nfkd, passphrase_nfkd)
+ seed_nfc = Mnemonic.to_seed(words_nfc, passphrase_nfc)
+ seed_nfkc = Mnemonic.to_seed(words_nfkc, passphrase_nfkc)
+ seed_nfd = Mnemonic.to_seed(words_nfd, passphrase_nfd)
+
+ self.assertEqual(seed_nfkd, seed_nfc)
+ self.assertEqual(seed_nfkd, seed_nfkc)
+ self.assertEqual(seed_nfkd, seed_nfd)
+
+ def test_to_entropy(self) -> None:
+ data = [bytes(random.getrandbits(8) for _ in range(32)) for _ in range(1024)]
+ data.append(b"Lorem ipsum dolor sit amet amet.")
+ m = Mnemonic("english")
+ for d in data:
+ self.assertEqual(m.to_entropy(m.to_mnemonic(d).split()), d)
+
+ def test_expand_word(self) -> None:
+ m = Mnemonic("english")
+ self.assertEqual("", m.expand_word(""))
+ self.assertEqual(" ", m.expand_word(" "))
+ self.assertEqual("access", m.expand_word("access")) # word in list
+ self.assertEqual(
+ "access", m.expand_word("acce")
+ ) # unique prefix expanded to word in list
+ self.assertEqual("acb", m.expand_word("acb")) # not found at all
+ self.assertEqual("acc", m.expand_word("acc")) # multi-prefix match
+ self.assertEqual("act", m.expand_word("act")) # exact three letter match
+ self.assertEqual(
+ "action", m.expand_word("acti")
+ ) # unique prefix expanded to word in list
+
+ def test_expand(self) -> None:
+ m = Mnemonic("english")
+ self.assertEqual("access", m.expand("access"))
+ self.assertEqual(
+ "access access acb acc act action", m.expand("access acce acb acc act acti")
+ )
+
+
+def __main__() -> None:
+ unittest.main()
+
+
+if __name__ == "__main__":
+ __main__()
diff --git a/tools/generate_vectors.py b/tools/generate_vectors.py
new file mode 100755
index 0000000..f03955a
--- /dev/null
+++ b/tools/generate_vectors.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+
+import json
+from random import choice, seed
+
+from bip32utils import BIP32Key
+from mnemonic import Mnemonic
+
+
+def process(data, lst):
+ code = mnemo.to_mnemonic(bytes.fromhex(data))
+ seed = Mnemonic.to_seed(code, passphrase="TREZOR")
+ xprv = BIP32Key.fromEntropy(seed).ExtendedKey()
+ seed = seed.hex()
+ print("input : %s (%d bits)" % (data, len(data) * 4))
+ print("mnemonic : %s (%d words)" % (code, len(code.split(" "))))
+ print("seed : %s (%d bits)" % (seed, len(seed) * 4))
+ print("xprv : %s" % xprv)
+ print()
+ lst.append((data, code, seed, xprv))
+
+
+if __name__ == "__main__":
+ out = {}
+ seed(1337)
+
+ for lang in ["english"]: # Mnemonic.list_languages():
+ mnemo = Mnemonic(lang)
+ out[lang] = []
+
+ # Generate corner cases
+ data = []
+ for length in range(16, 32 + 1, 8):
+ for b in ["00", "7f", "80", "ff"]:
+ process(b * length, out[lang])
+
+ # Generate random seeds
+ for i in range(12):
+ data = "".join(chr(choice(range(0, 256))) for _ in range(8 * (i % 3 + 2)))
+ data = data.encode("latin1")
+ process(data.hex(), out[lang])
+
+ with open("vectors.json", "w") as f:
+ json.dump(
+ out, f, sort_keys=True, indent=4, separators=(",", ": "), ensure_ascii=False
+ )
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..87ea5b2
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,13 @@
+[tox]
+envlist =
+ py35,
+ py36,
+ py37,
+ py38,
+ py39,
+ py310,
+ pypy,
+
+[testenv]
+commands =
+ python tests/test_mnemonic.py
Debdiff
[The following lists of changes regard files as different if they have different names, permissions or owners.]
Files in second set of .debs but not in first
-rw-r--r-- root/root /usr/lib/python3/dist-packages/mnemonic-0.20.egg-info/PKG-INFO -rw-r--r-- root/root /usr/lib/python3/dist-packages/mnemonic-0.20.egg-info/dependency_links.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/mnemonic-0.20.egg-info/not-zip-safe -rw-r--r-- root/root /usr/lib/python3/dist-packages/mnemonic-0.20.egg-info/top_level.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/mnemonic/py.typed -rw-r--r-- root/root /usr/share/doc/python3-mnemonic/changelog.gz
Files in first set of .debs but not in second
-rw-r--r-- root/root /usr/lib/python3/dist-packages/mnemonic-0.19.egg-info/PKG-INFO -rw-r--r-- root/root /usr/lib/python3/dist-packages/mnemonic-0.19.egg-info/dependency_links.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/mnemonic-0.19.egg-info/not-zip-safe -rw-r--r-- root/root /usr/lib/python3/dist-packages/mnemonic-0.19.egg-info/top_level.txt
No differences were encountered in the control files