aboutsummaryrefslogtreecommitdiff
path: root/frozen_deps/mnemonic/mnemonic.py
diff options
context:
space:
mode:
authorDeterminant <[email protected]>2022-11-17 18:08:59 -0800
committerDeterminant <[email protected]>2022-11-17 18:08:59 -0800
commit8154806fe2fccacdc3dafaa68181a07bcf8d6c4c (patch)
treef477e6a005599bb88c18db142c267b9297c6060b /frozen_deps/mnemonic/mnemonic.py
parentbe4dc086591c9bced04a507d127c83811c5700c4 (diff)
v0.1.7
Diffstat (limited to 'frozen_deps/mnemonic/mnemonic.py')
-rw-r--r--frozen_deps/mnemonic/mnemonic.py119
1 files changed, 56 insertions, 63 deletions
diff --git a/frozen_deps/mnemonic/mnemonic.py b/frozen_deps/mnemonic/mnemonic.py
index 935620a..e45111b 100644
--- a/frozen_deps/mnemonic/mnemonic.py
+++ b/frozen_deps/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))