Source code for graphenebase.account

# -*- coding: utf-8 -*-
from __future__ import absolute_import

import hashlib
import re
import os

from binascii import hexlify, unhexlify
from .base58 import ripemd160, Base58, doublesha256
from .dictionary import words as BrainKeyDictionary
from .utils import _bytes
from .prefix import Prefix

import ecdsa


class PasswordKey(Prefix):
    """ This class derives a private key given the account name, the
        role and a password. It leverages the technology of Brainkeys
        and allows people to have a secure private key by providing a
        passphrase only.
    """

    def __init__(self, account, password, role="active", prefix=None):
        self.set_prefix(prefix)
        self.account = account
        self.role = role
        self.password = password

[docs] def get_private(self): """ Derive private key from the brain key and the current sequence number """ a = _bytes(self.account + self.role + self.password) s = hashlib.sha256(a).digest() return PrivateKey(hexlify(s).decode("ascii"), prefix=self.prefix)
[docs] def get_public(self): return self.get_private().pubkey
[docs] def get_private_key(self): return self.get_private()
[docs] def get_public_key(self): return self.get_public()
class BrainKey(Prefix): """Brainkey implementation similar to the graphene-ui web-wallet. :param str brainkey: Brain Key :param int sequence: Sequence number for consecutive keys Keys in Graphene are derived from a seed brain key which is a string of 16 words out of a predefined dictionary with 49744 words. It is a simple single-chain key derivation scheme that is not compatible with BIP44 but easy to use. Given the brain key, a private key is derived as:: privkey = SHA256(SHA512(brainkey + " " + sequence)) Incrementing the sequence number yields a new key that can be regenerated given the brain key. """ def __init__(self, brainkey=None, sequence=0, prefix=None): self.set_prefix(prefix) if not brainkey: self.brainkey = BrainKey.suggest() else: self.brainkey = self.normalize(brainkey).strip() self.sequence = sequence def __next__(self): """ Get the next private key (sequence number increment) for iterators """ return self.next_sequence()
[docs] def next_sequence(self): """ Increment the sequence number by 1 """ self.sequence += 1 return self
[docs] def normalize(self, brainkey): """ Correct formating with single whitespace syntax and no trailing space """ return " ".join(re.compile("[\t\n\v\f\r ]+").split(brainkey))
[docs] def get_brainkey(self): """ Return brain key of this instance """ return self.normalize(self.brainkey)
[docs] def get_private(self): """ Derive private key from the brain key and the current sequence number """ encoded = "%s %d" % (self.brainkey, self.sequence) a = _bytes(encoded) s = hashlib.sha256(hashlib.sha512(a).digest()).digest() return PrivateKey(hexlify(s).decode("ascii"), prefix=self.prefix)
[docs] def get_blind_private(self): """ Derive private key from the brain key (and no sequence number) """ a = _bytes(self.brainkey) return PrivateKey(hashlib.sha256(a).hexdigest(), prefix=self.prefix)
[docs] def get_public(self): return self.get_private().pubkey
[docs] def get_private_key(self): return self.get_private()
[docs] def get_public_key(self): return self.get_public()
[docs] @staticmethod def suggest(): """ Suggest a new random brain key. Randomness is provided by the operating system using ``os.urandom()``. """ word_count = 16 brainkey = [None] * word_count dict_lines = BrainKeyDictionary.split(",") assert len(dict_lines) == 49744 for j in range(0, word_count): num = int.from_bytes(os.urandom(2), byteorder="little") rndMult = num / 2 ** 16 # returns float between 0..1 (inclusive) wIdx = round(len(dict_lines) * rndMult) brainkey[j] = dict_lines[wIdx] return " ".join(brainkey).upper()
class Address(Prefix): """ Address class This class serves as an address representation for Public Keys. :param str address: Base58 encoded address (defaults to ``None``) :param str pubkey: Base58 encoded pubkey (defaults to ``None``) :param str prefix: Network prefix (defaults to ``GPH``) Example:: Address("GPHFN9r6VYzBK8EKtMewfNbfiGCr56pHDBFi") """ def __init__(self, address, prefix=None): self.set_prefix(prefix) self._address = Base58(address, prefix=self.prefix)
[docs] @classmethod def from_pubkey(cls, pubkey, compressed=True, version=56, prefix=None): """ Load an address provided the public key. Version: 56 => PTS """ # Ensure this is a public key pubkey = PublicKey(pubkey, prefix=prefix or Prefix.prefix) if compressed: pubkey_plain = pubkey.compressed() else: pubkey_plain = pubkey.uncompressed() sha = hashlib.sha256(unhexlify(pubkey_plain)).hexdigest() rep = hexlify(ripemd160(sha)).decode("ascii") s = ("%.2x" % version) + rep result = s + hexlify(doublesha256(s)[:4]).decode("ascii") result = hexlify(ripemd160(result)).decode("ascii") return cls(result, prefix=pubkey.prefix)
def __repr__(self): """ Gives the hex representation of the ``GrapheneBase58CheckEncoded`` Graphene address. """ return repr(self._address) def __str__(self): """ Returns the readable Graphene address. This call is equivalent to ``format(Address, "GPH")`` """ return format(self._address, self.prefix) def __format__(self, _format): """ May be issued to get valid "MUSE", "PLAY" or any other Graphene compatible address with corresponding prefix. """ return format(self._address, _format) def __bytes__(self): """ Returns the raw content of the ``Base58CheckEncoded`` address """ return bytes(self._address) class GrapheneAddress(Address): """ Graphene Addresses are different. Hence we have a different class """ @classmethod def from_pubkey(cls, pubkey, compressed=True, version=56, prefix=None): # Ensure this is a public key pubkey = PublicKey(pubkey, prefix=prefix or Prefix.prefix) if compressed: pubkey_plain = pubkey.compressed() else: pubkey_plain = pubkey.uncompressed() """ Derive address using ``RIPEMD160(SHA512(x))`` """ addressbin = ripemd160(hashlib.sha512(unhexlify(pubkey_plain)).hexdigest()) result = Base58(hexlify(addressbin).decode("ascii")) return cls(result, prefix=pubkey.prefix) class PublicKey(Prefix): """ This class deals with Public Keys and inherits ``Address``. :param str pk: Base58 encoded public key :param str prefix: Network prefix (defaults to ``GPH``) Example::: PublicKey("GPH6UtYWWs3rkZGV8JA86qrgkG6tyFksgECefKE1MiH4HkLD8PFGL") .. note:: By default, graphene-based networks deal with **compressed** public keys. If an **uncompressed** key is required, the method ``unCompressed`` can be used:: PublicKey("xxxxx").unCompressed() """ def __init__(self, pk, prefix=None): self.set_prefix(prefix) if isinstance(pk, PublicKey): pk = format(pk, self.prefix) if str(pk).startswith("04"): # We only ever deal with compressed keys, so let's make it # compressed order = ecdsa.SECP256k1.order p = ecdsa.VerifyingKey.from_string( unhexlify(pk[2:]), curve=ecdsa.SECP256k1 ).pubkey.point x_str = ecdsa.util.number_to_string(p.x(), order) pk = hexlify(chr(2 + (p.y() & 1)).encode("ascii") + x_str).decode("ascii") self._pk = Base58(pk, prefix=self.prefix) @property def pubkey(self): return self._pk @property def compressed_key(self): return PublicKey(self.compressed()) def _derive_y_from_x(self, x, is_even): """ Derive y point from x point """ curve = ecdsa.SECP256k1.curve # The curve equation over F_p is: # y^2 = x^3 + ax + b a, b, p = curve.a(), curve.b(), curve.p() alpha = (pow(x, 3, p) + a * x + b) % p beta = ecdsa.numbertheory.square_root_mod_prime(alpha, p) if (beta % 2) == is_even: beta = p - beta return beta
[docs] def compressed(self): """ returns the compressed key """ return repr(self._pk)
[docs] def uncompressed(self): """ Derive uncompressed key """ public_key = repr(self._pk) prefix = public_key[0:2] assert prefix == "02" or prefix == "03" x = int(public_key[2:], 16) y = self._derive_y_from_x(x, (prefix == "02")) key = "04" + "%064x" % x + "%064x" % y return key
[docs] def point(self): """ Return the point for the public key """ string = unhexlify(self.unCompressed()) return ecdsa.VerifyingKey.from_string( string[1:], curve=ecdsa.SECP256k1 ).pubkey.point
[docs] def child(self, offset256): """ Derive new public key from this key and a sha256 "offset" """ a = bytes(self) + offset256 s = hashlib.sha256(a).digest() return self.add(s)
[docs] def add(self, digest256): """ Derive new public key from this key and a sha256 "digest" """ from .ecdsa import tweakaddPubkey return tweakaddPubkey(self, digest256)
[docs] @classmethod def from_privkey(cls, privkey, prefix=None): """ Derive uncompressed public key """ privkey = PrivateKey(privkey, prefix=prefix or Prefix.prefix) secret = unhexlify(repr(privkey)) order = ecdsa.SigningKey.from_string( secret, curve=ecdsa.SECP256k1 ).curve.generator.order() p = ecdsa.SigningKey.from_string( secret, curve=ecdsa.SECP256k1 ).verifying_key.pubkey.point x_str = ecdsa.util.number_to_string(p.x(), order) # y_str = ecdsa.util.number_to_string(p.y(), order) compressed = hexlify(chr(2 + (p.y() & 1)).encode("ascii") + x_str).decode( "ascii" ) # uncompressed = hexlify( # chr(4).encode('ascii') + x_str + y_str).decode('ascii') return cls(compressed, prefix=prefix or Prefix.prefix)
def __repr__(self): """ Gives the hex representation of the Graphene public key. """ return repr(self._pk) def __str__(self): """ Returns the readable Graphene public key. This call is equivalent to ``format(PublicKey, "GPH")`` """ return format(self._pk, self.prefix) def __format__(self, _format): """ Formats the instance of:doc:`Base58 <base58>` according to ``_format`` """ return format(self._pk, _format) def __bytes__(self): """ Returns the raw public key (has length 33)""" return bytes(self._pk) def __lt__(self, other): """ For sorting of public keys (due to graphene), we actually sort according to addresses """ assert isinstance(other, PublicKey) return repr(self.address) < repr(other.address)
[docs] def unCompressed(self): """ Alias for self.uncompressed() - LEGACY""" return self.uncompressed()
@property def address(self): """ Obtain a GrapheneAddress from a public key """ return GrapheneAddress.from_pubkey(repr(self), prefix=self.prefix) class PrivateKey(Prefix): """ Derives the compressed and uncompressed public keys and constructs two instances of ``PublicKey``: :param str wif: Base58check-encoded wif key :param str prefix: Network prefix (defaults to ``GPH``) Example::: PrivateKey("5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd") Compressed vs. Uncompressed: * ``PrivateKey("w-i-f").pubkey``: Instance of ``PublicKey`` using compressed key. * ``PrivateKey("w-i-f").pubkey.address``: Instance of ``Address`` using compressed key. * ``PrivateKey("w-i-f").uncompressed``: Instance of ``PublicKey`` using uncompressed key. * ``PrivateKey("w-i-f").uncompressed.address``: Instance of ``Address`` using uncompressed key. """ def __init__(self, wif=None, prefix=None): self.set_prefix(prefix) if wif is None: import os self._wif = Base58(hexlify(os.urandom(32)).decode("ascii")) elif isinstance(wif, PrivateKey): self._wif = wif._wif elif isinstance(wif, Base58): self._wif = wif else: self._wif = Base58(wif) # test for valid key by trying to obtain a public key assert len(repr(self._wif)) == 64 @property def bitcoin(self): return BitcoinPublicKey.from_privkey(self) @property def address(self): return Address.from_pubkey(self.pubkey, prefix=self.prefix) @property def pubkey(self): return self.compressed @property def compressed(self): return PublicKey.from_privkey(self, prefix=self.prefix) @property def uncompressed(self): return PublicKey(self.pubkey.uncompressed(), prefix=self.prefix)
[docs] def get_secret(self): """ Get sha256 digest of the wif key. """ return hashlib.sha256(bytes(self)).digest()
[docs] def derive_private_key(self, sequence): """ Derive new private key from this private key and an arbitrary sequence number """ encoded = "%s %d" % (str(self), sequence) a = bytes(encoded, "ascii") s = hashlib.sha256(hashlib.sha512(a).digest()).digest() return PrivateKey(hexlify(s).decode("ascii"), prefix=self.pubkey.prefix)
[docs] def child(self, offset256): """ Derive new private key from this key and a sha256 "offset" """ a = bytes(self.pubkey) + offset256 s = hashlib.sha256(a).digest() return self.derive_from_seed(s)
[docs] def derive_from_seed(self, offset): """ Derive private key using "generate_from_seed" method. Here, the key itself serves as a `seed`, and `offset` is expected to be a sha256 digest. """ seed = int(hexlify(bytes(self)).decode("ascii"), 16) z = int(hexlify(offset).decode("ascii"), 16) order = ecdsa.SECP256k1.order secexp = (seed + z) % order secret = "%0x" % secexp if len(secret) < 64: # left-pad with zeroes secret = ("0" * (64 - len(secret))) + secret return PrivateKey(secret, prefix=self.pubkey.prefix)
def __format__(self, _format): """ Formats the instance of:doc:`Base58 <base58>` according to ``_format`` """ return format(self._wif, _format) def __repr__(self): """ Gives the hex representation of the Graphene private key.""" return repr(self._wif) def __str__(self): """ Returns the readable (uncompressed wif format) Graphene private key. This call is equivalent to ``format(PrivateKey, "WIF")`` """ return format(self._wif, "WIF") def __bytes__(self): # pragma: no cover """ Returns the raw private key """ return bytes(self._wif) class BitcoinAddress(Address): @classmethod def from_pubkey(cls, pubkey, compressed=False, version=56, prefix=None): # Ensure this is a public key pubkey = PublicKey(pubkey) if compressed: pubkey = pubkey.compressed() else: pubkey = pubkey.uncompressed() """ Derive address using ``RIPEMD160(SHA256(x))`` """ addressbin = ripemd160(hexlify(hashlib.sha256(unhexlify(pubkey)).digest())) return cls(hexlify(addressbin).decode("ascii")) def __str__(self): """ Returns the readable Graphene address. This call is equivalent to ``format(Address, "GPH")`` """ return format(self._address, "BTC") class BitcoinPublicKey(PublicKey): @property def address(self): return BitcoinAddress.from_pubkey(repr(self))