"""
pyacryl2.utils.address_generator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Address generator for Acryl blockchain
"""
import hashlib
import struct
import base58
import sha3
import axolotl_curve25519
from mnemonic import Mnemonic
from pyacryl2.client import DEFAULT_NODE_ADDRESS, DEFAULT_CHAIN_ID
from pyacryl2.utils.address import AcrylAddress, DEFAULT_ADDRESS_VERSION
from pyacryl2.utils.async_address import AcrylAsyncAddress
[docs]class AcrylAddressGenerator:
"""
Acryl address generator
:param node_address: node API URL
:type node_address: str
:param async_address: return address with async client instead of default sync
:type async_address: bool
"""
def __init__(self, node_address=DEFAULT_NODE_ADDRESS, async_address=False):
self.node_address = node_address
self.async_address = async_address
[docs] def generate(self, value=None, private_key=None, public_key=None, seed=None, chain_id=DEFAULT_CHAIN_ID, nonce=0,
version=DEFAULT_ADDRESS_VERSION, online=True, client_request_params=None):
"""
Generate address
- if there is no seed and private key is specified then generate address value and public key
- if there is no seed and public key is specified then generate address value
- if there is no seed and value is specified with any key then validate them and return address object
- if there is no seed and no any key but value is specified validation or generation is not performed,\
address object would be returned with the same value and chain_id
:param value: address string value in base58
:param private_key: private key string value in base58
:param public_key: private key string value in base58
:param seed: seed
:param chain_id: chain id value
:param nonce: nonce
:param version: address version
:param online: if True send requests else return request params dict
:param: client_request_params:
:return: AcrylAddress or AsyncAcrylAddress object with different attributes
:rtype: AcrylAddress or AcrylAsyncAddress
"""
address_class = AcrylAddress if not self.async_address else AcrylAsyncAddress
if not seed and value and any((private_key, public_key)):
address_data = self.validate_address(value, private_key, public_key, chain_id)
return address_class(
address_data["value"], address_data["private_key"] if "private_key" in address_data else None,
address_data["public_key"] if "public_key" in address_data else None,
chain_id, nonce, node_address=self.node_address, online=online,
client_request_params=client_request_params
)
if not seed and not private_key and not public_key and value:
return address_class(
value, chain_id=chain_id, node_address=self.node_address, online=online,
client_request_params=client_request_params
)
if not seed and private_key:
address_data = self.generate_from_private_key(private_key, chain_id, version)
return address_class(
address_data["value"], private_key, address_data["public_key"], chain_id=address_data["chain_id"],
node_address=self.node_address, online=online, client_request_params=client_request_params
)
if not seed and public_key:
address_data = self.generate_from_public_key(public_key, chain_id, version)
return address_class(
address_data["value"], public_key=address_data["public_key"], chain_id=address_data["chain_id"],
node_address=self.node_address, online=online, client_request_params=client_request_params
)
if seed:
address_data = self.generate_from_seed(seed, chain_id, nonce, version)
return address_class(
address_data["value"], address_data["private_key"], address_data["public_key"], address_data["seed"],
address_data["chain_id"], nonce, node_address=self.node_address, online=online,
client_request_params=client_request_params
)
new_seed = self.generate_seed()
address_data = self.generate_from_seed(new_seed, chain_id, nonce, version)
return address_class(
address_data["value"], address_data["private_key"], address_data["public_key"], address_data["seed"],
address_data["chain_id"], nonce, node_address=self.node_address, online=online,
client_request_params=client_request_params
)
[docs] def validate_address(self, value, private_key=None, public_key=None, chain_id=DEFAULT_CHAIN_ID, version=None):
"""
Validate address. If address data is valid returns it else raises error
:param value: address value
:param chain_id: Acryl chain id
:param private_key: address private key
:param public_key: address public key
:param version: address version
:return: data for address object creation
:rtype: dict
:raises: ValueError
"""
if private_key:
address_data = self.generate_from_private_key(private_key, chain_id, version)
if address_data["value"] != value:
raise ValueError("Incorrect address for private key")
if public_key:
if address_data["public_key"] != public_key:
raise ValueError("Incorrect public key for private key")
return address_data
if public_key:
address_data = self.generate_from_public_key(public_key, chain_id, version)
if address_data["value"] != value:
raise ValueError("Incorrect address for public key")
return address_data
raise ValueError('No private key or public key provided')
[docs] def generate_from_private_key(self, private_key, chain_id, version):
"""
Generate address from private key (address value, public key)
:param private_key:
:param chain_id:
:return: data for address object creation
:rtype: dict
"""
decoded_private_key = base58.b58decode(private_key)
public_key = axolotl_curve25519.generatePublicKey(decoded_private_key)
raw_address = chr(version) + chain_id + self._hash_bytes(public_key)[0:20]
address_digest = self._hash_bytes(raw_address.encode('latin-1'))[0:4]
address_data = dict()
address_data["value"] = base58.b58encode((raw_address + address_digest).encode('latin-1')).decode()
address_data["public_key"] = base58.b58encode(public_key).decode()
address_data["private_key"] = private_key
address_data["chain_id"] = chain_id
return address_data
[docs] def generate_from_public_key(self, public_key, chain_id, version):
"""
Generate address from public key (address value only)
:param public_key: public key in base58
:param chain_id: chain id
:return: data for address object creation
:rtype: dict
"""
decoded_public_key = base58.b58decode(public_key)
raw_address = chr(version) + chain_id + self._hash_bytes(decoded_public_key)[0:20]
address_digest = self._hash_bytes(raw_address.encode('latin-1'))[0:4]
address_data = dict()
address_data["value"] = base58.b58encode((raw_address + address_digest).encode('latin-1')).decode()
address_data["public_key"] = public_key
address_data["chain_id"] = chain_id
return address_data
[docs] @classmethod
def generate_from_seed(cls, seed, chain_id, nonce, version):
"""
Generate address from seed (address value, private and public keys)
:return: data for address object creation
:rtype: dict
"""
private_key = cls.generate_private_key(seed, nonce)
public_key = axolotl_curve25519.generatePublicKey(private_key)
raw_address = chr(version) + chain_id + cls._hash_bytes(public_key)[0:20]
address_digest = cls._hash_bytes(raw_address.encode('latin-1'))[0:4]
address_data = dict()
address_data["value"] = base58.b58encode((raw_address + address_digest).encode('latin-1')).decode()
address_data["public_key"] = base58.b58encode(public_key).decode()
address_data["private_key"] = base58.b58encode(private_key).decode()
address_data["seed"] = seed
address_data["chain_id"] = chain_id
return address_data
[docs] @staticmethod
def generate_seed(language=None, strength=None):
"""
Generate seed
:return: seed string
:rtype: str
"""
mnemonic_object = Mnemonic(language or "english")
seed = mnemonic_object.generate(strength=strength or 160)
return seed
[docs] @classmethod
def generate_private_key(cls, seed, nonce=0):
"""
Generate private key from seed
:param seed: seed value
:param nonce: nonce value
:return:
"""
decoded_seed = seed.encode('latin-1')
nonce_bytes = struct.pack(">L", nonce)
seed_keccak_digest = cls._hash_bytes(nonce_bytes + decoded_seed)
seed_sha256_hash = hashlib.sha256(seed_keccak_digest.encode('latin-1')).digest()
private_key = axolotl_curve25519.generatePrivateKey(seed_sha256_hash)
return private_key
@staticmethod
def _hash_bytes(bytes_object):
"""
Hash bytes with BLAKE and KECCAK algorithms
:param bytes_object: bytes to hash
:type bytes_object: bytes
:return: keccak hash digest
:rtype
"""
blake_digest = hashlib.blake2b(bytes_object, digest_size=32).digest()
keccak_digest = sha3.keccak_256(blake_digest).digest().decode('latin-1')
return keccak_digest