aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDeterminant <tederminant@gmail.com>2020-11-17 18:38:01 -0500
committerDeterminant <tederminant@gmail.com>2020-11-17 18:38:01 -0500
commit92b8b8e9628cac41d37226416107adc76b10e01b (patch)
tree8d3ff896eeabc2877b3d1e63fea5c30b19832b11
parent069e181cc935dc626802e5dc43983b4b0f6319f9 (diff)
support AVAX keystore files
-rw-r--r--README.rst5
-rwxr-xr-xkeytree.py52
-rw-r--r--setup.py2
3 files changed, 49 insertions, 10 deletions
diff --git a/README.rst b/README.rst
index aa9e27a..8f981f9 100644
--- a/README.rst
+++ b/README.rst
@@ -9,3 +9,8 @@ keytree.py
(recommended). If you instead do a normal pip install and use
``keytree.py`` (without ``./`` prefix), it will use the latest deps fetched
by pip.
+- You can also load the mnemonic from an existing JSON keystore file (generated
+ by the official AVAX Wallet): ``./keytree.py --from-avax-keystore``.
+- To see all private keys and the mnemonic word, use ``--show-private`` (only
+ use it after you look around and ensure there is no one else looking at your
+ screen).
diff --git a/keytree.py b/keytree.py
index ebfaf46..46bea6f 100755
--- a/keytree.py
+++ b/keytree.py
@@ -30,7 +30,7 @@
# Use at your own risk.
#
# Example:
-# python ./keytree.py
+# python3 ./keytree.py
import os
import sys
@@ -45,6 +45,7 @@ import argparse
import hashlib
import hmac
import unicodedata
+import json
from getpass import getpass
import bech32
@@ -52,8 +53,9 @@ import mnemonic
from ecdsa import SigningKey, VerifyingKey, SECP256k1
from ecdsa.ecdsa import generator_secp256k1
from ecdsa.ellipticcurve import INFINITY
-from base58 import b58encode
+from base58 import b58encode, b58decode
from sha3 import keccak_256
+from Crypto.Cipher import AES
def sha256(data):
@@ -208,9 +210,37 @@ def get_btc_addr(pk):
h += checksum
return b58encode(h).decode("utf-8")
+def load_from_keystore(filename):
+ try:
+ with open(filename, "r") as f:
+ try:
+ parsed = json.load(f)
+ ciphertext = b58decode(parsed['keys'][0]['key'])[:-4]
+ iv = b58decode(parsed['keys'][0]['iv'])[:-4]
+ salt = b58decode(parsed['salt'])[:-4]
+ tag = b58decode(parsed['pass_hash'])[:-4]
+ passwd = getpass('Enter the password to unlock keystore: ').encode('utf-8')
+ key = hashlib.pbkdf2_hmac(
+ 'sha256',
+ sha256(passwd + salt), salt, 200000)
+ obj = AES.new(key,
+ mode=AES.MODE_GCM,
+ nonce=iv)
+ if tag != sha256(passwd + sha256(passwd + salt)):
+ raise KeytreeError("incorrect keystore password")
+ return obj.decrypt(ciphertext[:-16]).decode('utf-8')
+ except KeytreeError as e:
+ raise e
+ except:
+ raise KeytreeError("invalid or corrupted keystore file")
+ except FileNotFoundError:
+ raise KeytreeError("failed to open file")
+
+
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Derive BIP32 key pairs from BIP39 mnemonic')
- parser.add_argument('--show-private', action='store_true', default=False, help='also show private keys')
+ parser.add_argument('--from-avax-keystore', type=str, default=None, help='load mnemonic from an AVAX keystore file')
+ parser.add_argument('--show-private', action='store_true', default=False, help='also show private keys and the mnemonic')
parser.add_argument('--custom-words', action='store_true', default=False, help='use an arbitrary word combination as mnemonic')
parser.add_argument('--account-path', default="44'/9000'/0'/0", help="path prefix for key deriving (e.g. \"0/1'/2\")")
parser.add_argument('--gen-mnemonic', action='store_true', default=False, help='generate a mnemonic (instead of taking an input)')
@@ -227,15 +257,19 @@ if __name__ == '__main__':
if args.gen_mnemonic:
mgen = mnemonic.Mnemonic(args.lang)
words = mgen.generate(256)
- print("KEEP THIS PRIVATE: {}".format(words))
else:
- words = getpass('Enter the mnemonic: ').strip()
- if not args.custom_words:
- mchecker = mnemonic.Mnemonic(args.lang)
- if not mchecker.check(words):
- raise KeytreeError("invalid mnemonic")
+ if args.from_avax_keystore:
+ words = load_from_keystore(args.from_avax_keystore)
+ else:
+ words = getpass('Enter the mnemonic: ').strip()
+ if not args.custom_words:
+ mchecker = mnemonic.Mnemonic(args.lang)
+ if not mchecker.check(words):
+ raise KeytreeError("invalid mnemonic")
except FileNotFoundError:
raise KeytreeError("invalid language")
+ if args.show_private or args.gen_mnemonic:
+ print("KEEP THIS PRIVATE: {}".format(words))
seed = hashlib.pbkdf2_hmac('sha512', unicodedata.normalize('NFKD', words).encode("utf-8"), b"mnemonic", 2048)
gen = BIP32(seed)
if args.start_idx < 0 or args.end_idx < 0:
diff --git a/setup.py b/setup.py
index afa763b..352641a 100644
--- a/setup.py
+++ b/setup.py
@@ -9,4 +9,4 @@ setup(name='keytree.py',
license='MIT',
scripts=['keytree.py'],
py_modules=['bech32'],
- install_requires=['ecdsa', 'base58', 'pysha3', 'mnemonic'])
+ install_requires=['ecdsa', 'base58', 'pysha3', 'pycrypto', 'mnemonic'])