Compare commits

..

No commits in common. "c3033e9572fa8fbfe4220ecff448394ccab16b09" and "30c82b9cd1ea912ff872e699509a8fcac094405a" have entirely different histories.

8 changed files with 54 additions and 165 deletions

View File

@ -1,7 +1,5 @@
* 0.5.4 * 0.5.4
- Add message signer cli - Add message signer cli
- Add pbkdf2 support
- Add -0 flag for omitting newline on output
* 0.5.3 * 0.5.3
- Upgrade RLP to 3.0.0 (eliminates really annoying cytoolz warning on stdout) - Upgrade RLP to 3.0.0 (eliminates really annoying cytoolz warning on stdout)
--- ---

View File

@ -24,11 +24,10 @@ algo_keywords = [
'aes-128-ctr', 'aes-128-ctr',
] ]
hash_keywords = [ hash_keywords = [
'scrypt', 'scrypt'
'pbkdf2'
] ]
default_scrypt_kdfparams = { default_kdfparams = {
'dklen': 32, 'dklen': 32,
'n': 1 << 18, 'n': 1 << 18,
'p': 1, 'p': 1,
@ -36,12 +35,6 @@ default_scrypt_kdfparams = {
'salt': os.urandom(32).hex(), 'salt': os.urandom(32).hex(),
} }
default_pbkdf2_kdfparams = {
'c': 100000,
'dklen': 32,
'prf': 'sha256',
'salt': os.urandom(32).hex(),
}
def to_mac(mac_key, ciphertext_bytes): def to_mac(mac_key, ciphertext_bytes):
h = sha3.keccak_256() h = sha3.keccak_256()
@ -53,32 +46,18 @@ def to_mac(mac_key, ciphertext_bytes):
class Hashes: class Hashes:
@staticmethod @staticmethod
def from_scrypt(kdfparams=default_scrypt_kdfparams, passphrase=''): def from_scrypt(kdfparams=default_kdfparams, passphrase=''):
dklen = int(kdfparams['dklen']) dklen = int(kdfparams['dklen'])
n = int(kdfparams['n']) n = int(kdfparams['n'])
p = int(kdfparams['p']) p = int(kdfparams['p'])
r = int(kdfparams['r']) r = int(kdfparams['r'])
salt = bytes.fromhex(kdfparams['salt']) salt = bytes.fromhex(kdfparams['salt'])
return hashlib.scrypt(passphrase.encode('utf-8'), salt=salt, n=n, p=p, r=r, maxmem=1024 * 1024 * 1024, return hashlib.scrypt(passphrase.encode('utf-8'), salt=salt,n=n, p=p, r=r, maxmem=1024*1024*1024, dklen=dklen)
dklen=dklen)
@staticmethod
def from_pbkdf2(kdfparams=default_pbkdf2_kdfparams, passphrase=''):
if kdfparams['prf'] == 'hmac-sha256':
kdfparams['prf'].replace('hmac-sha256','sha256')
derived_key = hashlib.pbkdf2_hmac(
hash_name='sha256',
password=passphrase.encode('utf-8'),
salt=bytes.fromhex(kdfparams['salt']),
iterations=int(kdfparams['c']),
dklen=int(kdfparams['dklen'])
)
return derived_key
class Ciphers: class Ciphers:
aes_128_block_size = 1 << 7 aes_128_block_size = 1 << 7
aes_iv_len = 16 aes_iv_len = 16
@ -89,6 +68,7 @@ class Ciphers:
plaintext = cipher.decrypt(ciphertext) plaintext = cipher.decrypt(ciphertext)
return plaintext return plaintext
@staticmethod @staticmethod
def encrypt_aes_128_ctr(plaintext, encryption_key, iv): def encrypt_aes_128_ctr(plaintext, encryption_key, iv):
ctr = Counter.new(Ciphers.aes_128_block_size, initial_value=iv) ctr = Counter.new(Ciphers.aes_128_block_size, initial_value=iv)
@ -97,19 +77,11 @@ class Ciphers:
return ciphertext return ciphertext
def to_dict(private_key_bytes, kdf='scrypt', passphrase=''): def to_dict(private_key_bytes, passphrase=''):
private_key = coincurve.PrivateKey(secret=private_key_bytes) private_key = coincurve.PrivateKey(secret=private_key_bytes)
if kdf == 'scrypt':
encryption_key = Hashes.from_scrypt(passphrase=passphrase) encryption_key = Hashes.from_scrypt(passphrase=passphrase)
kdfparams = default_scrypt_kdfparams
elif kdf == 'pbkdf2':
encryption_key = Hashes.from_pbkdf2(passphrase=passphrase)
kdfparams = pbkdf2_kdfparams
else:
raise NotImplementedError("KDF not implemented: {0}".format(kdf))
address_hex = private_key_to_address(private_key) address_hex = private_key_to_address(private_key)
iv_bytes = os.urandom(Ciphers.aes_iv_len) iv_bytes = os.urandom(Ciphers.aes_iv_len)
@ -124,8 +96,8 @@ def to_dict(private_key_bytes, kdf='scrypt', passphrase=''):
'cipherparams': { 'cipherparams': {
'iv': iv_bytes.hex(), 'iv': iv_bytes.hex(),
}, },
'kdf': kdf, 'kdf': 'scrypt',
'kdfparams': kdfparams, 'kdfparams': default_kdfparams,
'mac': mac.hex(), 'mac': mac.hex(),
} }
@ -140,6 +112,7 @@ def to_dict(private_key_bytes, kdf='scrypt', passphrase=''):
def from_dict(o, passphrase=''): def from_dict(o, passphrase=''):
cipher = o['crypto']['cipher'] cipher = o['crypto']['cipher']
if cipher not in algo_keywords: if cipher not in algo_keywords:
raise NotImplementedError('cipher "{}" not implemented'.format(cipher)) raise NotImplementedError('cipher "{}" not implemented'.format(cipher))
@ -172,6 +145,7 @@ def from_dict(o, passphrase=''):
def from_file(filepath, passphrase=''): def from_file(filepath, passphrase=''):
f = open(filepath, 'r') f = open(filepath, 'r')
try: try:
o = json.load(f) o = json.load(f)

View File

@ -16,8 +16,6 @@ from funga.eth.keystore.keyfile import (
from_file, from_file,
to_dict, to_dict,
) )
from funga.eth.encoding import ( from funga.eth.encoding import (
private_key_to_address, private_key_to_address,
private_key_from_bytes, private_key_from_bytes,
@ -30,7 +28,6 @@ logg = logging.getLogger()
argparser = argparse.ArgumentParser() argparser = argparse.ArgumentParser()
argparser.add_argument('-d', '--decrypt', dest='d', type=str, help='decrypt file') argparser.add_argument('-d', '--decrypt', dest='d', type=str, help='decrypt file')
argparser.add_argument('--private-key', dest='private_key', action='store_true', help='output private key instead of address') argparser.add_argument('--private-key', dest='private_key', action='store_true', help='output private key instead of address')
argparser.add_argument('-0', dest='nonl', action='store_true', help='no newline at end of output')
argparser.add_argument('-z', action='store_true', help='zero-length password') argparser.add_argument('-z', action='store_true', help='zero-length password')
argparser.add_argument('-k', type=str, help='load key from file') argparser.add_argument('-k', type=str, help='load key from file')
argparser.add_argument('-v', action='store_true', help='be verbose') argparser.add_argument('-v', action='store_true', help='be verbose')
@ -80,12 +77,10 @@ def main():
else: else:
pk_bytes = os.urandom(32) pk_bytes = os.urandom(32)
pk = coincurve.PrivateKey(secret=pk_bytes) pk = coincurve.PrivateKey(secret=pk_bytes)
o = to_dict(pk_bytes, passphrase=passphrase) o = to_dict(pk_bytes, passphrase)
r = json.dumps(o) r = json.dumps(o)
if not args.nonl: print(r)
r += "\n"
sys.stdout.write(r)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -23,7 +23,6 @@ argparser = argparse.ArgumentParser()
argparser.add_argument('-f', type=str, help='Keyfile to use for signing') argparser.add_argument('-f', type=str, help='Keyfile to use for signing')
argparser.add_argument('-z', action='store_true', help='zero-length password') argparser.add_argument('-z', action='store_true', help='zero-length password')
argparser.add_argument('-v', action='store_true', help='be verbose') argparser.add_argument('-v', action='store_true', help='be verbose')
argparser.add_argument('-0', dest='nonl', action='store_true', help='no newline at end of output')
argparser.add_argument('msg', type=str, help='Message to sign') argparser.add_argument('msg', type=str, help='Message to sign')
args = argparser.parse_args() args = argparser.parse_args()
@ -43,11 +42,7 @@ def main():
signer = EIP155Signer(keystore) signer = EIP155Signer(keystore)
sig = signer.sign_ethereum_message(address, args.msg.encode('utf-8').hex(), password=passphrase) sig = signer.sign_ethereum_message(address, args.msg.encode('utf-8').hex(), password=passphrase)
sys.stdout.write(sig.hex())
r = sig.hex()
if not args.nonl:
r += "\n"
sys.stdout.write(r)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -33,7 +33,7 @@ f.close()
setup( setup(
name="funga-eth", name="funga-eth",
version="0.5.4b2", version="0.5.4b1",
description="Ethereum implementation of the funga keystore and signer", description="Ethereum implementation of the funga keystore and signer",
author="Louis Holbrook", author="Louis Holbrook",
author_email="dev@holbrook.no", author_email="dev@holbrook.no",

View File

@ -5,18 +5,10 @@ import os
# external imports # external imports
from hexathon import strip_0x from hexathon import strip_0x
from pathlib import Path
import sys
path_root = Path('/home/vincent/ida/grassroots/funga-eth/funga/eth/keystore')
sys.path.append(str(path_root))
print(sys.path)
# local imports # local imports
from funga.eth.signer import EIP155Signer from funga.eth.signer import EIP155Signer
from funga.eth.keystore.dict import DictKeystore from funga.eth.keystore.dict import DictKeystore
from funga.eth.cli.handle import SignRequestHandler from funga.eth.cli.handle import SignRequestHandler
from funga.eth.transaction import EIP155Transaction from funga.eth.transaction import EIP155Transaction
@ -26,7 +18,6 @@ logg = logging.getLogger()
script_dir = os.path.dirname(os.path.realpath(__file__)) script_dir = os.path.dirname(os.path.realpath(__file__))
data_dir = os.path.join(script_dir, 'testdata') data_dir = os.path.join(script_dir, 'testdata')
class TestCli(unittest.TestCase): class TestCli(unittest.TestCase):
def setUp(self): def setUp(self):
@ -38,6 +29,7 @@ class TestCli(unittest.TestCase):
SignRequestHandler.signer = self.signer SignRequestHandler.signer = self.signer
self.handler = SignRequestHandler() self.handler = SignRequestHandler()
def test_new_account(self): def test_new_account(self):
q = { q = {
'id': 0, 'id': 0,
@ -47,9 +39,9 @@ class TestCli(unittest.TestCase):
(rpc_id, result) = self.handler.process_input(q) (rpc_id, result) = self.handler.process_input(q)
self.assertTrue(self.keystore.get(result)) self.assertTrue(self.keystore.get(result))
def test_sign_tx(self): def test_sign_tx(self):
keystore_file = os.path.join(data_dir, keystore_file = os.path.join(data_dir, 'UTC--2021-01-08T18-37-01.187235289Z--00a329c0648769a73afac7f9381e08fb43dbea72')
'UTC--2021-01-08T18-37-01.187235289Z--00a329c0648769a73afac7f9381e08fb43dbea72')
sender = self.keystore.import_keystore_file(keystore_file) sender = self.keystore.import_keystore_file(keystore_file)
tx_hexs = { tx_hexs = {
'nonce': '0x', 'nonce': '0x',
@ -77,14 +69,10 @@ class TestCli(unittest.TestCase):
(rpc_id, result) = self.handler.process_input(q) (rpc_id, result) = self.handler.process_input(q)
logg.debug('result {}'.format(result)) logg.debug('result {}'.format(result))
self.assertEqual(strip_0x(result), self.assertEqual(strip_0x(result), 'f86c2a8504a817c8008252089435353535353535353535353535353535353535358203e884deadbeef82466aa0b7c1bbf52f736ada30fe253c7484176f44d6fd097a9720dc85ae5bbc7f060e54a07afee2563b0cf6d00333df51cc62b0d13c63108b2bce54ce2ad24e26ce7b4f25')
'f86c2a8504a817c8008252089435353535353535353535353535353535353535358203e884deadbeef82466aa0b7c1bbf52f736ada30fe253c7484176f44d6fd097a9720dc85ae5bbc7f060e54a07afee2563b0cf6d00333df51cc62b0d13c63108b2bce54ce2ad24e26ce7b4f25')
def test_sign_msg(self): def test_sign_msg(self):
keystore_file = os.path.join(data_dir, keystore_file = os.path.join(data_dir, 'UTC--2021-01-08T18-37-01.187235289Z--00a329c0648769a73afac7f9381e08fb43dbea72')
'UTC--2021-01-08T18-37-01.187235289Z--00a329c0648769a73afac7f9381e08fb43dbea72')
sender = self.keystore.import_keystore_file(keystore_file) sender = self.keystore.import_keystore_file(keystore_file)
q = { q = {
'id': 0, 'id': 0,
@ -93,8 +81,7 @@ class TestCli(unittest.TestCase):
} }
(rpc_id, result) = self.handler.process_input(q) (rpc_id, result) = self.handler.process_input(q)
logg.debug('result msg {}'.format(result)) logg.debug('result msg {}'.format(result))
self.assertEqual(strip_0x(result), self.assertEqual(strip_0x(result), '50320dda75190a121b7b5979de66edadafd02bdfbe4f6d49552e79c01410d2464aae35e385c0e5b61663ff7b44ef65fa0ac7ad8a57472cf405db399b9dba3e1600')
'50320dda75190a121b7b5979de66edadafd02bdfbe4f6d49552e79c01410d2464aae35e385c0e5b61663ff7b44ef65fa0ac7ad8a57472cf405db399b9dba3e1600')
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -1,59 +0,0 @@
#!/usr/bin/python
# standard imports
import unittest
import logging
import base64
import os
# external imports
from hexathon import (
strip_0x,
add_0x,
)
# local imports
from funga.error import UnknownAccountError
from funga.eth.keystore.dict import DictKeystore
from funga.eth.signer import EIP155Signer
logging.basicConfig(level=logging.DEBUG)
logg = logging.getLogger()
script_dir = os.path.realpath(os.path.dirname(__file__))
class TestDict(unittest.TestCase):
address_hex = None
db = None
def setUp(self):
self.db = DictKeystore()
keystore_filepath = os.path.join(script_dir, 'testdata',
'UTC--2022-01-24T10-34-04Z--cc47ad90-71a0-7fbe-0224-63326e27263a')
address_hex = self.db.import_keystore_file(keystore_filepath, 'test')
self.address_hex = add_0x(address_hex)
def tearDown(self):
pass
def test_get_key(self):
logg.debug('getting {}'.format(strip_0x(self.address_hex)))
pk = self.db.get(strip_0x(self.address_hex), '')
self.assertEqual(self.address_hex.lower(), '0xb8df77e1b4fa142e83bf9706f66fd76ad2a564f8')
bogus_account = os.urandom(20).hex()
with self.assertRaises(UnknownAccountError):
self.db.get(bogus_account, '')
def test_sign_message(self):
s = EIP155Signer(self.db)
z = s.sign_ethereum_message(strip_0x(self.address_hex), b'foo')
logg.debug('zzz {}'.format(str(z)))
if __name__ == '__main__':
unittest.main()

View File

@ -1 +0,0 @@
{"id":"cc47ad90-71a0-7fbe-0224-63326e27263a","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"7bff67c888a9878a88e8548a4598322d"},"ciphertext":"0cb0e3c69d224d0a645f2784b64f507e5aecdc7bb8a7ea31963d25e6b8020ccf","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"02f8b51b07a66a357c2d812952e6bee70fccc2e6a55e7cbd5c22d97d32fa8873"},"mac":"bb45aaabdb9fbbbde89631444ac39f8d76107381f16591799664274fd5d8c5bb"},"address":"b8df77e1b4fa142e83bf9706f66fd76ad2a564f8","name":"","meta":"{}"}