funga-eth

Ethereum implementation of the funga keystore and signer
Log | Files | Refs | README | LICENSE

commit 8895490e763e50bb96b7b49a785f079a4ae727d6
parent 176cf237ff58d39bddc6817ba0b2174218252ecf
Author: lash <dev@holbrook.no>
Date:   Mon, 27 Mar 2023 14:56:10 +0100

Add validator message handling (ERC191 v0x00)

Diffstat:
MCHANGELOG | 3+++
Afunga/eth/message.py | 32++++++++++++++++++++++++++++++++
Mfunga/eth/runnable/msg.py | 10+++++++++-
Mfunga/eth/signer/defaultsigner.py | 30++++++++++++++++++++++++------
Mrequirements.txt | 2+-
Msetup.py | 4++--
6 files changed, 71 insertions(+), 10 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG @@ -1,3 +1,6 @@ +* 0.6.5 + - Add signer for ERC191 validator message (version 00) + - Expose encoded messages for signing * 0.6.4 - Add passphrase file option to keyfile cli command * 0.6.3 diff --git a/funga/eth/message.py b/funga/eth/message.py @@ -0,0 +1,32 @@ +# external imports +import sha3 + +# local imports +from .encoding import to_checksum_address + + +def to_digest(data): + h = sha3.keccak_256() + h.update(data) + return h.digest() + +# ERC191 - version 0x00 +def to_validator_message(data, validator, digest=False): + a = to_checksum_address(validator) + v = bytes.fromhex(a) + r = b'\x19\x00' + v + data + if digest: + r = to_digest(r) + return r + + +# ERC191 - version 0x45 +def to_personal_message(data, digest=False): + ethereumed_message_header = b'\x19\x45' + 'thereum Signed Message:\n{}'.format(len(data)).encode('utf-8') + r = ethereumed_message_header + data + if digest: + r = to_digest(r) + return r + + + diff --git a/funga/eth/runnable/msg.py b/funga/eth/runnable/msg.py @@ -25,6 +25,7 @@ argparser.add_argument('-z', action='store_true', help='zero-length password') argparser.add_argument('-v', action='store_true', help='be verbose') argparser.add_argument('-0', dest='nonl', action='store_true', help='no newline at end of output') argparser.add_argument('-b', '--binary', dest='binary', action='store_true', help='parse input as binary hex') +argparser.add_argument('--validator', type=str, help='if set, will sign an ERC191 version 0 message') argparser.add_argument('msg', type=str, help='Message to sign') args = argparser.parse_args() @@ -50,7 +51,14 @@ def main(): msg = bytes.fromhex(hx) else: msg = args.msg.encode('utf-8').hex() - sig = signer.sign_ethereum_message(address, msg, password=passphrase) + + sig = None + if args.validator: + logg.info('signing validator message (ERC191 version 0)') + sig = signer.sign_validator_message(address, args.validator, msg, password=passphrase) + else: + logg.info('signing personal message (ERC191 version 0x45)') + sig = signer.sign_ethereum_message(address, msg, password=passphrase) r = sig.hex() if not args.nonl: diff --git a/funga/eth/signer/defaultsigner.py b/funga/eth/signer/defaultsigner.py @@ -9,6 +9,8 @@ from hexathon import int_to_minbytes # local imports from funga.signer import Signer from funga.eth.encoding import chain_id_to_v +from funga.eth.message import to_personal_message +from funga.eth.message import to_validator_message logg = logging.getLogger(__name__) @@ -41,7 +43,6 @@ class EIP155Signer(Signer): def sign_ethereum_message(self, address, message, password=None): - #k = keys.PrivateKey(self.keyGetter.get(address, password)) #z = keys.ecdsa_sign(message_hash=g, private_key=k) if type(message).__name__ == 'str': @@ -55,15 +56,30 @@ class EIP155Signer(Signer): logg.debug('unhandled format {}'.format(type(message).__name__)) raise ValueError('message must be type str or bytes, received {}'.format(type(message).__name__)) - ethereumed_message_header = b'\x19' + 'Ethereum Signed Message:\n{}'.format(len(message)).encode('utf-8') - h = sha3.keccak_256() - h.update(ethereumed_message_header + message) - message_to_sign = h.digest() - + message_to_sign = to_personal_message(message, digest=True) z = self.sign_pure(address, message_to_sign, password) return z + def sign_validator_message(self, address, validator, message, password=None): + #k = keys.PrivateKey(self.keyGetter.get(address, password)) + #z = keys.ecdsa_sign(message_hash=g, private_key=k) + if type(message).__name__ == 'str': + logg.debug('signing message in "str" format: {}'.format(message)) + #z = k.sign_msg(bytes.fromhex(message)) + message = bytes.fromhex(message) + elif type(message).__name__ == 'bytes': + logg.debug('signing message in "bytes" format: {}'.format(message.hex())) + #z = k.sign_msg(message) + else: + logg.debug('unhandled format {}'.format(type(message).__name__)) + raise ValueError('message must be type str or bytes, received {}'.format(type(message).__name__)) + + message_to_sign = to_validator_message(message, validator, digest=True) + z = self.sign_pure(address, message_to_sign, password) + return z + + # TODO: generic sign should be moved to non-eth context def sign_pure(self, address, message, password=None): pk = coincurve.PrivateKey(secret=self.keyGetter.get(address, password)) @@ -77,3 +93,5 @@ class EIP155Signer(Signer): elif dialect == 'eth': return self.sign_ethereum_message(address, message, password=password) raise ValueError('Unknown message sign dialect "{}"'.format(dialect)) + + diff --git a/requirements.txt b/requirements.txt @@ -7,4 +7,4 @@ confini~=0.6.0 coincurve==15.0.0 hexathon~=0.1.6 pycryptodome==3.10.1 -funga==0.5.2 +funga~=0.5.2 diff --git a/setup.py b/setup.py @@ -33,7 +33,7 @@ f.close() setup( name="funga-eth", - version="0.6.4", + version="0.6.5", description="Ethereum implementation of the funga keystore and signer", author="Louis Holbrook", author_email="dev@holbrook.no", @@ -58,6 +58,6 @@ setup( 'eth-sign-msg=funga.eth.runnable.msg:main', ], }, - url='https://git.defalsify.org/funga-eth.git', + url='https://git.defalsify.org/funga-eth', include_package_data=True, )