def get_default_account_keys(quantity=None): keys = KeyAPI() quantity = quantity or 10 for i in range(1, quantity + 1): pk_bytes = int_to_big_endian(i).rjust(32, b'\x00') private_key = keys.PrivateKey(pk_bytes) yield private_key
def get_default_account_keys(): keys = KeyAPI() for i in range(1, 11): pk_bytes = int_to_big_endian(i).rjust(32, b'\x00') private_key = keys.PrivateKey(pk_bytes) yield private_key
def sign(msg_hash: bytes, priv_key: bytes) -> bytes: ''' Sign the message hash. (not the message itself) Parameters ---------- msg_hash: bytes The message hash. priv_key: bytes The private key in bytes. Returns ------- bytes The signing result. Raises ------ ValueError If the input is malformed. ''' if not _is_valid_private_key(priv_key): raise ValueError('Private Key not valid.') sig = KeyAPI().ecdsa_sign(msg_hash, KeyAPI.PrivateKey(priv_key)) r = sig.r.to_bytes(32, byteorder='big') s = sig.s.to_bytes(32, byteorder='big') v = sig.v.to_bytes(1, byteorder='big') # public key recovery bit. return b''.join([r, s, v]) # 32 + 32 + 1 bytes
def test(): priv = KeyAPI.PrivateKey( bytes.fromhex( "208065a247edbe5df4d86fbdc0171303f23a76961be9f6013850dd2bdc759bbb") ) addr = priv.public_key.to_canonical_address() assert addr == b"\x0b\xedz\xbda$v5\xc1\x97>\xb3\x84t\xa2Qn\xd1\xd8\x84"
def customer_private_key(): from eth_keys import KeyAPI from eth_utils import int_to_big_endian keys = KeyAPI() pk_bytes = int_to_big_endian(2).rjust(32, b'\x00') private_key = keys.PrivateKey(pk_bytes) return private_key
def get_account_keys_from_mnemonic(mnemonic, quantity=None): keys = KeyAPI() seed = seed_from_mnemonic(mnemonic, "") quantity = quantity or 10 for i in range(0, quantity): hd_path = HDPath(f"m/44'/60'/0'/{i}") private_key = keys.PrivateKey(hd_path.derive(seed)) yield private_key
def test_signature(self): header = RootBlockHeader() private_key = KeyAPI.PrivateKey(Identity.create_random_identity().get_key()) self.assertEqual(header.signature, bytes(65)) self.assertFalse(header.is_signed()) self.assertFalse(header.verify_signature(private_key.public_key)) header.sign_with_private_key(private_key) self.assertNotEqual(header.signature, bytes(65)) self.assertTrue(header.is_signed()) self.assertTrue(header.verify_signature(private_key.public_key))
def __init__(self, private_key): """Work with private key. Getting: PublicKey, PublicToAddress Example::: PrivateKey("4d1bc37b069b9f2e975c37770b7c87185dc3a10454e3ea024ce1fce8f3eb78bf") """ _private = unhexlify(bytes(private_key, encoding='utf8')) self._key = KeyAPI.PrivateKey(_private) # Key length must not exceed 64 length assert len(repr(self._key)) != 64
def sign(self, keys): """ Sign the transaction with keys. It doesn't mean the transaction is valid in the chain since it doesn't check whether the tx_input's addresses (recipents) match the keys """ sign_list = [] for key in keys: sig = KeyAPI.PrivateKey(key).sign_msg(self.get_hash_unsigned()) sign_list.append( sig.r.to_bytes(32, byteorder="big") + sig.s.to_bytes(32, byteorder="big") + sig.v.to_bytes(1, byteorder="big")) self.sign_list = sign_list return self
def root_signer_private_key(self) -> Optional[KeyAPI.PrivateKey]: if self._cached_root_signer_private_key: return self._cached_root_signer_private_key # cache miss ret = None if self.ROOT_SIGNER_PRIVATE_KEY: # make sure private key and public key match # noinspection PyCallByClass privkey = KeyAPI.PrivateKey( private_key_bytes=bytes.fromhex(self.ROOT_SIGNER_PRIVATE_KEY)) ret = privkey self._cached_root_signer_private_key = ret return ret
def guardian_private_key(self) -> Optional[KeyAPI.PrivateKey]: if self._cached_guardian_private_key: return self._cached_guardian_private_key # cache miss ret = None if self.GUARDIAN_PRIVATE_KEY: # make sure private key and public key match # noinspection PyCallByClass privkey = KeyAPI.PrivateKey( private_key_bytes=bytes.fromhex(self.GUARDIAN_PRIVATE_KEY)) assert privkey.public_key == self.guardian_public_key ret = privkey self._cached_guardian_private_key = ret return ret
def __init__(self, keyfile=None, password=None, private_key=None, path='./'): if isinstance(password, str): password = password.encode() # create new account if none given if all(p is None for p in [keyfile, private_key]): private_key = os.urandom(32) if password is None: password = os.urandom(10) if len(password): log.info('-' * 15, 'Save this password somewhere', '-' * 15) log.info('<', password.hex(), '>') log.info('-' * 58) else: log.info('-' * 5, '<empty password>', '-' * 5) keyfile_data = create_keyfile_json(private_key, password) # filename = 'keyfile--'+keyfile_data['address'] filename = os.path.join(path, 'keyfile--' + keyfile_data['address']) with open(filename, 'w') as file: file.write(json.dumps(keyfile_data)) # load account from existing keyfile else: if private_key is None: private_key = extract_key_from_keyfile(keyfile, password) # init stuff for this account self.private_key = KeyAPI.PrivateKey(private_key) self.public_key = keys.PublicKey.from_private(self.private_key) self.address = self.public_key.to_address() self.nonce = 0 log.debug('public_key=%s type %s', self.public_key, type(self.public_key)) log.debug('address=%s, type %s', self.address, type(self.address)) """ convert and keep private_key, public_key, signature in to_bytes() """
def address(self): ''' Get the checksummed address of this hd account :returns: the checksummed public address for this account. :rtype : str .. code-block:: python >>> my_account.address "0xF0109fC8DF283027b6285cc889F5aA624EaC1F55" ''' rawtuple = bip32_deserialize(self.__key) key = rawtuple[-1] if rawtuple[0] in PRIVATE: # slice the last byte, since it is the WIF-Compressed information key = KeyAPI.PrivateKey(key[:-1]).public_key else: # remove 04 prefix for KeyAPI key = KeyAPI.PublicKey(decompress(key)[1:]) return key.to_checksum_address()
def __init__(self, private_key: bytes): self.privkey = KeyAPI.PrivateKey(private_key)
class Account(object): """ The primary entry point for working with Ethereum private keys. It does **not** require a connection to an Ethereum node. """ _keys = keys _default_kdf = os.getenv('ETH_ACCOUNT_KDF', 'scrypt') # Enable unaudited features (off by default) _use_unaudited_hdwallet_features = False @classmethod def enable_unaudited_hdwallet_features(cls): """ Use this flag to enable unaudited HD Wallet features. """ cls._use_unaudited_hdwallet_features = True @combomethod def create(self, extra_entropy=''): r""" Creates a new private key, and returns it as a :class:`~eth_account.local.LocalAccount`. :param extra_entropy: Add extra randomness to whatever randomness your OS can provide :type extra_entropy: str or bytes or int :returns: an object with private key and convenience methods .. code-block:: python >>> from eth_account import Account >>> acct = Account.create('KEYSMASH FJAFJKLDSKF7JKFDJ 1530') >>> acct.address '0x5ce9454909639D2D17A3F753ce7d93fa0b9aB12E' >>> acct.key HexBytes('0x8676e9a8c86c8921e922e61e0bb6e9e9689aad4c99082620610b00140e5f21b8') # These methods are also available: sign_message(), sign_transaction(), encrypt() # They correspond to the same-named methods in Account.* # but without the private key argument """ extra_key_bytes = text_if_str(to_bytes, extra_entropy) key_bytes = keccak(os.urandom(32) + extra_key_bytes) return self.from_key(key_bytes) @staticmethod def decrypt(keyfile_json, password): """ Decrypts a private key. The key may have been encrypted using an Ethereum client or :meth:`~Account.encrypt`. :param keyfile_json: The encrypted key :type keyfile_json: dict or str :param str password: The password that was used to encrypt the key :returns: the raw private key :rtype: ~hexbytes.main.HexBytes .. doctest:: python >>> encrypted = { ... 'address': '5ce9454909639d2d17a3f753ce7d93fa0b9ab12e', ... 'crypto': {'cipher': 'aes-128-ctr', ... 'cipherparams': {'iv': '482ef54775b0cc59f25717711286f5c8'}, ... 'ciphertext': 'cb636716a9fd46adbb31832d964df2082536edd5399a3393327dc89b0193a2be', ... 'kdf': 'scrypt', ... 'kdfparams': {}, ... 'kdfparams': {'dklen': 32, ... 'n': 262144, ... 'p': 8, ... 'r': 1, ... 'salt': 'd3c9a9945000fcb6c9df0f854266d573'}, ... 'mac': '4f626ec5e7fea391b2229348a65bfef532c2a4e8372c0a6a814505a350a7689d'}, ... 'id': 'b812f3f9-78cc-462a-9e89-74418aa27cb0', ... 'version': 3} >>> Account.decrypt(encrypted, 'password') HexBytes('0xb25c7db31feed9122727bf0939dc769a96564b2de4c4726d035b36ecf1e5b364') """ if isinstance(keyfile_json, str): keyfile = json.loads(keyfile_json) elif is_dict(keyfile_json): keyfile = keyfile_json else: raise TypeError("The keyfile should be supplied as a JSON string, or a dictionary.") password_bytes = text_if_str(to_bytes, password) return HexBytes(decode_keyfile_json(keyfile, password_bytes)) @classmethod def encrypt(cls, private_key, password, kdf=None, iterations=None): """ Creates a dictionary with an encrypted version of your private key. To import this keyfile into Ethereum clients like geth and parity: encode this dictionary with :func:`json.dumps` and save it to disk where your client keeps key files. :param private_key: The raw private key :type private_key: hex str, bytes, int or :class:`eth_keys.datatypes.PrivateKey` :param str password: The password which you will need to unlock the account in your client :param str kdf: The key derivation function to use when encrypting your private key :param int iterations: The work factor for the key derivation function :returns: The data to use in your encrypted file :rtype: dict If kdf is not set, the default key derivation function falls back to the environment variable :envvar:`ETH_ACCOUNT_KDF`. If that is not set, then 'scrypt' will be used as the default. .. doctest:: python >>> from pprint import pprint >>> encrypted = Account.encrypt( ... 0xb25c7db31feed9122727bf0939dc769a96564b2de4c4726d035b36ecf1e5b364, ... 'password' ... ) >>> pprint(encrypted) {'address': '5ce9454909639d2d17a3f753ce7d93fa0b9ab12e', 'crypto': {'cipher': 'aes-128-ctr', 'cipherparams': {'iv': '...'}, 'ciphertext': '...', 'kdf': 'scrypt', 'kdfparams': {'dklen': 32, 'n': 262144, 'p': 8, 'r': 1, 'salt': '...'}, 'mac': '...'}, 'id': '...', 'version': 3} >>> with open('my-keyfile', 'w') as f: # doctest: +SKIP ... f.write(json.dumps(encrypted)) """ if isinstance(private_key, keys.PrivateKey): key_bytes = private_key.to_bytes() else: key_bytes = HexBytes(private_key) if kdf is None: kdf = cls._default_kdf password_bytes = text_if_str(to_bytes, password) assert len(key_bytes) == 32 return create_keyfile_json(key_bytes, password_bytes, kdf=kdf, iterations=iterations) @combomethod def privateKeyToAccount(self, private_key): """ .. CAUTION:: Deprecated for :meth:`~eth_account.account.Account.from_key`. This method will be removed in v0.5 """ warnings.warn( "privateKeyToAccount is deprecated in favor of from_key", category=DeprecationWarning, ) return self.from_key(private_key) @combomethod def from_key(self, private_key): r""" Returns a convenient object for working with the given private key. :param private_key: The raw private key :type private_key: hex str, bytes, int or :class:`eth_keys.datatypes.PrivateKey` :return: object with methods for signing and encrypting :rtype: LocalAccount .. doctest:: python >>> acct = Account.from_key( ... 0xb25c7db31feed9122727bf0939dc769a96564b2de4c4726d035b36ecf1e5b364) >>> acct.address '0x5ce9454909639D2D17A3F753ce7d93fa0b9aB12E' >>> acct.key HexBytes('0xb25c7db31feed9122727bf0939dc769a96564b2de4c4726d035b36ecf1e5b364') # These methods are also available: sign_message(), sign_transaction(), encrypt() # They correspond to the same-named methods in Account.* # but without the private key argument """ key = self._parsePrivateKey(private_key) return LocalAccount(key, self) @combomethod def from_mnemonic(self, mnemonic: str, passphrase: str = "", account_path: str = ETHEREUM_DEFAULT_PATH): """ Generate an account from a mnemonic. .. CAUTION:: This feature is experimental, unaudited, and likely to change soon :param str mnemonic: space-separated list of BIP39 mnemonic seed words :param str passphrase: Optional passphrase used to encrypt the mnemonic :param str account_path: Specify an alternate HD path for deriving the seed using BIP32 HD wallet key derivation. :return: object with methods for signing and encrypting :rtype: LocalAccount .. doctest:: python >>> from eth_account import Account >>> Account.enable_unaudited_hdwallet_features() >>> acct = Account.from_mnemonic( ... "coral allow abandon recipe top tray caught video climb similar prepare bracket " ... "antenna rubber announce gauge volume hub hood burden skill immense add acid") >>> acct.address '0x9AdA5dAD14d925f4df1378409731a9B71Bc8569d' # These methods are also available: sign_message(), sign_transaction(), encrypt() # They correspond to the same-named methods in Account.* # but without the private key argument """ if not self._use_unaudited_hdwallet_features: raise AttributeError( "The use of the Mnemonic features of Account is disabled by default until " "its API stabilizes. To use these features, please enable them by running " "`Account.enable_unaudited_hdwallet_features()` and try again." ) seed = seed_from_mnemonic(mnemonic, passphrase) private_key = key_from_seed(seed, account_path) key = self._parsePrivateKey(private_key) return LocalAccount(key, self) @combomethod def create_with_mnemonic(self, passphrase: str = "", num_words: int = 12, language: str = "english", account_path: str = ETHEREUM_DEFAULT_PATH): r""" Create a new private key and related mnemonic. .. CAUTION:: This feature is experimental, unaudited, and likely to change soon Creates a new private key, and returns it as a :class:`~eth_account.local.LocalAccount`, alongside the mnemonic that can used to regenerate it using any BIP39-compatible wallet. :param str passphrase: Extra passphrase to encrypt the seed phrase :param int num_words: Number of words to use with seed phrase. Default is 12 words. Must be one of [12, 15, 18, 21, 24]. :param str language: Language to use for BIP39 mnemonic seed phrase. :param str account_path: Specify an alternate HD path for deriving the seed using BIP32 HD wallet key derivation. :returns: A tuple consisting of an object with private key and convenience methods, and the mnemonic seed phrase that can be used to restore the account. :rtype: (LocalAccount, str) .. doctest:: python >>> from eth_account import Account >>> Account.enable_unaudited_hdwallet_features() >>> acct, mnemonic = Account.create_with_mnemonic() >>> acct.address # doctest: +SKIP '0x5ce9454909639D2D17A3F753ce7d93fa0b9aB12E' >>> acct == Account.from_mnemonic(mnemonic) True # These methods are also available: sign_message(), sign_transaction(), encrypt() # They correspond to the same-named methods in Account.* # but without the private key argument """ if not self._use_unaudited_hdwallet_features: raise AttributeError( "The use of the Mnemonic features of Account is disabled by default until " "its API stabilizes. To use these features, please enable them by running " "`Account.enable_unaudited_hdwallet_features()` and try again." ) mnemonic = generate_mnemonic(num_words, language) return self.from_mnemonic(mnemonic, passphrase, account_path), mnemonic @combomethod def recover_message(self, signable_message: SignableMessage, vrs=None, signature=None): r""" Get the address of the account that signed the given message. You must specify exactly one of: vrs or signature :param signable_message: the message that was signed :param vrs: the three pieces generated by an elliptic curve signature :type vrs: tuple(v, r, s), each element is hex str, bytes or int :param signature: signature bytes concatenated as r+s+v :type signature: hex str or bytes or int :returns: address of signer, hex-encoded & checksummed :rtype: str .. doctest:: python >>> from eth_account.messages import encode_defunct >>> from eth_account import Account >>> message = encode_defunct(text="I♥SF") >>> vrs = ( ... 28, ... '0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb3', ... '0x3e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce') >>> Account.recover_message(message, vrs=vrs) '0x5ce9454909639D2D17A3F753ce7d93fa0b9aB12E' # All of these recover calls are equivalent: # variations on vrs >>> vrs = ( ... '0x1c', ... '0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb3', ... '0x3e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce') >>> Account.recover_message(message, vrs=vrs) '0x5ce9454909639D2D17A3F753ce7d93fa0b9aB12E' >>> # Caution about this approach: likely problems if there are leading 0s >>> vrs = ( ... 0x1c, ... 0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb3, ... 0x3e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce) >>> Account.recover_message(message, vrs=vrs) '0x5ce9454909639D2D17A3F753ce7d93fa0b9aB12E' >>> vrs = ( ... b'\x1c', ... b'\xe6\xca\x9b\xbaX\xc8\x86\x11\xfa\xd6jl\xe8\xf9\x96\x90\x81\x95Y8\x07\xc4\xb3\x8b\xd5(\xd2\xcf\xf0\x9dN\xb3', # noqa: E501 ... b'>[\xfb\xbfM>9\xb1\xa2\xfd\x81jv\x80\xc1\x9e\xbe\xba\xf3\xa1A\xb29\x93J\xd4<\xb3?\xce\xc8\xce') # noqa: E501 >>> Account.recover_message(message, vrs=vrs) '0x5ce9454909639D2D17A3F753ce7d93fa0b9aB12E' # variations on signature >>> signature = '0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb33e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce1c' # noqa: E501 >>> Account.recover_message(message, signature=signature) '0x5ce9454909639D2D17A3F753ce7d93fa0b9aB12E' >>> signature = b'\xe6\xca\x9b\xbaX\xc8\x86\x11\xfa\xd6jl\xe8\xf9\x96\x90\x81\x95Y8\x07\xc4\xb3\x8b\xd5(\xd2\xcf\xf0\x9dN\xb3>[\xfb\xbfM>9\xb1\xa2\xfd\x81jv\x80\xc1\x9e\xbe\xba\xf3\xa1A\xb29\x93J\xd4<\xb3?\xce\xc8\xce\x1c' # noqa: E501 >>> Account.recover_message(message, signature=signature) '0x5ce9454909639D2D17A3F753ce7d93fa0b9aB12E' >>> # Caution about this approach: likely problems if there are leading 0s >>> signature = 0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb33e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce1c # noqa: E501 >>> Account.recover_message(message, signature=signature) '0x5ce9454909639D2D17A3F753ce7d93fa0b9aB12E' """ message_hash = _hash_eip191_message(signable_message) return self._recover_hash(message_hash, vrs, signature) @combomethod def recoverHash(self, message_hash, vrs=None, signature=None): """ Get the address of the account that signed the message with the given hash. You must specify exactly one of: vrs or signature .. CAUTION:: Deprecated for :meth:`~eth_account.account.Account.recover_message`. This method might be removed as early as v0.5 :param message_hash: the hash of the message that you want to verify :type message_hash: hex str or bytes or int :param vrs: the three pieces generated by an elliptic curve signature :type vrs: tuple(v, r, s), each element is hex str, bytes or int :param signature: signature bytes concatenated as r+s+v :type signature: hex str or bytes or int :returns: address of signer, hex-encoded & checksummed :rtype: str """ warnings.warn( "recoverHash is deprecated in favor of recover_message", category=DeprecationWarning, ) return self._recover_hash(message_hash, vrs, signature) @combomethod def _recover_hash(self, message_hash, vrs=None, signature=None): hash_bytes = HexBytes(message_hash) if len(hash_bytes) != 32: raise ValueError("The message hash must be exactly 32-bytes") if vrs is not None: v, r, s = map(hexstr_if_str(to_int), vrs) v_standard = to_standard_v(v) signature_obj = self._keys.Signature(vrs=(v_standard, r, s)) elif signature is not None: signature_bytes = HexBytes(signature) signature_bytes_standard = to_standard_signature_bytes(signature_bytes) signature_obj = self._keys.Signature(signature_bytes=signature_bytes_standard) else: raise TypeError("You must supply the vrs tuple or the signature bytes") pubkey = signature_obj.recover_public_key_from_msg_hash(hash_bytes) return pubkey.to_checksum_address() @combomethod def recoverTransaction(self, serialized_transaction): """ .. CAUTION:: Deprecated for :meth:`~eth_account.account.Account.recover_transaction`. This method will be removed in v0.5 """ warnings.warn( "recoverTransaction is deprecated in favor of recover_transaction", category=DeprecationWarning, ) return self.recover_transaction(serialized_transaction) @combomethod def recover_transaction(self, serialized_transaction): """ Get the address of the account that signed this transaction. :param serialized_transaction: the complete signed transaction :type serialized_transaction: hex str, bytes or int :returns: address of signer, hex-encoded & checksummed :rtype: str .. doctest:: python >>> raw_transaction = '0xf86a8086d55698372431831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca008025a009ebb6ca057a0535d6186462bc0b465b561c94a295bdb0621fc19208ab149a9ca0440ffd775ce91a833ab410777204d5341a6f9fa91216a6f3ee2c051fea6a0428' # noqa: E501 >>> Account.recover_transaction(raw_transaction) '0x2c7536E3605D9C16a7a3D7b1898e529396a65c23' """ txn_bytes = HexBytes(serialized_transaction) if len(txn_bytes) > 0 and txn_bytes[0] <= 0x7f: # We are dealing with a typed transaction. typed_transaction = TypedTransaction.from_bytes(txn_bytes) msg_hash = typed_transaction.hash() vrs = typed_transaction.vrs() return self._recover_hash(msg_hash, vrs=vrs) txn = Transaction.from_bytes(txn_bytes) msg_hash = hash_of_signed_transaction(txn) return self._recover_hash(msg_hash, vrs=vrs_from(txn)) def setKeyBackend(self, backend): """ .. CAUTION:: Deprecated for :meth:`~eth_account.account.Account.set_key_backend`. This method will be removed in v0.5 """ warnings.warn( "setKeyBackend is deprecated in favor of set_key_backend", category=DeprecationWarning, ) self.set_key_backend(backend) def set_key_backend(self, backend): """ Change the backend used by the underlying eth-keys library. *(The default is fine for most users)* :param backend: any backend that works in `eth_keys.KeyApi(backend) <https://github.com/ethereum/eth-keys/#keyapibackendnone>`_ """ self._keys = KeyAPI(backend) @combomethod def sign_message(self, signable_message: SignableMessage, private_key): r""" Sign the provided message. This API supports any messaging format that will encode to EIP-191_ messages. If you would like historical compatibility with :meth:`w3.eth.sign() <web3.eth.Eth.sign>` you can use :meth:`~eth_account.messages.encode_defunct`. Other options are the "validator", or "structured data" standards. (Both of these are in *DRAFT* status currently, so be aware that the implementation is not guaranteed to be stable). You can import all supported message encoders in ``eth_account.messages``. :param signable_message: the encoded message for signing :param private_key: the key to sign the message with :type private_key: hex str, bytes, int or :class:`eth_keys.datatypes.PrivateKey` :returns: Various details about the signature - most importantly the fields: v, r, and s :rtype: ~eth_account.datastructures.SignedMessage .. doctest:: python >>> msg = "I♥SF" >>> from eth_account.messages import encode_defunct >>> msghash = encode_defunct(text=msg) >>> msghash SignableMessage(version=b'E', header=b'thereum Signed Message:\n6', body=b'I\xe2\x99\xa5SF') >>> # If you're curious about the internal fields of SignableMessage, take a look at EIP-191, linked above # noqa: E501 >>> key = "0xb25c7db31feed9122727bf0939dc769a96564b2de4c4726d035b36ecf1e5b364" >>> Account.sign_message(msghash, key) SignedMessage(messageHash=HexBytes('0x1476abb745d423bf09273f1afd887d951181d25adc66c4834a70491911b7f750'), r=104389933075820307925104709181714897380569894203213074526835978196648170704563, s=28205917190874851400050446352651915501321657673772411533993420917949420456142, v=28, signature=HexBytes('0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb33e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce1c')) .. _EIP-191: https://eips.ethereum.org/EIPS/eip-191 """ message_hash = _hash_eip191_message(signable_message) return self._sign_hash(message_hash, private_key) @combomethod def signHash(self, message_hash, private_key): """ Sign the provided hash. .. WARNING:: *Never* sign a hash that you didn't generate, it can be an arbitrary transaction. For example, it might send all of your account's ether to an attacker. Instead, prefer :meth:`~eth_account.account.Account.sign_message`, which cannot accidentally sign a transaction. .. CAUTION:: Deprecated for :meth:`~eth_account.account.Account.sign_message`. This method will be removed in v0.6 :param message_hash: the 32-byte message hash to be signed :type message_hash: hex str, bytes or int :param private_key: the key to sign the message with :type private_key: hex str, bytes, int or :class:`eth_keys.datatypes.PrivateKey` :returns: Various details about the signature - most importantly the fields: v, r, and s :rtype: ~eth_account.datastructures.SignedMessage """ warnings.warn( "signHash is deprecated in favor of sign_message", category=DeprecationWarning, ) return self._sign_hash(message_hash, private_key) @combomethod def _sign_hash(self, message_hash, private_key): msg_hash_bytes = HexBytes(message_hash) if len(msg_hash_bytes) != 32: raise ValueError("The message hash must be exactly 32-bytes") key = self._parsePrivateKey(private_key) (v, r, s, eth_signature_bytes) = sign_message_hash(key, msg_hash_bytes) return SignedMessage( messageHash=msg_hash_bytes, r=r, s=s, v=v, signature=HexBytes(eth_signature_bytes), ) @combomethod def signTransaction(self, transaction_dict, private_key): """ .. CAUTION:: Deprecated for :meth:`~eth_account.account.Account.sign_transaction`. This method will be removed in v0.5 """ warnings.warn( "signTransaction is deprecated in favor of sign_transaction", category=DeprecationWarning, ) return self.sign_transaction(transaction_dict, private_key) @combomethod def sign_transaction(self, transaction_dict, private_key): """ Sign a transaction using a local private key. It produces signature details and the hex-encoded transaction suitable for broadcast using :meth:`w3.eth.sendRawTransaction() <web3.eth.Eth.sendRawTransaction>`. To create the transaction dict that calls a contract, use contract object: `my_contract.functions.my_function().buildTransaction() <http://web3py.readthedocs.io/en/latest/contracts.html#methods>`_ Note: For non-legacy (typed) transactions, if the transaction type is not explicitly provided, it may be determined from the transaction parameters of a well-formed transaction. See below for examples on how to sign with different transaction types. :param dict transaction_dict: the transaction with available keys, depending on the type of transaction: nonce, chainId, to, data, value, gas, gasPrice, type, accessList, maxFeePerGas, and maxPriorityFeePerGas :param private_key: the private key to sign the data with :type private_key: hex str, bytes, int or :class:`eth_keys.datatypes.PrivateKey` :returns: Various details about the signature - most importantly the fields: v, r, and s :rtype: AttributeDict .. code-block:: python >>> # EIP-1559 dynamic fee transaction (more efficient and preferred over legacy txn) >>> dynamic_fee_transaction = { "type": 2, # optional - can be implicitly determined based on max fee params # noqa: E501 "gas": 100000, "maxFeePerGas": 2000000000, "maxPriorityFeePerGas": 2000000000, "data": "0x616263646566", "nonce": 34, "to": "0x09616C3d61b3331fc4109a9E41a8BDB7d9776609", "value": "0x5af3107a4000", "accessList": ( # optional { "address": "0x0000000000000000000000000000000000000001", "storageKeys": ( "0x0100000000000000000000000000000000000000000000000000000000000000", # noqa: E501 ) }, ), "chainId": 1900, } >>> key = '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318' >>> signed = Account.sign_transaction(dynamic_fee_transaction, key) {'hash': HexBytes('0x126431f2a7fda003aada7c2ce52b0ce3cbdbb1896230d3333b9eea24f42d15b0'), 'r': 110093478023675319011132687961420618950720745285952062287904334878381994888509, 'rawTransaction': HexBytes('0x02f8b282076c2284773594008477359400830186a09409616c3d61b3331fc4109a9e41a8bdb7d9776609865af3107a400086616263646566f838f7940000000000000000000000000000000000000001e1a0010000000000000000000000000000000000000000000000000000000000000080a0f366b34a5c206859b9778b4c909207e53443cca9e0b82e0b94bc4b47e6434d3da04a731eda413a944d4ea2d2236671e586e57388d0e9d40db53044ae4089f2aec8'), # noqa: E501 's': 33674551144139401179914073499472892825822542092106065756005379322302694600392, 'v': 0} >>> w3.eth.sendRawTransaction(signed.rawTransaction) .. code-block:: python >>> # legacy transaction (less efficient than EIP-1559 dynamic fee txn) >>> legacy_transaction = { # Note that the address must be in checksum format or native bytes: 'to': '0xF0109fC8DF283027b6285cc889F5aA624EaC1F55', 'value': 1000000000, 'gas': 2000000, 'gasPrice': 234567897654321, 'nonce': 0, 'chainId': 1 } >>> key = '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318' >>> signed = Account.sign_transaction(legacy_transaction, key) {'hash': HexBytes('0x6893a6ee8df79b0f5d64a180cd1ef35d030f3e296a5361cf04d02ce720d32ec5'), 'r': 4487286261793418179817841024889747115779324305375823110249149479905075174044, 'rawTransaction': HexBytes('0xf86a8086d55698372431831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca008025a009ebb6ca057a0535d6186462bc0b465b561c94a295bdb0621fc19208ab149a9ca0440ffd775ce91a833ab410777204d5341a6f9fa91216a6f3ee2c051fea6a0428'), # noqa: E501 's': 30785525769477805655994251009256770582792548537338581640010273753578382951464, 'v': 37} >>> w3.eth.sendRawTransaction(signed.rawTransaction) .. code-block:: python >>> access_list_transaction = { "type": 1, # optional - can be implicitly determined based on 'accessList' and 'gasPrice' params # noqa: E501 "gas": 100000, "gasPrice": 1000000000, "data": "0x616263646566", "nonce": 34, "to": "0x09616C3d61b3331fc4109a9E41a8BDB7d9776609", "value": "0x5af3107a4000", "accessList": ( { "address": "0x0000000000000000000000000000000000000001", "storageKeys": ( "0x0100000000000000000000000000000000000000000000000000000000000000", # noqa: E501 ) }, ), "chainId": 1900, } >>> key = '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318' >>> signed = Account.sign_transaction(access_list_transaction, key) {'hash': HexBytes('0x2864ca20a74ca5e044067ad4139a22ff5a0853434f5f1dc00108f24ef5f1f783'), 'r': 105940705063391628472351883894091935317142890114440570831409400676736873197702, 'rawTransaction': HexBytes('0x01f8ad82076c22843b9aca00830186a09409616c3d61b3331fc4109a9e41a8bdb7d9776609865af3107a400086616263646566f838f7940000000000000000000000000000000000000001e1a0010000000000000000000000000000000000000000000000000000000000000080a0ea38506c4afe4bb402e030877fbe1011fa1da47aabcf215db8da8fee5d3af086a051e9af653b8eb98e74e894a766cf88904dbdb10b0bc1fbd12f18f661fa2797a4'), # noqa: E501 's': 37050226636175381535892585331727388340134760347943439553552848647212419749796, 'v': 0} >>> w3.eth.sendRawTransaction(signed.rawTransaction) """ if not isinstance(transaction_dict, Mapping): raise TypeError("transaction_dict must be dict-like, got %r" % transaction_dict) account = self.from_key(private_key) # allow from field, *only* if it matches the private key if 'from' in transaction_dict: if transaction_dict['from'] == account.address: sanitized_transaction = dissoc(transaction_dict, 'from') else: raise TypeError("from field must match key's %s, but it was %s" % ( account.address, transaction_dict['from'], )) else: sanitized_transaction = transaction_dict # sign transaction ( v, r, s, encoded_transaction, ) = sign_transaction_dict(account._key_obj, sanitized_transaction) transaction_hash = keccak(encoded_transaction) return SignedTransaction( rawTransaction=HexBytes(encoded_transaction), hash=HexBytes(transaction_hash), r=r, s=s, v=v, ) @combomethod def _parsePrivateKey(self, key): """ Generate a :class:`eth_keys.datatypes.PrivateKey` from the provided key. If the key is already of type :class:`eth_keys.datatypes.PrivateKey`, return the key. :param key: the private key from which a :class:`eth_keys.datatypes.PrivateKey` will be generated :type key: hex str, bytes, int or :class:`eth_keys.datatypes.PrivateKey` :returns: the provided key represented as a :class:`eth_keys.datatypes.PrivateKey` """ if isinstance(key, self._keys.PrivateKey): return key try: return self._keys.PrivateKey(HexBytes(key)) except ValidationError as original_exception: raise ValueError( "The private key must be exactly 32 bytes long, instead of " "%d bytes." % len(key) ) from original_exception
def init_private_key_from_hex(key_hex): keys = KeyAPI(NativeECCBackend) return keys.PrivateKey(key_hex)
def load_keystore(keystore, passphrase): keys = KeyAPI(NativeECCBackend) priv = extract_key_from_keyfile(keystore, passphrase) acc = Account.from_key(priv) return keys.PrivateKey(priv), acc
class Account(object): _keys = keys @combomethod def create(self, extra_entropy=''): extra_key_bytes = text_if_str(to_bytes, extra_entropy) key_bytes = keccak(os.urandom(32) + extra_key_bytes) return self.privateKeyToAccount(key_bytes) @staticmethod def decrypt(keyfile_json, password): if isinstance(keyfile_json, str): keyfile = json.loads(keyfile_json) elif is_dict(keyfile_json): keyfile = keyfile_json else: raise TypeError( "The keyfile should be supplied as a JSON string, or a dictionary." ) password_bytes = text_if_str(to_bytes, password) return decode_keyfile_json(keyfile, password_bytes) @staticmethod def encrypt(private_key, password): key_bytes = HexBytes(private_key) password_bytes = text_if_str(to_bytes, password) assert len(key_bytes) == 32 return create_keyfile_json(key_bytes, password_bytes) @staticmethod def hashMessage(data=None, hexstr=None, text=None): message_bytes = to_bytes(data, hexstr=hexstr, text=text) recovery_hasher = compose(HexBytes, keccak, signature_wrapper) return recovery_hasher(message_bytes) @combomethod def privateKeyToAccount(self, private_key): key_bytes = HexBytes(private_key) try: key_obj = self._keys.PrivateKey(key_bytes) return LocalAccount(key_obj, self) except ValidationError as original_exception: raise ValueError( "The private key must be exactly 32 bytes long, instead of " "%d bytes." % len(key_bytes)) from original_exception @combomethod def recover(self, msghash, vrs=None, signature=None): hash_bytes = HexBytes(msghash) if vrs is not None: v, r, s = map(hexstr_if_str(to_int), vrs) v_standard = to_standard_v(v) signature_obj = self._keys.Signature(vrs=(v_standard, r, s)) elif signature is not None: signature_bytes = HexBytes(signature) signature_bytes_standard = to_standard_signature_bytes( signature_bytes) signature_obj = self._keys.Signature( signature_bytes=signature_bytes_standard) else: raise TypeError( "You must supply the vrs tuple or the signature bytes") pubkey = signature_obj.recover_public_key_from_msg_hash(hash_bytes) return pubkey.to_checksum_address() @combomethod def recoverMessage(self, data=None, hexstr=None, text=None, vrs=None, signature=None): msg_hash = self.hashMessage(data, hexstr=hexstr, text=text) return self.recover(msg_hash, vrs=vrs, signature=signature) @combomethod def recoverTransaction(self, serialized_transaction): txn_bytes = HexBytes(serialized_transaction) txn = Transaction.from_bytes(txn_bytes) msg_hash = hash_of_signed_transaction(txn) return self.recover(msg_hash, vrs=vrs_from(txn)) def setKeyBackend(self, backend): self._keys = KeyAPI(backend) @combomethod def sign(self, message=None, private_key=None, message_hexstr=None, message_text=None): ''' @param private_key in bytes, str, or int. ''' msg_bytes = to_bytes(message, hexstr=message_hexstr, text=message_text) msg_hash = self.hashMessage(msg_bytes) key_bytes = HexBytes(private_key) key = self._keys.PrivateKey(key_bytes) (v, r, s, eth_signature_bytes) = sign_message_hash(key, msg_hash) return AttributeDict({ 'message': HexBytes(msg_bytes), 'messageHash': msg_hash, 'r': r, 's': s, 'v': v, 'signature': HexBytes(eth_signature_bytes), }) @combomethod def signTransaction(self, transaction_dict, private_key): ''' @param private_key in bytes, str, or int. ''' assert isinstance(transaction_dict, Mapping) account = self.privateKeyToAccount(private_key) # sign transaction ( v, r, s, rlp_encoded, ) = sign_transaction_dict(account._key_obj, transaction_dict) transaction_hash = keccak(rlp_encoded) return AttributeDict({ 'rawTransaction': HexBytes(rlp_encoded), 'hash': HexBytes(transaction_hash), 'r': r, 's': s, 'v': v, })
class Account(object): """ The primary entry point for working with Ethereum private keys. It does **not** require a connection to an Ethereum node. """ _keys = keys _default_kdf = os.getenv('ETH_ACCOUNT_KDF', 'scrypt') @combomethod def create(self, extra_entropy=''): r""" Creates a new private key, and returns it as a :class:`~eth_account.local.LocalAccount`. :param extra_entropy: Add extra randomness to whatever randomness your OS can provide :type extra_entropy: str or bytes or int :returns: an object with private key and convenience methods .. code-block:: python >>> from eth_account import Account >>> acct = Account.create('KEYSMASH FJAFJKLDSKF7JKFDJ 1530') >>> acct.address '0x5ce9454909639D2D17A3F753ce7d93fa0b9aB12E' >>> acct.key b"\xb2\}\xb3\x1f\xee\xd9\x12''\xbf\t9\xdcv\x9a\x96VK-\xe4\xc4rm\x03[6\xec\xf1\xe5\xb3d" # These methods are also available: sign_message(), sign_transaction(), encrypt() # They correspond to the same-named methods in Account.* # but without the private key argument """ extra_key_bytes = text_if_str(to_bytes, extra_entropy) key_bytes = keccak(os.urandom(32) + extra_key_bytes) return self.from_key(key_bytes) @staticmethod def decrypt(keyfile_json, password): """ Decrypts a private key that was encrypted using an Ethereum client or :meth:`~Account.encrypt`. :param keyfile_json: The encrypted key :type keyfile_json: dict or str :param str password: The password that was used to encrypt the key :returns: the raw private key :rtype: ~hexbytes.main.HexBytes .. code-block:: python >>> encrypted = { 'address': '5ce9454909639d2d17a3f753ce7d93fa0b9ab12e', 'crypto': {'cipher': 'aes-128-ctr', 'cipherparams': {'iv': '78f214584844e0b241b433d7c3bb8d5f'}, 'ciphertext': 'd6dbb56e4f54ba6db2e8dc14df17cb7352fdce03681dd3f90ce4b6c1d5af2c4f', 'kdf': 'pbkdf2', 'kdfparams': {'c': 1000000, 'dklen': 32, 'prf': 'hmac-sha256', 'salt': '45cf943b4de2c05c2c440ef96af914a2'}, 'mac': 'f5e1af09df5ded25c96fcf075ada313fb6f79735a914adc8cb02e8ddee7813c3'}, 'id': 'b812f3f9-78cc-462a-9e89-74418aa27cb0', 'version': 3} >>> import getpass >>> Account.decrypt(encrypted, getpass.getpass()) HexBytes('0xb25c7db31feed9122727bf0939dc769a96564b2de4c4726d035b36ecf1e5b364') """ if isinstance(keyfile_json, str): keyfile = json.loads(keyfile_json) elif is_dict(keyfile_json): keyfile = keyfile_json else: raise TypeError( "The keyfile should be supplied as a JSON string, or a dictionary." ) password_bytes = text_if_str(to_bytes, password) return HexBytes(decode_keyfile_json(keyfile, password_bytes)) @classmethod def encrypt(cls, private_key, password, kdf=None, iterations=None): """ Creates a dictionary with an encrypted version of your private key. To import this keyfile into Ethereum clients like geth and parity: encode this dictionary with :func:`json.dumps` and save it to disk where your client keeps key files. :param private_key: The raw private key :type private_key: hex str, bytes, int or :class:`eth_keys.datatypes.PrivateKey` :param str password: The password which you will need to unlock the account in your client :param str kdf: The key derivation function to use when encrypting your private key :param int iterations: The work factor for the key derivation function :returns: The data to use in your encrypted file :rtype: dict If kdf is not set, the default key derivation function falls back to the environment variable :envvar:`ETH_ACCOUNT_KDF`. If that is not set, then 'scrypt' will be used as the default. .. code-block:: python >>> import getpass >>> encrypted = Account.encrypt( 0xb25c7db31feed9122727bf0939dc769a96564b2de4c4726d035b36ecf1e5b364, getpass.getpass() ) { 'address': '5ce9454909639d2d17a3f753ce7d93fa0b9ab12e', 'crypto': { 'cipher': 'aes-128-ctr', 'cipherparams': { 'iv': '0b7845a5c3597d3d378bde9b7c7319b7' }, 'ciphertext': 'a494f1feb3c854e99c1ff01e6aaa17d43c0752009073503b908457dc8de5d2a5', # noqa: E501 'kdf': 'scrypt', 'kdfparams': { 'dklen': 32, 'n': 262144, 'p': 8, 'r': 1, 'salt': '13c4a48123affaa29189e9097726c698' }, 'mac': 'f4cfb027eb0af9bd7a320b4374a3fa7bef02cfbafe0ec5d1fd7ad129401de0b1' }, 'id': 'a60e0578-0e5b-4a75-b991-d55ec6451a6f', 'version': 3 } >>> with open('my-keyfile', 'w') as f: f.write(json.dumps(encrypted)) """ if isinstance(private_key, keys.PrivateKey): key_bytes = private_key.to_bytes() else: key_bytes = HexBytes(private_key) if kdf is None: kdf = cls._default_kdf password_bytes = text_if_str(to_bytes, password) assert len(key_bytes) == 32 return create_keyfile_json(key_bytes, password_bytes, kdf=kdf, iterations=iterations) @combomethod def privateKeyToAccount(self, private_key): """ .. CAUTION:: Deprecated for :meth:`~eth_account.account.Account.from_key`. This method will be removed in v0.5 """ warnings.warn( "privateKeyToAccount is deprecated in favor of from_key", category=DeprecationWarning, ) return self.from_key(private_key) @combomethod def from_key(self, private_key): r""" Returns a convenient object for working with the given private key. :param private_key: The raw private key :type private_key: hex str, bytes, int or :class:`eth_keys.datatypes.PrivateKey` :return: object with methods for signing and encrypting :rtype: LocalAccount .. code-block:: python >>> acct = Account.from_key( 0xb25c7db31feed9122727bf0939dc769a96564b2de4c4726d035b36ecf1e5b364) >>> acct.address '0x5ce9454909639D2D17A3F753ce7d93fa0b9aB12E' >>> acct.key b"\xb2\}\xb3\x1f\xee\xd9\x12''xbf\t9\xdcv\x9a\x96VK-\xe4\xc4rm\x03[6\xec\xf1\xe5\xb3d" # These methods are also available: sign_message(), sign_transaction(), encrypt() # They correspond to the same-named methods in Account.* # but without the private key argument """ key = self._parsePrivateKey(private_key) return LocalAccount(key, self) @combomethod def recover_message(self, signable_message: SignableMessage, vrs=None, signature=None): r""" Get the address of the account that signed the given message. You must specify exactly one of: vrs or signature :param signable_message: the message that was signed :param vrs: the three pieces generated by an elliptic curve signature :type vrs: tuple(v, r, s), each element is hex str, bytes or int :param signature: signature bytes concatenated as r+s+v :type signature: hex str or bytes or int :returns: address of signer, hex-encoded & checksummed :rtype: str .. code-block:: python >>> from eth_account.messages import encode_defunct >>> message = encode_defunct(text="I♥SF") >>> vrs = ( 28, '0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb3', '0x3e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce') >>> Account.recover_message(message, vrs=vrs) '0x5ce9454909639D2D17A3F753ce7d93fa0b9aB12E' # All of these recover calls are equivalent: # variations on vrs >>> vrs = ( '0x1c', '0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb3', '0x3e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce') >>> Account.recover_message(message, vrs=vrs) >>> vrs = ( b'\x1c', b'\\xe6\\xca\\x9b\\xbaX\\xc8\\x86\\x11\\xfa\\xd6jl\\xe8\\xf9\\x96\\x90\\x81\\x95Y8\\x07\\xc4\\xb3\\x8b\\xd5(\\xd2\\xcf\\xf0\\x9dN\\xb3', # noqa: E501 b'>[\\xfb\\xbfM>9\\xb1\\xa2\\xfd\\x81jv\\x80\\xc1\\x9e\\xbe\\xba\\xf3\\xa1A\\xb29\\x93J\\xd4<\\xb3?\\xce\\xc8\\xce') # noqa: E501 >>> Account.recover_message(message, vrs=vrs) >>> # Caution about this approach: likely problems if there are leading 0s >>> vrs = ( 0x1c, 0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb3, 0x3e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce) >>> Account.recover_message(message, vrs=vrs) # variations on signature >>> signature = '0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb33e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce1c' # noqa: E501 >>> Account.recover_message(message, signature=signature) >>> signature = b'\\xe6\\xca\\x9b\\xbaX\\xc8\\x86\\x11\\xfa\\xd6jl\\xe8\\xf9\\x96\\x90\\x81\\x95Y8\\x07\\xc4\\xb3\\x8b\\xd5(\\xd2\\xcf\\xf0\\x9dN\\xb3>[\\xfb\\xbfM>9\\xb1\\xa2\\xfd\\x81jv\\x80\\xc1\\x9e\\xbe\\xba\\xf3\\xa1A\\xb29\\x93J\\xd4<\\xb3?\\xce\\xc8\\xce\\x1c' # noqa: E501 >>> Account.recover_message(message, signature=signature) >>> # Caution about this approach: likely problems if there are leading 0s >>> signature = 0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb33e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce1c # noqa: E501 >>> Account.recover_message(message, signature=signature) """ message_hash = _hash_eip191_message(signable_message) return self._recover_hash(message_hash, vrs, signature) @combomethod def recoverHash(self, message_hash, vrs=None, signature=None): """ Get the address of the account that signed the message with the given hash. You must specify exactly one of: vrs or signature .. CAUTION:: Deprecated for :meth:`~eth_account.account.Account.recover_message`. This method might be removed as early as v0.5 :param message_hash: the hash of the message that you want to verify :type message_hash: hex str or bytes or int :param vrs: the three pieces generated by an elliptic curve signature :type vrs: tuple(v, r, s), each element is hex str, bytes or int :param signature: signature bytes concatenated as r+s+v :type signature: hex str or bytes or int :returns: address of signer, hex-encoded & checksummed :rtype: str """ warnings.warn( "recoverHash is deprecated in favor of recover_message", category=DeprecationWarning, ) return self._recover_hash(message_hash, vrs, signature) @combomethod def _recover_hash(self, message_hash, vrs=None, signature=None): hash_bytes = HexBytes(message_hash) if len(hash_bytes) != 32: raise ValueError("The message hash must be exactly 32-bytes") if vrs is not None: v, r, s = map(hexstr_if_str(to_int), vrs) v_standard = to_standard_v(v) signature_obj = self._keys.Signature(vrs=(v_standard, r, s)) elif signature is not None: signature_bytes = HexBytes(signature) signature_bytes_standard = to_standard_signature_bytes( signature_bytes) signature_obj = self._keys.Signature( signature_bytes=signature_bytes_standard) else: raise TypeError( "You must supply the vrs tuple or the signature bytes") pubkey = signature_obj.recover_public_key_from_msg_hash(hash_bytes) return pubkey.to_checksum_address() @combomethod def recoverTransaction(self, serialized_transaction): """ .. CAUTION:: Deprecated for :meth:`~eth_account.account.Account.recover_transaction`. This method will be removed in v0.5 """ warnings.warn( "recoverTransaction is deprecated in favor of recover_transaction", category=DeprecationWarning, ) return self.recover_transaction(serialized_transaction) @combomethod def recover_transaction(self, serialized_transaction): """ Get the address of the account that signed this transaction. :param serialized_transaction: the complete signed transaction :type serialized_transaction: hex str, bytes or int :returns: address of signer, hex-encoded & checksummed :rtype: str .. code-block:: python >>> raw_transaction = '0xf86a8086d55698372431831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca008025a009ebb6ca057a0535d6186462bc0b465b561c94a295bdb0621fc19208ab149a9ca0440ffd775ce91a833ab410777204d5341a6f9fa91216a6f3ee2c051fea6a0428', # noqa: E501 >>> Account.recover_transaction(raw_transaction) '0x2c7536E3605D9C16a7a3D7b1898e529396a65c23' """ txn_bytes = HexBytes(serialized_transaction) txn = Transaction.from_bytes(txn_bytes) msg_hash = hash_of_signed_transaction(txn) return self._recover_hash(msg_hash, vrs=vrs_from(txn)) def setKeyBackend(self, backend): """ .. CAUTION:: Deprecated for :meth:`~eth_account.account.Account.set_key_backend`. This method will be removed in v0.5 """ warnings.warn( "setKeyBackend is deprecated in favor of set_key_backend", category=DeprecationWarning, ) self.set_key_backend(backend) def set_key_backend(self, backend): """ Change the backend used by the underlying eth-keys library. *(The default is fine for most users)* :param backend: any backend that works in `eth_keys.KeyApi(backend) <https://github.com/ethereum/eth-keys/#keyapibackendnone>`_ """ self._keys = KeyAPI(backend) @combomethod def sign_message(self, signable_message: SignableMessage, private_key): r""" Sign the provided message. This API supports any messaging format that will encode to EIP-191_ messages. If you would like historical compatibility with :meth:`w3.eth.sign() <web3.eth.Eth.sign>` you can use :meth:`~eth_account.messages.encode_defunct`. Other options are the "validator", or "structured data" standards. (Both of these are in *DRAFT* status currently, so be aware that the implementation is not guaranteed to be stable). You can import all supported message encoders in ``eth_account.messages``. :param signable_message: the encoded message for signing :param private_key: the key to sign the message with :type private_key: hex str, bytes, int or :class:`eth_keys.datatypes.PrivateKey` :returns: Various details about the signature - most importantly the fields: v, r, and s :rtype: ~eth_account.datastructures.AttributeDict .. code-block:: python >>> msg = "I♥SF" >>> from eth_account.messages import encode_defunct >>> msghash = encode_defunct(text=msg) SignableMessage(version=b'E', header=b'thereum Signed Message:\n6', body=b'I\xe2\x99\xa5SF') >>> # If you're curious about the internal fields of SignableMessage, take a look at EIP-191, linked above >>> key = "0xb25c7db31feed9122727bf0939dc769a96564b2de4c4726d035b36ecf1e5b364" >>> Account.sign_message(msghash, key) {'messageHash': HexBytes('0x1476abb745d423bf09273f1afd887d951181d25adc66c4834a70491911b7f750'), # noqa: E501 'r': 104389933075820307925104709181714897380569894203213074526835978196648170704563, 's': 28205917190874851400050446352651915501321657673772411533993420917949420456142, 'signature': HexBytes('0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb33e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce1c'), # noqa: E501 'v': 28} .. _EIP-191: https://eips.ethereum.org/EIPS/eip-191 """ message_hash = _hash_eip191_message(signable_message) return self._sign_hash(message_hash, private_key) @combomethod def signHash(self, message_hash, private_key): """ .. WARNING:: *Never* sign a hash that you didn't generate, it can be an arbitrary transaction. For example, it might send all of your account's ether to an attacker. Instead, prefer :meth:`~eth_account.account.Account.sign_message`, which cannot accidentally sign a transaction. Sign the provided hash. .. CAUTION:: Deprecated for :meth:`~eth_account.account.Account.sign_message`. This method will be removed in v0.5 :param message_hash: the 32-byte message hash to be signed :type message_hash: hex str, bytes or int :param private_key: the key to sign the message with :type private_key: hex str, bytes, int or :class:`eth_keys.datatypes.PrivateKey` :returns: Various details about the signature - most importantly the fields: v, r, and s :rtype: ~eth_account.datastructures.AttributeDict """ warnings.warn( "signHash is deprecated in favor of sign_message", category=DeprecationWarning, ) return self._sign_hash(message_hash, private_key) @combomethod def _sign_hash(self, message_hash, private_key): msg_hash_bytes = HexBytes(message_hash) if len(msg_hash_bytes) != 32: raise ValueError("The message hash must be exactly 32-bytes") key = self._parsePrivateKey(private_key) (v, r, s, eth_signature_bytes) = sign_message_hash(key, msg_hash_bytes) return AttributeDict({ 'messageHash': msg_hash_bytes, 'r': r, 's': s, 'v': v, 'signature': HexBytes(eth_signature_bytes), }) @combomethod def signTransaction(self, transaction_dict, private_key): """ .. CAUTION:: Deprecated for :meth:`~eth_account.account.Account.sign_transaction`. This method will be removed in v0.5 """ warnings.warn( "signTransaction is deprecated in favor of sign_transaction", category=DeprecationWarning, ) return self.sign_transaction(transaction_dict, private_key) @combomethod def sign_transaction(self, transaction_dict, private_key): """ Sign a transaction using a local private key. Produces signature details and the hex-encoded transaction suitable for broadcast using :meth:`w3.eth.sendRawTransaction() <web3.eth.Eth.sendRawTransaction>`. Create the transaction dict for a contract method with `my_contract.functions.my_function().buildTransaction() <http://web3py.readthedocs.io/en/latest/contracts.html#methods>`_ :param dict transaction_dict: the transaction with keys: nonce, chainId, to, data, value, gas, and gasPrice. :param private_key: the private key to sign the data with :type private_key: hex str, bytes, int or :class:`eth_keys.datatypes.PrivateKey` :returns: Various details about the signature - most importantly the fields: v, r, and s :rtype: AttributeDict .. code-block:: python >>> transaction = { # Note that the address must be in checksum format or native bytes: 'to': '0xF0109fC8DF283027b6285cc889F5aA624EaC1F55', 'value': 1000000000, 'gas': 2000000, 'gasPrice': 234567897654321, 'nonce': 0, 'chainId': 1 } >>> key = '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318' >>> signed = Account.sign_transaction(transaction, key) {'hash': HexBytes('0x6893a6ee8df79b0f5d64a180cd1ef35d030f3e296a5361cf04d02ce720d32ec5'), 'r': 4487286261793418179817841024889747115779324305375823110249149479905075174044, 'rawTransaction': HexBytes('0xf86a8086d55698372431831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca008025a009ebb6ca057a0535d6186462bc0b465b561c94a295bdb0621fc19208ab149a9ca0440ffd775ce91a833ab410777204d5341a6f9fa91216a6f3ee2c051fea6a0428'), # noqa: E501 's': 30785525769477805655994251009256770582792548537338581640010273753578382951464, 'v': 37} >>> w3.eth.sendRawTransaction(signed.rawTransaction) """ if not isinstance(transaction_dict, Mapping): raise TypeError("transaction_dict must be dict-like, got %r" % transaction_dict) account = self.from_key(private_key) # allow from field, *only* if it matches the private key if 'from' in transaction_dict: if transaction_dict['from'] == account.address: sanitized_transaction = dissoc(transaction_dict, 'from') else: raise TypeError( "from field must match key's %s, but it was %s" % ( account.address, transaction_dict['from'], )) else: sanitized_transaction = transaction_dict # sign transaction ( v, r, s, rlp_encoded, ) = sign_transaction_dict(account._key_obj, sanitized_transaction) transaction_hash = keccak(rlp_encoded) return AttributeDict({ 'rawTransaction': HexBytes(rlp_encoded), 'hash': HexBytes(transaction_hash), 'r': r, 's': s, 'v': v, }) @combomethod def _parsePrivateKey(self, key): """ Generate a :class:`eth_keys.datatypes.PrivateKey` from the provided key. If the key is already of type :class:`eth_keys.datatypes.PrivateKey`, return the key. :param key: the private key from which a :class:`eth_keys.datatypes.PrivateKey` will be generated :type key: hex str, bytes, int or :class:`eth_keys.datatypes.PrivateKey` :returns: the provided key represented as a :class:`eth_keys.datatypes.PrivateKey` """ if isinstance(key, self._keys.PrivateKey): return key try: return self._keys.PrivateKey(HexBytes(key)) except ValidationError as original_exception: raise ValueError( "The private key must be exactly 32 bytes long, instead of " "%d bytes." % len(key)) from original_exception
def add_account(self, private_key): keys = KeyAPI() self.account_keys = self.account_keys + ( keys.PrivateKey(private_key), )
def private_key_to_public_key(private_k): priv = KeyAPI.PrivateKey(private_k) public_k = KeyAPI.PublicKey.from_private(priv) return public_k
class Account(object): ''' This is the primary entry point for working with Ethereum private keys. It does **not** require a connection to an Ethereum node. ''' _keys = keys @combomethod def create(self, extra_entropy=''): ''' Creates a new private key, and returns it as a :class:`~eth_account.local.LocalAccount`. :param extra_entropy: Add extra randomness to whatever randomness your OS can provide :type extra_entropy: str or bytes or int :returns: an object with private key and convenience methods .. code-block:: python >>> from eth_account import Account >>> acct = Account.create('KEYSMASH FJAFJKLDSKF7JKFDJ 1530') >>> acct.address '0x5ce9454909639D2D17A3F753ce7d93fa0b9aB12E' >>> acct.privateKey b"\\xb2\\}\\xb3\\x1f\\xee\\xd9\\x12''\\xbf\\t9\\xdcv\\x9a\\x96VK-\\xe4\\xc4rm\\x03[6\\xec\\xf1\\xe5\\xb3d" # These methods are also available: signHash(), signTransaction(), encrypt() # They correspond to the same-named methods in Account.* # but without the private key argument ''' extra_key_bytes = text_if_str(to_bytes, extra_entropy) key_bytes = keccak(os.urandom(32) + extra_key_bytes) return self.privateKeyToAccount(key_bytes) @staticmethod def decrypt(keyfile_json, password): ''' Decrypts a private key that was encrypted using an Ethereum client or :meth:`~Account.encrypt`. :param keyfile_json: The encrypted key :type keyfile_json: dict or str :param str password: The password that was used to encrypt the key :returns: the raw private key :rtype: ~hexbytes.main.HexBytes .. code-block:: python >>> encrypted = { 'address': '5ce9454909639d2d17a3f753ce7d93fa0b9ab12e', 'crypto': {'cipher': 'aes-128-ctr', 'cipherparams': {'iv': '78f214584844e0b241b433d7c3bb8d5f'}, 'ciphertext': 'd6dbb56e4f54ba6db2e8dc14df17cb7352fdce03681dd3f90ce4b6c1d5af2c4f', 'kdf': 'pbkdf2', 'kdfparams': {'c': 1000000, 'dklen': 32, 'prf': 'hmac-sha256', 'salt': '45cf943b4de2c05c2c440ef96af914a2'}, 'mac': 'f5e1af09df5ded25c96fcf075ada313fb6f79735a914adc8cb02e8ddee7813c3'}, 'id': 'b812f3f9-78cc-462a-9e89-74418aa27cb0', 'version': 3} >>> import getpass >>> Account.decrypt(encrypted, getpass.getpass()) HexBytes('0xb25c7db31feed9122727bf0939dc769a96564b2de4c4726d035b36ecf1e5b364') ''' if isinstance(keyfile_json, str): keyfile = json.loads(keyfile_json) elif is_dict(keyfile_json): keyfile = keyfile_json else: raise TypeError( "The keyfile should be supplied as a JSON string, or a dictionary." ) password_bytes = text_if_str(to_bytes, password) return HexBytes(decode_keyfile_json(keyfile, password_bytes)) @staticmethod def encrypt(private_key, password): ''' Creates a dictionary with an encrypted version of your private key. To import this keyfile into Ethereum clients like geth and parity: encode this dictionary with :func:`json.dumps` and save it to disk where your client keeps key files. :param private_key: The raw private key :type private_key: hex str, bytes, int or :class:`eth_keys.datatypes.PrivateKey` :param str password: The password which you will need to unlock the account in your client :returns: The data to use in your encrypted file :rtype: dict .. code-block:: python >>> import getpass >>> encrypted = Account.encrypt( 0xb25c7db31feed9122727bf0939dc769a96564b2de4c4726d035b36ecf1e5b364, getpass.getpass() ) {'address': '5ce9454909639d2d17a3f753ce7d93fa0b9ab12e', 'crypto': {'cipher': 'aes-128-ctr', 'cipherparams': {'iv': '78f214584844e0b241b433d7c3bb8d5f'}, 'ciphertext': 'd6dbb56e4f54ba6db2e8dc14df17cb7352fdce03681dd3f90ce4b6c1d5af2c4f', 'kdf': 'pbkdf2', 'kdfparams': {'c': 1000000, 'dklen': 32, 'prf': 'hmac-sha256', 'salt': '45cf943b4de2c05c2c440ef96af914a2'}, 'mac': 'f5e1af09df5ded25c96fcf075ada313fb6f79735a914adc8cb02e8ddee7813c3'}, 'id': 'b812f3f9-78cc-462a-9e89-74418aa27cb0', 'version': 3} >>> with open('my-keyfile', 'w') as f: f.write(json.dumps(encrypted)) ''' if isinstance(private_key, keys.PrivateKey): key_bytes = private_key.to_bytes() else: key_bytes = HexBytes(private_key) password_bytes = text_if_str(to_bytes, password) assert len(key_bytes) == 32 return create_keyfile_json(key_bytes, password_bytes) @combomethod def privateKeyToAccount(self, private_key): ''' Returns a convenient object for working with the given private key. :param private_key: The raw private key :type private_key: hex str, bytes, int or :class:`eth_keys.datatypes.PrivateKey` :return: object with methods for signing and encrypting :rtype: LocalAccount .. code-block:: python >>> acct = Account.privateKeyToAccount( 0xb25c7db31feed9122727bf0939dc769a96564b2de4c4726d035b36ecf1e5b364) >>> acct.address '0x5ce9454909639D2D17A3F753ce7d93fa0b9aB12E' >>> acct.privateKey b"\\xb2\\}\\xb3\\x1f\\xee\\xd9\\x12''\\xbf\\t9\\xdcv\\x9a\\x96VK-\\xe4\\xc4rm\\x03[6\\xec\\xf1\\xe5\\xb3d" # These methods are also available: signHash(), signTransaction(), encrypt() # They correspond to the same-named methods in Account.* # but without the private key argument ''' key = self._parsePrivateKey(private_key) return LocalAccount(key, self) @combomethod def recoverHash(self, message_hash, vrs=None, signature=None): ''' Get the address of the account that signed the message with the given hash. You must specify exactly one of: vrs or signature :param message_hash: the hash of the message that you want to verify :type message_hash: hex str or bytes or int :param vrs: the three pieces generated by an elliptic curve signature :type vrs: tuple(v, r, s), each element is hex str, bytes or int :param signature: signature bytes concatenated as r+s+v :type signature: hex str or bytes or int :returns: address of signer, hex-encoded & checksummed :rtype: str .. code-block:: python >>> msg = "I♥SF" >>> msghash = '0x1476abb745d423bf09273f1afd887d951181d25adc66c4834a70491911b7f750' >>> vrs = ( 28, '0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb3', '0x3e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce') >>> Account.recoverHash(msghash, vrs=vrs) '0x5ce9454909639D2D17A3F753ce7d93fa0b9aB12E' # All of these recover calls are equivalent: # variations on msghash >>> msghash = b"\\x14v\\xab\\xb7E\\xd4#\\xbf\\t'?\\x1a\\xfd\\x88}\\x95\\x11\\x81\\xd2Z\\xdcf\\xc4\\x83JpI\\x19\\x11\\xb7\\xf7P" # noqa: E501 >>> Account.recoverHash(msghash, vrs=vrs) >>> msghash = 0x1476abb745d423bf09273f1afd887d951181d25adc66c4834a70491911b7f750 >>> Account.recoverHash(msghash, vrs=vrs) # variations on vrs >>> vrs = ( '0x1c', '0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb3', '0x3e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce') >>> Account.recoverHash(msghash, vrs=vrs) >>> vrs = ( b'\\x1c', b'\\xe6\\xca\\x9b\\xbaX\\xc8\\x86\\x11\\xfa\\xd6jl\\xe8\\xf9\\x96\\x90\\x81\\x95Y8\\x07\\xc4\\xb3\\x8b\\xd5(\\xd2\\xcf\\xf0\\x9dN\\xb3', # noqa: E501 b'>[\\xfb\\xbfM>9\\xb1\\xa2\\xfd\\x81jv\\x80\\xc1\\x9e\\xbe\\xba\\xf3\\xa1A\\xb29\\x93J\\xd4<\\xb3?\\xce\\xc8\\xce') # noqa: E501 >>> Account.recoverHash(msghash, vrs=vrs) >>> vrs = ( 0x1c, 0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb3, 0x3e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce) >>> Account.recoverHash(msghash, vrs=vrs) # variations on signature >>> signature = '0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb33e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce1c' # noqa: E501 >>> Account.recoverHash(msghash, signature=signature) >>> signature = b'\\xe6\\xca\\x9b\\xbaX\\xc8\\x86\\x11\\xfa\\xd6jl\\xe8\\xf9\\x96\\x90\\x81\\x95Y8\\x07\\xc4\\xb3\\x8b\\xd5(\\xd2\\xcf\\xf0\\x9dN\\xb3>[\\xfb\\xbfM>9\\xb1\\xa2\\xfd\\x81jv\\x80\\xc1\\x9e\\xbe\\xba\\xf3\\xa1A\\xb29\\x93J\\xd4<\\xb3?\\xce\\xc8\\xce\\x1c' # noqa: E501 >>> Account.recoverHash(msghash, signature=signature) >>> signature = 0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb33e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce1c # noqa: E501 >>> Account.recoverHash(msghash, signature=signature) ''' hash_bytes = HexBytes(message_hash) if len(hash_bytes) != 32: raise ValueError("The message hash must be exactly 32-bytes") if vrs is not None: v, r, s = map(hexstr_if_str(to_int), vrs) v_standard = to_standard_v(v) signature_obj = self._keys.Signature(vrs=(v_standard, r, s)) elif signature is not None: signature_bytes = HexBytes(signature) signature_bytes_standard = to_standard_signature_bytes( signature_bytes) signature_obj = self._keys.Signature( signature_bytes=signature_bytes_standard) else: raise TypeError( "You must supply the vrs tuple or the signature bytes") pubkey = signature_obj.recover_public_key_from_msg_hash(hash_bytes) return pubkey.to_checksum_address() @combomethod def recoverTransaction(self, serialized_transaction): ''' Get the address of the account that signed this transaction. :param serialized_transaction: the complete signed transaction :type serialized_transaction: hex str, bytes or int :returns: address of signer, hex-encoded & checksummed :rtype: str .. code-block:: python >>> raw_transaction = '0xf86a8086d55698372431831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca008025a009ebb6ca057a0535d6186462bc0b465b561c94a295bdb0621fc19208ab149a9ca0440ffd775ce91a833ab410777204d5341a6f9fa91216a6f3ee2c051fea6a0428', # noqa: E501 >>> Account.recoverTransaction(raw_transaction) '0x2c7536E3605D9C16a7a3D7b1898e529396a65c23' ''' txn_bytes = HexBytes(serialized_transaction) txn = Transaction.from_bytes(txn_bytes) msg_hash = hash_of_signed_transaction(txn) return self.recoverHash(msg_hash, vrs=vrs_from(txn)) def setKeyBackend(self, backend): ''' Change the backend used by the underlying eth-keys library. *(The default is fine for most users)* :param backend: any backend that works in `eth_keys.KeyApi(backend) <https://github.com/ethereum/eth-keys/#keyapibackendnone>`_ ''' self._keys = KeyAPI(backend) @combomethod def signHash(self, message_hash, private_key): ''' Sign the hash provided. .. WARNING:: *Never* sign a hash that you didn't generate, it can be an arbitrary transaction. For example, it might send all of your account's ether to an attacker. If you would like compatibility with :meth:`w3.eth.sign() <web3.eth.Eth.sign>` you can use :meth:`~eth_account.messages.defunct_hash_message`. Several other message standards are proposed, but none have a clear consensus. You'll need to manually comply with any of those message standards manually. :param message_hash: the 32-byte message hash to be signed :type message_hash: hex str, bytes or int :param private_key: the key to sign the message with :type private_key: hex str, bytes, int or :class:`eth_keys.datatypes.PrivateKey` :returns: Various details about the signature - most importantly the fields: v, r, and s :rtype: ~eth_account.datastructures.AttributeDict .. code-block:: python >>> msg = "I♥SF" >>> from eth_account.messages import defunct_hash_message >>> msghash = defunct_hash_message(text=msg) HexBytes('0x1476abb745d423bf09273f1afd887d951181d25adc66c4834a70491911b7f750') >>> key = "0xb25c7db31feed9122727bf0939dc769a96564b2de4c4726d035b36ecf1e5b364" >>> Account.signHash(msghash, key) {'messageHash': HexBytes('0x1476abb745d423bf09273f1afd887d951181d25adc66c4834a70491911b7f750'), # noqa: E501 'r': 104389933075820307925104709181714897380569894203213074526835978196648170704563, 's': 28205917190874851400050446352651915501321657673772411533993420917949420456142, 'signature': HexBytes('0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb33e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce1c'), # noqa: E501 'v': 28} # these are equivalent: >>> Account.signHash( 0x1476abb745d423bf09273f1afd887d951181d25adc66c4834a70491911b7f750, key ) >>> Account.signHash( "0x1476abb745d423bf09273f1afd887d951181d25adc66c4834a70491911b7f750", key ) ''' msg_hash_bytes = HexBytes(message_hash) if len(msg_hash_bytes) != 32: raise ValueError("The message hash must be exactly 32-bytes") key = self._parsePrivateKey(private_key) (v, r, s, eth_signature_bytes) = sign_message_hash(key, msg_hash_bytes) return AttributeDict({ 'messageHash': msg_hash_bytes, 'r': r, 's': s, 'v': v, 'signature': HexBytes(eth_signature_bytes), }) @combomethod def signTransaction(self, transaction_dict, private_key): ''' Sign a transaction using a local private key. Produces signature details and the hex-encoded transaction suitable for broadcast using :meth:`w3.eth.sendRawTransaction() <web3.eth.Eth.sendRawTransaction>`. Create the transaction dict for a contract method with `my_contract.functions.my_function().buildTransaction() <http://web3py.readthedocs.io/en/latest/contracts.html#methods>`_ :param dict transaction_dict: the transaction with keys: nonce, chainId, to, data, value, gas, and gasPrice. :param private_key: the private key to sign the data with :type private_key: hex str, bytes, int or :class:`eth_keys.datatypes.PrivateKey` :returns: Various details about the signature - most importantly the fields: v, r, and s :rtype: AttributeDict .. code-block:: python >>> transaction = { # Note that the address must be in checksum format: 'to': '0xF0109fC8DF283027b6285cc889F5aA624EaC1F55', 'value': 1000000000, 'gas': 2000000, 'gasPrice': 234567897654321, 'nonce': 0, 'chainId': 1 } >>> key = '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318' >>> signed = Account.signTransaction(transaction, key) {'hash': HexBytes('0x6893a6ee8df79b0f5d64a180cd1ef35d030f3e296a5361cf04d02ce720d32ec5'), 'r': 4487286261793418179817841024889747115779324305375823110249149479905075174044, 'rawTransaction': HexBytes('0xf86a8086d55698372431831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca008025a009ebb6ca057a0535d6186462bc0b465b561c94a295bdb0621fc19208ab149a9ca0440ffd775ce91a833ab410777204d5341a6f9fa91216a6f3ee2c051fea6a0428'), # noqa: E501 's': 30785525769477805655994251009256770582792548537338581640010273753578382951464, 'v': 37} >>> w3.eth.sendRawTransaction(signed.rawTransaction) ''' if not isinstance(transaction_dict, Mapping): raise TypeError("transaction_dict must be dict-like, got %r" % transaction_dict) account = self.privateKeyToAccount(private_key) # allow from field, *only* if it matches the private key if 'from' in transaction_dict: if transaction_dict['from'] == account.address: sanitized_transaction = dissoc(transaction_dict, 'from') else: raise TypeError( "from field must match key's %s, but it was %s" % ( account.address, transaction_dict['from'], )) else: sanitized_transaction = transaction_dict # sign transaction ( v, r, s, rlp_encoded, ) = sign_transaction_dict(account._key_obj, sanitized_transaction) transaction_hash = keccak(rlp_encoded) return AttributeDict({ 'rawTransaction': HexBytes(rlp_encoded), 'hash': HexBytes(transaction_hash), 'r': r, 's': s, 'v': v, }) @combomethod def _parsePrivateKey(self, key): ''' Generate a :class:`eth_keys.datatypes.PrivateKey` from the provided key. If the key is already of type :class:`eth_keys.datatypes.PrivateKey`, return the key. :param key: the private key from which a :class:`eth_keys.datatypes.PrivateKey` will be generated :type key: hex str, bytes, int or :class:`eth_keys.datatypes.PrivateKey` :returns: the provided key represented as a :class:`eth_keys.datatypes.PrivateKey` ''' if isinstance(key, self._keys.PrivateKey): return key try: return self._keys.PrivateKey(HexBytes(key)) except ValidationError as original_exception: raise ValueError( "The private key must be exactly 32 bytes long, instead of " "%d bytes." % len(key)) from original_exception
class Account(object): _keys = keys def create(self, extra_entropy=''): extra_key_bytes = text_if_str(to_bytes, extra_entropy) key_bytes = keccak(os.urandom(32) + extra_key_bytes) if sys.version_info.major < 3: key_bytes = to_hex(key_bytes) return self.privateKeyToAccount(key_bytes) @staticmethod def decrypt(keyfile_json, password): if isinstance(keyfile_json, str) or (sys.version_info.major < 3 and isinstance( keyfile_json, unicode)): # noqa: 821 keyfile = json.loads(keyfile_json) elif is_dict(keyfile_json): keyfile = keyfile_json else: raise TypeError( "The keyfile should be supplied as a JSON string, or a dictionary." ) password_bytes = text_if_str(to_bytes, password) return decode_keyfile_json(keyfile, password_bytes) @staticmethod def encrypt(private_key, password): key_bytes = hexstr_if_str(to_bytes, private_key) password_bytes = text_if_str(to_bytes, password) assert len(key_bytes) == 32 return create_keyfile_json(key_bytes, password_bytes) @staticmethod def hashMessage(data=None, hexstr=None, text=None): message_bytes = to_bytes(data, hexstr=hexstr, text=text) recovery_hasher = compose(to_hex, keccak, signature_wrapper) return recovery_hasher(message_bytes) def privateKeyToAccount(self, private_key): key_bytes = hexstr_if_str(to_bytes, private_key) try: key_obj = self._keys.PrivateKey(key_bytes) return LocalAccount(key_obj, self) except ValidationError as original_exception: raise_from( ValueError( "The private key must be exactly 32 bytes long, instead of " "%d bytes." % len(key_bytes)), original_exception) def recover(self, msghash, vrs=None, signature=None): hash_bytes = hexstr_if_str(to_bytes, msghash) if vrs is not None: v, r, s = map(hexstr_if_str(to_decimal), vrs) v_standard = to_standard_v(v) signature_obj = self._keys.Signature(vrs=(v_standard, r, s)) elif signature is not None: signature_bytes = hexstr_if_str(to_bytes, signature) signature_bytes_standard = to_standard_signature_bytes( signature_bytes) signature_obj = self._keys.Signature( signature_bytes=signature_bytes_standard) else: raise TypeError( "You must supply the vrs tuple or the signature bytes") pubkey = signature_obj.recover_public_key_from_msg_hash(hash_bytes) return pubkey.to_checksum_address() def recoverMessage(self, data=None, hexstr=None, text=None, vrs=None, signature=None): msg_hash = self.hashMessage(data, hexstr=hexstr, text=text) return self.recover(msg_hash, vrs=vrs, signature=signature) def recoverTransaction(self, serialized_transaction): txn_bytes = hexstr_if_str(to_bytes, serialized_transaction) txn = Transaction.from_bytes(txn_bytes) msg_hash = hash_of_signed_transaction(txn) if sys.version_info.major < 3: msg_hash = to_hex(msg_hash) return self.recover(msg_hash, vrs=vrs_from(txn)) def setKeyBackend(self, backend): self._keys = KeyAPI(backend) def sign(self, message=None, private_key=None, message_hexstr=None, message_text=None): ''' @param private_key in bytes, str, or int. In Python 2, a bytes, unicode or str object will be interpreted as hexstr In Python 3, only a str object will be interpreted as hexstr ''' msg_bytes = to_bytes(message, hexstr=message_hexstr, text=message_text) msg_hash = self.hashMessage(msg_bytes) key_bytes = hexstr_if_str(to_bytes, private_key) key = self._keys.PrivateKey(key_bytes) (v, r, s, eth_signature_bytes) = sign_message_hash(key, msg_hash) (r_hex, s_hex, eth_signature_hex) = map(to_hex, (r, s, eth_signature_bytes)) return AttributeDict({ 'message': msg_bytes, 'messageHash': msg_hash, 'r': r_hex, 's': s_hex, 'v': v, 'signature': eth_signature_hex, }) def signTransaction(self, transaction_dict, private_key): ''' @param private_key in bytes, str, or int. In Python 2, a bytes, unicode or str object will be interpreted as hexstr In Python 3, only a str object will be interpreted as hexstr ''' assert isinstance(transaction_dict, Mapping) account = self.privateKeyToAccount(private_key) # sign transaction ( v, r, s, transaction_hash, rlp_encoded, ) = sign_transaction_dict(account._key_obj, transaction_dict) # format most returned elements as hex signature_info = { key: to_hex_with_size(val, 256) # minimum size is 32 bytes for key, val in ( ('rawTransaction', rlp_encoded), ('hash', transaction_hash), ('r', r), ('s', s), ) } signature_info['v'] = v return AttributeDict(signature_info)