funga-eth

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

commit 240cbbf372da62257abbf65ddb115ef229e6695c
parent 8895490e763e50bb96b7b49a785f079a4ae727d6
Author: lash <dev@holbrook.no>
Date:   Wed, 29 Mar 2023 10:29:41 +0100

Add typed data signing support (ERC191 v0x01 / ERC712)

Diffstat:
MCHANGELOG | 2++
Mfunga/eth/message.py | 15+++++++++++++++
Mfunga/eth/signer/defaultsigner.py | 22++++++++--------------
Msetup.py | 2+-
Dtests/test_keystore_reference.py | 64----------------------------------------------------------------
5 files changed, 26 insertions(+), 79 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG @@ -1,3 +1,5 @@ +* 0.6.6 + - EIP712 Typed data signer support * 0.6.5 - Add signer for ERC191 validator message (version 00) - Expose encoded messages for signing diff --git a/funga/eth/message.py b/funga/eth/message.py @@ -1,9 +1,11 @@ # external imports import sha3 +import logging # local imports from .encoding import to_checksum_address +logg = logging.getLogger(__name__) def to_digest(data): h = sha3.keccak_256() @@ -15,6 +17,18 @@ def to_validator_message(data, validator, digest=False): a = to_checksum_address(validator) v = bytes.fromhex(a) r = b'\x19\x00' + v + data + logg.debug('raw message data: ' + r.hex()) + if digest: + r = to_digest(r) + return r + + +# ERC191/ERC712 - version 0x01 +def to_typed_message(data, domain, digest=False): + assert len(data) == 32 + assert len(domain) == 32 + r = b'\x19\x01' + domain + data + logg.debug('raw message data: ' + r.hex()) if digest: r = to_digest(r) return r @@ -24,6 +38,7 @@ def to_validator_message(data, validator, digest=False): 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 + logg.debug('raw message data: ' + r.hex()) if digest: r = to_digest(r) return r diff --git a/funga/eth/signer/defaultsigner.py b/funga/eth/signer/defaultsigner.py @@ -11,6 +11,7 @@ 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 +from funga.eth.message import to_typed_message logg = logging.getLogger(__name__) @@ -62,24 +63,17 @@ class EIP155Signer(Signer): 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 - + + def sign_typed_message(self, address, domain, message, password=None): + message_to_sign = to_typed_message(message, domain, 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)) diff --git a/setup.py b/setup.py @@ -33,7 +33,7 @@ f.close() setup( name="funga-eth", - version="0.6.5", + version="0.6.6", description="Ethereum implementation of the funga keystore and signer", author="Louis Holbrook", author_email="dev@holbrook.no", diff --git a/tests/test_keystore_reference.py b/tests/test_keystore_reference.py @@ -1,64 +0,0 @@ -#!/usr/bin/python - -# standard imports -import unittest -import logging -import base64 -import os - -# external imports -import psycopg2 -from psycopg2 import sql -from cryptography.fernet import Fernet, InvalidToken - -# local imports -from funga.eth.keystore.sql import SQLKeystore -from funga.error import UnknownAccountError - -logging.basicConfig(level=logging.DEBUG) -logg = logging.getLogger() - - -class TestDatabase(unittest.TestCase): - - conn = None - cur = None - symkey = None - address_hex = None - db = None - - def setUp(self): - logg.debug('setup') - # arbitrary value - symkey_hex = 'E92431CAEE69313A7BE9E443C4ABEED9BF8157E9A13553B4D5D6E7D51B5021D9' - self.symkey = bytes.fromhex(symkey_hex) - self.address_hex = '9FA61f0E52A5C51b43f0d32404625BC436bb7041' - - kw = { - 'symmetric_key': self.symkey, - } - self.db = SQLKeystore('postgres+psycopg2://postgres@localhost:5432/signer_test', **kw) - self.address_hex = self.db.new('foo') - #self.address_hex = add_0x(address_hex) - - - def tearDown(self): - self.db.db_session.execute('DROP INDEX ethereum_address_idx;') - self.db.db_session.execute('DROP TABLE ethereum;') - self.db.db_session.commit() - - - - def test_get_key(self): - logg.debug('getting {}'.format(self.address_hex)) - self.db.get(self.address_hex, 'foo') - with self.assertRaises(InvalidToken): - self.db.get(self.address_hex, 'bar') - - bogus_account = '0x' + os.urandom(20).hex() - with self.assertRaises(UnknownAccountError): - self.db.get(bogus_account, 'bar') - - -if __name__ == '__main__': - unittest.main()