working sync

This commit is contained in:
Lex Berezhny 2019-03-11 12:04:06 -04:00
parent 3a1cd8985c
commit b07ca20e0e
3 changed files with 62 additions and 10 deletions

View file

@ -1,3 +1,4 @@
import json
import asyncio import asyncio
import random import random
import typing import typing
@ -5,7 +6,7 @@ from typing import Dict, Tuple, Type, Optional, Any, List
from torba.client.mnemonic import Mnemonic from torba.client.mnemonic import Mnemonic
from torba.client.bip32 import PrivateKey, PubKey, from_extended_key_string from torba.client.bip32 import PrivateKey, PubKey, from_extended_key_string
from torba.client.hash import aes_encrypt, aes_decrypt from torba.client.hash import aes_encrypt, aes_decrypt, sha256
from torba.client.constants import COIN from torba.client.constants import COIN
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
@ -235,7 +236,8 @@ class BaseAccount:
) )
@classmethod @classmethod
def from_dict(cls, ledger: 'baseledger.BaseLedger', wallet: 'basewallet.Wallet', d: dict): def keys_from_dict(cls, ledger: 'baseledger.BaseLedger', d: dict) \
-> Tuple[str, Optional[PrivateKey], PubKey]:
seed = d.get('seed', '') seed = d.get('seed', '')
private_key_string = d.get('private_key', '') private_key_string = d.get('private_key', '')
private_key = None private_key = None
@ -250,6 +252,11 @@ class BaseAccount:
public_key = private_key.public_key public_key = private_key.public_key
if public_key is None: if public_key is None:
public_key = from_extended_key_string(ledger, d['public_key']) public_key = from_extended_key_string(ledger, d['public_key'])
return seed, private_key, public_key
@classmethod
def from_dict(cls, ledger: 'baseledger.BaseLedger', wallet: 'basewallet.Wallet', d: dict):
seed, private_key, public_key = cls.keys_from_dict(ledger, d)
name = d.get('name') name = d.get('name')
if not name: if not name:
name = 'Account #{}'.format(public_key.address) name = 'Account #{}'.format(public_key.address)
@ -258,8 +265,8 @@ class BaseAccount:
wallet=wallet, wallet=wallet,
name=name, name=name,
seed=seed, seed=seed,
private_key_string=private_key_string, private_key_string=d.get('private_key', ''),
encrypted=encrypted, encrypted=d.get('encrypted', False),
private_key=private_key, private_key=private_key,
public_key=public_key, public_key=public_key,
address_generator=d.get('address_generator', {}) address_generator=d.get('address_generator', {})
@ -273,8 +280,8 @@ class BaseAccount:
assert None not in [self.seed_encryption_init_vector, self.private_key_encryption_init_vector] assert None not in [self.seed_encryption_init_vector, self.private_key_encryption_init_vector]
private_key_string = aes_encrypt( private_key_string = aes_encrypt(
self.password, private_key_string, self.private_key_encryption_init_vector self.password, private_key_string, self.private_key_encryption_init_vector
) )[0]
seed = aes_encrypt(self.password, self.seed, self.seed_encryption_init_vector) seed = aes_encrypt(self.password, self.seed, self.seed_encryption_init_vector)[0]
return { return {
'ledger': self.ledger.get_id(), 'ledger': self.ledger.get_id(),
'name': self.name, 'name': self.name,
@ -285,6 +292,10 @@ class BaseAccount:
'address_generator': self.address_generator.to_dict(self.receiving, self.change) 'address_generator': self.address_generator.to_dict(self.receiving, self.change)
} }
@property
def hash(self) -> bytes:
return sha256(json.dumps(self.to_dict()).encode())
async def get_details(self, show_seed=False, **kwargs): async def get_details(self, show_seed=False, **kwargs):
satoshis = await self.get_balance(**kwargs) satoshis = await self.get_balance(**kwargs)
details = { details = {
@ -329,10 +340,10 @@ class BaseAccount:
assert not self.encrypted, "Key is already encrypted." assert not self.encrypted, "Key is already encrypted."
assert isinstance(self.private_key, PrivateKey) assert isinstance(self.private_key, PrivateKey)
self.seed = aes_encrypt(password, self.seed, self.seed_encryption_init_vector) self.seed = aes_encrypt(password, self.seed, self.seed_encryption_init_vector)[0]
self.private_key_string = aes_encrypt( self.private_key_string = aes_encrypt(
password, self.private_key.extended_key_string(), self.private_key_encryption_init_vector password, self.private_key.extended_key_string(), self.private_key_encryption_init_vector
) )[0]
self.private_key = None self.private_key = None
self.password = None self.password = None
self.encrypted = True self.encrypted = True

View file

@ -121,7 +121,7 @@ def hex_str_to_hash(x):
return reversed(unhexlify(x)) return reversed(unhexlify(x))
def aes_encrypt(secret: str, value: str, init_vector: bytes = None) -> str: def aes_encrypt(secret: str, value: str, init_vector: bytes = None) -> typing.Tuple[str, bytes]:
if init_vector is not None: if init_vector is not None:
assert len(init_vector) == 16 assert len(init_vector) == 16
else: else:
@ -144,6 +144,25 @@ def aes_decrypt(secret: str, value: str) -> typing.Tuple[str, bytes]:
return result.decode(), init_vector return result.decode(), init_vector
def better_aes_encrypt(secret: str, value: bytes) -> bytes:
init_vector = os.urandom(16)
key = double_sha256(secret.encode())
encryptor = Cipher(AES(key), modes.CBC(init_vector), default_backend()).encryptor()
padder = PKCS7(AES.block_size).padder()
padded_data = padder.update(value) + padder.finalize()
encrypted_data = encryptor.update(padded_data) + encryptor.finalize()
return base64.b64encode(init_vector + encrypted_data)
def better_aes_decrypt(secret: str, value: bytes) -> bytes:
data = base64.b64decode(value)
key = double_sha256(secret.encode())
init_vector, data = data[:16], data[16:]
decryptor = Cipher(AES(key), modes.CBC(init_vector), default_backend()).decryptor()
unpadder = PKCS7(AES.block_size).unpadder()
return unpadder.update(decryptor.update(data)) + unpadder.finalize()
class Base58Error(Exception): class Base58Error(Exception):
""" Exception used for Base58 errors. """ """ Exception used for Base58 errors. """

View file

@ -1,8 +1,12 @@
import os
import stat import stat
import json import json
import os import zlib
import typing import typing
from hashlib import sha256
from typing import Sequence, MutableSequence from typing import Sequence, MutableSequence
from torba.client.hash import better_aes_encrypt, better_aes_decrypt
from operator import attrgetter
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from torba.client import basemanager, baseaccount, baseledger from torba.client import basemanager, baseaccount, baseledger
@ -55,6 +59,24 @@ class Wallet:
for account in self.accounts: for account in self.accounts:
return account return account
@property
def hash(self) -> str:
h = sha256()
for account in sorted(self.accounts, key=attrgetter('id')):
h.update(account.hash)
return h.digest()
def pack(self, password):
new_data = json.dumps(self.to_dict())
new_data_compressed = zlib.compress(new_data.encode())
return better_aes_encrypt(password, new_data_compressed)
@classmethod
def unpack(cls, password, encrypted):
decrypted = better_aes_decrypt(password, encrypted)
decompressed = zlib.decompress(decrypted)
return json.loads(decompressed)
class WalletStorage: class WalletStorage: