diff --git a/README.md b/README.md index f3f320fba936c27634b867a75f18eb80f15bc533..6608f91dd714084da25a5693819cdd01efd2d662 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Silkaj is based on Python dependencies: - [Commandlines](https://github.com/chrissimpkins/commandlines): to parse command and sub-commands. - [PyNaCl](https://github.com/pyca/pynacl/): Cryptography (NaCl) library. - [scrypt](https://bitbucket.org/mhallin/py-scrypt): scrypt key derivation function. +- [pyaes](https://github.com/ricmoo/pyaes): Pure-Python implementation of AES #### From pip ```bash diff --git a/requirements.txt b/requirements.txt index 9ef73f830dae12f0664f47a848c86f3cb48b97d8..d7d531fd42c74c244c47839a95bf58d48ff6ed3a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ ipaddress tabulate pynacl scrypt +pyaes diff --git a/src/auth.py b/src/auth.py index d4681a063b9f23f9c1a9001642c10208c7ed6ac1..a557799f18f09f56129c030057a2219fbf1edd64 100644 --- a/src/auth.py +++ b/src/auth.py @@ -1,4 +1,9 @@ from tools import * +import nacl.encoding +import nacl.signing +import nacl.hash +import scrypt +import pyaes import getpass import os @@ -24,7 +29,8 @@ def generate_auth_file(c): seed = auth_method(c) with open(file, "w") as f: f.write(seed) - print("Authfile generated for the public key: ", get_publickey_from_seed(seed)) + print("Authfile generated for the public key: ", + get_publickey_from_seed(seed)) def auth_by_auth_file(c): @@ -56,6 +62,7 @@ def auth_by_seed(): def auth_by_scrypt(c): salt = input("Please enter your Scrypt Salt (Secret identifier): ") password = getpass.getpass("Please enter your Scrypt password (masked): ") + if c.contains_definitions('n') and c.contains_definitions('r') and c.contains_definitions('p'): n, r, p = c.get_definition('n'), c.get_definition('r'), c.get_definition('p') if n.isnumeric() and r.isnumeric() and p.isnumeric(): @@ -75,6 +82,120 @@ def auth_by_scrypt(c): def auth_by_wif(): - wif = input("Please enter your WIF address: ") - seed = get_seed_from_wif(wif) - return seed + wif = input("Please enter your WIF or Encrypted WIF address: ") + + regex = re.compile('^[1-9A-HJ-NP-Za-km-z]*$') + if not re.search(regex, wif): + print("Error: the format of WIF is invalid") + exit(1) + + wif_bytes = b58_decode(wif) + fi = wif_bytes[0:1] + + if fi == b'\x01': + return get_seed_from_wifv1(wif) + elif fi == b'\x02': + password = getpass.getpass("Please enter the " + + "password of WIF (masked): ") + return get_seed_from_ewifv1(wif, password) + + print("Error: the format of WIF is invalid or unknown") + exit(1) + + +def get_seed_from_scrypt(salt, password, N=4096, r=16, p=1): + seed = scrypt.hash(password, salt, N, r, p, 32) + seedhex = nacl.encoding.HexEncoder.encode(seed).decode("utf-8") + return seedhex + + +def get_seed_from_wifv1(wif): + regex = re.compile('^[1-9A-HJ-NP-Za-km-z]*$') + if not re.search(regex, wif): + print("Error: the format of WIF is invalid") + exit(1) + + wif_bytes = b58_decode(wif) + if len(wif_bytes) != 35: + print("Error: the size of WIF is invalid") + exit(1) + + checksum_from_wif = wif_bytes[-2:] + fi = wif_bytes[0:1] + seed = wif_bytes[1:-2] + seed_fi = wif_bytes[0:-2] + + if fi != b'\x01': + print("Error: It's not a WIF format") + exit(1) + + # checksum control + checksum = nacl.hash.sha256( + nacl.hash.sha256(seed_fi, nacl.encoding.RawEncoder), + nacl.encoding.RawEncoder)[0:2] + if checksum_from_wif != checksum: + print("Error: bad checksum of the WIF") + exit(1) + + seedhex = nacl.encoding.HexEncoder.encode(seed).decode("utf-8") + return seedhex + + +def get_seed_from_ewifv1(ewif, password): + regex = re.compile('^[1-9A-HJ-NP-Za-km-z]*$') + if not re.search(regex, ewif): + print("Error: the format of EWIF is invalid") + exit(1) + + wif_bytes = b58_decode(ewif) + if len(wif_bytes) != 39: + print("Error: the size of EWIF is invalid") + exit(1) + + wif_no_checksum = wif_bytes[0:-2] + checksum_from_ewif = wif_bytes[-2:] + fi = wif_bytes[0:1] + salt = wif_bytes[1:5] + encryptedhalf1 = wif_bytes[5:21] + encryptedhalf2 = wif_bytes[21:37] + + if fi != b'\x02': + print("Error: It's not a EWIF format") + exit(1) + + # Checksum Control + checksum = nacl.hash.sha256( + nacl.hash.sha256(wif_no_checksum, nacl.encoding.RawEncoder), + nacl.encoding.RawEncoder)[0:2] + if checksum_from_ewif != checksum: + print("Error: bad checksum of EWIF address") + exit(1) + + # SCRYPT + password = password.encode("utf-8") + scrypt_seed = scrypt.hash(password, salt, 16384, 8, 8, 64) + derivedhalf1 = scrypt_seed[0:32] + derivedhalf2 = scrypt_seed[32:64] + + # AES + aes = pyaes.AESModeOfOperationECB(derivedhalf2) + decryptedhalf1 = aes.decrypt(encryptedhalf1) + decryptedhalf2 = aes.decrypt(encryptedhalf2) + + # XOR + seed1 = xor_bytes(decryptedhalf1, derivedhalf1[0:16]) + seed2 = xor_bytes(decryptedhalf2, derivedhalf1[16:32]) + seed = seed1+seed2 + seedhex = nacl.encoding.HexEncoder.encode(seed).decode("utf-8") + + # Password Control + salt_from_seed = nacl.hash.sha256( + nacl.hash.sha256( + b58_decode(get_publickey_from_seed(seedhex)), + nacl.encoding.RawEncoder), + nacl.encoding.RawEncoder)[0:4] + if salt_from_seed != salt: + print("Error: bad Password of EWIF address") + exit(1) + + return seedhex diff --git a/src/tools.py b/src/tools.py index 4590617729899525e9b5863a5835770f4e3dd587..e19695878b533e4e53f44d494dac6a6dda30ac83 100644 --- a/src/tools.py +++ b/src/tools.py @@ -2,9 +2,7 @@ import datetime import nacl.encoding import nacl.signing import nacl.hash -import scrypt import re -import sys from network_tools import * from constants import * @@ -46,48 +44,12 @@ def get_current_block(ep): return request(ep, "blockchain/current") -def get_seed_from_scrypt(salt, password, N=4096, r=16, p=1): - seed = scrypt.hash(password, salt, N, r, p, 32) - seedhex = nacl.encoding.HexEncoder.encode(seed).decode("utf-8") - return seedhex - - -def get_seed_from_wif(wif): - regex = re.compile('^[1-9A-HJ-NP-Za-km-z]*$') - if not re.search(regex, wif): - print("Error: the format of WIF is invalid") - exit(1) - - wif_bytes = b58_decode(wif) - if len(wif_bytes) != 35: - print("Error: the size of WIF is invalid") - exit(1) - - checksum_from_wif = wif_bytes[-2:] - fi = wif_bytes[0:1] - seed = wif_bytes[1:-2] - seed_fi = wif_bytes[0:-2] - - if fi != b'\x01': - print("Error: It's not a WIF format") - exit(1) - - #checksum control - checksum = nacl.hash.sha256(nacl.hash.sha256(seed_fi, nacl.encoding.RawEncoder),nacl.encoding.RawEncoder)[0:2] - if checksum_from_wif != checksum: - print("Error: bad checksum of the WIF") - exit(1) - - seedhex = nacl.encoding.HexEncoder.encode(seed).decode("utf-8") - return seedhex - - def sign_document_from_seed(document, seed): seed = bytes(seed, 'utf-8') signing_key = nacl.signing.SigningKey(seed, nacl.encoding.HexEncoder) signed = signing_key.sign(bytes(document, 'utf-8')) - signed_b64 = nacl.encoding.Base64Encoder.encode(signed.signature).decode("utf-8") - return signed_b64 + signed_b64 = nacl.encoding.Base64Encoder.encode(signed.signature) + return signed_b64.decode("utf-8") def get_publickey_from_seed(seed): @@ -99,13 +61,16 @@ def get_publickey_from_seed(seed): def check_public_key(pubkey): regex = re.compile('^[1-9A-HJ-NP-Za-km-z]{43,44}$') - regex_checksum = re.compile('^[1-9A-HJ-NP-Za-km-z]{43,44}:[1-9A-HJ-NP-Za-km-z]{3}$') + regex_checksum = re.compile('^[1-9A-HJ-NP-Za-km-z]{43,44}' + + ':[1-9A-HJ-NP-Za-km-z]{3}$') if re.search(regex, pubkey): return pubkey if re.search(regex_checksum, pubkey): pubkey, checksum = pubkey.split(":") pubkey_byte = b58_decode(pubkey) - checksum_calculed = b58_encode(nacl.hash.sha256(nacl.hash.sha256(pubkey_byte, nacl.encoding.RawEncoder), nacl.encoding.RawEncoder))[:3] + checksum_calculed = b58_encode(nacl.hash.sha256( + nacl.hash.sha256(pubkey_byte, nacl.encoding.RawEncoder), + nacl.encoding.RawEncoder))[:3] if checksum_calculed == checksum: return pubkey else: @@ -123,7 +88,11 @@ def get_amount_from_pubkey(ep, pubkey): amount = 0 for source in sources: amount += source["amount"] * 10 ** source["base"] - listinput.append(str(source["amount"]) + ":" + str(source["base"]) + ":" + str(source["type"]) + ":" + str(source["identifier"]) + ":" + str(source["noffset"])) + listinput.append(str(source["amount"]) + ":" + + str(source["base"]) + ":" + + str(source["type"]) + ":" + + str(source["identifier"]) + ":" + + str(source["noffset"])) # pending source history = request(ep, "tx/history/" + pubkey + "/pending")["history"] @@ -144,7 +113,11 @@ def get_amount_from_pubkey(ep, pubkey): for output in pending["outputs"]: outputsplited = output.split(":") if outputsplited[2] == "SIG(" + pubkey + ")": - inputgenerated = str(outputsplited[0]) + ":" + str(outputsplited[1]) + ":T:" + identifier + ":" + str(i) + inputgenerated = ( + str(outputsplited[0]) + ":" + + str(outputsplited[1]) + ":T:" + + identifier + ":" + str(i) + ) if inputgenerated not in listinput: listinput.append(inputgenerated) i += 1 @@ -190,10 +163,7 @@ def b58_encode(b): res = ''.join(res[::-1]) # Encode leading zeros as base58 zeros - czero = b'\x00' - if sys.version > '3': - # In Python3 indexing a bytes returns numbers, not characters. - czero = 0 + czero = 0 pad = 0 for c in b: if c == czero: @@ -212,7 +182,8 @@ def b58_decode(s): for c in s: n *= 58 if c not in b58_digits: - raise InvalidBase58Error('Character %r is not a valid base58 character' % c) + raise InvalidBase58Error('Character %r is not a ' + + 'valid base58 character' % c) digit = b58_digits.index(c) n += digit @@ -230,3 +201,10 @@ def b58_decode(s): else: break return b'\x00' * pad + res + + +def xor_bytes(b1, b2): + result = bytearray() + for b1, b2 in zip(b1, b2): + result.append(b1 ^ b2) + return result