From 41456fd1ba6e8d2d9168ff8cda4a5ea93eeaed73 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Tue, 11 Feb 2025 20:50:50 +0000 Subject: [PATCH 01/25] feat: rlp encoding --- crypto/identity/private_key.py | 37 ++++-- .../types/abstract_transaction.py | 26 +++-- crypto/utils/rlp_decoder.py | 109 ++++++++++++++++++ crypto/utils/rlp_encoder.py | 91 +++++++++++++++ crypto/utils/transaction_hasher.py | 94 --------------- crypto/utils/transaction_utils.py | 79 +++++++++++++ 6 files changed, 326 insertions(+), 110 deletions(-) create mode 100644 crypto/utils/rlp_decoder.py create mode 100644 crypto/utils/rlp_encoder.py delete mode 100644 crypto/utils/transaction_hasher.py create mode 100644 crypto/utils/transaction_utils.py diff --git a/crypto/identity/private_key.py b/crypto/identity/private_key.py index d15d9ae1..369d0504 100644 --- a/crypto/identity/private_key.py +++ b/crypto/identity/private_key.py @@ -1,12 +1,18 @@ from binascii import hexlify from hashlib import sha256 -from coincurve import PrivateKey as PvtKey +from btclib.to_prv_key import PrvKey, int_from_prv_key +from btclib.to_pub_key import pub_keyinfo_from_key +from btclib.ecc import bms class PrivateKey(object): + private_key: PrvKey + private_key_raw: str + def __init__(self, private_key: str): - self.private_key = PvtKey.from_hex(private_key) - self.public_key = hexlify(self.private_key.public_key.format()).decode() + self.private_key_raw = private_key + self.private_key = int_from_prv_key(private_key) + self.public_key = hexlify(pub_keyinfo_from_key(self.private_key)[0]).decode() def sign(self, message: bytes) -> bytes: """Sign a message with this private key object @@ -17,9 +23,26 @@ def sign(self, message: bytes) -> bytes: Returns: bytes: signature of the signed message """ - signature = self.private_key.sign(message) - - return hexlify(signature).decode() + signature = bms.sign(message, self.private_key) + + return signature.serialize() + + def sign_compact(self, message: bytes) -> bms.Sig: + """Sign a message with this private key object + + Args: + message (bytes): bytes data you want to sign + + Returns: + bytes: signature of the signed message + """ + return bms.sign(message, self.private_key) + # wif, address = bms.gen_keys(self.private_key, compressed=True) + + # # print('WIF', wif) + # # print('address', address) + + # return bms.sign(message, wif, address) def to_hex(self): """Returns a private key in hex format @@ -27,7 +50,7 @@ def to_hex(self): Returns: str: private key in hex format """ - return self.private_key.to_hex() + return hexlify(self.private_key.to_bytes(32, 'big')).decode() @classmethod def from_passphrase(cls, passphrase: str): diff --git a/crypto/transactions/types/abstract_transaction.py b/crypto/transactions/types/abstract_transaction.py index d7506152..89c6c4d9 100644 --- a/crypto/transactions/types/abstract_transaction.py +++ b/crypto/transactions/types/abstract_transaction.py @@ -4,7 +4,7 @@ from crypto.configuration.network import get_network from crypto.identity.address import address_from_public_key from crypto.identity.private_key import PrivateKey -from crypto.utils.transaction_hasher import TransactionHasher +from crypto.utils.transaction_utils import TransactionUtils from coincurve import PublicKey from crypto.utils.abi_decoder import AbiDecoder @@ -19,7 +19,7 @@ def get_payload(self) -> str: def decode_payload(self, data: dict) -> Optional[dict]: if 'data' not in data or data['data'] == '': return None - + payload = data['data'] decoder = AbiDecoder() @@ -31,17 +31,24 @@ def refresh_payload_data(self): self.data['data'] = self.get_payload().lstrip('0x') def get_id(self) -> str: - return self.hash(skip_signature=False).hex() + return self.hash(skip_signature=False) def get_bytes(self, skip_signature: bool = False) -> bytes: from crypto.transactions.serializer import Serializer + return Serializer.get_bytes(self, skip_signature) def sign(self, private_key: PrivateKey): - hash_ = self.hash(skip_signature=True) - signature_with_recid = private_key.private_key.sign_recoverable(hash_, hasher=None) - signature_hex = signature_with_recid.hex() - self.data['signature'] = signature_hex + transaction_hash = self.hash(skip_signature=True) + + message = bytes.fromhex(transaction_hash) + + transaction_signature = private_key.sign_compact(message) + + self.data['v'] = transaction_signature.rf + self.data['r'] = transaction_signature.dsa_sig.r.to_bytes(32, 'big').hex() + self.data['s'] = transaction_signature.dsa_sig.s.to_bytes(32, 'big').hex() + return self def get_public_key(self, compact_signature, hash_): @@ -84,11 +91,12 @@ def to_dict(self) -> dict: def to_json(self) -> str: return json.dumps(self.to_dict()) - def hash(self, skip_signature: bool) -> bytes: + def hash(self, skip_signature: bool) -> str: hash_data = self.data.copy() if skip_signature: hash_data['signature'] = None - return TransactionHasher.to_hash(hash_data, skip_signature) + + return TransactionUtils.to_hash(hash_data, skip_signature) def get_signature(self): signature_hex = self.data.get('signature') diff --git a/crypto/utils/rlp_decoder.py b/crypto/utils/rlp_decoder.py new file mode 100644 index 00000000..a5f62a0b --- /dev/null +++ b/crypto/utils/rlp_decoder.py @@ -0,0 +1,109 @@ +import re + +class RlpDecoder: + @classmethod + def decode(cls, data: str): + bytes_data = cls.get_bytes(data, 'data') + decoded = cls._decode(bytes_data, 0) + + if decoded['consumed'] != len(bytes_data): + raise ValueError('unexpected junk after RLP payload') + + return decoded['result'] + + @staticmethod + def get_bytes(value: str, name: str = 'value') -> list: + if re.match(r'^0x(?:[0-9a-fA-F]{2})*$', value): + hex_value = value[2:] + length = len(hex_value) // 2 + bytes_data = [int(hex_value[i * 2:i * 2 + 2], 16) for i in range(length)] + return bytes_data + + raise ValueError(f'Invalid BytesLike value for "{name}": {value}') + + @staticmethod + def hexlify(data) -> str: + return '0x' + ''.join(f'{byte:02x}' for byte in data) + + @staticmethod + def hexlify_byte(value) -> str: + return f'0x{value & 0xff:02x}' + + @staticmethod + def unarrayify_integer(data, offset, length) -> int: + result = 0 + for i in range(length): + result = (result << 8) + data[offset + i] + return result + + @classmethod + def _decode_children(cls, data, offset, child_offset, length) -> dict: + result = [] + end = offset + 1 + length + + while child_offset < end: + decoded = cls._decode(data, child_offset) + result.append(decoded['result']) + child_offset += decoded['consumed'] + + if child_offset > end: + raise ValueError('child data too short or malformed') + + return { + 'consumed': 1 + length, + 'result': result, + } + + @classmethod + def _decode(cls, data, offset) -> dict[str, int | str]: + cls.check_offset(offset, data) + + prefix = data[offset] + + if prefix >= 0xf8: + length_length = prefix - 0xf7 + cls.check_offset(offset + length_length, data) + + length = cls.unarrayify_integer(data, offset + 1, length_length) + cls.check_offset(offset + 1 + length_length + length - 1, data) + + return cls._decode_children(data, offset, offset + 1 + length_length, length_length + length) + elif prefix >= 0xc0: + length = prefix - 0xc0 + if length > 0: + cls.check_offset(offset + 1 + length - 1, data) + + return cls._decode_children(data, offset, offset + 1, length) + elif prefix >= 0xb8: + length_length = prefix - 0xb7 + cls.check_offset(offset + length_length, data) + + length = cls.unarrayify_integer(data, offset + 1, length_length) + if length > 0: + cls.check_offset(offset + 1 + length_length + length - 1, data) + slice_data = data[offset + 1 + length_length:offset + 1 + length_length + length] + + return { + 'consumed': 1 + length_length + length, + 'result': cls.hexlify(slice_data), + } + elif prefix >= 0x80: + length = prefix - 0x80 + if length > 0: + cls.check_offset(offset + 1 + length - 1, data) + slice_data = data[offset + 1:offset + 1 + length] + + return { + 'consumed': 1 + length, + 'result': cls.hexlify(slice_data), + } + + return { + 'consumed': 1, + 'result': cls.hexlify_byte(prefix), + } + + @staticmethod + def check_offset(offset, data) -> None: + if offset > len(data): + raise ValueError('data short segment or out of range') diff --git a/crypto/utils/rlp_encoder.py b/crypto/utils/rlp_encoder.py new file mode 100644 index 00000000..21ed885d --- /dev/null +++ b/crypto/utils/rlp_encoder.py @@ -0,0 +1,91 @@ +from binascii import hexlify, unhexlify + + +class RlpEncoder: + @classmethod + def encode(cls, obj) -> str: + encoded = cls._encode(obj) + hex_str = '' + nibbles = '0123456789abcdef' + + for byte in encoded: + hex_str += nibbles[byte >> 4] + hex_str += nibbles[byte & 0x0f] + + return hex_str + + @classmethod + def _encode(cls, obj) -> list: + if isinstance(obj, list): + payload = [] + for child in obj: + payload.extend(cls._encode(child)) + + payload_length = len(payload) + if payload_length <= 55: + payload.insert(0, 0xc0 + payload_length) + + return payload + + length = cls.arrayify_integer(payload_length) + length.insert(0, 0xf7 + (len(length))) + + return length + payload + + data = cls.get_bytes(obj) + data_length = len(data) + if data_length == 1 and data[0] <= 0x7f: + return data + + if data_length <= 55: + data.insert(0, 0x80 + data_length) + + return data + + length = cls.arrayify_integer(len(data)) + length.insert(0, 0xb7 + len(length)) + + return length + data + + @staticmethod + def arrayify_integer(value) -> list: + result = [] + while value > 0: + result.insert(0, value & 0xff) + value >>= 8 + + return result + + @staticmethod + def get_bytes(value) -> list: + if isinstance(value, str) or isinstance(value, bytes): + if isinstance(value, str): + value = value.encode() + + if value.startswith(b'0x'): + hex_str = value[2:] + if hex_str == '': + return [] + + if len(hex_str) % 2 != 0: + hex_str = b'0' + hex_str + + return [int(hex_str[i:i+2], 16) for i in range(0, len(hex_str), 2)] + + return [char for char in value] + + if isinstance(value, int): + if value == 0: + return [] + + result = [] + while value > 0: + result.insert(0, value & 0xff) + value >>= 8 + + return result + + if isinstance(value, list): + return [v & 0xff for v in value] + + raise ValueError('invalid type', type(value)) diff --git a/crypto/utils/transaction_hasher.py b/crypto/utils/transaction_hasher.py deleted file mode 100644 index 23ba6beb..00000000 --- a/crypto/utils/transaction_hasher.py +++ /dev/null @@ -1,94 +0,0 @@ -import hashlib -from binascii import unhexlify - - -class TransactionHasher: - @staticmethod - def to_hash(transaction: dict, skip_signature: bool = False) -> bytes: - # Process recipientAddress - hex_address = transaction.get('recipientAddress', '').lstrip('0x') - # Pad with leading zero if necessary - if len(hex_address) % 2 != 0: - hex_address = '0' + hex_address - recipient_address = bytes.fromhex(hex_address.lower()) - - # Build the fields array - fields = [ - TransactionHasher.to_be_array(int(transaction['network'])), - TransactionHasher.to_be_array(int(transaction['nonce'])), - TransactionHasher.to_be_array(int(transaction['gasPrice'])), # maxPriorityFeePerGas - TransactionHasher.to_be_array(int(transaction['gasPrice'])), # maxFeePerGas - TransactionHasher.to_be_array(int(transaction['gasLimit'])), - recipient_address, - TransactionHasher.to_be_array(int(transaction['value'])), - bytes.fromhex(transaction.get('data', '').lstrip('0x')) if transaction.get('data') else b'', - [], # Access list is unused - ] - - if not skip_signature and 'signature' in transaction: - signature_buffer = bytes.fromhex(transaction['signature']) - r = signature_buffer[0:32] - s = signature_buffer[32:64] - v = signature_buffer[64] - fields.extend([ - TransactionHasher.to_be_array(v), - r, - s, - ]) - - eip1559_prefix = b'\x02' # Marker for Type 2 (EIP-1559) transaction - - encoded = TransactionHasher.encode_rlp(fields) - hash_input = eip1559_prefix + encoded - - # Use SHA256 for hashing - return hashlib.sha256(hash_input).digest() - - @staticmethod - def to_be_array(value): - if isinstance(value, int): - if value == 0: - return b'' # Empty bytes represent zero - else: - return value.to_bytes((value.bit_length() + 7) // 8, byteorder='big') - elif isinstance(value, bytes): - return value - else: - raise TypeError("Unsupported type for to_be_array") - - @staticmethod - def encode_length(length): - if length == 0: - return b'' - result = [] - while length > 0: - result.insert(0, length & 0xFF) - length >>= 8 - return bytes(result) - - @staticmethod - def encode_rlp(input_data): - if isinstance(input_data, bytes): - input_len = len(input_data) - if input_len == 1 and input_data[0] <= 0x7f: - return input_data - elif input_len <= 55: - return bytes([0x80 + input_len]) + input_data - else: - len_bytes = TransactionHasher.encode_length(input_len) - return bytes([0xb7 + len(len_bytes)]) + len_bytes + input_data - elif isinstance(input_data, list): - output = b''.join([TransactionHasher.encode_rlp(item) for item in input_data]) - output_len = len(output) - if output_len <= 55: - return bytes([0xc0 + output_len]) + output - else: - len_bytes = TransactionHasher.encode_length(output_len) - return bytes([0xf7 + len(len_bytes)]) + len_bytes + output - elif isinstance(input_data, int): - return TransactionHasher.encode_rlp(TransactionHasher.to_be_array(input_data)) - elif input_data is None: - return TransactionHasher.encode_rlp(b'') - else: - # Handle other types by converting to bytes - return TransactionHasher.encode_rlp(str(input_data).encode('utf-8')) diff --git a/crypto/utils/transaction_utils.py b/crypto/utils/transaction_utils.py new file mode 100644 index 00000000..899fcd4e --- /dev/null +++ b/crypto/utils/transaction_utils.py @@ -0,0 +1,79 @@ +from binascii import hexlify, unhexlify +import hashlib + +from crypto.utils.rlp_encoder import RlpEncoder + +class TransactionUtils: + EIP1559_PREFIX = '02' + + @classmethod + def to_buffer(cls, transaction: dict, skip_signature: bool = False) -> bytes: + # Process recipientAddress + hex_address = transaction.get('recipientAddress', '').lstrip('0x') + + # Pad with leading zero if necessary + if len(hex_address) % 2 != 0: + hex_address = '0' + hex_address + + recipient_address = bytes.fromhex(hex_address.lower()) + + # Build the fields array + fields = [ + cls.to_be_array(int(transaction['network'])), + cls.to_be_array(int(transaction.get('nonce', 0))), + cls.to_be_array(0), + cls.to_be_array(int(transaction['gasPrice'])), + cls.to_be_array(int(transaction['gasLimit'])), + recipient_address, + cls.to_be_array(int(transaction.get('value', 0))), + bytes.fromhex(transaction.get('data', '').lstrip('0x')) if transaction.get('data') else b'', + [], + ] + + if not skip_signature and 'signature' in transaction: + signature_buffer = bytes.fromhex(transaction['signature']) + r = signature_buffer[0:32] + s = signature_buffer[32:64] + v = signature_buffer[64] + fields.extend([ + cls.to_be_array(v), + r, + s, + ]) + + # print('FIELDS', fields, len(fields)) + # print('FIELDS', [f.hex() for f in fields]) + + # FIELDS [b'1e', b'01', b'', b'05', b'5208', b'6f0182a0cc707b055322ccf6d4cb6a5aff1aeb22', b'05f5e100', b''] + + encoded = RlpEncoder.encode(fields) + + # print('ENCODED', encoded) + # \xf8C\x821e\x8201\x80\x8205\x845208\xa86f0182a0cc707b055322ccf6d4cb6a5aff1aeb22\x8805f5e100\x80\xc0 + # print('ACTUAL 0xe31e018005825208946f0182a0cc707b055322ccf6d4cb6a5aff1aeb228405f5e10080c0') + + hash_input = cls.EIP1559_PREFIX + encoded + + print('HASH_INPUT', hash_input) + print('EXPECTED 02e31e018005825208946f0182a0cc707b055322ccf6d4cb6a5aff1aeb228405f5e10080c0') + print('MATCH', hash_input == '02e31e018005825208946f0182a0cc707b055322ccf6d4cb6a5aff1aeb228405f5e10080c0') + + # Use SHA256 for hashing + return hash_input.encode() + + @classmethod + def to_hash(cls, transaction: dict, skip_signature: bool = False) -> str: + return hashlib.sha256(unhexlify(cls.to_buffer(transaction, skip_signature))).hexdigest() + + @staticmethod + def to_be_array(value): + if isinstance(value, int): + if value == 0: + return b'' + else: + return value.to_bytes((value.bit_length() + 7) // 8, byteorder='big') + + if isinstance(value, bytes): + return value + + raise TypeError("Unsupported type for to_be_array") From b7cb1f239f5f1a03cd20c9a1c2553ba4a003ae25 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Tue, 11 Feb 2025 20:51:03 +0000 Subject: [PATCH 02/25] test --- tests/fixtures/evm-sign.json | 14 ++++---- tests/fixtures/multipayment-empty.json | 18 ++++++++++ tests/fixtures/multipayment-single.json | 18 ++++++++++ tests/fixtures/multipayment.json | 19 +++++++++++ tests/fixtures/transfer-0.json | 18 ++++++++++ tests/fixtures/transfer-large-amount.json | 18 ++++++++++ tests/fixtures/transfer.json | 14 ++++---- tests/fixtures/unvote.json | 20 ++++++----- tests/fixtures/username-registration.json | 18 ++++++++++ tests/fixtures/username-resignation.json | 17 ++++++++++ tests/fixtures/validator-registration.json | 20 ++++++----- tests/fixtures/validator-resignation.json | 20 ++++++----- tests/fixtures/vote.json | 20 ++++++----- tests/identity/conftest.py | 13 ++++++++ tests/identity/test_private_key.py | 33 +++++++++++++++++++ .../builder/test_transfer_builder.py | 28 +++++++++++----- 16 files changed, 251 insertions(+), 57 deletions(-) create mode 100644 tests/fixtures/multipayment-empty.json create mode 100644 tests/fixtures/multipayment-single.json create mode 100644 tests/fixtures/multipayment.json create mode 100644 tests/fixtures/transfer-0.json create mode 100644 tests/fixtures/transfer-large-amount.json create mode 100644 tests/fixtures/username-registration.json create mode 100644 tests/fixtures/username-resignation.json diff --git a/tests/fixtures/evm-sign.json b/tests/fixtures/evm-sign.json index 600ffa2d..a171db39 100644 --- a/tests/fixtures/evm-sign.json +++ b/tests/fixtures/evm-sign.json @@ -1,16 +1,18 @@ { "data": { "network": 30, - "nonce": "13", + "nonce": "1", "gasPrice": 5, "gasLimit": 1000000, "value": "0", - "recipientAddress": "0xE536720791A7DaDBeBdBCD8c8546fb0791a11901", - "data": "a9059cbb00000000000000000000000027fa7caffaae77ddb9ab232fdbda56d5e5af2393000000000000000000000000000000000000000000000000016345785d8a0000", - "signature": "ba30f9042519079895c7408b0e92046c3f20680e0a9294e38ab3cfdd19b26cd4036fe2a80644abb922f1ad7cd682811a83c20120a8030df47b244a3bc44f4dbd00", + "recipientAddress": "0xe536720791a7dadbebdbcd8c8546fb0791a11901", + "data": "a9059cbb00000000000000000000000027fa7caffaae77ddb9ab232fdbda56d5e5af23930000000000000000000000000000000000000000000000000000000000000064", "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", - "id": "3935ff0fe84ea6ac42fc889ed7cda4f97ddd11fd2d1c31e9201f14866acb6edc" + "id": "2cfa9d51e71f014f8054881052e41dc61267587f9dba04c59a31cc881f8ce35b", + "v": 27, + "r": "a56cf78a7203927af0c8216fdbc804182a0788569e460067efec519b6f7b2e55", + "s": "52ce951a20c7406a4a34707557f02e5a2af1451cc4e216645778c4ee0391a4cd" }, - "serialized": "1e0d000000000000000500000040420f00000000000000000000000000000000000000000000000000000000000000000001e536720791a7dadbebdbcd8c8546fb0791a1190144000000a9059cbb00000000000000000000000027fa7caffaae77ddb9ab232fdbda56d5e5af2393000000000000000000000000000000000000000000000000016345785d8a0000ba30f9042519079895c7408b0e92046c3f20680e0a9294e38ab3cfdd19b26cd4036fe2a80644abb922f1ad7cd682811a83c20120a8030df47b244a3bc44f4dbd00" + "serialized": "02f8a81e018005830f424094e536720791a7dadbebdbcd8c8546fb0791a1190180b844a9059cbb00000000000000000000000027fa7caffaae77ddb9ab232fdbda56d5e5af23930000000000000000000000000000000000000000000000000000000000000064c080a0a56cf78a7203927af0c8216fdbc804182a0788569e460067efec519b6f7b2e55a052ce951a20c7406a4a34707557f02e5a2af1451cc4e216645778c4ee0391a4cd" } diff --git a/tests/fixtures/multipayment-empty.json b/tests/fixtures/multipayment-empty.json new file mode 100644 index 00000000..7a235183 --- /dev/null +++ b/tests/fixtures/multipayment-empty.json @@ -0,0 +1,18 @@ +{ + "data": { + "network": 30, + "nonce": "1", + "gasPrice": 5, + "gasLimit": 1000000, + "value": "0", + "recipientAddress": "0x83769BeEB7e5405ef0B7dc3C66C43E3a51A6d27f", + "data": "084ce7080000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", + "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", + "id": "d816a0f8b51eb618c4497aeddcc9d4596f94fefc39de306282f50596c9e1c071", + "v": 28, + "r": "bfab2a6634cfbf8bc802d0259dea575b7618131e8de9e29ca7df1950e68ce8eb", + "s": "5e7eeb4c2f973a8397f38b68520123798b07b4f1bf251bbaaacafb36e8e05a55" + }, + "serialized": "02f8e81e018005830f42409483769beeb7e5405ef0b7dc3c66c43e3a51a6d27f80b884084ce7080000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c001a0bfab2a6634cfbf8bc802d0259dea575b7618131e8de9e29ca7df1950e68ce8eba05e7eeb4c2f973a8397f38b68520123798b07b4f1bf251bbaaacafb36e8e05a55" +} diff --git a/tests/fixtures/multipayment-single.json b/tests/fixtures/multipayment-single.json new file mode 100644 index 00000000..b2e6098b --- /dev/null +++ b/tests/fixtures/multipayment-single.json @@ -0,0 +1,18 @@ +{ + "data": { + "network": 30, + "nonce": "1", + "gasPrice": 5, + "gasLimit": 1000000, + "value": "100000000", + "recipientAddress": "0x83769BeEB7e5405ef0B7dc3C66C43E3a51A6d27f", + "data": "084ce7080000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008233f6df6449d7655f4643d2e752dc8d2283fad500000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000005f5e100", + "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", + "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", + "id": "d3775117889fce2397bded88564427dda03c31f2b85a839567f98ae776e3ff4c", + "v": 28, + "r": "c00a8f6a658c1dda434bceb708c74d62d086ec143939c2373010284221d25995", + "s": "43f6e7e5988f37c4336638c1b6a0cf4aa6caeefd5bda6937aa03464bdf44536c" + }, + "serialized": "02f9012c1e018005830f42409483769beeb7e5405ef0b7dc3c66c43e3a51a6d27f8405f5e100b8c4084ce7080000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008233f6df6449d7655f4643d2e752dc8d2283fad500000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000005f5e100c001a0c00a8f6a658c1dda434bceb708c74d62d086ec143939c2373010284221d25995a043f6e7e5988f37c4336638c1b6a0cf4aa6caeefd5bda6937aa03464bdf44536c" +} diff --git a/tests/fixtures/multipayment.json b/tests/fixtures/multipayment.json new file mode 100644 index 00000000..20e781c9 --- /dev/null +++ b/tests/fixtures/multipayment.json @@ -0,0 +1,19 @@ +{ + "data": { + "network": 30, + "nonce": "1", + "gasPrice": 5, + "gasLimit": 1000000, + "valaue": "3000000000000000000", + "value": "300000000", + "recipientAddress": "0x83769BeEB7e5405ef0B7dc3C66C43E3a51A6d27f", + "data": "084ce708000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000008233f6df6449d7655f4643d2e752dc8d2283fad50000000000000000000000008233f6df6449d7655f4643d2e752dc8d2283fad500000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000000bebc200", + "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", + "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", + "id": "8ef542e888c41642297bf3e4cb0f58cfe3a07e38adcb74cd2f121b5653c4db9e", + "v": 27, + "r": "39e6a6fb8c41c33d1c56c6b0f8c15f977582f9fcf626dbc84b82526db201c8dc", + "s": "285f0849073a3952511bdc34682668272799eacf3cd7556a9800f46d57e557ea" + }, + "serialized": "02f9016d1e018005830f42409483769beeb7e5405ef0b7dc3c66c43e3a51a6d27f8411e1a300b90104084ce708000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000008233f6df6449d7655f4643d2e752dc8d2283fad50000000000000000000000008233f6df6449d7655f4643d2e752dc8d2283fad500000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000000bebc200c080a039e6a6fb8c41c33d1c56c6b0f8c15f977582f9fcf626dbc84b82526db201c8dca0285f0849073a3952511bdc34682668272799eacf3cd7556a9800f46d57e557ea" +} diff --git a/tests/fixtures/transfer-0.json b/tests/fixtures/transfer-0.json new file mode 100644 index 00000000..8d69083d --- /dev/null +++ b/tests/fixtures/transfer-0.json @@ -0,0 +1,18 @@ +{ + "data": { + "network": 30, + "nonce": "123", + "gasPrice": 5, + "gasLimit": 21000, + "recipientAddress": "0xb693449adda7efc015d87944eae8b7c37eb1690a", + "value": "0", + "data": "", + "v": 28, + "r": "f7bae93b75f06c39600f123ca6c805a38636db9643eac29d20d21c6bf0d2eca2", + "s": "17265ff237bff3fe0620c53ff1615c4b8ad9136fe3d61dca38562c60dc5fcae2", + "senderPublicKey": "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd", + "senderAddress": "0x41aD2bc63A2059f9b623533d87fe99887D794847", + "id": "7d6ad61dabbc9907484080100096c9c7c6364c130ca003f9dd730901c112437f" + }, + "serialized": "02f8621e7b800582520894b693449adda7efc015d87944eae8b7c37eb1690a8080c001a0f7bae93b75f06c39600f123ca6c805a38636db9643eac29d20d21c6bf0d2eca2a017265ff237bff3fe0620c53ff1615c4b8ad9136fe3d61dca38562c60dc5fcae2" +} diff --git a/tests/fixtures/transfer-large-amount.json b/tests/fixtures/transfer-large-amount.json new file mode 100644 index 00000000..0fcee465 --- /dev/null +++ b/tests/fixtures/transfer-large-amount.json @@ -0,0 +1,18 @@ +{ + "data": { + "network": 30, + "nonce": "17", + "gasPrice": 5, + "gasLimit": 21000, + "recipientAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", + "value": "10000000000000000000", + "data": "", + "v": 27, + "r": "c741f8ccf811e7080216b71e0cbcad1138a4f187b08c749c5e8780568f1ff4bc", + "s": "530341bef473fa92db1fe96922758ed010ba8eb80f2e527c9441771fa4c5368b", + "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", + "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", + "id": "fb9e521867f484cae4b59585d55ac55a99a8c6adfa0cf8b8b50d242115cbb40d" + }, + "serialized": "02f86a1e118005825208946f0182a0cc707b055322ccf6d4cb6a5aff1aeb22888ac7230489e8000080c080a0c741f8ccf811e7080216b71e0cbcad1138a4f187b08c749c5e8780568f1ff4bca0530341bef473fa92db1fe96922758ed010ba8eb80f2e527c9441771fa4c5368b" +} diff --git a/tests/fixtures/transfer.json b/tests/fixtures/transfer.json index 4c577082..24a2b1aa 100644 --- a/tests/fixtures/transfer.json +++ b/tests/fixtures/transfer.json @@ -1,16 +1,18 @@ { "data": { "network": 30, - "nonce": "12", + "nonce": "1", "gasPrice": 5, "gasLimit": 21000, - "value": "10000000000000000000", - "recipientAddress": "0x07Ac3E438719be72a9e2591bB6015F10E8Af2468", + "recipientAddress": "0x6f0182a0cc707b055322ccf6d4cb6a5aff1aeb22", + "value": "100000000", "data": "", - "signature": "b3bc84c8caf1b75c18a78dde87df9f555161003d341eafad659ab672501185e413a26284c3c95056809c7d440c4ffab26179c538864c4d14534ebd5a961852bf01", + "v": 27, + "r": "0567c4def813a66e03fa1cd499a27c6922698a67e25e0b38458d8f4bb0e581fc", + "s": "25fcdf9d110b82bde15bae4a49118deb83cfc3ec1656c2a29286d7836d328abe", "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", - "id": "b5d7b17d30da123d9eebc8bb6012c1a4e950e1dad2b080404bb052c30b8a8b2e" + "id": "0b8a05781cc5ea130573f33330a739f9a17f7d889bcfab519b09ee5ccc7f359d" }, - "serialized": "1e0c0000000000000005000000085200000000000000000000000000000000000000000000000000008ac7230489e800000107ac3e438719be72a9e2591bb6015f10e8af246800000000b3bc84c8caf1b75c18a78dde87df9f555161003d341eafad659ab672501185e413a26284c3c95056809c7d440c4ffab26179c538864c4d14534ebd5a961852bf01" + "serialized": "02f8661e018005825208946f0182a0cc707b055322ccf6d4cb6a5aff1aeb228405f5e10080c080a00567c4def813a66e03fa1cd499a27c6922698a67e25e0b38458d8f4bb0e581fca025fcdf9d110b82bde15bae4a49118deb83cfc3ec1656c2a29286d7836d328abe" } diff --git a/tests/fixtures/unvote.json b/tests/fixtures/unvote.json index d5e3a7cf..5dccb90f 100644 --- a/tests/fixtures/unvote.json +++ b/tests/fixtures/unvote.json @@ -1,16 +1,18 @@ { "data": { - "network": 30, - "nonce": "13", "gasPrice": 5, - "gasLimit": 200000, - "value": "0", - "recipientAddress": "0x522B3294E6d06aA25Ad0f1B8891242E335D3B459", - "data": "3174b689", - "signature": "d7534ec92c06a8547d0f2b3d3259dff5b0b17f8673d68dff9af023009c9c450e24205cb5f4fd6165d71c8b3ba3e9f741d1853110d44bd1e798e87f1a5d6a89c501", + "network": 30, + "id": "4f43cc9b97433bfc86f3aaa55dbedfad1f15c76b527ec68f7a80fc15105d2e43", + "gasLimit": 1000000, + "nonce": "1", "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", - "id": "92ca281a6699a4eb08e8e5c4a644c216026f6c6d3560611c50cab54d1300b690" + "recipientAddress": "0x535b3d7a252fa034ed71f0c53ec0c6f784cb64e1", + "value": "0", + "data": "3174b689", + "v": 27, + "r": "2853135c30a6b131e9270ce1b52999b8d2bdc18b25bef5b1c13ef4ca8a20ce9e", + "s": "3999a9d5d1ff826efa13052394b28b101535c92dcd8744c916c003431e08744e" }, - "serialized": "1e0d0000000000000005000000400d0300000000000000000000000000000000000000000000000000000000000000000001522b3294e6d06aa25ad0f1b8891242e335d3b459040000003174b689d7534ec92c06a8547d0f2b3d3259dff5b0b17f8673d68dff9af023009c9c450e24205cb5f4fd6165d71c8b3ba3e9f741d1853110d44bd1e798e87f1a5d6a89c501" + "serialized": "02f8671e018005830f424094535b3d7a252fa034ed71f0c53ec0c6f784cb64e180843174b689c080a02853135c30a6b131e9270ce1b52999b8d2bdc18b25bef5b1c13ef4ca8a20ce9ea03999a9d5d1ff826efa13052394b28b101535c92dcd8744c916c003431e08744e" } diff --git a/tests/fixtures/username-registration.json b/tests/fixtures/username-registration.json new file mode 100644 index 00000000..46d03a63 --- /dev/null +++ b/tests/fixtures/username-registration.json @@ -0,0 +1,18 @@ +{ + "data": { + "gasPrice": 5, + "network": 30, + "id": "6643b3dec8d2e8aaa188fb83b96c9ebf2ed21d547ff641f267ddc5ef29152f6b", + "gasLimit": 1000000, + "nonce": "1", + "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", + "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", + "recipientAddress": "0x2c1de3b4dbb4adebebb5dcecae825be2a9fc6eb6", + "value": "0", + "data": "36a94134000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000037068700000000000000000000000000000000000000000000000000000000000", + "v": 27, + "r": "48f36028509a88c8670b2baaac14b5ba6a8c88bbd90cce4149a3070e57f0fa42", + "s": "297fe9a4d2df4c682e8705311d1143d2df9c89dc8689853544c1eb96121f5e85" + }, + "serialized": "02f8c81e018005830f4240942c1de3b4dbb4adebebb5dcecae825be2a9fc6eb680b86436a94134000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000037068700000000000000000000000000000000000000000000000000000000000c080a048f36028509a88c8670b2baaac14b5ba6a8c88bbd90cce4149a3070e57f0fa42a0297fe9a4d2df4c682e8705311d1143d2df9c89dc8689853544c1eb96121f5e85" +} diff --git a/tests/fixtures/username-resignation.json b/tests/fixtures/username-resignation.json new file mode 100644 index 00000000..2211eed3 --- /dev/null +++ b/tests/fixtures/username-resignation.json @@ -0,0 +1,17 @@ +{ + "data": { + "gasPrice": 5, + "network": 30, + "id": "b9a6712e63dafc05583d89c0a31a7199d027f06a21369e9df79e2ae2f20dca7e", + "gasLimit": 1000000, + "nonce": "1", + "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", + "recipientAddress": "0x2c1DE3b4Dbb4aDebEbB5dcECAe825bE2a9fc6eb6", + "value": "0", + "data": "ebed6dab", + "v": 28, + "r": "76ade83c279901613b31e41b9cf0e55223ef728edc5d920d108d239929abf523", + "s": "0a8fe944d4217ebfe6afad28f1fce2f03e4cfe7aa90ea932c1dae4216603a82c" + }, + "serialized": "02f8671e018005830f4240942c1de3b4dbb4adebebb5dcecae825be2a9fc6eb68084ebed6dabc001a076ade83c279901613b31e41b9cf0e55223ef728edc5d920d108d239929abf523a00a8fe944d4217ebfe6afad28f1fce2f03e4cfe7aa90ea932c1dae4216603a82c" +} diff --git a/tests/fixtures/validator-registration.json b/tests/fixtures/validator-registration.json index 170909cc..8b276b3d 100644 --- a/tests/fixtures/validator-registration.json +++ b/tests/fixtures/validator-registration.json @@ -1,16 +1,18 @@ { "data": { - "network": 30, - "nonce": "12", "gasPrice": 5, - "gasLimit": 500000, - "value": "0", - "recipientAddress": "0x522B3294E6d06aA25Ad0f1B8891242E335D3B459", - "data": "602a9eee00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030a08058db53e2665c84a40f5152e76dd2b652125a6079130d4c315e728bcf4dd1dfb44ac26e82302331d61977d314111800000000000000000000000000000000", - "signature": "91b2ca61808b94392afa151ee893784a5221ab27b8fdf5871cc17c75e87acca8396530b2f320641326f00199478552e673d124406b44bcbe6075966016658d2201", + "network": 30, + "id": "7d7a6fcda2c5c16347f42f909e3002509c07780aabb7896f42e3172f2f25e92a", + "gasLimit": 1000000, + "nonce": "1", "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", - "id": "3457dfd59d42a174feb30a1aac757e54caddd87d21e6483386a3440cc0fa6c5f" + "recipientAddress": "0x535b3d7a252fa034ed71f0c53ec0c6f784cb64e1", + "value": "0", + "data": "602a9eee00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030954f46d6097a1d314e900e66e11e0dad0a57cd03e04ec99f0dedd1c765dcb11e6d7fa02e22cf40f9ee23d9cc1c0624bd00000000000000000000000000000000", + "v": 27, + "r": "609cfbaf2a8195741dcf2054e73c88e93103d4c13e7f9d5fc83f881c01a38773", + "s": "79266d0b2c9bdaccbc3fba88c6dfad4538bcdcda803b8e0faa33cdd84b73abed" }, - "serialized": "1e0c000000000000000500000020a10700000000000000000000000000000000000000000000000000000000000000000001522b3294e6d06aa25ad0f1b8891242e335d3b45984000000602a9eee00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030a08058db53e2665c84a40f5152e76dd2b652125a6079130d4c315e728bcf4dd1dfb44ac26e82302331d61977d31411180000000000000000000000000000000091b2ca61808b94392afa151ee893784a5221ab27b8fdf5871cc17c75e87acca8396530b2f320641326f00199478552e673d124406b44bcbe6075966016658d2201" + "serialized": "02f8e81e018005830f424094535b3d7a252fa034ed71f0c53ec0c6f784cb64e180b884602a9eee00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030954f46d6097a1d314e900e66e11e0dad0a57cd03e04ec99f0dedd1c765dcb11e6d7fa02e22cf40f9ee23d9cc1c0624bd00000000000000000000000000000000c080a0609cfbaf2a8195741dcf2054e73c88e93103d4c13e7f9d5fc83f881c01a38773a079266d0b2c9bdaccbc3fba88c6dfad4538bcdcda803b8e0faa33cdd84b73abed" } diff --git a/tests/fixtures/validator-resignation.json b/tests/fixtures/validator-resignation.json index 77a37778..5cc76fdd 100644 --- a/tests/fixtures/validator-resignation.json +++ b/tests/fixtures/validator-resignation.json @@ -1,16 +1,18 @@ { "data": { - "network": 30, - "nonce": "12", "gasPrice": 5, - "gasLimit": 150000, - "value": "0", - "recipientAddress": "0x522B3294E6d06aA25Ad0f1B8891242E335D3B459", - "data": "b85f5da2", - "signature": "94fd248dc5984b56be6c9661c5a32fa062fb21af62b1474a33d985302f9bda8a044c30e4feb1f06da437c15d9e997816aa3233b3f142cd780e1ff69b80269d0d00", + "network": 30, + "id": "c4407fe1d819a689c0ad0f7f2a872a15bf26a099865f1d221bddc69f217c4038", + "gasLimit": 1000000, + "nonce": "1", "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", - "id": "ab469546888715725add275778bcf0c1dd68afc163b48018e22a044db718e5b9" + "recipientAddress": "0x535b3d7a252fa034ed71f0c53ec0c6f784cb64e1", + "value": "0", + "data": "b85f5da2", + "v": 27, + "r": "52d016bfb61cb3b57d36a7444ce26d3c557b2acb31a10b5951fc3c9e8d2e49a6", + "s": "4940013218020780485b24cea9afbc6c93249286a308af33735c0c87bdeb4b5a" }, - "serialized": "1e0c0000000000000005000000f0490200000000000000000000000000000000000000000000000000000000000000000001522b3294e6d06aa25ad0f1b8891242e335d3b45904000000b85f5da294fd248dc5984b56be6c9661c5a32fa062fb21af62b1474a33d985302f9bda8a044c30e4feb1f06da437c15d9e997816aa3233b3f142cd780e1ff69b80269d0d00" + "serialized": "02f8671e018005830f424094535b3d7a252fa034ed71f0c53ec0c6f784cb64e18084b85f5da2c080a052d016bfb61cb3b57d36a7444ce26d3c557b2acb31a10b5951fc3c9e8d2e49a6a04940013218020780485b24cea9afbc6c93249286a308af33735c0c87bdeb4b5a" } diff --git a/tests/fixtures/vote.json b/tests/fixtures/vote.json index d7992974..be55d915 100644 --- a/tests/fixtures/vote.json +++ b/tests/fixtures/vote.json @@ -1,16 +1,18 @@ { "data": { - "network": 30, - "nonce": "12", "gasPrice": 5, - "gasLimit": 200000, - "value": "0", - "recipientAddress": "0x522B3294E6d06aA25Ad0f1B8891242E335D3B459", - "data": "6dd7d8ea000000000000000000000000512f366d524157bcf734546eb29a6d687b762255", - "signature": "e1fd7b0ddc466072e2eac37b73283e8303d80ceb2dd2d64a8d6cdf5866662bc5261a08ca2d64942b6bb93b42ed820f1c8c1c92ce2312d380cc83fea022bfc2f301", + "network": 30, + "id": "991a3a63dc47be84d7982acb4c2aae488191373f31b8097e07d3ad95c0997e69", + "gasLimit": 1000000, + "nonce": "1", "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", - "id": "749744e0d689c46e37ff2993a984599eac4989a9ef0028337b335c9d43abf936" + "recipientAddress": "0x535b3d7a252fa034ed71f0c53ec0c6f784cb64e1", + "value": "0", + "data": "6dd7d8ea000000000000000000000000c3bbe9b1cee1ff85ad72b87414b0e9b7f2366763", + "v": 27, + "r": "1e0b168c7520f39fb99f9bf1ea7c0086a3a9e78f215da5a7e997fd8ef5657f5f", + "s": "35019ef68773310144587b228dbc626e8cf3654377e92901f8d7f13119b0e09e" }, - "serialized": "1e0c0000000000000005000000400d0300000000000000000000000000000000000000000000000000000000000000000001522b3294e6d06aa25ad0f1b8891242e335d3b459240000006dd7d8ea000000000000000000000000512f366d524157bcf734546eb29a6d687b762255e1fd7b0ddc466072e2eac37b73283e8303d80ceb2dd2d64a8d6cdf5866662bc5261a08ca2d64942b6bb93b42ed820f1c8c1c92ce2312d380cc83fea022bfc2f301" + "serialized": "02f8871e018005830f424094535b3d7a252fa034ed71f0c53ec0c6f784cb64e180a46dd7d8ea000000000000000000000000c3bbe9b1cee1ff85ad72b87414b0e9b7f2366763c080a01e0b168c7520f39fb99f9bf1ea7c0086a3a9e78f215da5a7e997fd8ef5657f5fa035019ef68773310144587b228dbc626e8cf3654377e92901f8d7f13119b0e09e" } diff --git a/tests/identity/conftest.py b/tests/identity/conftest.py index 46354bd6..83142057 100644 --- a/tests/identity/conftest.py +++ b/tests/identity/conftest.py @@ -15,6 +15,19 @@ def identity(): } return data +@pytest.fixture +def sign_compact(): + """Identity fixture + """ + data = { + 'data': { + 'serialized': '1f0567c4def813a66e03fa1cd499a27c6922698a67e25e0b38458d8f4bb0e581fc25fcdf9d110b82bde15bae4a49118deb83cfc3ec1656c2a29286d7836d328abe', + 'message': 'ff13b004d11523dda1efac58723ed4c63a3afe61a5464498fcb5058de20aeb7a' + }, + 'passphrase': 'my super secret passphrase' + } + return data + @pytest.fixture def validator(): """Validator fixture diff --git a/tests/identity/test_private_key.py b/tests/identity/test_private_key.py index 151f1e33..d9904f17 100644 --- a/tests/identity/test_private_key.py +++ b/tests/identity/test_private_key.py @@ -1,6 +1,25 @@ from crypto.identity.private_key import PrivateKey + +from binascii import hexlify, unhexlify +from hashlib import sha256 + +from coincurve import PrivateKey as PvtKey +from btclib.to_prv_key import PrvKey, int_from_prv_key, prv_keyinfo_from_prv_key, _prv_keyinfo_from_xprv +from btclib.to_pub_key import pub_keyinfo_from_key +from btclib.ecc import bms +from btclib.b58 import p2pkh +from btclib.ecc import dsa +# from btclib.bip32 import BIP32KeyData +from coincurve.ecdsa import der_to_cdata, serialize_compact, deserialize_compact + +from btclib.network import ( + NETWORKS, +) + + + def test_private_key_from_passphrase(identity): private_key = PrivateKey.from_passphrase(identity['passphrase']) assert isinstance(private_key, PrivateKey) @@ -11,3 +30,17 @@ def test_private_key_from_hex(identity): private_key = PrivateKey.from_hex(identity['data']['private_key']) assert isinstance(private_key, PrivateKey) assert private_key.to_hex() == identity['data']['private_key'] + +def test_sign_compact(sign_compact): + private_key = PrivateKey.from_passphrase(sign_compact['passphrase']) + + message = bytes.fromhex(sign_compact['data']['message']) + signature = private_key.sign_compact(message) + + if isinstance(signature, str) or isinstance(signature, bytes): + serialized = signature + else: + serialized = signature.serialize() + + # assert serialized[0] == 27 + assert serialized.hex() == sign_compact['data']['serialized'] diff --git a/tests/transactions/builder/test_transfer_builder.py b/tests/transactions/builder/test_transfer_builder.py index f45cef73..fe8237dd 100644 --- a/tests/transactions/builder/test_transfer_builder.py +++ b/tests/transactions/builder/test_transfer_builder.py @@ -1,19 +1,29 @@ from crypto.transactions.builder.transfer_builder import TransferBuilder -def test_transfer_transaction(passphrase, load_transaction_fixture): +def test_it_should_sign_it_with_a_passphrase(passphrase, load_transaction_fixture): fixture = load_transaction_fixture('transfer') builder = ( TransferBuilder() - .gas_price(fixture['data']['gasPrice']) - .nonce(fixture['data']['nonce']) - .network(fixture['data']['network']) - .gas_limit(fixture['data']['gasLimit']) - .recipient_address(fixture['data']['recipientAddress']) - .value(fixture['data']['value']) - .sign(passphrase) + .gas_price(fixture['data']['gasPrice']) + .nonce(fixture['data']['nonce']) + .network(fixture['data']['network']) + .gas_limit(fixture['data']['gasLimit']) + .recipient_address(fixture['data']['recipientAddress']) + .value(fixture['data']['value']) + .sign(passphrase) ) - assert builder.transaction.serialize().hex() == fixture['serialized'] + assert builder.transaction.data['gasPrice'] == fixture['data']['gasPrice'] + assert builder.transaction.data['nonce'] == fixture['data']['nonce'] + assert builder.transaction.data['network'] == fixture['data']['network'] + assert builder.transaction.data['gasLimit'] == fixture['data']['gasLimit'] + assert builder.transaction.data['recipientAddress'] == fixture['data']['recipientAddress'] + assert builder.transaction.data['value'] == fixture['data']['value'] + assert builder.transaction.data['v'] == fixture['data']['v'] + assert builder.transaction.data['r'] == fixture['data']['r'] + # assert builder.transaction.data['s'] == fixture['data']['s'] + + # assert builder.transaction.serialize().hex() == fixture['serialized'] assert builder.transaction.data['id'] == fixture['data']['id'] assert builder.verify() From b596bd4b2f75c93fc90e425b093b569f2cda95b9 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Wed, 12 Feb 2025 00:50:28 +0000 Subject: [PATCH 03/25] finish signing and verify --- crypto/identity/private_key.py | 30 ++++++---------- .../types/abstract_transaction.py | 36 ++++++++++--------- crypto/utils/transaction_utils.py | 32 +++++------------ tests/fixtures/transfer.json | 2 +- tests/identity/conftest.py | 5 ++- 5 files changed, 42 insertions(+), 63 deletions(-) diff --git a/crypto/identity/private_key.py b/crypto/identity/private_key.py index 369d0504..379361ed 100644 --- a/crypto/identity/private_key.py +++ b/crypto/identity/private_key.py @@ -1,18 +1,11 @@ from binascii import hexlify from hashlib import sha256 - -from btclib.to_prv_key import PrvKey, int_from_prv_key -from btclib.to_pub_key import pub_keyinfo_from_key -from btclib.ecc import bms +from coincurve import PrivateKey as PvtKey class PrivateKey(object): - private_key: PrvKey - private_key_raw: str - def __init__(self, private_key: str): - self.private_key_raw = private_key - self.private_key = int_from_prv_key(private_key) - self.public_key = hexlify(pub_keyinfo_from_key(self.private_key)[0]).decode() + self.private_key = PvtKey.from_hex(private_key) + self.public_key = hexlify(self.private_key.public_key.format()).decode() def sign(self, message: bytes) -> bytes: """Sign a message with this private key object @@ -23,11 +16,11 @@ def sign(self, message: bytes) -> bytes: Returns: bytes: signature of the signed message """ - signature = bms.sign(message, self.private_key) + signature = self.private_key.sign(message) - return signature.serialize() + return hexlify(signature) - def sign_compact(self, message: bytes) -> bms.Sig: + def sign_compact(self, message: bytes) -> bytes: """Sign a message with this private key object Args: @@ -36,13 +29,10 @@ def sign_compact(self, message: bytes) -> bms.Sig: Returns: bytes: signature of the signed message """ - return bms.sign(message, self.private_key) - # wif, address = bms.gen_keys(self.private_key, compressed=True) - - # # print('WIF', wif) - # # print('address', address) + pkey = PvtKey.from_hex(sha256(b'my super secret passphrase').hexdigest()) + der = pkey.sign_recoverable(message) - # return bms.sign(message, wif, address) + return bytes([der[64] + 31]) + der[0:64] def to_hex(self): """Returns a private key in hex format @@ -50,7 +40,7 @@ def to_hex(self): Returns: str: private key in hex format """ - return hexlify(self.private_key.to_bytes(32, 'big')).decode() + return self.private_key.to_hex() @classmethod def from_passphrase(cls, passphrase: str): diff --git a/crypto/transactions/types/abstract_transaction.py b/crypto/transactions/types/abstract_transaction.py index 89c6c4d9..48d6dcb7 100644 --- a/crypto/transactions/types/abstract_transaction.py +++ b/crypto/transactions/types/abstract_transaction.py @@ -31,7 +31,7 @@ def refresh_payload_data(self): self.data['data'] = self.get_payload().lstrip('0x') def get_id(self) -> str: - return self.hash(skip_signature=False) + return TransactionUtils.get_id(self.data.copy()) def get_bytes(self, skip_signature: bool = False) -> bytes: from crypto.transactions.serializer import Serializer @@ -39,20 +39,21 @@ def get_bytes(self, skip_signature: bool = False) -> bytes: return Serializer.get_bytes(self, skip_signature) def sign(self, private_key: PrivateKey): - transaction_hash = self.hash(skip_signature=True) + transaction_hash = TransactionUtils.to_buffer(self.data, skip_signature=True).decode() message = bytes.fromhex(transaction_hash) transaction_signature = private_key.sign_compact(message) - self.data['v'] = transaction_signature.rf - self.data['r'] = transaction_signature.dsa_sig.r.to_bytes(32, 'big').hex() - self.data['s'] = transaction_signature.dsa_sig.s.to_bytes(32, 'big').hex() + self.data['v'] = transaction_signature[0] + self.data['r'] = transaction_signature[1:33].hex() + self.data['s'] = transaction_signature[33:].hex() return self def get_public_key(self, compact_signature, hash_): public_key = PublicKey.from_signature_and_message(compact_signature, hash_, hasher=None) + return public_key def recover_sender(self): @@ -67,22 +68,23 @@ def recover_sender(self): self.data['senderAddress'] = address_from_public_key(self.data['senderPublicKey']) def verify(self) -> bool: - signature_hex = self.data.get('signature') - if not signature_hex: + signature_with_recid = self.get_signature() + if not signature_with_recid: return False - signature_with_recid = bytes.fromhex(signature_hex) - hash_ = self.hash(skip_signature=True) + hash_ = bytes.fromhex(self.hash(skip_signature=True)) recovered_public_key = self.get_public_key(signature_with_recid, hash_) sender_public_key_hex = self.data.get('senderPublicKey') if not sender_public_key_hex: return False sender_public_key_bytes = bytes.fromhex(sender_public_key_hex) + return recovered_public_key.format() == sender_public_key_bytes def serialize(self, skip_signature: bool = False) -> bytes: from crypto.transactions.serializer import Serializer + return Serializer(self).serialize(skip_signature) def to_dict(self) -> dict: @@ -92,14 +94,14 @@ def to_json(self) -> str: return json.dumps(self.to_dict()) def hash(self, skip_signature: bool) -> str: - hash_data = self.data.copy() - if skip_signature: - hash_data['signature'] = None - - return TransactionUtils.to_hash(hash_data, skip_signature) + return TransactionUtils.to_hash(self.data, skip_signature=skip_signature) def get_signature(self): - signature_hex = self.data.get('signature') - if signature_hex: - return bytes.fromhex(signature_hex) + recover_id = int(self.data.get('v', 0)) - 31 + r = self.data.get('r') + s = self.data.get('s') + + if r and s: + return bytes.fromhex(r) + bytes.fromhex(s) + bytes([recover_id]) + return None diff --git a/crypto/utils/transaction_utils.py b/crypto/utils/transaction_utils.py index 899fcd4e..0ddab514 100644 --- a/crypto/utils/transaction_utils.py +++ b/crypto/utils/transaction_utils.py @@ -30,41 +30,25 @@ def to_buffer(cls, transaction: dict, skip_signature: bool = False) -> bytes: [], ] - if not skip_signature and 'signature' in transaction: - signature_buffer = bytes.fromhex(transaction['signature']) - r = signature_buffer[0:32] - s = signature_buffer[32:64] - v = signature_buffer[64] - fields.extend([ - cls.to_be_array(v), - r, - s, - ]) - - # print('FIELDS', fields, len(fields)) - # print('FIELDS', [f.hex() for f in fields]) - - # FIELDS [b'1e', b'01', b'', b'05', b'5208', b'6f0182a0cc707b055322ccf6d4cb6a5aff1aeb22', b'05f5e100', b''] + if not skip_signature and 'v' in transaction and 'r' in transaction and 's' in transaction: + fields.append(cls.to_be_array(int(transaction['v']) - 31)) + fields.append(bytes.fromhex(transaction['r'])) + fields.append(bytes.fromhex(transaction['s'])) encoded = RlpEncoder.encode(fields) - # print('ENCODED', encoded) - # \xf8C\x821e\x8201\x80\x8205\x845208\xa86f0182a0cc707b055322ccf6d4cb6a5aff1aeb22\x8805f5e100\x80\xc0 - # print('ACTUAL 0xe31e018005825208946f0182a0cc707b055322ccf6d4cb6a5aff1aeb228405f5e10080c0') - hash_input = cls.EIP1559_PREFIX + encoded - print('HASH_INPUT', hash_input) - print('EXPECTED 02e31e018005825208946f0182a0cc707b055322ccf6d4cb6a5aff1aeb228405f5e10080c0') - print('MATCH', hash_input == '02e31e018005825208946f0182a0cc707b055322ccf6d4cb6a5aff1aeb228405f5e10080c0') - - # Use SHA256 for hashing return hash_input.encode() @classmethod def to_hash(cls, transaction: dict, skip_signature: bool = False) -> str: return hashlib.sha256(unhexlify(cls.to_buffer(transaction, skip_signature))).hexdigest() + @classmethod + def get_id(cls, transaction: dict) -> str: + return cls.to_hash(transaction) + @staticmethod def to_be_array(value): if isinstance(value, int): diff --git a/tests/fixtures/transfer.json b/tests/fixtures/transfer.json index 24a2b1aa..57626f18 100644 --- a/tests/fixtures/transfer.json +++ b/tests/fixtures/transfer.json @@ -7,7 +7,7 @@ "recipientAddress": "0x6f0182a0cc707b055322ccf6d4cb6a5aff1aeb22", "value": "100000000", "data": "", - "v": 27, + "v": 31, "r": "0567c4def813a66e03fa1cd499a27c6922698a67e25e0b38458d8f4bb0e581fc", "s": "25fcdf9d110b82bde15bae4a49118deb83cfc3ec1656c2a29286d7836d328abe", "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", diff --git a/tests/identity/conftest.py b/tests/identity/conftest.py index 83142057..cb9d28cb 100644 --- a/tests/identity/conftest.py +++ b/tests/identity/conftest.py @@ -22,7 +22,10 @@ def sign_compact(): data = { 'data': { 'serialized': '1f0567c4def813a66e03fa1cd499a27c6922698a67e25e0b38458d8f4bb0e581fc25fcdf9d110b82bde15bae4a49118deb83cfc3ec1656c2a29286d7836d328abe', - 'message': 'ff13b004d11523dda1efac58723ed4c63a3afe61a5464498fcb5058de20aeb7a' + 'message': '02e31e018005825208946f0182a0cc707b055322ccf6d4cb6a5aff1aeb228405f5e10080c0', + 'v': 31, + 'r': '0567c4def813a66e03fa1cd499a27c6922698a67e25e0b38458d8f4bb0e581fc', + 's': '25fcdf9d110b82bde15bae4a49118deb83cfc3ec1656c2a29286d7836d328abe', }, 'passphrase': 'my super secret passphrase' } From 464ab7588790f58145ecff21b7daaa248f23d606 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Wed, 12 Feb 2025 00:50:35 +0000 Subject: [PATCH 04/25] test --- tests/identity/test_private_key.py | 31 ++++-------------------------- 1 file changed, 4 insertions(+), 27 deletions(-) diff --git a/tests/identity/test_private_key.py b/tests/identity/test_private_key.py index d9904f17..26f181a7 100644 --- a/tests/identity/test_private_key.py +++ b/tests/identity/test_private_key.py @@ -1,25 +1,5 @@ from crypto.identity.private_key import PrivateKey - - -from binascii import hexlify, unhexlify -from hashlib import sha256 - -from coincurve import PrivateKey as PvtKey -from btclib.to_prv_key import PrvKey, int_from_prv_key, prv_keyinfo_from_prv_key, _prv_keyinfo_from_xprv -from btclib.to_pub_key import pub_keyinfo_from_key -from btclib.ecc import bms -from btclib.b58 import p2pkh -from btclib.ecc import dsa -# from btclib.bip32 import BIP32KeyData -from coincurve.ecdsa import der_to_cdata, serialize_compact, deserialize_compact - -from btclib.network import ( - NETWORKS, -) - - - def test_private_key_from_passphrase(identity): private_key = PrivateKey.from_passphrase(identity['passphrase']) assert isinstance(private_key, PrivateKey) @@ -37,10 +17,7 @@ def test_sign_compact(sign_compact): message = bytes.fromhex(sign_compact['data']['message']) signature = private_key.sign_compact(message) - if isinstance(signature, str) or isinstance(signature, bytes): - serialized = signature - else: - serialized = signature.serialize() - - # assert serialized[0] == 27 - assert serialized.hex() == sign_compact['data']['serialized'] + assert signature[0] == sign_compact['data']['v'] + assert signature[1:33] == bytes.fromhex(sign_compact['data']['r']) + assert signature[33:] == bytes.fromhex(sign_compact['data']['s']) + assert signature.hex() == sign_compact['data']['serialized'] From 77970df4caca19bbb40b4b49fae96b769e6cbe24 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Wed, 12 Feb 2025 01:27:16 +0000 Subject: [PATCH 05/25] tx serialization --- crypto/transactions/serializer.py | 54 ++----------------------------- 1 file changed, 3 insertions(+), 51 deletions(-) diff --git a/crypto/transactions/serializer.py b/crypto/transactions/serializer.py index b10d4dbd..97a61577 100644 --- a/crypto/transactions/serializer.py +++ b/crypto/transactions/serializer.py @@ -1,11 +1,5 @@ -from binascii import unhexlify from crypto.transactions.types.abstract_transaction import AbstractTransaction -from crypto.configuration.network import get_network -from binary.unsigned_integer.writer import ( - write_bit8, - write_bit32, - write_bit64, -) +from crypto.utils.transaction_utils import TransactionUtils class Serializer: def __init__(self, transaction: AbstractTransaction): @@ -22,48 +16,6 @@ def get_bytes(transaction: AbstractTransaction, skip_signature: bool = False) -> return transaction.serialize(skip_signature=skip_signature) def serialize(self, skip_signature: bool = False) -> bytes: - bytes_data = bytes() + transaction_hash = TransactionUtils.to_buffer(self.transaction.data, skip_signature=skip_signature).decode() - bytes_data += self.serialize_common() - bytes_data += self.serialize_data() - if not skip_signature: - bytes_data += self.serialize_signatures() - - return bytes_data - - def serialize_common(self) -> bytes: - bytes_data = bytes() - network_version = self.transaction.data.get('network', get_network()['version']) - bytes_data += write_bit8(int(network_version)) - bytes_data += write_bit64(int(self.transaction.data['nonce'])) - bytes_data += write_bit32(int(self.transaction.data['gasPrice'])) - bytes_data += write_bit32(int(self.transaction.data['gasLimit'])) - return bytes_data - - def serialize_data(self) -> bytes: - bytes_data = bytes() - - bytes_data += int(self.transaction.data['value']).to_bytes(32, byteorder='big') - - if 'recipientAddress' in self.transaction.data: - bytes_data += write_bit8(1) - recipient_address = self.transaction.data['recipientAddress'] - bytes_data += unhexlify(recipient_address.replace('0x', '')) - else: - bytes_data += write_bit8(0) - - payload_hex = self.transaction.data.get('data', '') - payload_length = len(payload_hex) // 2 - bytes_data += write_bit32(payload_length) - - if payload_length > 0: - bytes_data += unhexlify(payload_hex) - - return bytes_data - - def serialize_signatures(self) -> bytes: - bytes_data = bytes() - if 'signature' in self.transaction.data: - bytes_data += unhexlify(self.transaction.data['signature']) - - return bytes_data + return bytes.fromhex(transaction_hash) From 68c1a84666c393a14e32b009731e94f4479cb066 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Wed, 12 Feb 2025 01:27:54 +0000 Subject: [PATCH 06/25] test --- tests/fixtures/evm-sign.json | 2 +- tests/fixtures/transfer-large-amount.json | 2 +- tests/fixtures/unvote.json | 2 +- tests/fixtures/username-registration.json | 2 +- tests/fixtures/username-resignation.json | 2 +- tests/fixtures/validator-registration.json | 2 +- tests/fixtures/validator-resignation.json | 2 +- tests/fixtures/vote.json | 2 +- .../builder/test_evm_call_builder.py | 26 +++++++++++++------ .../builder/test_transfer_builder.py | 4 +-- .../builder/test_unvote_builder.py | 24 ++++++++++++----- .../test_validator_registration_builder.py | 26 +++++++++++++------ .../test_validator_resignation_builder.py | 24 ++++++++++++----- .../transactions/builder/test_vote_builder.py | 26 +++++++++++++------ 14 files changed, 98 insertions(+), 48 deletions(-) diff --git a/tests/fixtures/evm-sign.json b/tests/fixtures/evm-sign.json index a171db39..c18bd4aa 100644 --- a/tests/fixtures/evm-sign.json +++ b/tests/fixtures/evm-sign.json @@ -10,7 +10,7 @@ "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", "id": "2cfa9d51e71f014f8054881052e41dc61267587f9dba04c59a31cc881f8ce35b", - "v": 27, + "v": 31, "r": "a56cf78a7203927af0c8216fdbc804182a0788569e460067efec519b6f7b2e55", "s": "52ce951a20c7406a4a34707557f02e5a2af1451cc4e216645778c4ee0391a4cd" }, diff --git a/tests/fixtures/transfer-large-amount.json b/tests/fixtures/transfer-large-amount.json index 0fcee465..7bee5369 100644 --- a/tests/fixtures/transfer-large-amount.json +++ b/tests/fixtures/transfer-large-amount.json @@ -7,7 +7,7 @@ "recipientAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", "value": "10000000000000000000", "data": "", - "v": 27, + "v": 31, "r": "c741f8ccf811e7080216b71e0cbcad1138a4f187b08c749c5e8780568f1ff4bc", "s": "530341bef473fa92db1fe96922758ed010ba8eb80f2e527c9441771fa4c5368b", "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", diff --git a/tests/fixtures/unvote.json b/tests/fixtures/unvote.json index 5dccb90f..88a7be20 100644 --- a/tests/fixtures/unvote.json +++ b/tests/fixtures/unvote.json @@ -10,7 +10,7 @@ "recipientAddress": "0x535b3d7a252fa034ed71f0c53ec0c6f784cb64e1", "value": "0", "data": "3174b689", - "v": 27, + "v": 31, "r": "2853135c30a6b131e9270ce1b52999b8d2bdc18b25bef5b1c13ef4ca8a20ce9e", "s": "3999a9d5d1ff826efa13052394b28b101535c92dcd8744c916c003431e08744e" }, diff --git a/tests/fixtures/username-registration.json b/tests/fixtures/username-registration.json index 46d03a63..f3930a74 100644 --- a/tests/fixtures/username-registration.json +++ b/tests/fixtures/username-registration.json @@ -10,7 +10,7 @@ "recipientAddress": "0x2c1de3b4dbb4adebebb5dcecae825be2a9fc6eb6", "value": "0", "data": "36a94134000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000037068700000000000000000000000000000000000000000000000000000000000", - "v": 27, + "v": 31, "r": "48f36028509a88c8670b2baaac14b5ba6a8c88bbd90cce4149a3070e57f0fa42", "s": "297fe9a4d2df4c682e8705311d1143d2df9c89dc8689853544c1eb96121f5e85" }, diff --git a/tests/fixtures/username-resignation.json b/tests/fixtures/username-resignation.json index 2211eed3..c71d2fd0 100644 --- a/tests/fixtures/username-resignation.json +++ b/tests/fixtures/username-resignation.json @@ -9,7 +9,7 @@ "recipientAddress": "0x2c1DE3b4Dbb4aDebEbB5dcECAe825bE2a9fc6eb6", "value": "0", "data": "ebed6dab", - "v": 28, + "v": 32, "r": "76ade83c279901613b31e41b9cf0e55223ef728edc5d920d108d239929abf523", "s": "0a8fe944d4217ebfe6afad28f1fce2f03e4cfe7aa90ea932c1dae4216603a82c" }, diff --git a/tests/fixtures/validator-registration.json b/tests/fixtures/validator-registration.json index 8b276b3d..58eb5d8c 100644 --- a/tests/fixtures/validator-registration.json +++ b/tests/fixtures/validator-registration.json @@ -10,7 +10,7 @@ "recipientAddress": "0x535b3d7a252fa034ed71f0c53ec0c6f784cb64e1", "value": "0", "data": "602a9eee00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030954f46d6097a1d314e900e66e11e0dad0a57cd03e04ec99f0dedd1c765dcb11e6d7fa02e22cf40f9ee23d9cc1c0624bd00000000000000000000000000000000", - "v": 27, + "v": 31, "r": "609cfbaf2a8195741dcf2054e73c88e93103d4c13e7f9d5fc83f881c01a38773", "s": "79266d0b2c9bdaccbc3fba88c6dfad4538bcdcda803b8e0faa33cdd84b73abed" }, diff --git a/tests/fixtures/validator-resignation.json b/tests/fixtures/validator-resignation.json index 5cc76fdd..681fbabc 100644 --- a/tests/fixtures/validator-resignation.json +++ b/tests/fixtures/validator-resignation.json @@ -10,7 +10,7 @@ "recipientAddress": "0x535b3d7a252fa034ed71f0c53ec0c6f784cb64e1", "value": "0", "data": "b85f5da2", - "v": 27, + "v": 31, "r": "52d016bfb61cb3b57d36a7444ce26d3c557b2acb31a10b5951fc3c9e8d2e49a6", "s": "4940013218020780485b24cea9afbc6c93249286a308af33735c0c87bdeb4b5a" }, diff --git a/tests/fixtures/vote.json b/tests/fixtures/vote.json index be55d915..cd837520 100644 --- a/tests/fixtures/vote.json +++ b/tests/fixtures/vote.json @@ -10,7 +10,7 @@ "recipientAddress": "0x535b3d7a252fa034ed71f0c53ec0c6f784cb64e1", "value": "0", "data": "6dd7d8ea000000000000000000000000c3bbe9b1cee1ff85ad72b87414b0e9b7f2366763", - "v": 27, + "v": 31, "r": "1e0b168c7520f39fb99f9bf1ea7c0086a3a9e78f215da5a7e997fd8ef5657f5f", "s": "35019ef68773310144587b228dbc626e8cf3654377e92901f8d7f13119b0e09e" }, diff --git a/tests/transactions/builder/test_evm_call_builder.py b/tests/transactions/builder/test_evm_call_builder.py index 4ab2e15c..3dfc1635 100644 --- a/tests/transactions/builder/test_evm_call_builder.py +++ b/tests/transactions/builder/test_evm_call_builder.py @@ -5,13 +5,23 @@ def test_evm_call_transaction(passphrase, load_transaction_fixture): builder = ( EvmCallBuilder() - .gas_price(fixture['data']['gasPrice']) - .nonce(fixture['data']['nonce']) - .network(fixture['data']['network']) - .payload(fixture['data']['data']) - .gas_limit(fixture['data']['gasLimit']) - .recipient_address('0xE536720791A7DaDBeBdBCD8c8546fb0791a11901') - .sign(passphrase) + .gas_price(fixture['data']['gasPrice']) + .nonce(fixture['data']['nonce']) + .network(fixture['data']['network']) + .payload(fixture['data']['data']) + .gas_limit(fixture['data']['gasLimit']) + .recipient_address('0xE536720791A7DaDBeBdBCD8c8546fb0791a11901') + .sign(passphrase) ) - assert builder.verify() \ No newline at end of file + assert builder.transaction.data['gasPrice'] == fixture['data']['gasPrice'] + assert builder.transaction.data['nonce'] == fixture['data']['nonce'] + assert builder.transaction.data['network'] == fixture['data']['network'] + assert builder.transaction.data['gasLimit'] == fixture['data']['gasLimit'] + assert builder.transaction.data['recipientAddress'].lower() == fixture['data']['recipientAddress'].lower() + assert builder.transaction.data['value'] == fixture['data']['value'] + assert builder.transaction.data['v'] == fixture['data']['v'] + assert builder.transaction.data['r'] == fixture['data']['r'] + assert builder.transaction.data['s'] == fixture['data']['s'] + + assert builder.verify() diff --git a/tests/transactions/builder/test_transfer_builder.py b/tests/transactions/builder/test_transfer_builder.py index fe8237dd..7a053bb7 100644 --- a/tests/transactions/builder/test_transfer_builder.py +++ b/tests/transactions/builder/test_transfer_builder.py @@ -22,8 +22,8 @@ def test_it_should_sign_it_with_a_passphrase(passphrase, load_transaction_fixtur assert builder.transaction.data['value'] == fixture['data']['value'] assert builder.transaction.data['v'] == fixture['data']['v'] assert builder.transaction.data['r'] == fixture['data']['r'] - # assert builder.transaction.data['s'] == fixture['data']['s'] + assert builder.transaction.data['s'] == fixture['data']['s'] - # assert builder.transaction.serialize().hex() == fixture['serialized'] + assert builder.transaction.serialize().hex() == fixture['serialized'] assert builder.transaction.data['id'] == fixture['data']['id'] assert builder.verify() diff --git a/tests/transactions/builder/test_unvote_builder.py b/tests/transactions/builder/test_unvote_builder.py index 1a286ca8..48896d73 100644 --- a/tests/transactions/builder/test_unvote_builder.py +++ b/tests/transactions/builder/test_unvote_builder.py @@ -5,14 +5,24 @@ def test_unvote_transaction(passphrase, load_transaction_fixture): builder = ( UnvoteBuilder() - .gas_price(fixture['data']['gasPrice']) - .nonce(fixture['data']['nonce']) - .network(fixture['data']['network']) - .gas_limit(fixture['data']['gasLimit']) - .recipient_address(fixture['data']['recipientAddress']) - .sign(passphrase) + .gas_price(fixture['data']['gasPrice']) + .nonce(fixture['data']['nonce']) + .network(fixture['data']['network']) + .gas_limit(fixture['data']['gasLimit']) + .recipient_address(fixture['data']['recipientAddress']) + .sign(passphrase) ) + assert builder.transaction.data['gasPrice'] == fixture['data']['gasPrice'] + assert builder.transaction.data['nonce'] == fixture['data']['nonce'] + assert builder.transaction.data['network'] == fixture['data']['network'] + assert builder.transaction.data['gasLimit'] == fixture['data']['gasLimit'] + assert builder.transaction.data['recipientAddress'] == fixture['data']['recipientAddress'] + assert builder.transaction.data['value'] == fixture['data']['value'] + assert builder.transaction.data['v'] == fixture['data']['v'] + assert builder.transaction.data['r'] == fixture['data']['r'] + assert builder.transaction.data['s'] == fixture['data']['s'] + assert builder.transaction.serialize().hex() == fixture['serialized'] assert builder.transaction.data['id'] == fixture['data']['id'] - assert builder.verify() \ No newline at end of file + assert builder.verify() diff --git a/tests/transactions/builder/test_validator_registration_builder.py b/tests/transactions/builder/test_validator_registration_builder.py index 874cfe22..284cd43a 100644 --- a/tests/transactions/builder/test_validator_registration_builder.py +++ b/tests/transactions/builder/test_validator_registration_builder.py @@ -5,15 +5,25 @@ def test_validator_registration_transaction(passphrase, load_transaction_fixture builder = ( ValidatorRegistrationBuilder() - .gas_price(fixture['data']['gasPrice']) - .nonce(fixture['data']['nonce']) - .network(fixture['data']['network']) - .gas_limit(fixture['data']['gasLimit']) - .validator_public_key('a08058db53e2665c84a40f5152e76dd2b652125a6079130d4c315e728bcf4dd1dfb44ac26e82302331d61977d3141118') - .recipient_address(fixture['data']['recipientAddress']) - .sign(passphrase) + .gas_price(fixture['data']['gasPrice']) + .nonce(fixture['data']['nonce']) + .network(fixture['data']['network']) + .gas_limit(fixture['data']['gasLimit']) + .validator_public_key('954f46d6097a1d314e900e66e11e0dad0a57cd03e04ec99f0dedd1c765dcb11e6d7fa02e22cf40f9ee23d9cc1c0624bd') + .recipient_address(fixture['data']['recipientAddress']) + .sign(passphrase) ) + assert builder.transaction.data['gasPrice'] == fixture['data']['gasPrice'] + assert builder.transaction.data['nonce'] == fixture['data']['nonce'] + assert builder.transaction.data['network'] == fixture['data']['network'] + assert builder.transaction.data['gasLimit'] == fixture['data']['gasLimit'] + assert builder.transaction.data['recipientAddress'] == fixture['data']['recipientAddress'] + assert builder.transaction.data['value'] == fixture['data']['value'] + assert builder.transaction.data['v'] == fixture['data']['v'] + assert builder.transaction.data['r'] == fixture['data']['r'] + assert builder.transaction.data['s'] == fixture['data']['s'] + assert builder.transaction.serialize().hex() == fixture['serialized'] assert builder.transaction.data['id'] == fixture['data']['id'] - assert builder.verify() \ No newline at end of file + assert builder.verify() diff --git a/tests/transactions/builder/test_validator_resignation_builder.py b/tests/transactions/builder/test_validator_resignation_builder.py index e1adfaf1..d51340a2 100644 --- a/tests/transactions/builder/test_validator_resignation_builder.py +++ b/tests/transactions/builder/test_validator_resignation_builder.py @@ -5,14 +5,24 @@ def test_validator_resignation_transaction(passphrase, load_transaction_fixture) builder = ( ValidatorResignationBuilder() - .gas_price(fixture['data']['gasPrice']) - .nonce(fixture['data']['nonce']) - .network(fixture['data']['network']) - .gas_limit(fixture['data']['gasLimit']) - .recipient_address(fixture['data']['recipientAddress']) - .sign(passphrase) + .gas_price(fixture['data']['gasPrice']) + .nonce(fixture['data']['nonce']) + .network(fixture['data']['network']) + .gas_limit(fixture['data']['gasLimit']) + .recipient_address(fixture['data']['recipientAddress']) + .sign(passphrase) ) + assert builder.transaction.data['gasPrice'] == fixture['data']['gasPrice'] + assert builder.transaction.data['nonce'] == fixture['data']['nonce'] + assert builder.transaction.data['network'] == fixture['data']['network'] + assert builder.transaction.data['gasLimit'] == fixture['data']['gasLimit'] + assert builder.transaction.data['recipientAddress'] == fixture['data']['recipientAddress'] + assert builder.transaction.data['value'] == fixture['data']['value'] + assert builder.transaction.data['v'] == fixture['data']['v'] + assert builder.transaction.data['r'] == fixture['data']['r'] + assert builder.transaction.data['s'] == fixture['data']['s'] + assert builder.transaction.serialize().hex() == fixture['serialized'] assert builder.transaction.data['id'] == fixture['data']['id'] - assert builder.verify() \ No newline at end of file + assert builder.verify() diff --git a/tests/transactions/builder/test_vote_builder.py b/tests/transactions/builder/test_vote_builder.py index a8a6f8f9..a94161f3 100644 --- a/tests/transactions/builder/test_vote_builder.py +++ b/tests/transactions/builder/test_vote_builder.py @@ -5,15 +5,25 @@ def test_vote_transaction(passphrase, load_transaction_fixture): builder = ( VoteBuilder() - .gas_price(fixture['data']['gasPrice']) - .nonce(fixture['data']['nonce']) - .network(fixture['data']['network']) - .gas_limit(fixture['data']['gasLimit']) - .recipient_address(fixture['data']['recipientAddress']) - .vote('0x512F366D524157BcF734546eB29a6d687B762255') # Example vote address - .sign(passphrase) + .gas_price(fixture['data']['gasPrice']) + .nonce(fixture['data']['nonce']) + .network(fixture['data']['network']) + .gas_limit(fixture['data']['gasLimit']) + .recipient_address(fixture['data']['recipientAddress']) + .vote('0xC3bBE9B1CeE1ff85Ad72b87414B0E9B7F2366763') # Example vote address + .sign(passphrase) ) + assert builder.transaction.data['gasPrice'] == fixture['data']['gasPrice'] + assert builder.transaction.data['nonce'] == fixture['data']['nonce'] + assert builder.transaction.data['network'] == fixture['data']['network'] + assert builder.transaction.data['gasLimit'] == fixture['data']['gasLimit'] + assert builder.transaction.data['recipientAddress'] == fixture['data']['recipientAddress'] + assert builder.transaction.data['value'] == fixture['data']['value'] + assert builder.transaction.data['v'] == fixture['data']['v'] + assert builder.transaction.data['r'] == fixture['data']['r'] + assert builder.transaction.data['s'] == fixture['data']['s'] + assert builder.transaction.serialize().hex() == fixture['serialized'] assert builder.transaction.data['id'] == fixture['data']['id'] - assert builder.verify() \ No newline at end of file + assert builder.verify() From c654e0d9be1e1a4b912e6a46384aad85da910e8c Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Wed, 12 Feb 2025 11:24:09 +0000 Subject: [PATCH 07/25] updates to deserialize & rlp decoding --- crypto/transactions/deserializer.py | 112 ++++++++++++---------------- crypto/utils/rlp_decoder.py | 16 +++- 2 files changed, 61 insertions(+), 67 deletions(-) diff --git a/crypto/transactions/deserializer.py b/crypto/transactions/deserializer.py index 416a5c40..1c2cf2e8 100644 --- a/crypto/transactions/deserializer.py +++ b/crypto/transactions/deserializer.py @@ -1,3 +1,4 @@ +import re from crypto.transactions.types.abstract_transaction import AbstractTransaction from crypto.transactions.types.transfer import Transfer from crypto.transactions.types.evm_call import EvmCall @@ -5,94 +6,60 @@ from crypto.transactions.types.unvote import Unvote from crypto.transactions.types.validator_registration import ValidatorRegistration from crypto.transactions.types.validator_resignation import ValidatorResignation -from binascii import unhexlify, hexlify +from binascii import unhexlify -from binary.unsigned_integer.reader import ( - read_bit8, - read_bit32, - read_bit64, -) from crypto.enums.abi_function import AbiFunction from crypto.utils.abi_decoder import AbiDecoder +from crypto.utils.rlp_decoder import RlpDecoder class Deserializer: SIGNATURE_SIZE = 64 RECOVERY_SIZE = 1 + EIP1559_PREFIX = '02' def __init__(self, serialized: str): self.serialized = unhexlify(serialized) if isinstance(serialized, str) else serialized self.pointer = 0 + self.encoded_rlp = '0x' + serialized[2:] + @staticmethod def new(serialized: str): return Deserializer(serialized) def deserialize(self) -> AbstractTransaction: - data = {} - - self.deserialize_common(data) - self.deserialize_data(data) - transaction = self.guess_transaction_from_data(data) - self.deserialize_signatures(data) - - transaction.data = data - transaction.recover_sender() - - transaction.data['id'] = transaction.hash(skip_signature=False).hex() - - return transaction - - def read_bytes(self, length: int) -> bytes: - result = self.serialized[self.pointer:self.pointer + length] - self.pointer += length - return result - - def deserialize_common(self, data: dict): - data['network'] = read_bit8(self.serialized, self.pointer) - self.pointer += 1 - - nonce = read_bit64(self.serialized, self.pointer) - data['nonce'] = str(nonce) - self.pointer += 8 - - gas_price = read_bit32(self.serialized, self.pointer) - data['gasPrice'] = gas_price - self.pointer += 4 + print('ENCODED_RLP:', self.encoded_rlp) - gas_limit = read_bit32(self.serialized, self.pointer) - data['gasLimit'] = gas_limit - self.pointer += 4 + decoded_rlp = RlpDecoder.decode(self.encoded_rlp) - data['value'] = '0' + print('DECODED_RLP:', decoded_rlp) - def deserialize_data(self, data: dict): - value = int.from_bytes(self.serialized[self.pointer:self.pointer + 32], byteorder='big') - self.pointer += 32 - - data['value'] = str(value) + data = { + 'network': Deserializer.parse_number(decoded_rlp[0]), + 'nonce': Deserializer.parse_big_number(decoded_rlp[1]), + 'gasPrice': Deserializer.parse_number(decoded_rlp[3]), + 'gasLimit': Deserializer.parse_number(decoded_rlp[4]), + 'recipientAddress': Deserializer.parse_address(decoded_rlp[5]), + 'value': Deserializer.parse_big_number(decoded_rlp[6]), + 'data': Deserializer.parse_hex(decoded_rlp[7]), + } - recipient_marker = read_bit8(self.serialized, self.pointer) - self.pointer += 1 + if len(decoded_rlp) == 12: + data['v'] = Deserializer.parse_number(decoded_rlp[9]) + 31 + data['r'] = Deserializer.parse_hex(decoded_rlp[10]) + data['s'] = Deserializer.parse_hex(decoded_rlp[11]) - if recipient_marker == 1: - recipient_address_bytes = self.read_bytes(20) - recipient_address = '0x' + hexlify(recipient_address_bytes).decode() - data['recipientAddress'] = recipient_address + transaction = self.guess_transaction_from_data(data) - payload_length = read_bit32(self.serialized, self.pointer) - self.pointer += 4 + serialized_hex = self.EIP1559_PREFIX + self.encoded_rlp + # transaction.serialized = serialized_hex - payload_hex = '' - if payload_length > 0: - payload_bytes = self.read_bytes(payload_length) - payload_hex = hexlify(payload_bytes).decode() + transaction.data = data + transaction.recover_sender() - data['data'] = payload_hex + transaction.data['id'] = transaction.get_id() - def deserialize_signatures(self, data: dict): - signature_length = self.SIGNATURE_SIZE + self.RECOVERY_SIZE - signature_bytes = self.read_bytes(signature_length) - data['signature'] = hexlify(signature_bytes).decode() + return transaction def guess_transaction_from_data(self, data: dict) -> AbstractTransaction: if data['value'] != '0': @@ -115,7 +82,7 @@ def guess_transaction_from_data(self, data: dict) -> AbstractTransaction: else: return EvmCall(data) - def decode_payload(self, data: dict) -> dict: + def decode_payload(self, data: dict) -> dict | None: payload = data.get('data', '') if payload == '': @@ -126,4 +93,21 @@ def decode_payload(self, data: dict) -> dict: return decoder.decode_function_data(payload) except Exception as e: print(f"Error decoding payload: {str(e)}") - return None \ No newline at end of file + + return None + + @staticmethod + def parse_number(value: str) -> int: + return 0 if value == '0x' else int(value, 16) + + @staticmethod + def parse_big_number(value: str) -> str: + return str(Deserializer.parse_number(value)) + + @staticmethod + def parse_hex(value: str) -> str: + return re.sub(r'^0x', '', value) + + @staticmethod + def parse_address(value: str) -> str | None: + return None if value == '0x' else value diff --git a/crypto/utils/rlp_decoder.py b/crypto/utils/rlp_decoder.py index a5f62a0b..c7a4a6ad 100644 --- a/crypto/utils/rlp_decoder.py +++ b/crypto/utils/rlp_decoder.py @@ -1,8 +1,13 @@ import re +from typing import TypedDict + +class DecodedType(TypedDict): + consumed: int + result: str | list class RlpDecoder: @classmethod - def decode(cls, data: str): + def decode(cls, data: str) -> str | list: bytes_data = cls.get_bytes(data, 'data') decoded = cls._decode(bytes_data, 0) @@ -17,6 +22,7 @@ def get_bytes(value: str, name: str = 'value') -> list: hex_value = value[2:] length = len(hex_value) // 2 bytes_data = [int(hex_value[i * 2:i * 2 + 2], 16) for i in range(length)] + return bytes_data raise ValueError(f'Invalid BytesLike value for "{name}": {value}') @@ -34,10 +40,11 @@ def unarrayify_integer(data, offset, length) -> int: result = 0 for i in range(length): result = (result << 8) + data[offset + i] + return result @classmethod - def _decode_children(cls, data, offset, child_offset, length) -> dict: + def _decode_children(cls, data, offset, child_offset, length) -> DecodedType: result = [] end = offset + 1 + length @@ -55,7 +62,7 @@ def _decode_children(cls, data, offset, child_offset, length) -> dict: } @classmethod - def _decode(cls, data, offset) -> dict[str, int | str]: + def _decode(cls, data, offset) -> DecodedType: cls.check_offset(offset, data) prefix = data[offset] @@ -68,12 +75,14 @@ def _decode(cls, data, offset) -> dict[str, int | str]: cls.check_offset(offset + 1 + length_length + length - 1, data) return cls._decode_children(data, offset, offset + 1 + length_length, length_length + length) + elif prefix >= 0xc0: length = prefix - 0xc0 if length > 0: cls.check_offset(offset + 1 + length - 1, data) return cls._decode_children(data, offset, offset + 1, length) + elif prefix >= 0xb8: length_length = prefix - 0xb7 cls.check_offset(offset + length_length, data) @@ -87,6 +96,7 @@ def _decode(cls, data, offset) -> dict[str, int | str]: 'consumed': 1 + length_length + length, 'result': cls.hexlify(slice_data), } + elif prefix >= 0x80: length = prefix - 0x80 if length > 0: From b18007c1c8bf87029454e52884140bce1f9d8a37 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Wed, 12 Feb 2025 11:24:29 +0000 Subject: [PATCH 08/25] update recover sender to use updated hash --- crypto/transactions/types/abstract_transaction.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crypto/transactions/types/abstract_transaction.py b/crypto/transactions/types/abstract_transaction.py index 48d6dcb7..9f621dd3 100644 --- a/crypto/transactions/types/abstract_transaction.py +++ b/crypto/transactions/types/abstract_transaction.py @@ -57,12 +57,11 @@ def get_public_key(self, compact_signature, hash_): return public_key def recover_sender(self): - signature_hex = self.data.get('signature') - if not signature_hex: - raise ValueError("No signature to recover from") + signature_with_recid = self.get_signature() + if not signature_with_recid: + return False - signature_with_recid = bytes.fromhex(signature_hex) - hash_ = self.hash(skip_signature=True) + hash_ = bytes.fromhex(self.hash(skip_signature=True)) public_key = self.get_public_key(signature_with_recid, hash_) self.data['senderPublicKey'] = public_key.format().hex() self.data['senderAddress'] = address_from_public_key(self.data['senderPublicKey']) From d5fabf310708db7b46e037a73a2aea2d1ada97b0 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Wed, 12 Feb 2025 11:27:16 +0000 Subject: [PATCH 09/25] remove debug --- crypto/transactions/deserializer.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/crypto/transactions/deserializer.py b/crypto/transactions/deserializer.py index 1c2cf2e8..6a81a2bf 100644 --- a/crypto/transactions/deserializer.py +++ b/crypto/transactions/deserializer.py @@ -28,12 +28,8 @@ def new(serialized: str): return Deserializer(serialized) def deserialize(self) -> AbstractTransaction: - print('ENCODED_RLP:', self.encoded_rlp) - decoded_rlp = RlpDecoder.decode(self.encoded_rlp) - print('DECODED_RLP:', decoded_rlp) - data = { 'network': Deserializer.parse_number(decoded_rlp[0]), 'nonce': Deserializer.parse_big_number(decoded_rlp[1]), @@ -51,9 +47,6 @@ def deserialize(self) -> AbstractTransaction: transaction = self.guess_transaction_from_data(data) - serialized_hex = self.EIP1559_PREFIX + self.encoded_rlp - # transaction.serialized = serialized_hex - transaction.data = data transaction.recover_sender() From 9b4052e475971c2ee086f07d24e9f608c7882b61 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Wed, 12 Feb 2025 11:27:23 +0000 Subject: [PATCH 10/25] update tests --- tests/transactions/test_deserializer.py | 28 ++++++++++++------------- tests/transactions/test_transaction.py | 17 ++++++++++----- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/tests/transactions/test_deserializer.py b/tests/transactions/test_deserializer.py index 50357cd2..3da00119 100644 --- a/tests/transactions/test_deserializer.py +++ b/tests/transactions/test_deserializer.py @@ -16,33 +16,33 @@ def assert_deserialized(fixture, keys): def test_deserialize_transfer(load_transaction_fixture): fixture = load_transaction_fixture('transfer') - transaction = assert_deserialized(fixture, ['id', 'nonce', 'gasPrice', 'gasLimit', 'value', 'signature']) - + transaction = assert_deserialized(fixture, ['id', 'nonce', 'gasPrice', 'gasLimit', 'value', 'v', 'r', 's']) + assert isinstance(transaction, Transfer) - assert transaction.data['value'] == '10000000000000000000' + assert transaction.data['value'] == '100000000' def test_deserialize_vote(load_transaction_fixture): fixture = load_transaction_fixture('vote') - transaction = assert_deserialized(fixture, ['id', 'nonce', 'gasPrice', 'gasLimit', 'signature']) - + transaction = assert_deserialized(fixture, ['id', 'nonce', 'gasPrice', 'gasLimit', 'v', 'r', 's']) + assert isinstance(transaction, Vote) - assert transaction.data['vote'] == '0x512F366D524157BcF734546eB29a6d687B762255' - assert transaction.data['id'] == '749744e0d689c46e37ff2993a984599eac4989a9ef0028337b335c9d43abf936' + assert transaction.data['vote'].lower() == '0xc3bbe9b1cee1ff85ad72b87414b0e9b7f2366763' + assert transaction.data['id'] == '991a3a63dc47be84d7982acb4c2aae488191373f31b8097e07d3ad95c0997e69' def test_deserialize_unvote(load_transaction_fixture): fixture = load_transaction_fixture('unvote') - transaction = assert_deserialized(fixture, ['id', 'nonce', 'gasPrice', 'gasLimit', 'signature']) - + transaction = assert_deserialized(fixture, ['id', 'nonce', 'gasPrice', 'gasLimit', 'v', 'r', 's']) + assert isinstance(transaction, Unvote) def test_deserialize_validator_registration(load_transaction_fixture): fixture = load_transaction_fixture('validator-registration') - transaction = assert_deserialized(fixture, ['id', 'nonce', 'gasPrice', 'gasLimit', 'signature']) - + transaction = assert_deserialized(fixture, ['id', 'nonce', 'gasPrice', 'gasLimit', 'v', 'r', 's']) + assert isinstance(transaction, ValidatorRegistration) def test_deserialize_validator_resignation(load_transaction_fixture): fixture = load_transaction_fixture('validator-resignation') - transaction = assert_deserialized(fixture, ['id', 'nonce', 'gasPrice', 'gasLimit', 'signature']) - - assert isinstance(transaction, ValidatorResignation) \ No newline at end of file + transaction = assert_deserialized(fixture, ['id', 'nonce', 'gasPrice', 'gasLimit', 'v', 'r', 's']) + + assert isinstance(transaction, ValidatorResignation) diff --git a/tests/transactions/test_transaction.py b/tests/transactions/test_transaction.py index 1b316f35..8b1a5f65 100644 --- a/tests/transactions/test_transaction.py +++ b/tests/transactions/test_transaction.py @@ -1,6 +1,5 @@ from crypto.identity.private_key import PrivateKey from crypto.transactions.deserializer import Deserializer -from crypto.transactions.types.abstract_transaction import AbstractTransaction def test_compute_id_of_transaction(load_transaction_fixture): transaction = Deserializer.new(load_transaction_fixture('transfer')['serialized']).deserialize() @@ -10,11 +9,19 @@ def test_sign_transaction_with_passphrase(load_transaction_fixture): private_key = PrivateKey.from_passphrase('this is a top secret passphrase') transaction = Deserializer.new(load_transaction_fixture('transfer')['serialized']).deserialize() - transaction.data['signature'] = '' - - assert 'signature' not in transaction.data or transaction.data['signature'] == '' + transaction.data['v'] = '' + transaction.data['r'] = '' + transaction.data['s'] = '' + + assert 'v' not in transaction.data or transaction.data['v'] == '' + assert 'r' not in transaction.data or transaction.data['r'] == '' + assert 's' not in transaction.data or transaction.data['s'] == '' + transaction.sign(private_key) - assert transaction.data['signature'] != '' + + assert transaction.data['v'] != '' + assert transaction.data['r'] != '' + assert transaction.data['s'] != '' def test_verify_transaction(load_transaction_fixture): transaction = Deserializer.new(load_transaction_fixture('transfer')['serialized']).deserialize(); From 3e60ef52359b24edf93fc15c10071a044d9db1c1 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Wed, 12 Feb 2025 17:51:32 +0000 Subject: [PATCH 11/25] adjust typehints --- crypto/utils/rlp_decoder.py | 14 +++++++------- crypto/utils/rlp_encoder.py | 16 ++++++++-------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/crypto/utils/rlp_decoder.py b/crypto/utils/rlp_decoder.py index c7a4a6ad..d9c22a02 100644 --- a/crypto/utils/rlp_decoder.py +++ b/crypto/utils/rlp_decoder.py @@ -17,7 +17,7 @@ def decode(cls, data: str) -> str | list: return decoded['result'] @staticmethod - def get_bytes(value: str, name: str = 'value') -> list: + def get_bytes(value: str, name: str = 'value') -> list[int]: if re.match(r'^0x(?:[0-9a-fA-F]{2})*$', value): hex_value = value[2:] length = len(hex_value) // 2 @@ -28,15 +28,15 @@ def get_bytes(value: str, name: str = 'value') -> list: raise ValueError(f'Invalid BytesLike value for "{name}": {value}') @staticmethod - def hexlify(data) -> str: + def hexlify(data: list[int]) -> str: return '0x' + ''.join(f'{byte:02x}' for byte in data) @staticmethod - def hexlify_byte(value) -> str: + def hexlify_byte(value: int) -> str: return f'0x{value & 0xff:02x}' @staticmethod - def unarrayify_integer(data, offset, length) -> int: + def unarrayify_integer(data: list[int], offset: int, length: int) -> int: result = 0 for i in range(length): result = (result << 8) + data[offset + i] @@ -44,7 +44,7 @@ def unarrayify_integer(data, offset, length) -> int: return result @classmethod - def _decode_children(cls, data, offset, child_offset, length) -> DecodedType: + def _decode_children(cls, data: list[int], offset: int, child_offset: int, length: int) -> DecodedType: result = [] end = offset + 1 + length @@ -62,7 +62,7 @@ def _decode_children(cls, data, offset, child_offset, length) -> DecodedType: } @classmethod - def _decode(cls, data, offset) -> DecodedType: + def _decode(cls, data: list[int], offset: int) -> DecodedType: cls.check_offset(offset, data) prefix = data[offset] @@ -114,6 +114,6 @@ def _decode(cls, data, offset) -> DecodedType: } @staticmethod - def check_offset(offset, data) -> None: + def check_offset(offset: int, data: list[int]) -> None: if offset > len(data): raise ValueError('data short segment or out of range') diff --git a/crypto/utils/rlp_encoder.py b/crypto/utils/rlp_encoder.py index 21ed885d..70b4d72d 100644 --- a/crypto/utils/rlp_encoder.py +++ b/crypto/utils/rlp_encoder.py @@ -1,10 +1,10 @@ from binascii import hexlify, unhexlify - +from typing import Union class RlpEncoder: @classmethod - def encode(cls, obj) -> str: - encoded = cls._encode(obj) + def encode(cls, data: Union[str, bytes, int, list]) -> str: + encoded = cls._encode(data) hex_str = '' nibbles = '0123456789abcdef' @@ -15,10 +15,10 @@ def encode(cls, obj) -> str: return hex_str @classmethod - def _encode(cls, obj) -> list: - if isinstance(obj, list): + def _encode(cls, data: Union[str, bytes, int, list]) -> list: + if isinstance(data, list): payload = [] - for child in obj: + for child in data: payload.extend(cls._encode(child)) payload_length = len(payload) @@ -32,7 +32,7 @@ def _encode(cls, obj) -> list: return length + payload - data = cls.get_bytes(obj) + data = cls.get_bytes(data) data_length = len(data) if data_length == 1 and data[0] <= 0x7f: return data @@ -57,7 +57,7 @@ def arrayify_integer(value) -> list: return result @staticmethod - def get_bytes(value) -> list: + def get_bytes(value: Union[str, bytes, int, list]) -> list: if isinstance(value, str) or isinstance(value, bytes): if isinstance(value, str): value = value.encode() From 8aefce3946d1c777f4ec62bc913562504629b70a Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Wed, 12 Feb 2025 17:51:40 +0000 Subject: [PATCH 12/25] additional deserializer tests --- tests/transactions/test_deserializer.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/transactions/test_deserializer.py b/tests/transactions/test_deserializer.py index 3da00119..4c4c44ba 100644 --- a/tests/transactions/test_deserializer.py +++ b/tests/transactions/test_deserializer.py @@ -46,3 +46,27 @@ def test_deserialize_validator_resignation(load_transaction_fixture): transaction = assert_deserialized(fixture, ['id', 'nonce', 'gasPrice', 'gasLimit', 'v', 'r', 's']) assert isinstance(transaction, ValidatorResignation) + +def test_parse_number(): + assert Deserializer.parse_number('0x01') == 1 + assert Deserializer.parse_number('0x0100') == 256 + assert Deserializer.parse_number('0x010000') == 65536 + assert Deserializer.parse_number('0x') == 0 + +def test_parse_big_number(): + assert Deserializer.parse_big_number('0x01') == '1' + assert Deserializer.parse_big_number('0x0100') == '256' + assert Deserializer.parse_big_number('0x010000') == '65536' + assert Deserializer.parse_big_number('0x') == '0' + assert Deserializer.parse_big_number('0x52B7D2DCC80CD2E4000000') == '100000000000000000000000000' + +def test_parse_hex(): + assert Deserializer.parse_hex('0x01') == '01' + assert Deserializer.parse_hex('0x0100') == '0100' + assert Deserializer.parse_hex('0x010000') == '010000' + assert Deserializer.parse_hex('0x') == '' + assert Deserializer.parse_hex('0x52B7D2DCC80CD2E4000000') == '52B7D2DCC80CD2E4000000' + +def test_parse_address(): + assert Deserializer.parse_address('0x52B7D2DCC80CD2E4000000') == '0x52B7D2DCC80CD2E4000000' + assert Deserializer.parse_address('0x') == None From 0a792fa4f66d8c4e56ee57ee046e1fb115774c47 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Wed, 12 Feb 2025 17:51:45 +0000 Subject: [PATCH 13/25] test --- tests/utils/test_rpl_decoder.py | 43 +++++++++++++++++++++++++++++++++ tests/utils/test_rpl_encoder.py | 31 ++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 tests/utils/test_rpl_decoder.py create mode 100644 tests/utils/test_rpl_encoder.py diff --git a/tests/utils/test_rpl_decoder.py b/tests/utils/test_rpl_decoder.py new file mode 100644 index 00000000..c0098dd9 --- /dev/null +++ b/tests/utils/test_rpl_decoder.py @@ -0,0 +1,43 @@ +from crypto.transactions.deserializer import Deserializer +from crypto.utils.rlp_decoder import RlpDecoder +from crypto.utils.transaction_utils import TransactionUtils + + +def test_decode_function_call(load_transaction_fixture): + fixture = load_transaction_fixture('transfer') + + decoded_rlp = RlpDecoder.decode('0x' + fixture['serialized'][2:]) + + assert len(decoded_rlp) == 12 + assert decoded_rlp[0] == '0x1e' + assert decoded_rlp[1] == '0x01' + assert decoded_rlp[2] == '0x' + assert decoded_rlp[3] == '0x05' + assert decoded_rlp[4] == '0x5208' + assert decoded_rlp[5] == '0x6f0182a0cc707b055322ccf6d4cb6a5aff1aeb22' + assert decoded_rlp[6] == '0x05f5e100' + assert decoded_rlp[7] == '0x' + assert decoded_rlp[8] == [] + assert decoded_rlp[9] == '0x' + assert decoded_rlp[10] == '0x0567c4def813a66e03fa1cd499a27c6922698a67e25e0b38458d8f4bb0e581fc' + assert decoded_rlp[11] == '0x25fcdf9d110b82bde15bae4a49118deb83cfc3ec1656c2a29286d7836d328abe' + +def test_decoding_str(): + decoded = RlpDecoder.decode('0x8774657374696e67') + + assert decoded == '0x74657374696e67' + +def test_decoding_bytes(): + decoded = RlpDecoder.decode('0x8774657374696e67') + + assert decoded == '0x74657374696e67' + +def test_decoding_list(): + decoded = RlpDecoder.decode('0xc88774657374696e67') + + assert decoded == ['0x74657374696e67'] + +def test_decoding_int(): + decoded = RlpDecoder.decode('0x86313233343536') + + assert decoded == '0x313233343536' diff --git a/tests/utils/test_rpl_encoder.py b/tests/utils/test_rpl_encoder.py new file mode 100644 index 00000000..ca73437f --- /dev/null +++ b/tests/utils/test_rpl_encoder.py @@ -0,0 +1,31 @@ +from crypto.utils.rlp_encoder import RlpEncoder +from crypto.utils.transaction_utils import TransactionUtils + + +def test_encode_function_call(load_transaction_fixture): + fixture = load_transaction_fixture('transfer') + + # Calls RlpEncoder.encode() with the given transaction + transaction_hash = TransactionUtils.to_buffer(fixture['data']) + + assert transaction_hash.decode() == fixture['serialized'] + +def test_encoding_str(): + encoded = RlpEncoder.encode('testing') + + assert encoded == '8774657374696e67' + +def test_encoding_bytes(): + encoded = RlpEncoder.encode(b'testing') + + assert encoded == '8774657374696e67' + +def test_encoding_list(): + encoded = RlpEncoder.encode(['testing']) + + assert encoded == 'c88774657374696e67' + +def test_encoding_int(): + encoded = RlpEncoder.encode('123456') + + assert encoded == '86313233343536' From 9e5fba820518e5cb469fe0afba197a17fb63f2a9 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Wed, 12 Feb 2025 18:05:33 +0000 Subject: [PATCH 14/25] use union --- crypto/utils/rlp_decoder.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crypto/utils/rlp_decoder.py b/crypto/utils/rlp_decoder.py index d9c22a02..352b2473 100644 --- a/crypto/utils/rlp_decoder.py +++ b/crypto/utils/rlp_decoder.py @@ -1,13 +1,13 @@ import re -from typing import TypedDict +from typing import TypedDict, Union class DecodedType(TypedDict): consumed: int - result: str | list + result: Union[str, list] class RlpDecoder: @classmethod - def decode(cls, data: str) -> str | list: + def decode(cls, data: str) -> Union[str, list]: bytes_data = cls.get_bytes(data, 'data') decoded = cls._decode(bytes_data, 0) From 71c71117de954f7337bbdb4cd193a46d76328457 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Wed, 12 Feb 2025 18:07:43 +0000 Subject: [PATCH 15/25] optional typing --- crypto/transactions/deserializer.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crypto/transactions/deserializer.py b/crypto/transactions/deserializer.py index 6a81a2bf..4875daa7 100644 --- a/crypto/transactions/deserializer.py +++ b/crypto/transactions/deserializer.py @@ -1,4 +1,5 @@ import re +from typing import Optional from crypto.transactions.types.abstract_transaction import AbstractTransaction from crypto.transactions.types.transfer import Transfer from crypto.transactions.types.evm_call import EvmCall @@ -75,7 +76,7 @@ def guess_transaction_from_data(self, data: dict) -> AbstractTransaction: else: return EvmCall(data) - def decode_payload(self, data: dict) -> dict | None: + def decode_payload(self, data: dict) -> Optional[dict]: payload = data.get('data', '') if payload == '': @@ -102,5 +103,5 @@ def parse_hex(value: str) -> str: return re.sub(r'^0x', '', value) @staticmethod - def parse_address(value: str) -> str | None: + def parse_address(value: str) -> Optional[str]: return None if value == '0x' else value From e03f1893dcd10ccbe20744e3c23a70b546db399e Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Wed, 12 Feb 2025 23:47:09 +0000 Subject: [PATCH 16/25] tidy up --- crypto/transactions/deserializer.py | 2 +- crypto/utils/rlp_decoder.py | 55 ++++++++++++++------------ crypto/utils/rlp_encoder.py | 24 +++++------ crypto/utils/transaction_utils.py | 2 +- tests/transactions/test_transaction.py | 8 ++-- tests/utils/test_rpl_decoder.py | 2 - 6 files changed, 48 insertions(+), 45 deletions(-) diff --git a/crypto/transactions/deserializer.py b/crypto/transactions/deserializer.py index 4875daa7..6ca72ddf 100644 --- a/crypto/transactions/deserializer.py +++ b/crypto/transactions/deserializer.py @@ -1,4 +1,5 @@ import re +from binascii import unhexlify from typing import Optional from crypto.transactions.types.abstract_transaction import AbstractTransaction from crypto.transactions.types.transfer import Transfer @@ -7,7 +8,6 @@ from crypto.transactions.types.unvote import Unvote from crypto.transactions.types.validator_registration import ValidatorRegistration from crypto.transactions.types.validator_resignation import ValidatorResignation -from binascii import unhexlify from crypto.enums.abi_function import AbiFunction from crypto.utils.abi_decoder import AbiDecoder diff --git a/crypto/utils/rlp_decoder.py b/crypto/utils/rlp_decoder.py index 352b2473..a42fe729 100644 --- a/crypto/utils/rlp_decoder.py +++ b/crypto/utils/rlp_decoder.py @@ -8,8 +8,11 @@ class DecodedType(TypedDict): class RlpDecoder: @classmethod def decode(cls, data: str) -> Union[str, list]: - bytes_data = cls.get_bytes(data, 'data') - decoded = cls._decode(bytes_data, 0) + """ + Decode RLP data from a hex string. + """ + bytes_data = cls.__get_bytes(data) + decoded = cls.__decode(bytes_data, 0) if decoded['consumed'] != len(bytes_data): raise ValueError('unexpected junk after RLP payload') @@ -17,7 +20,7 @@ def decode(cls, data: str) -> Union[str, list]: return decoded['result'] @staticmethod - def get_bytes(value: str, name: str = 'value') -> list[int]: + def __get_bytes(value: str) -> list[int]: if re.match(r'^0x(?:[0-9a-fA-F]{2})*$', value): hex_value = value[2:] length = len(hex_value) // 2 @@ -25,18 +28,18 @@ def get_bytes(value: str, name: str = 'value') -> list[int]: return bytes_data - raise ValueError(f'Invalid BytesLike value for "{name}": {value}') + raise ValueError(f'Invalid BytesLike value: {value}') @staticmethod - def hexlify(data: list[int]) -> str: + def __hexlify(data: list[int]) -> str: return '0x' + ''.join(f'{byte:02x}' for byte in data) @staticmethod - def hexlify_byte(value: int) -> str: + def __hexlify_byte(value: int) -> str: return f'0x{value & 0xff:02x}' @staticmethod - def unarrayify_integer(data: list[int], offset: int, length: int) -> int: + def __unarrayify_integer(data: list[int], offset: int, length: int) -> int: result = 0 for i in range(length): result = (result << 8) + data[offset + i] @@ -44,12 +47,12 @@ def unarrayify_integer(data: list[int], offset: int, length: int) -> int: return result @classmethod - def _decode_children(cls, data: list[int], offset: int, child_offset: int, length: int) -> DecodedType: + def __decode_children(cls, data: list[int], offset: int, child_offset: int, length: int) -> DecodedType: result = [] end = offset + 1 + length while child_offset < end: - decoded = cls._decode(data, child_offset) + decoded = cls.__decode(data, child_offset) result.append(decoded['result']) child_offset += decoded['consumed'] @@ -62,58 +65,58 @@ def _decode_children(cls, data: list[int], offset: int, child_offset: int, lengt } @classmethod - def _decode(cls, data: list[int], offset: int) -> DecodedType: - cls.check_offset(offset, data) + def __decode(cls, data: list[int], offset: int) -> DecodedType: + cls.__check_offset(offset, data) prefix = data[offset] if prefix >= 0xf8: length_length = prefix - 0xf7 - cls.check_offset(offset + length_length, data) + cls.__check_offset(offset + length_length, data) - length = cls.unarrayify_integer(data, offset + 1, length_length) - cls.check_offset(offset + 1 + length_length + length - 1, data) + length = cls.__unarrayify_integer(data, offset + 1, length_length) + cls.__check_offset(offset + 1 + length_length + length - 1, data) - return cls._decode_children(data, offset, offset + 1 + length_length, length_length + length) + return cls.__decode_children(data, offset, offset + 1 + length_length, length_length + length) elif prefix >= 0xc0: length = prefix - 0xc0 if length > 0: - cls.check_offset(offset + 1 + length - 1, data) + cls.__check_offset(offset + 1 + length - 1, data) - return cls._decode_children(data, offset, offset + 1, length) + return cls.__decode_children(data, offset, offset + 1, length) elif prefix >= 0xb8: length_length = prefix - 0xb7 - cls.check_offset(offset + length_length, data) + cls.__check_offset(offset + length_length, data) - length = cls.unarrayify_integer(data, offset + 1, length_length) + length = cls.__unarrayify_integer(data, offset + 1, length_length) if length > 0: - cls.check_offset(offset + 1 + length_length + length - 1, data) + cls.__check_offset(offset + 1 + length_length + length - 1, data) slice_data = data[offset + 1 + length_length:offset + 1 + length_length + length] return { 'consumed': 1 + length_length + length, - 'result': cls.hexlify(slice_data), + 'result': cls.__hexlify(slice_data), } elif prefix >= 0x80: length = prefix - 0x80 if length > 0: - cls.check_offset(offset + 1 + length - 1, data) + cls.__check_offset(offset + 1 + length - 1, data) slice_data = data[offset + 1:offset + 1 + length] return { 'consumed': 1 + length, - 'result': cls.hexlify(slice_data), + 'result': cls.__hexlify(slice_data), } return { 'consumed': 1, - 'result': cls.hexlify_byte(prefix), + 'result': cls.__hexlify_byte(prefix), } - @staticmethod - def check_offset(offset: int, data: list[int]) -> None: + @classmethod + def __check_offset(cls, offset: int, data: list[int]) -> None: if offset > len(data): raise ValueError('data short segment or out of range') diff --git a/crypto/utils/rlp_encoder.py b/crypto/utils/rlp_encoder.py index 70b4d72d..e5e63e85 100644 --- a/crypto/utils/rlp_encoder.py +++ b/crypto/utils/rlp_encoder.py @@ -1,10 +1,12 @@ -from binascii import hexlify, unhexlify from typing import Union class RlpEncoder: @classmethod def encode(cls, data: Union[str, bytes, int, list]) -> str: - encoded = cls._encode(data) + """ + Encodes the given data into RLP format. + """ + encoded = cls.__encode(data) hex_str = '' nibbles = '0123456789abcdef' @@ -15,11 +17,11 @@ def encode(cls, data: Union[str, bytes, int, list]) -> str: return hex_str @classmethod - def _encode(cls, data: Union[str, bytes, int, list]) -> list: + def __encode(cls, data: Union[str, bytes, int, list]) -> list: if isinstance(data, list): payload = [] for child in data: - payload.extend(cls._encode(child)) + payload.extend(cls.__encode(child)) payload_length = len(payload) if payload_length <= 55: @@ -27,12 +29,12 @@ def _encode(cls, data: Union[str, bytes, int, list]) -> list: return payload - length = cls.arrayify_integer(payload_length) + length = cls.__arrayify_integer(payload_length) length.insert(0, 0xf7 + (len(length))) return length + payload - data = cls.get_bytes(data) + data = cls.__get_bytes(data) data_length = len(data) if data_length == 1 and data[0] <= 0x7f: return data @@ -42,13 +44,13 @@ def _encode(cls, data: Union[str, bytes, int, list]) -> list: return data - length = cls.arrayify_integer(len(data)) + length = cls.__arrayify_integer(len(data)) length.insert(0, 0xb7 + len(length)) return length + data - @staticmethod - def arrayify_integer(value) -> list: + @classmethod + def __arrayify_integer(cls, value: int) -> list: result = [] while value > 0: result.insert(0, value & 0xff) @@ -56,8 +58,8 @@ def arrayify_integer(value) -> list: return result - @staticmethod - def get_bytes(value: Union[str, bytes, int, list]) -> list: + @classmethod + def __get_bytes(cls, value: Union[str, bytes, int, list]) -> list: if isinstance(value, str) or isinstance(value, bytes): if isinstance(value, str): value = value.encode() diff --git a/crypto/utils/transaction_utils.py b/crypto/utils/transaction_utils.py index 0ddab514..46904105 100644 --- a/crypto/utils/transaction_utils.py +++ b/crypto/utils/transaction_utils.py @@ -1,4 +1,4 @@ -from binascii import hexlify, unhexlify +from binascii import unhexlify import hashlib from crypto.utils.rlp_encoder import RlpEncoder diff --git a/tests/transactions/test_transaction.py b/tests/transactions/test_transaction.py index 8b1a5f65..477348ac 100644 --- a/tests/transactions/test_transaction.py +++ b/tests/transactions/test_transaction.py @@ -24,20 +24,20 @@ def test_sign_transaction_with_passphrase(load_transaction_fixture): assert transaction.data['s'] != '' def test_verify_transaction(load_transaction_fixture): - transaction = Deserializer.new(load_transaction_fixture('transfer')['serialized']).deserialize(); + transaction = Deserializer.new(load_transaction_fixture('transfer')['serialized']).deserialize() assert transaction.verify() def test_transaction_to_bytes(load_transaction_fixture): - transaction = Deserializer.new(load_transaction_fixture('transfer')['serialized']).deserialize(); + transaction = Deserializer.new(load_transaction_fixture('transfer')['serialized']).deserialize() actual = transaction.get_bytes() assert isinstance(actual, bytes) def test_transaction_to_array(load_transaction_fixture): - transaction = Deserializer.new(load_transaction_fixture('transfer')['serialized']).deserialize(); + transaction = Deserializer.new(load_transaction_fixture('transfer')['serialized']).deserialize() actual = transaction.to_dict() assert isinstance(actual, dict) def test_transaction_to_json(load_transaction_fixture): - transaction = Deserializer.new(load_transaction_fixture('transfer')['serialized']).deserialize(); + transaction = Deserializer.new(load_transaction_fixture('transfer')['serialized']).deserialize() actual = transaction.to_json() assert isinstance(actual, str) diff --git a/tests/utils/test_rpl_decoder.py b/tests/utils/test_rpl_decoder.py index c0098dd9..e896c426 100644 --- a/tests/utils/test_rpl_decoder.py +++ b/tests/utils/test_rpl_decoder.py @@ -1,6 +1,4 @@ -from crypto.transactions.deserializer import Deserializer from crypto.utils.rlp_decoder import RlpDecoder -from crypto.utils.transaction_utils import TransactionUtils def test_decode_function_call(load_transaction_fixture): From 7ebd0bf0ffc8844c57c1e88f839c834e4b2e4c8f Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Thu, 13 Feb 2025 12:53:00 +0000 Subject: [PATCH 17/25] use constants for recovery id offset --- crypto/enums/constants.py | 5 +++++ crypto/identity/private_key.py | 4 +++- crypto/transactions/deserializer.py | 4 ++-- crypto/transactions/types/abstract_transaction.py | 3 ++- crypto/utils/transaction_utils.py | 7 +++---- 5 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 crypto/enums/constants.py diff --git a/crypto/enums/constants.py b/crypto/enums/constants.py new file mode 100644 index 00000000..53ed6e28 --- /dev/null +++ b/crypto/enums/constants.py @@ -0,0 +1,5 @@ +from enum import Enum + +class Constants(Enum): + ETHEREUM_RECOVERY_ID_OFFSET = 27 + EIP_1559_PREFIX = '02' diff --git a/crypto/identity/private_key.py b/crypto/identity/private_key.py index 379361ed..1b3ca963 100644 --- a/crypto/identity/private_key.py +++ b/crypto/identity/private_key.py @@ -2,6 +2,8 @@ from hashlib import sha256 from coincurve import PrivateKey as PvtKey +from crypto.enums.constants import Constants + class PrivateKey(object): def __init__(self, private_key: str): self.private_key = PvtKey.from_hex(private_key) @@ -32,7 +34,7 @@ def sign_compact(self, message: bytes) -> bytes: pkey = PvtKey.from_hex(sha256(b'my super secret passphrase').hexdigest()) der = pkey.sign_recoverable(message) - return bytes([der[64] + 31]) + der[0:64] + return bytes([der[64] + Constants.ETHEREUM_RECOVERY_ID_OFFSET.value]) + der[0:64] def to_hex(self): """Returns a private key in hex format diff --git a/crypto/transactions/deserializer.py b/crypto/transactions/deserializer.py index 6ca72ddf..ab21002b 100644 --- a/crypto/transactions/deserializer.py +++ b/crypto/transactions/deserializer.py @@ -1,6 +1,7 @@ import re from binascii import unhexlify from typing import Optional +from crypto.enums.constants import Constants from crypto.transactions.types.abstract_transaction import AbstractTransaction from crypto.transactions.types.transfer import Transfer from crypto.transactions.types.evm_call import EvmCall @@ -16,7 +17,6 @@ class Deserializer: SIGNATURE_SIZE = 64 RECOVERY_SIZE = 1 - EIP1559_PREFIX = '02' def __init__(self, serialized: str): self.serialized = unhexlify(serialized) if isinstance(serialized, str) else serialized @@ -42,7 +42,7 @@ def deserialize(self) -> AbstractTransaction: } if len(decoded_rlp) == 12: - data['v'] = Deserializer.parse_number(decoded_rlp[9]) + 31 + data['v'] = Deserializer.parse_number(decoded_rlp[9]) + Constants.ETHEREUM_RECOVERY_ID_OFFSET.value data['r'] = Deserializer.parse_hex(decoded_rlp[10]) data['s'] = Deserializer.parse_hex(decoded_rlp[11]) diff --git a/crypto/transactions/types/abstract_transaction.py b/crypto/transactions/types/abstract_transaction.py index 9f621dd3..c5186155 100644 --- a/crypto/transactions/types/abstract_transaction.py +++ b/crypto/transactions/types/abstract_transaction.py @@ -2,6 +2,7 @@ from typing import Optional from crypto.configuration.network import get_network +from crypto.enums.constants import Constants from crypto.identity.address import address_from_public_key from crypto.identity.private_key import PrivateKey from crypto.utils.transaction_utils import TransactionUtils @@ -96,7 +97,7 @@ def hash(self, skip_signature: bool) -> str: return TransactionUtils.to_hash(self.data, skip_signature=skip_signature) def get_signature(self): - recover_id = int(self.data.get('v', 0)) - 31 + recover_id = int(self.data.get('v', 0)) - Constants.ETHEREUM_RECOVERY_ID_OFFSET.value r = self.data.get('r') s = self.data.get('s') diff --git a/crypto/utils/transaction_utils.py b/crypto/utils/transaction_utils.py index 46904105..3cfbda5b 100644 --- a/crypto/utils/transaction_utils.py +++ b/crypto/utils/transaction_utils.py @@ -1,11 +1,10 @@ from binascii import unhexlify import hashlib +from crypto.enums.constants import Constants from crypto.utils.rlp_encoder import RlpEncoder class TransactionUtils: - EIP1559_PREFIX = '02' - @classmethod def to_buffer(cls, transaction: dict, skip_signature: bool = False) -> bytes: # Process recipientAddress @@ -31,13 +30,13 @@ def to_buffer(cls, transaction: dict, skip_signature: bool = False) -> bytes: ] if not skip_signature and 'v' in transaction and 'r' in transaction and 's' in transaction: - fields.append(cls.to_be_array(int(transaction['v']) - 31)) + fields.append(cls.to_be_array(int(transaction['v']) - Constants.ETHEREUM_RECOVERY_ID_OFFSET.value)) fields.append(bytes.fromhex(transaction['r'])) fields.append(bytes.fromhex(transaction['s'])) encoded = RlpEncoder.encode(fields) - hash_input = cls.EIP1559_PREFIX + encoded + hash_input = Constants.EIP_1559_PREFIX.value + encoded return hash_input.encode() From 9b5f1859200b36b030d6739728cdf91836fd3c81 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Thu, 13 Feb 2025 12:53:28 +0000 Subject: [PATCH 18/25] revert fixtures to 27/28 --- tests/fixtures/evm-sign.json | 2 +- tests/fixtures/transfer-large-amount.json | 2 +- tests/fixtures/transfer.json | 2 +- tests/fixtures/unvote.json | 2 +- tests/fixtures/username-registration.json | 2 +- tests/fixtures/username-resignation.json | 2 +- tests/fixtures/validator-registration.json | 2 +- tests/fixtures/validator-resignation.json | 2 +- tests/fixtures/vote.json | 2 +- tests/identity/conftest.py | 4 ++-- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/fixtures/evm-sign.json b/tests/fixtures/evm-sign.json index c18bd4aa..a171db39 100644 --- a/tests/fixtures/evm-sign.json +++ b/tests/fixtures/evm-sign.json @@ -10,7 +10,7 @@ "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", "id": "2cfa9d51e71f014f8054881052e41dc61267587f9dba04c59a31cc881f8ce35b", - "v": 31, + "v": 27, "r": "a56cf78a7203927af0c8216fdbc804182a0788569e460067efec519b6f7b2e55", "s": "52ce951a20c7406a4a34707557f02e5a2af1451cc4e216645778c4ee0391a4cd" }, diff --git a/tests/fixtures/transfer-large-amount.json b/tests/fixtures/transfer-large-amount.json index 7bee5369..0fcee465 100644 --- a/tests/fixtures/transfer-large-amount.json +++ b/tests/fixtures/transfer-large-amount.json @@ -7,7 +7,7 @@ "recipientAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", "value": "10000000000000000000", "data": "", - "v": 31, + "v": 27, "r": "c741f8ccf811e7080216b71e0cbcad1138a4f187b08c749c5e8780568f1ff4bc", "s": "530341bef473fa92db1fe96922758ed010ba8eb80f2e527c9441771fa4c5368b", "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", diff --git a/tests/fixtures/transfer.json b/tests/fixtures/transfer.json index 57626f18..24a2b1aa 100644 --- a/tests/fixtures/transfer.json +++ b/tests/fixtures/transfer.json @@ -7,7 +7,7 @@ "recipientAddress": "0x6f0182a0cc707b055322ccf6d4cb6a5aff1aeb22", "value": "100000000", "data": "", - "v": 31, + "v": 27, "r": "0567c4def813a66e03fa1cd499a27c6922698a67e25e0b38458d8f4bb0e581fc", "s": "25fcdf9d110b82bde15bae4a49118deb83cfc3ec1656c2a29286d7836d328abe", "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", diff --git a/tests/fixtures/unvote.json b/tests/fixtures/unvote.json index 88a7be20..5dccb90f 100644 --- a/tests/fixtures/unvote.json +++ b/tests/fixtures/unvote.json @@ -10,7 +10,7 @@ "recipientAddress": "0x535b3d7a252fa034ed71f0c53ec0c6f784cb64e1", "value": "0", "data": "3174b689", - "v": 31, + "v": 27, "r": "2853135c30a6b131e9270ce1b52999b8d2bdc18b25bef5b1c13ef4ca8a20ce9e", "s": "3999a9d5d1ff826efa13052394b28b101535c92dcd8744c916c003431e08744e" }, diff --git a/tests/fixtures/username-registration.json b/tests/fixtures/username-registration.json index f3930a74..46d03a63 100644 --- a/tests/fixtures/username-registration.json +++ b/tests/fixtures/username-registration.json @@ -10,7 +10,7 @@ "recipientAddress": "0x2c1de3b4dbb4adebebb5dcecae825be2a9fc6eb6", "value": "0", "data": "36a94134000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000037068700000000000000000000000000000000000000000000000000000000000", - "v": 31, + "v": 27, "r": "48f36028509a88c8670b2baaac14b5ba6a8c88bbd90cce4149a3070e57f0fa42", "s": "297fe9a4d2df4c682e8705311d1143d2df9c89dc8689853544c1eb96121f5e85" }, diff --git a/tests/fixtures/username-resignation.json b/tests/fixtures/username-resignation.json index c71d2fd0..2211eed3 100644 --- a/tests/fixtures/username-resignation.json +++ b/tests/fixtures/username-resignation.json @@ -9,7 +9,7 @@ "recipientAddress": "0x2c1DE3b4Dbb4aDebEbB5dcECAe825bE2a9fc6eb6", "value": "0", "data": "ebed6dab", - "v": 32, + "v": 28, "r": "76ade83c279901613b31e41b9cf0e55223ef728edc5d920d108d239929abf523", "s": "0a8fe944d4217ebfe6afad28f1fce2f03e4cfe7aa90ea932c1dae4216603a82c" }, diff --git a/tests/fixtures/validator-registration.json b/tests/fixtures/validator-registration.json index 58eb5d8c..8b276b3d 100644 --- a/tests/fixtures/validator-registration.json +++ b/tests/fixtures/validator-registration.json @@ -10,7 +10,7 @@ "recipientAddress": "0x535b3d7a252fa034ed71f0c53ec0c6f784cb64e1", "value": "0", "data": "602a9eee00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030954f46d6097a1d314e900e66e11e0dad0a57cd03e04ec99f0dedd1c765dcb11e6d7fa02e22cf40f9ee23d9cc1c0624bd00000000000000000000000000000000", - "v": 31, + "v": 27, "r": "609cfbaf2a8195741dcf2054e73c88e93103d4c13e7f9d5fc83f881c01a38773", "s": "79266d0b2c9bdaccbc3fba88c6dfad4538bcdcda803b8e0faa33cdd84b73abed" }, diff --git a/tests/fixtures/validator-resignation.json b/tests/fixtures/validator-resignation.json index 681fbabc..5cc76fdd 100644 --- a/tests/fixtures/validator-resignation.json +++ b/tests/fixtures/validator-resignation.json @@ -10,7 +10,7 @@ "recipientAddress": "0x535b3d7a252fa034ed71f0c53ec0c6f784cb64e1", "value": "0", "data": "b85f5da2", - "v": 31, + "v": 27, "r": "52d016bfb61cb3b57d36a7444ce26d3c557b2acb31a10b5951fc3c9e8d2e49a6", "s": "4940013218020780485b24cea9afbc6c93249286a308af33735c0c87bdeb4b5a" }, diff --git a/tests/fixtures/vote.json b/tests/fixtures/vote.json index cd837520..be55d915 100644 --- a/tests/fixtures/vote.json +++ b/tests/fixtures/vote.json @@ -10,7 +10,7 @@ "recipientAddress": "0x535b3d7a252fa034ed71f0c53ec0c6f784cb64e1", "value": "0", "data": "6dd7d8ea000000000000000000000000c3bbe9b1cee1ff85ad72b87414b0e9b7f2366763", - "v": 31, + "v": 27, "r": "1e0b168c7520f39fb99f9bf1ea7c0086a3a9e78f215da5a7e997fd8ef5657f5f", "s": "35019ef68773310144587b228dbc626e8cf3654377e92901f8d7f13119b0e09e" }, diff --git a/tests/identity/conftest.py b/tests/identity/conftest.py index cb9d28cb..faa90bb6 100644 --- a/tests/identity/conftest.py +++ b/tests/identity/conftest.py @@ -21,9 +21,9 @@ def sign_compact(): """ data = { 'data': { - 'serialized': '1f0567c4def813a66e03fa1cd499a27c6922698a67e25e0b38458d8f4bb0e581fc25fcdf9d110b82bde15bae4a49118deb83cfc3ec1656c2a29286d7836d328abe', + 'serialized': '1b0567c4def813a66e03fa1cd499a27c6922698a67e25e0b38458d8f4bb0e581fc25fcdf9d110b82bde15bae4a49118deb83cfc3ec1656c2a29286d7836d328abe', 'message': '02e31e018005825208946f0182a0cc707b055322ccf6d4cb6a5aff1aeb228405f5e10080c0', - 'v': 31, + 'v': 27, 'r': '0567c4def813a66e03fa1cd499a27c6922698a67e25e0b38458d8f4bb0e581fc', 's': '25fcdf9d110b82bde15bae4a49118deb83cfc3ec1656c2a29286d7836d328abe', }, From b9ecf9f28a9fdf218b4159489b6651ce29063932 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Thu, 13 Feb 2025 12:53:39 +0000 Subject: [PATCH 19/25] no need to copy data when getting id --- crypto/transactions/types/abstract_transaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/transactions/types/abstract_transaction.py b/crypto/transactions/types/abstract_transaction.py index c5186155..d66ed034 100644 --- a/crypto/transactions/types/abstract_transaction.py +++ b/crypto/transactions/types/abstract_transaction.py @@ -32,7 +32,7 @@ def refresh_payload_data(self): self.data['data'] = self.get_payload().lstrip('0x') def get_id(self) -> str: - return TransactionUtils.get_id(self.data.copy()) + return TransactionUtils.get_id(self.data) def get_bytes(self, skip_signature: bool = False) -> bytes: from crypto.transactions.serializer import Serializer From 6be369e1b72007991c6666fb10865db436386d15 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:07:41 +0000 Subject: [PATCH 20/25] remove debug --- crypto/identity/private_key.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crypto/identity/private_key.py b/crypto/identity/private_key.py index 1b3ca963..6ff93a8f 100644 --- a/crypto/identity/private_key.py +++ b/crypto/identity/private_key.py @@ -31,8 +31,7 @@ def sign_compact(self, message: bytes) -> bytes: Returns: bytes: signature of the signed message """ - pkey = PvtKey.from_hex(sha256(b'my super secret passphrase').hexdigest()) - der = pkey.sign_recoverable(message) + der = self.private_key.sign_recoverable(message) return bytes([der[64] + Constants.ETHEREUM_RECOVERY_ID_OFFSET.value]) + der[0:64] From 19b9f7b932c70f62f11b6cc53e1ba83fa9f11362 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Fri, 14 Feb 2025 01:54:04 +0000 Subject: [PATCH 21/25] handle multiple abis --- .../utils/{ => abi/json}/Abi.Consensus.json | 0 crypto/utils/abi/json/Abi.Usernames.json | 900 ++++++++++++++++++ crypto/utils/abi_base.py | 21 +- 3 files changed, 918 insertions(+), 3 deletions(-) rename crypto/utils/{ => abi/json}/Abi.Consensus.json (100%) create mode 100644 crypto/utils/abi/json/Abi.Usernames.json diff --git a/crypto/utils/Abi.Consensus.json b/crypto/utils/abi/json/Abi.Consensus.json similarity index 100% rename from crypto/utils/Abi.Consensus.json rename to crypto/utils/abi/json/Abi.Consensus.json diff --git a/crypto/utils/abi/json/Abi.Usernames.json b/crypto/utils/abi/json/Abi.Usernames.json new file mode 100644 index 00000000..424ccc19 --- /dev/null +++ b/crypto/utils/abi/json/Abi.Usernames.json @@ -0,0 +1,900 @@ +{ + "abi": [ + { + "type": "function", + "name": "UPGRADE_INTERFACE_VERSION", + "inputs": [], + "outputs": [ + { "name": "", "type": "string", "internalType": "string" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "addUsername", + "inputs": [ + { + "name": "user", + "type": "address", + "internalType": "address" + }, + { + "name": "username", + "type": "string", + "internalType": "string" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "getUsername", + "inputs": [ + { "name": "user", "type": "address", "internalType": "address" } + ], + "outputs": [ + { "name": "", "type": "string", "internalType": "string" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getUsernames", + "inputs": [ + { + "name": "addresses", + "type": "address[]", + "internalType": "address[]" + } + ], + "outputs": [ + { + "name": "", + "type": "tuple[]", + "internalType": "struct UsernamesV1.User[]", + "components": [ + { + "name": "addr", + "type": "address", + "internalType": "address" + }, + { + "name": "username", + "type": "string", + "internalType": "string" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "initialize", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "isUsernameRegistered", + "inputs": [ + { + "name": "username", + "type": "string", + "internalType": "string" + } + ], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "isUsernameValid", + "inputs": [ + { + "name": "username", + "type": "string", + "internalType": "string" + } + ], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { "name": "", "type": "address", "internalType": "address" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "proxiableUUID", + "inputs": [], + "outputs": [ + { "name": "", "type": "bytes32", "internalType": "bytes32" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "registerUsername", + "inputs": [ + { + "name": "username", + "type": "string", + "internalType": "string" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "resignUsername", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "upgradeToAndCall", + "inputs": [ + { + "name": "newImplementation", + "type": "address", + "internalType": "address" + }, + { "name": "data", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "version", + "inputs": [], + "outputs": [ + { "name": "", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "pure" + }, + { + "type": "event", + "name": "Initialized", + "inputs": [ + { + "name": "version", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Upgraded", + "inputs": [ + { + "name": "implementation", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "UsernameRegistered", + "inputs": [ + { + "name": "addr", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "username", + "type": "string", + "indexed": false, + "internalType": "string" + }, + { + "name": "previousUsername", + "type": "string", + "indexed": false, + "internalType": "string" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "UsernameResigned", + "inputs": [ + { + "name": "addr", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "username", + "type": "string", + "indexed": false, + "internalType": "string" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "AddressEmptyCode", + "inputs": [ + { + "name": "target", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "ERC1967InvalidImplementation", + "inputs": [ + { + "name": "implementation", + "type": "address", + "internalType": "address" + } + ] + }, + { "type": "error", "name": "ERC1967NonPayable", "inputs": [] }, + { "type": "error", "name": "FailedCall", "inputs": [] }, + { "type": "error", "name": "InvalidInitialization", "inputs": [] }, + { "type": "error", "name": "InvalidUsername", "inputs": [] }, + { "type": "error", "name": "NotInitializing", "inputs": [] }, + { + "type": "error", + "name": "OwnableInvalidOwner", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "OwnableUnauthorizedAccount", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + } + ] + }, + { "type": "error", "name": "TakenUsername", "inputs": [] }, + { + "type": "error", + "name": "UUPSUnauthorizedCallContext", + "inputs": [] + }, + { + "type": "error", + "name": "UUPSUnsupportedProxiableUUID", + "inputs": [ + { "name": "slot", "type": "bytes32", "internalType": "bytes32" } + ] + }, + { "type": "error", "name": "UsernameNotRegistered", "inputs": [] } + ], + "bytecode": { + "object": "0x60a0604052306080523480156012575f5ffd5b5060805161186b6100395f395f8181610d7c01528181610da50152610ee4015261186b5ff3fe6080604052600436106100e4575f3560e01c80638129fc1c11610087578063e5abdcef11610057578063e5abdcef14610285578063ebed6dab146102a4578063f2fde38b146102b8578063f5ab196e146102d7575f5ffd5b80638129fc1c146101cf5780638da5cb5b146101e3578063ad3cb1cc14610229578063ce43c03214610266575f5ffd5b806352ac2091116100c257806352ac20911461015057806352d1902d1461018657806354fd4d50146101a8578063715018a6146101bb575f5ffd5b8063117720be146100e857806336a941341461011c5780634f1ef2861461013d575b5f5ffd5b3480156100f3575f5ffd5b50610107610102366004611313565b610303565b60405190151581526020015b60405180910390f35b348015610127575f5ffd5b5061013b610136366004611313565b610313565b005b61013b61014b366004611368565b610349565b34801561015b575f5ffd5b5061010761016a366004611313565b80516020918201205f9081526001909152604090205460ff1690565b348015610191575f5ffd5b5061019a610364565b604051908152602001610113565b3480156101b3575f5ffd5b50600161019a565b3480156101c6575f5ffd5b5061013b61037f565b3480156101da575f5ffd5b5061013b610392565b3480156101ee575f5ffd5b507f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546040516001600160a01b039091168152602001610113565b348015610234575f5ffd5b50610259604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516101139190611413565b348015610271575f5ffd5b50610259610280366004611425565b6104a0565b348015610290575f5ffd5b5061013b61029f36600461143e565b610549565b3480156102af575f5ffd5b5061013b610592565b3480156102c3575f5ffd5b5061013b6102d2366004611425565b6106e9565b3480156102e2575f5ffd5b506102f66102f136600461147f565b610728565b60405161011391906114f0565b5f61030d826109eb565b92915050565b8061031d816109eb565b61033a57604051630a1de65160e31b815260040160405180910390fd5b610345338383610bfc565b5050565b610351610d71565b61035a82610e15565b6103458282610e1d565b5f61036d610ed9565b505f5160206118165f395f51905f5290565b610387610f22565b6103905f610f7d565b565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f811580156103d75750825b90505f8267ffffffffffffffff1660011480156103f35750303b155b905081158015610401575080155b1561041f5760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561044957845460ff60401b1916600160401b1785555b61045233610fed565b831561049957845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2906020015b60405180910390a15b5050505050565b6001600160a01b0381165f9081526020819052604090208054606091906104c690611573565b80601f01602080910402602001604051908101604052809291908181526020018280546104f290611573565b801561053d5780601f106105145761010080835404028352916020019161053d565b820191905f5260205f20905b81548152906001019060200180831161052057829003601f168201915b50505050509050919050565b610551610f22565b8051819060011180610564575060148151115b1561058257604051630a1de65160e31b815260040160405180910390fd5b61058d838383610bfc565b505050565b335f90815260208190526040812080546105ab90611573565b80601f01602080910402602001604051908101604052809291908181526020018280546105d790611573565b80156106225780601f106105f957610100808354040283529160200191610622565b820191905f5260205f20905b81548152906001019060200180831161060557829003601f168201915b505050505090505f815111156106cd57335f908152602081905260408082209051600191839161065291906115ab565b604080519182900390912082526020808301939093529081015f908120805460ff19169415159490941790935533835290829052812061069191611220565b7ff5a79d28213d53340730f0c5a952f4809e33db20cbe21a2a0b5fa7d77fa107b333826040516106c292919061161c565b60405180910390a150565b6040516341c5396760e01b815260040160405180910390fd5b50565b6106f1610f22565b6001600160a01b03811661071f57604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6106e681610f7d565b60605f8267ffffffffffffffff8111156107445761074461126a565b60405190808252806020026020018201604052801561078957816020015b604080518082019091525f8152606060208201528152602001906001900390816107625790505b5090505f805b84811015610935575f5f8787848181106107ab576107ab61163f565b90506020020160208101906107c09190611425565b6001600160a01b03166001600160a01b031681526020019081526020015f2080546107ea90611573565b15905061092d57604051806040016040528087878481811061080e5761080e61163f565b90506020020160208101906108239190611425565b6001600160a01b031681526020015f5f8989868181106108455761084561163f565b905060200201602081019061085a9190611425565b6001600160a01b03166001600160a01b031681526020019081526020015f20805461088490611573565b80601f01602080910402602001604051908101604052809291908181526020018280546108b090611573565b80156108fb5780601f106108d2576101008083540402835291602001916108fb565b820191905f5260205f20905b8154815290600101906020018083116108de57829003601f168201915b505050505081525083838061090f90611667565b9450815181106109215761092161163f565b60200260200101819052505b60010161078f565b505f8167ffffffffffffffff8111156109505761095061126a565b60405190808252806020026020018201604052801561099557816020015b604080518082019091525f81526060602082015281526020019060019003908161096e5790505b5090505f5b828110156109e1578381815181106109b4576109b461163f565b60200260200101518282815181106109ce576109ce61163f565b602090810291909101015260010161099a565b5095945050505050565b5f6001825110806109fd575060148251115b15610a0957505f919050565b815f81518110610a1b57610a1b61163f565b6020910101516001600160f81b031916605f60f81b1480610a6d57508160018351610a46919061167f565b81518110610a5657610a5661163f565b6020910101516001600160f81b031916605f60f81b145b15610a7957505f919050565b5f5b8251811015610bf357603060f81b838281518110610a9b57610a9b61163f565b01602001516001600160f81b03191610801590610adc5750603960f81b838281518110610aca57610aca61163f565b01602001516001600160f81b03191611155b158015610b3e5750606160f81b838281518110610afb57610afb61163f565b01602001516001600160f81b03191610801590610b3c5750607a60f81b838281518110610b2a57610b2a61163f565b01602001516001600160f81b03191611155b155b8015610b6f5750828181518110610b5757610b5761163f565b6020910101516001600160f81b031916605f60f81b14155b15610b7c57505f92915050565b828181518110610b8e57610b8e61163f565b6020910101516001600160f81b031916605f60f81b148015610bde575082610bb7826001611692565b81518110610bc757610bc761163f565b6020910101516001600160f81b031916605f60f81b145b15610beb57505f92915050565b600101610a7b565b50600192915050565b80516020808301919091205f818152600190925260409091205460ff1615610c375760405163506517a760e11b815260040160405180910390fd5b6001600160a01b0384165f9081526020819052604081208054610c5990611573565b80601f0160208091040260200160405190810160405280929190818152602001828054610c8590611573565b8015610cd05780601f10610ca757610100808354040283529160200191610cd0565b820191905f5260205f20905b815481529060010190602001808311610cb357829003601f168201915b505050505090505f81511115610d015780516020808301919091205f908152600190915260409020805460ff191690555b6001600160a01b0385165f908152602081905260409020610d2285826116e9565b505f82815260016020819052604091829020805460ff19169091179055517fdc393f1f31882fea068a12acfed8ed6e9f7e88a6ed213355b5afb78ad76a704590610490908790879085906117a4565b306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161480610df757507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316610deb5f5160206118165f395f51905f52546001600160a01b031690565b6001600160a01b031614155b156103905760405163703e46dd60e11b815260040160405180910390fd5b6106e6610f22565b816001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015610e77575060408051601f3d908101601f19168201909252610e74918101906117e3565b60015b610e9f57604051634c9c8ce360e01b81526001600160a01b0383166004820152602401610716565b5f5160206118165f395f51905f528114610ecf57604051632a87526960e21b815260048101829052602401610716565b61058d8383610ffe565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146103905760405163703e46dd60e11b815260040160405180910390fd5b33610f547f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b6001600160a01b0316146103905760405163118cdaa760e01b8152336004820152602401610716565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b610ff5611053565b6106e68161109c565b611007826110a4565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a280511561104b5761058d8282611107565b610345611179565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff1661039057604051631afcd79f60e31b815260040160405180910390fd5b6106f1611053565b806001600160a01b03163b5f036110d957604051634c9c8ce360e01b81526001600160a01b0382166004820152602401610716565b5f5160206118165f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b60605f5f846001600160a01b03168460405161112391906117fa565b5f60405180830381855af49150503d805f811461115b576040519150601f19603f3d011682016040523d82523d5f602084013e611160565b606091505b5091509150611170858383611198565b95945050505050565b34156103905760405163b398979f60e01b815260040160405180910390fd5b6060826111ad576111a8826111f7565b6111f0565b81511580156111c457506001600160a01b0384163b155b156111ed57604051639996b31560e01b81526001600160a01b0385166004820152602401610716565b50805b9392505050565b8051156112075780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b50805461122c90611573565b5f825580601f1061123b575050565b601f0160209004905f5260205f20908101906106e691905b80821115611266575f8155600101611253565b5090565b634e487b7160e01b5f52604160045260245ffd5b5f5f67ffffffffffffffff8411156112985761129861126a565b50604051601f19601f85018116603f0116810181811067ffffffffffffffff821117156112c7576112c761126a565b6040528381529050808284018510156112de575f5ffd5b838360208301375f60208583010152509392505050565b5f82601f830112611304575f5ffd5b6111f08383356020850161127e565b5f60208284031215611323575f5ffd5b813567ffffffffffffffff811115611339575f5ffd5b611345848285016112f5565b949350505050565b80356001600160a01b0381168114611363575f5ffd5b919050565b5f5f60408385031215611379575f5ffd5b6113828361134d565b9150602083013567ffffffffffffffff81111561139d575f5ffd5b8301601f810185136113ad575f5ffd5b6113bc8582356020840161127e565b9150509250929050565b5f5b838110156113e05781810151838201526020016113c8565b50505f910152565b5f81518084526113ff8160208601602086016113c6565b601f01601f19169290920160200192915050565b602081525f6111f060208301846113e8565b5f60208284031215611435575f5ffd5b6111f08261134d565b5f5f6040838503121561144f575f5ffd5b6114588361134d565b9150602083013567ffffffffffffffff811115611473575f5ffd5b6113bc858286016112f5565b5f5f60208385031215611490575f5ffd5b823567ffffffffffffffff8111156114a6575f5ffd5b8301601f810185136114b6575f5ffd5b803567ffffffffffffffff8111156114cc575f5ffd5b8560208260051b84010111156114e0575f5ffd5b6020919091019590945092505050565b5f602082016020835280845180835260408501915060408160051b8601019250602086015f5b8281101561156757868503603f19018452815180516001600160a01b03168652602090810151604091870182905290611551908701826113e8565b9550506020938401939190910190600101611516565b50929695505050505050565b600181811c9082168061158757607f821691505b6020821081036115a557634e487b7160e01b5f52602260045260245ffd5b50919050565b5f5f83546115b881611573565b6001821680156115cf57600181146115e457611611565b60ff1983168652811515820286019350611611565b865f5260205f205f5b83811015611609578154888201526001909101906020016115ed565b505081860193505b509195945050505050565b6001600160a01b03831681526040602082018190525f90611345908301846113e8565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b5f6001820161167857611678611653565b5060010190565b8181038181111561030d5761030d611653565b8082018082111561030d5761030d611653565b601f82111561058d57805f5260205f20601f840160051c810160208510156116ca5750805b601f840160051c820191505b81811015610499575f81556001016116d6565b815167ffffffffffffffff8111156117035761170361126a565b611717816117118454611573565b846116a5565b6020601f821160018114611749575f83156117325750848201515b5f19600385901b1c1916600184901b178455610499565b5f84815260208120601f198516915b828110156117785787850151825560209485019460019092019101611758565b508482101561179557868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b6001600160a01b03841681526060602082018190525f906117c7908301856113e8565b82810360408401526117d981856113e8565b9695505050505050565b5f602082840312156117f3575f5ffd5b5051919050565b5f825161180b8184602087016113c6565b919091019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca26469706673582212202cd847cb3edf0b7eef198ed0ae022f99a0d18fa21cb2a945077443915317943b64736f6c634300081b0033", + "sourceMap": "285:5015:37:-:0;;;1171:4:25;1128:48;;285:5015:37;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;", + "linkReferences": {} + }, + "deployedBytecode": { + "object": "0x6080604052600436106100e4575f3560e01c80638129fc1c11610087578063e5abdcef11610057578063e5abdcef14610285578063ebed6dab146102a4578063f2fde38b146102b8578063f5ab196e146102d7575f5ffd5b80638129fc1c146101cf5780638da5cb5b146101e3578063ad3cb1cc14610229578063ce43c03214610266575f5ffd5b806352ac2091116100c257806352ac20911461015057806352d1902d1461018657806354fd4d50146101a8578063715018a6146101bb575f5ffd5b8063117720be146100e857806336a941341461011c5780634f1ef2861461013d575b5f5ffd5b3480156100f3575f5ffd5b50610107610102366004611313565b610303565b60405190151581526020015b60405180910390f35b348015610127575f5ffd5b5061013b610136366004611313565b610313565b005b61013b61014b366004611368565b610349565b34801561015b575f5ffd5b5061010761016a366004611313565b80516020918201205f9081526001909152604090205460ff1690565b348015610191575f5ffd5b5061019a610364565b604051908152602001610113565b3480156101b3575f5ffd5b50600161019a565b3480156101c6575f5ffd5b5061013b61037f565b3480156101da575f5ffd5b5061013b610392565b3480156101ee575f5ffd5b507f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546040516001600160a01b039091168152602001610113565b348015610234575f5ffd5b50610259604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516101139190611413565b348015610271575f5ffd5b50610259610280366004611425565b6104a0565b348015610290575f5ffd5b5061013b61029f36600461143e565b610549565b3480156102af575f5ffd5b5061013b610592565b3480156102c3575f5ffd5b5061013b6102d2366004611425565b6106e9565b3480156102e2575f5ffd5b506102f66102f136600461147f565b610728565b60405161011391906114f0565b5f61030d826109eb565b92915050565b8061031d816109eb565b61033a57604051630a1de65160e31b815260040160405180910390fd5b610345338383610bfc565b5050565b610351610d71565b61035a82610e15565b6103458282610e1d565b5f61036d610ed9565b505f5160206118165f395f51905f5290565b610387610f22565b6103905f610f7d565b565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f811580156103d75750825b90505f8267ffffffffffffffff1660011480156103f35750303b155b905081158015610401575080155b1561041f5760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561044957845460ff60401b1916600160401b1785555b61045233610fed565b831561049957845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2906020015b60405180910390a15b5050505050565b6001600160a01b0381165f9081526020819052604090208054606091906104c690611573565b80601f01602080910402602001604051908101604052809291908181526020018280546104f290611573565b801561053d5780601f106105145761010080835404028352916020019161053d565b820191905f5260205f20905b81548152906001019060200180831161052057829003601f168201915b50505050509050919050565b610551610f22565b8051819060011180610564575060148151115b1561058257604051630a1de65160e31b815260040160405180910390fd5b61058d838383610bfc565b505050565b335f90815260208190526040812080546105ab90611573565b80601f01602080910402602001604051908101604052809291908181526020018280546105d790611573565b80156106225780601f106105f957610100808354040283529160200191610622565b820191905f5260205f20905b81548152906001019060200180831161060557829003601f168201915b505050505090505f815111156106cd57335f908152602081905260408082209051600191839161065291906115ab565b604080519182900390912082526020808301939093529081015f908120805460ff19169415159490941790935533835290829052812061069191611220565b7ff5a79d28213d53340730f0c5a952f4809e33db20cbe21a2a0b5fa7d77fa107b333826040516106c292919061161c565b60405180910390a150565b6040516341c5396760e01b815260040160405180910390fd5b50565b6106f1610f22565b6001600160a01b03811661071f57604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6106e681610f7d565b60605f8267ffffffffffffffff8111156107445761074461126a565b60405190808252806020026020018201604052801561078957816020015b604080518082019091525f8152606060208201528152602001906001900390816107625790505b5090505f805b84811015610935575f5f8787848181106107ab576107ab61163f565b90506020020160208101906107c09190611425565b6001600160a01b03166001600160a01b031681526020019081526020015f2080546107ea90611573565b15905061092d57604051806040016040528087878481811061080e5761080e61163f565b90506020020160208101906108239190611425565b6001600160a01b031681526020015f5f8989868181106108455761084561163f565b905060200201602081019061085a9190611425565b6001600160a01b03166001600160a01b031681526020019081526020015f20805461088490611573565b80601f01602080910402602001604051908101604052809291908181526020018280546108b090611573565b80156108fb5780601f106108d2576101008083540402835291602001916108fb565b820191905f5260205f20905b8154815290600101906020018083116108de57829003601f168201915b505050505081525083838061090f90611667565b9450815181106109215761092161163f565b60200260200101819052505b60010161078f565b505f8167ffffffffffffffff8111156109505761095061126a565b60405190808252806020026020018201604052801561099557816020015b604080518082019091525f81526060602082015281526020019060019003908161096e5790505b5090505f5b828110156109e1578381815181106109b4576109b461163f565b60200260200101518282815181106109ce576109ce61163f565b602090810291909101015260010161099a565b5095945050505050565b5f6001825110806109fd575060148251115b15610a0957505f919050565b815f81518110610a1b57610a1b61163f565b6020910101516001600160f81b031916605f60f81b1480610a6d57508160018351610a46919061167f565b81518110610a5657610a5661163f565b6020910101516001600160f81b031916605f60f81b145b15610a7957505f919050565b5f5b8251811015610bf357603060f81b838281518110610a9b57610a9b61163f565b01602001516001600160f81b03191610801590610adc5750603960f81b838281518110610aca57610aca61163f565b01602001516001600160f81b03191611155b158015610b3e5750606160f81b838281518110610afb57610afb61163f565b01602001516001600160f81b03191610801590610b3c5750607a60f81b838281518110610b2a57610b2a61163f565b01602001516001600160f81b03191611155b155b8015610b6f5750828181518110610b5757610b5761163f565b6020910101516001600160f81b031916605f60f81b14155b15610b7c57505f92915050565b828181518110610b8e57610b8e61163f565b6020910101516001600160f81b031916605f60f81b148015610bde575082610bb7826001611692565b81518110610bc757610bc761163f565b6020910101516001600160f81b031916605f60f81b145b15610beb57505f92915050565b600101610a7b565b50600192915050565b80516020808301919091205f818152600190925260409091205460ff1615610c375760405163506517a760e11b815260040160405180910390fd5b6001600160a01b0384165f9081526020819052604081208054610c5990611573565b80601f0160208091040260200160405190810160405280929190818152602001828054610c8590611573565b8015610cd05780601f10610ca757610100808354040283529160200191610cd0565b820191905f5260205f20905b815481529060010190602001808311610cb357829003601f168201915b505050505090505f81511115610d015780516020808301919091205f908152600190915260409020805460ff191690555b6001600160a01b0385165f908152602081905260409020610d2285826116e9565b505f82815260016020819052604091829020805460ff19169091179055517fdc393f1f31882fea068a12acfed8ed6e9f7e88a6ed213355b5afb78ad76a704590610490908790879085906117a4565b306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161480610df757507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316610deb5f5160206118165f395f51905f52546001600160a01b031690565b6001600160a01b031614155b156103905760405163703e46dd60e11b815260040160405180910390fd5b6106e6610f22565b816001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015610e77575060408051601f3d908101601f19168201909252610e74918101906117e3565b60015b610e9f57604051634c9c8ce360e01b81526001600160a01b0383166004820152602401610716565b5f5160206118165f395f51905f528114610ecf57604051632a87526960e21b815260048101829052602401610716565b61058d8383610ffe565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146103905760405163703e46dd60e11b815260040160405180910390fd5b33610f547f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b6001600160a01b0316146103905760405163118cdaa760e01b8152336004820152602401610716565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b610ff5611053565b6106e68161109c565b611007826110a4565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a280511561104b5761058d8282611107565b610345611179565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff1661039057604051631afcd79f60e31b815260040160405180910390fd5b6106f1611053565b806001600160a01b03163b5f036110d957604051634c9c8ce360e01b81526001600160a01b0382166004820152602401610716565b5f5160206118165f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b60605f5f846001600160a01b03168460405161112391906117fa565b5f60405180830381855af49150503d805f811461115b576040519150601f19603f3d011682016040523d82523d5f602084013e611160565b606091505b5091509150611170858383611198565b95945050505050565b34156103905760405163b398979f60e01b815260040160405180910390fd5b6060826111ad576111a8826111f7565b6111f0565b81511580156111c457506001600160a01b0384163b155b156111ed57604051639996b31560e01b81526001600160a01b0385166004820152602401610716565b50805b9392505050565b8051156112075780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b50805461122c90611573565b5f825580601f1061123b575050565b601f0160209004905f5260205f20908101906106e691905b80821115611266575f8155600101611253565b5090565b634e487b7160e01b5f52604160045260245ffd5b5f5f67ffffffffffffffff8411156112985761129861126a565b50604051601f19601f85018116603f0116810181811067ffffffffffffffff821117156112c7576112c761126a565b6040528381529050808284018510156112de575f5ffd5b838360208301375f60208583010152509392505050565b5f82601f830112611304575f5ffd5b6111f08383356020850161127e565b5f60208284031215611323575f5ffd5b813567ffffffffffffffff811115611339575f5ffd5b611345848285016112f5565b949350505050565b80356001600160a01b0381168114611363575f5ffd5b919050565b5f5f60408385031215611379575f5ffd5b6113828361134d565b9150602083013567ffffffffffffffff81111561139d575f5ffd5b8301601f810185136113ad575f5ffd5b6113bc8582356020840161127e565b9150509250929050565b5f5b838110156113e05781810151838201526020016113c8565b50505f910152565b5f81518084526113ff8160208601602086016113c6565b601f01601f19169290920160200192915050565b602081525f6111f060208301846113e8565b5f60208284031215611435575f5ffd5b6111f08261134d565b5f5f6040838503121561144f575f5ffd5b6114588361134d565b9150602083013567ffffffffffffffff811115611473575f5ffd5b6113bc858286016112f5565b5f5f60208385031215611490575f5ffd5b823567ffffffffffffffff8111156114a6575f5ffd5b8301601f810185136114b6575f5ffd5b803567ffffffffffffffff8111156114cc575f5ffd5b8560208260051b84010111156114e0575f5ffd5b6020919091019590945092505050565b5f602082016020835280845180835260408501915060408160051b8601019250602086015f5b8281101561156757868503603f19018452815180516001600160a01b03168652602090810151604091870182905290611551908701826113e8565b9550506020938401939190910190600101611516565b50929695505050505050565b600181811c9082168061158757607f821691505b6020821081036115a557634e487b7160e01b5f52602260045260245ffd5b50919050565b5f5f83546115b881611573565b6001821680156115cf57600181146115e457611611565b60ff1983168652811515820286019350611611565b865f5260205f205f5b83811015611609578154888201526001909101906020016115ed565b505081860193505b509195945050505050565b6001600160a01b03831681526040602082018190525f90611345908301846113e8565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b5f6001820161167857611678611653565b5060010190565b8181038181111561030d5761030d611653565b8082018082111561030d5761030d611653565b601f82111561058d57805f5260205f20601f840160051c810160208510156116ca5750805b601f840160051c820191505b81811015610499575f81556001016116d6565b815167ffffffffffffffff8111156117035761170361126a565b611717816117118454611573565b846116a5565b6020601f821160018114611749575f83156117325750848201515b5f19600385901b1c1916600184901b178455610499565b5f84815260208120601f198516915b828110156117785787850151825560209485019460019092019101611758565b508482101561179557868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b6001600160a01b03841681526060602082018190525f906117c7908301856113e8565b82810360408401526117d981856113e8565b9695505050505050565b5f602082840312156117f3575f5ffd5b5051919050565b5f825161180b8184602087016113c6565b919091019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca26469706673582212202cd847cb3edf0b7eef198ed0ae022f99a0d18fa21cb2a945077443915317943b64736f6c634300081b0033", + "sourceMap": "285:5015:37:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2491:134;;;;;;;;;;-1:-1:-1;2491:134:37;;;;;:::i;:::-;;:::i;:::-;;;1586:14:40;;1579:22;1561:41;;1549:2;1534:18;2491:134:37;;;;;;;;1375:273;;;;;;;;;;-1:-1:-1;1375:273:37;;;;;:::i;:::-;;:::i;:::-;;4161:214:25;;;;;;:::i;:::-;;:::i;2335:150:37:-;;;;;;;;;;-1:-1:-1;2335:150:37;;;;;:::i;:::-;2451:26;;;;;;;2412:4;2435:43;;;:15;:43;;;;;;;;;;2335:150;3708:134:25;;;;;;;;;;;;;:::i;:::-;;;2466:25:40;;;2454:2;2439:18;3708:134:25;2320:177:40;2134:76:37;;;;;;;;;;-1:-1:-1;2202:1:37;2134:76;;3155:101:23;;;;;;;;;;;;;:::i;788:84:37:-;;;;;;;;;;;;;:::i;2441:144:23:-;;;;;;;;;;-1:-1:-1;1313:22:23;2570:8;2441:144;;-1:-1:-1;;;;;2570:8:23;;;2830:51:40;;2818:2;2803:18;2441:144:23;2684:203:40;1819:58:25;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;1819:58:25;;;;;;;;;;;;:::i;2216:113:37:-;;;;;;;;;;-1:-1:-1;2216:113:37;;;;;:::i;:::-;;:::i;1011:358::-;;;;;;;;;;-1:-1:-1;1011:358:37;;;;;:::i;:::-;;:::i;1654:434::-;;;;;;;;;;;;;:::i;3405:215:23:-;;;;;;;;;;-1:-1:-1;3405:215:23;;;;;:::i;:::-;;:::i;2631:621:37:-;;;;;;;;;;-1:-1:-1;2631:621:37;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;2491:134::-;2563:4;2586:32;2608:8;2586:15;:32::i;:::-;2579:39;2491:134;-1:-1:-1;;2491:134:37:o;1375:273::-;1496:8;1520:18;1496:8;1520:15;:18::i;:::-;1515:74;;1561:17;;-1:-1:-1;;;1561:17:37;;;;;;;;;;;1515:74;1599:42;1617:10;1629:8;1639:1;1599:17;:42::i;:::-;1434:214;1375:273;:::o;4161:214:25:-;2655:13;:11;:13::i;:::-;4276:36:::1;4294:17;4276;:36::i;:::-;4322:46;4344:17;4363:4;4322:21;:46::i;3708:134::-:0;3777:7;2926:20;:18;:20::i;:::-;-1:-1:-1;;;;;;;;;;;;3708:134:25;:::o;3155:101:23:-;2334:13;:11;:13::i;:::-;3219:30:::1;3246:1;3219:18;:30::i;:::-;3155:101::o:0;788:84:37:-;8870:21:24;4302:15;;-1:-1:-1;;;4302:15:24;;;;4301:16;;4348:14;;4158:30;4726:16;;:34;;;;;4746:14;4726:34;4706:54;;4770:17;4790:11;:16;;4805:1;4790:16;:50;;;;-1:-1:-1;4818:4:24;4810:25;:30;4790:50;4770:70;;4856:12;4855:13;:30;;;;;4873:12;4872:13;4855:30;4851:91;;;4908:23;;-1:-1:-1;;;4908:23:24;;;;;;;;;;;4851:91;4951:18;;-1:-1:-1;;4951:18:24;4968:1;4951:18;;;4979:67;;;;5013:22;;-1:-1:-1;;;;5013:22:24;-1:-1:-1;;;5013:22:24;;;4979:67;839:26:37::1;854:10;839:14;:26::i;:::-;5070:14:24::0;5066:101;;;5100:23;;-1:-1:-1;;;;5100:23:24;;;5142:14;;-1:-1:-1;6020:50:40;;5142:14:24;;6008:2:40;5993:18;5142:14:24;;;;;;;;5066:101;4092:1081;;;;;788:84:37:o;2216:113::-;-1:-1:-1;;;;;2306:16:37;;:10;:16;;;;;;;;;;2299:23;;2274:13;;2306:16;2299:23;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2216:113;;;:::o;1011:358::-;2334:13:23;:11;:13::i;:::-;1236:8:37;;1122;;1247:1:::1;-1:-1:-1::0;1236:12:37;:29:::1;;;1263:2;1252:1;:8;:13;1236:29;1232:84;;;1288:17;;-1:-1:-1::0;;;1288:17:37::1;;;;;;;;;;;1232:84;1326:36;1344:4;1350:8;1360:1;1326:17;:36::i;:::-;1089:280;1011:358:::0;;:::o;1654:434::-;1735:10;1699:22;1724;;;;;;;;;;1699:47;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1827:1;1808:8;1802:22;:26;1798:284;;;1887:10;1904:5;1876:22;;;;;;;;;;;1860:40;;1844:15;;1904:5;;1860:40;;1876:22;1860:40;:::i;:::-;;;;;;;;;;;1844:57;;;;;;;;;;;;;-1:-1:-1;1844:57:37;;;:65;;-1:-1:-1;;1844:65:37;;;;;;;;;;;1941:10;1930:22;;;;;;;;1923:29;;;:::i;:::-;1972:38;1989:10;2001:8;1972:38;;;;;;;:::i;:::-;;;;;;;;1689:399;1654:434::o;1798:284::-;2048:23;;-1:-1:-1;;;2048:23:37;;;;;;;;;;;1798:284;1689:399;1654:434::o;3405:215:23:-;2334:13;:11;:13::i;:::-;-1:-1:-1;;;;;3489:22:23;::::1;3485:91;;3534:31;::::0;-1:-1:-1;;;3534:31:23;;3562:1:::1;3534:31;::::0;::::1;2830:51:40::0;2803:18;;3534:31:23::1;;;;;;;;3485:91;3585:28;3604:8;3585:18;:28::i;2631:621:37:-:0;2706:13;2731:19;2764:9;2753:28;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;;;;;;;;;;;;;2753:28:37;;;;;;;;;;;;;;;-1:-1:-1;2731:50:37;-1:-1:-1;2791:13:37;;2818:214;2838:20;;;2818:214;;;2889:10;:24;2900:9;;2910:1;2900:12;;;;;;;:::i;:::-;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;2889:24:37;-1:-1:-1;;;;;2889:24:37;;;;;;;;;;;;2883:38;;;;;:::i;:::-;:43;;-1:-1:-1;2879:143:37;;2963:44;;;;;;;;2968:9;;2978:1;2968:12;;;;;;;:::i;:::-;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;2963:44:37;;;;;2982:10;:24;2993:9;;3003:1;2993:12;;;;;;;:::i;:::-;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;2982:24:37;-1:-1:-1;;;;;2982:24:37;;;;;;;;;;;;2963:44;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2946:5;2952:7;;;;;:::i;:::-;;;2946:14;;;;;;;;:::i;:::-;;;;;;:61;;;;2879:143;2860:3;;2818:214;;;;3091:20;3125:5;3114:17;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;;;;;;;;;;;;;3114:17:37;;;;;;;;;;;;;;;-1:-1:-1;3091:40:37;-1:-1:-1;3146:9:37;3141:81;3165:5;3161:1;:9;3141:81;;;3203:5;3209:1;3203:8;;;;;;;;:::i;:::-;;;;;;;3191:6;3198:1;3191:9;;;;;;;;:::i;:::-;;;;;;;;;;:20;3172:3;;3141:81;;;-1:-1:-1;3239:6:37;2631:621;-1:-1:-1;;;;;2631:621:37:o;3541:1108::-;3612:4;3734:1;3716:8;:15;:19;:43;;;;3757:2;3739:8;:15;:20;3716:43;3712:86;;;-1:-1:-1;3782:5:37;;3541:1108;-1:-1:-1;3541:1108:37:o;3712:86::-;3859:8;3868:1;3859:11;;;;;;;;:::i;:::-;;;;;;-1:-1:-1;;;;;;3859:11:37;-1:-1:-1;;;3859:19:37;;:60;;;3882:8;3909:1;3891:8;:15;:19;;;;:::i;:::-;3882:29;;;;;;;;:::i;:::-;;;;;;-1:-1:-1;;;;;;3882:29:37;-1:-1:-1;;;3882:37:37;3859:60;3855:103;;;-1:-1:-1;3942:5:37;;3541:1108;-1:-1:-1;3541:1108:37:o;3855:103::-;3973:9;3968:653;3992:8;:15;3988:1;:19;3968:653;;;4141:4;4126:19;;:8;4135:1;4126:11;;;;;;;;:::i;:::-;;;;;-1:-1:-1;;;;;;4126:11:37;:19;;;;:42;;;4164:4;4149:19;;:8;4158:1;4149:11;;;;;;;;:::i;:::-;;;;;-1:-1:-1;;;;;;4149:11:37;:19;;4126:42;4124:45;:121;;;;;4217:4;4202:19;;:8;4211:1;4202:11;;;;;;;;:::i;:::-;;;;;-1:-1:-1;;;;;;4202:11:37;:19;;;;:42;;;4240:4;4225:19;;:8;4234:1;4225:11;;;;;;;;:::i;:::-;;;;;-1:-1:-1;;;;;;4225:11:37;:19;;4202:42;4200:45;4124:121;:174;;;;;4278:8;4287:1;4278:11;;;;;;;;:::i;:::-;;;;;;-1:-1:-1;;;;;;4278:11:37;-1:-1:-1;;;4278:19:37;4276:22;4124:174;4103:260;;;-1:-1:-1;4343:5:37;;3541:1108;-1:-1:-1;;3541:1108:37:o;4103:260::-;4518:8;4527:1;4518:11;;;;;;;;:::i;:::-;;;;;;-1:-1:-1;;;;;;4518:11:37;-1:-1:-1;;;4518:19:37;:46;;;;-1:-1:-1;4541:8:37;4550:5;:1;4554;4550:5;:::i;:::-;4541:15;;;;;;;;:::i;:::-;;;;;;-1:-1:-1;;;;;;4541:15:37;-1:-1:-1;;;4541:23:37;4518:46;4514:97;;;-1:-1:-1;4591:5:37;;3541:1108;-1:-1:-1;;3541:1108:37:o;4514:97::-;4009:3;;3968:653;;;-1:-1:-1;4638:4:37;;3541:1108;-1:-1:-1;;3541:1108:37:o;4655:643::-;4778:12;;;;;;;;;;4755:20;4804:29;;;:15;:29;;;;;;;;;;4800:82;;;4856:15;;-1:-1:-1;;;4856:15:37;;;;;;;;;;;4800:82;-1:-1:-1;;;;;4925:16:37;;4892:30;4925:16;;;;;;;;;;4892:49;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5030:1;5003:16;4997:30;:34;4993:147;;;5063:34;;;;;;;;;;5101:5;5047:51;;;:15;:51;;;;;;:59;;-1:-1:-1;;5047:59:37;;;4993:147;-1:-1:-1;;;;;5150:16:37;;:10;:16;;;;;;;;;;:27;5169:8;5150:16;:27;:::i;:::-;-1:-1:-1;5187:29:37;;;;5219:4;5187:29;;;;;;;;;:36;;-1:-1:-1;;5187:36:37;;;;;;5239:52;;;;;5258:4;;5264:8;;5274:16;;5239:52;:::i;4603:312:25:-;4683:4;-1:-1:-1;;;;;4692:6:25;4675:23;;;:120;;;4789:6;-1:-1:-1;;;;;4753:42:25;:32;-1:-1:-1;;;;;;;;;;;1519:53:30;-1:-1:-1;;;;;1519:53:30;;1441:138;4753:32:25;-1:-1:-1;;;;;4753:42:25;;;4675:120;4658:251;;;4869:29;;-1:-1:-1;;;4869:29:25;;;;;;;;;;;895:84:37;2334:13:23;:11;:13::i;6057:538:25:-;6174:17;-1:-1:-1;;;;;6156:50:25;;:52;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;6156:52:25;;;;;;;;-1:-1:-1;;6156:52:25;;;;;;;;;;;;:::i;:::-;;;6152:437;;6518:60;;-1:-1:-1;;;6518:60:25;;-1:-1:-1;;;;;2848:32:40;;6518:60:25;;;2830:51:40;2803:18;;6518:60:25;2684:203:40;6152:437:25;-1:-1:-1;;;;;;;;;;;6250:40:25;;6246:120;;6317:34;;-1:-1:-1;;;6317:34:25;;;;;2466:25:40;;;2439:18;;6317:34:25;2320:177:40;6246:120:25;6379:54;6409:17;6428:4;6379:29;:54::i;5032:213::-;5106:4;-1:-1:-1;;;;;5115:6:25;5098:23;;5094:145;;5199:29;;-1:-1:-1;;;5199:29:25;;;;;;;;;;;2658:162:23;966:10:26;2717:7:23;1313:22;2570:8;-1:-1:-1;;;;;2570:8:23;;2441:144;2717:7;-1:-1:-1;;;;;2717:23:23;;2713:101;;2763:40;;-1:-1:-1;;;2763:40:23;;966:10:26;2763:40:23;;;2830:51:40;2803:18;;2763:40:23;2684:203:40;3774:248:23;1313:22;3923:8;;-1:-1:-1;;;;;;3941:19:23;;-1:-1:-1;;;;;3941:19:23;;;;;;;;3975:40;;3923:8;;;;;3975:40;;3847:24;;3975:40;3837:185;;3774:248;:::o;1847:127::-;6931:20:24;:18;:20::i;:::-;1929:38:23::1;1954:12;1929:24;:38::i;2264:344:30:-:0;2355:37;2374:17;2355:18;:37::i;:::-;2407:36;;-1:-1:-1;;;;;2407:36:30;;;;;;;;2458:11;;:15;2454:148;;2489:53;2518:17;2537:4;2489:28;:53::i;2454:148::-;2573:18;:16;:18::i;7084:141:24:-;8870:21;8560:40;-1:-1:-1;;;8560:40:24;;;;7146:73;;7191:17;;-1:-1:-1;;;7191:17:24;;;;;;;;;;;1980:235:23;6931:20:24;:18;:20::i;1671:281:30:-;1748:17;-1:-1:-1;;;;;1748:29:30;;1781:1;1748:34;1744:119;;1805:47;;-1:-1:-1;;;1805:47:30;;-1:-1:-1;;;;;2848:32:40;;1805:47:30;;;2830:51:40;2803:18;;1805:47:30;2684:203:40;1744:119:30;-1:-1:-1;;;;;;;;;;;1872:73:30;;-1:-1:-1;;;;;;1872:73:30;-1:-1:-1;;;;;1872:73:30;;;;;;;;;;1671:281::o;3900:253:34:-;3983:12;4008;4022:23;4049:6;-1:-1:-1;;;;;4049:19:34;4069:4;4049:25;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4007:67;;;;4091:55;4118:6;4126:7;4135:10;4091:26;:55::i;:::-;4084:62;3900:253;-1:-1:-1;;;;;3900:253:34:o;6113:122:30:-;6163:9;:13;6159:70;;6199:19;;-1:-1:-1;;;6199:19:30;;;;;;;;;;;4421:582:34;4565:12;4594:7;4589:408;;4617:19;4625:10;4617:7;:19::i;:::-;4589:408;;;4841:17;;:22;:49;;;;-1:-1:-1;;;;;;4867:18:34;;;:23;4841:49;4837:119;;;4917:24;;-1:-1:-1;;;4917:24:34;;-1:-1:-1;;;;;2848:32:40;;4917:24:34;;;2830:51:40;2803:18;;4917:24:34;2684:203:40;4837:119:34;-1:-1:-1;4976:10:34;4589:408;4421:582;;;;;:::o;5543:487::-;5674:17;;:21;5670:354;;5871:10;5865:17;5927:15;5914:10;5910:2;5906:19;5899:44;5670:354;5994:19;;-1:-1:-1;;;5994:19:34;;;;;;;;;;;-1:-1:-1;;;;;;;:::i;:::-;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;14:127:40:-;75:10;70:3;66:20;63:1;56:31;106:4;103:1;96:15;130:4;127:1;120:15;146:716;211:5;243:1;267:18;259:6;256:30;253:56;;;289:18;;:::i;:::-;-1:-1:-1;444:2:40;438:9;-1:-1:-1;;357:2:40;336:15;;332:29;;502:2;490:15;486:29;474:42;;567:22;;;546:18;531:34;;528:62;525:88;;;593:18;;:::i;:::-;629:2;622:22;677;;;662:6;-1:-1:-1;662:6:40;714:16;;;711:25;-1:-1:-1;708:45:40;;;749:1;746;739:12;708:45;799:6;794:3;787:4;779:6;775:17;762:44;854:1;847:4;838:6;830;826:19;822:30;815:41;;146:716;;;;;:::o;867:222::-;910:5;963:3;956:4;948:6;944:17;940:27;930:55;;981:1;978;971:12;930:55;1003:80;1079:3;1070:6;1057:20;1050:4;1042:6;1038:17;1003:80;:::i;1094:322::-;1163:6;1216:2;1204:9;1195:7;1191:23;1187:32;1184:52;;;1232:1;1229;1222:12;1184:52;1272:9;1259:23;1305:18;1297:6;1294:30;1291:50;;;1337:1;1334;1327:12;1291:50;1360;1402:7;1393:6;1382:9;1378:22;1360:50;:::i;:::-;1350:60;1094:322;-1:-1:-1;;;;1094:322:40:o;1613:173::-;1681:20;;-1:-1:-1;;;;;1730:31:40;;1720:42;;1710:70;;1776:1;1773;1766:12;1710:70;1613:173;;;:::o;1791:524::-;1868:6;1876;1929:2;1917:9;1908:7;1904:23;1900:32;1897:52;;;1945:1;1942;1935:12;1897:52;1968:29;1987:9;1968:29;:::i;:::-;1958:39;;2048:2;2037:9;2033:18;2020:32;2075:18;2067:6;2064:30;2061:50;;;2107:1;2104;2097:12;2061:50;2130:22;;2183:4;2175:13;;2171:27;-1:-1:-1;2161:55:40;;2212:1;2209;2202:12;2161:55;2235:74;2301:7;2296:2;2283:16;2278:2;2274;2270:11;2235:74;:::i;:::-;2225:84;;;1791:524;;;;;:::o;2892:250::-;2977:1;2987:113;3001:6;2998:1;2995:13;2987:113;;;3077:11;;;3071:18;3058:11;;;3051:39;3023:2;3016:10;2987:113;;;-1:-1:-1;;3134:1:40;3116:16;;3109:27;2892:250::o;3147:271::-;3189:3;3227:5;3221:12;3254:6;3249:3;3242:19;3270:76;3339:6;3332:4;3327:3;3323:14;3316:4;3309:5;3305:16;3270:76;:::i;:::-;3400:2;3379:15;-1:-1:-1;;3375:29:40;3366:39;;;;3407:4;3362:50;;3147:271;-1:-1:-1;;3147:271:40:o;3423:220::-;3572:2;3561:9;3554:21;3535:4;3592:45;3633:2;3622:9;3618:18;3610:6;3592:45;:::i;3648:186::-;3707:6;3760:2;3748:9;3739:7;3735:23;3731:32;3728:52;;;3776:1;3773;3766:12;3728:52;3799:29;3818:9;3799:29;:::i;3839:396::-;3917:6;3925;3978:2;3966:9;3957:7;3953:23;3949:32;3946:52;;;3994:1;3991;3984:12;3946:52;4017:29;4036:9;4017:29;:::i;:::-;4007:39;;4097:2;4086:9;4082:18;4069:32;4124:18;4116:6;4113:30;4110:50;;;4156:1;4153;4146:12;4110:50;4179;4221:7;4212:6;4201:9;4197:22;4179:50;:::i;4240:610::-;4326:6;4334;4387:2;4375:9;4366:7;4362:23;4358:32;4355:52;;;4403:1;4400;4393:12;4355:52;4443:9;4430:23;4476:18;4468:6;4465:30;4462:50;;;4508:1;4505;4498:12;4462:50;4531:22;;4584:4;4576:13;;4572:27;-1:-1:-1;4562:55:40;;4613:1;4610;4603:12;4562:55;4653:2;4640:16;4679:18;4671:6;4668:30;4665:50;;;4711:1;4708;4701:12;4665:50;4764:7;4759:2;4749:6;4746:1;4742:14;4738:2;4734:23;4730:32;4727:45;4724:65;;;4785:1;4782;4775:12;4724:65;4816:2;4808:11;;;;;4838:6;;-1:-1:-1;4240:610:40;-1:-1:-1;;;4240:610:40:o;4855:1007::-;5043:4;5091:2;5080:9;5076:18;5121:2;5110:9;5103:21;5144:6;5179;5173:13;5210:6;5202;5195:22;5248:2;5237:9;5233:18;5226:25;;5310:2;5300:6;5297:1;5293:14;5282:9;5278:30;5274:39;5260:53;;5348:2;5340:6;5336:15;5369:1;5379:454;5393:6;5390:1;5387:13;5379:454;;;5458:22;;;-1:-1:-1;;5454:36:40;5442:49;;5514:13;;5559:9;;-1:-1:-1;;;;;5555:35:40;5540:51;;5638:2;5630:11;;;5624:18;5679:2;5662:15;;;5655:27;;;5624:18;5705:48;;5737:15;;5624:18;5705:48;:::i;:::-;5695:58;-1:-1:-1;;5788:2:40;5811:12;;;;5776:15;;;;;5415:1;5408:9;5379:454;;;-1:-1:-1;5850:6:40;;4855:1007;-1:-1:-1;;;;;;4855:1007:40:o;6081:380::-;6160:1;6156:12;;;;6203;;;6224:61;;6278:4;6270:6;6266:17;6256:27;;6224:61;6331:2;6323:6;6320:14;6300:18;6297:38;6294:161;;6377:10;6372:3;6368:20;6365:1;6358:31;6412:4;6409:1;6402:15;6440:4;6437:1;6430:15;6294:161;;6081:380;;;:::o;6595:799::-;6725:3;6754:1;6787:6;6781:13;6817:36;6843:9;6817:36;:::i;:::-;6884:1;6869:17;;6895:133;;;;7042:1;7037:332;;;;6862:507;;6895:133;-1:-1:-1;;6928:24:40;;6916:37;;7001:14;;6994:22;6982:35;;6973:45;;;-1:-1:-1;6895:133:40;;7037:332;7068:6;7065:1;7058:17;7116:4;7113:1;7103:18;7143:1;7157:166;7171:6;7168:1;7165:13;7157:166;;;7251:14;;7238:11;;;7231:35;7307:1;7294:15;;;;7193:4;7186:12;7157:166;;;7161:3;;7352:6;7347:3;7343:16;7336:23;;6862:507;-1:-1:-1;7385:3:40;;6595:799;-1:-1:-1;;;;;6595:799:40:o;7399:317::-;-1:-1:-1;;;;;7576:32:40;;7558:51;;7645:2;7640;7625:18;;7618:30;;;-1:-1:-1;;7665:45:40;;7691:18;;7683:6;7665:45;:::i;7721:127::-;7782:10;7777:3;7773:20;7770:1;7763:31;7813:4;7810:1;7803:15;7837:4;7834:1;7827:15;7853:127;7914:10;7909:3;7905:20;7902:1;7895:31;7945:4;7942:1;7935:15;7969:4;7966:1;7959:15;7985:135;8024:3;8045:17;;;8042:43;;8065:18;;:::i;:::-;-1:-1:-1;8112:1:40;8101:13;;7985:135::o;8125:128::-;8192:9;;;8213:11;;;8210:37;;;8227:18;;:::i;8258:125::-;8323:9;;;8344:10;;;8341:36;;;8357:18;;:::i;8388:518::-;8490:2;8485:3;8482:11;8479:421;;;8526:5;8523:1;8516:16;8570:4;8567:1;8557:18;8640:2;8628:10;8624:19;8621:1;8617:27;8611:4;8607:38;8676:4;8664:10;8661:20;8658:47;;;-1:-1:-1;8699:4:40;8658:47;8754:2;8749:3;8745:12;8742:1;8738:20;8732:4;8728:31;8718:41;;8809:81;8827:2;8820:5;8817:13;8809:81;;;8886:1;8872:16;;8853:1;8842:13;8809:81;;9082:1302;9208:3;9202:10;9235:18;9227:6;9224:30;9221:56;;;9257:18;;:::i;:::-;9286:97;9376:6;9336:38;9368:4;9362:11;9336:38;:::i;:::-;9330:4;9286:97;:::i;:::-;9432:4;9463:2;9452:14;;9480:1;9475:652;;;;10171:1;10188:6;10185:89;;;-1:-1:-1;10240:19:40;;;10234:26;10185:89;-1:-1:-1;;9039:1:40;9035:11;;;9031:24;9027:29;9017:40;9063:1;9059:11;;;9014:57;10287:81;;9445:933;;9475:652;6542:1;6535:14;;;6579:4;6566:18;;-1:-1:-1;;9511:20:40;;;9632:222;9646:7;9643:1;9640:14;9632:222;;;9728:19;;;9722:26;9707:42;;9835:4;9820:20;;;;9788:1;9776:14;;;;9662:12;9632:222;;;9636:3;9882:6;9873:7;9870:19;9867:201;;;9943:19;;;9937:26;-1:-1:-1;;10026:1:40;10022:14;;;10038:3;10018:24;10014:37;10010:42;9995:58;9980:74;;9867:201;-1:-1:-1;;;;10114:1:40;10098:14;;;10094:22;10081:36;;-1:-1:-1;9082:1302:40:o;10389:480::-;-1:-1:-1;;;;;10614:32:40;;10596:51;;10683:2;10678;10663:18;;10656:30;;;-1:-1:-1;;10709:45:40;;10735:18;;10727:6;10709:45;:::i;:::-;10802:9;10794:6;10790:22;10785:2;10774:9;10770:18;10763:50;10830:33;10856:6;10848;10830:33;:::i;:::-;10822:41;10389:480;-1:-1:-1;;;;;;10389:480:40:o;10874:184::-;10944:6;10997:2;10985:9;10976:7;10972:23;10968:32;10965:52;;;11013:1;11010;11003:12;10965:52;-1:-1:-1;11036:16:40;;10874:184;-1:-1:-1;10874:184:40:o;11063:287::-;11192:3;11230:6;11224:13;11246:66;11305:6;11300:3;11293:4;11285:6;11281:17;11246:66;:::i;:::-;11328:16;;;;;11063:287;-1:-1:-1;;11063:287:40:o", + "linkReferences": {}, + "immutableReferences": { + "40567": [ + { "start": 3452, "length": 32 }, + { "start": 3493, "length": 32 }, + { "start": 3812, "length": 32 } + ] + } + }, + "methodIdentifiers": { + "UPGRADE_INTERFACE_VERSION()": "ad3cb1cc", + "addUsername(address,string)": "e5abdcef", + "getUsername(address)": "ce43c032", + "getUsernames(address[])": "f5ab196e", + "initialize()": "8129fc1c", + "isUsernameRegistered(string)": "52ac2091", + "isUsernameValid(string)": "117720be", + "owner()": "8da5cb5b", + "proxiableUUID()": "52d1902d", + "registerUsername(string)": "36a94134", + "renounceOwnership()": "715018a6", + "resignUsername()": "ebed6dab", + "transferOwnership(address)": "f2fde38b", + "upgradeToAndCall(address,bytes)": "4f1ef286", + "version()": "54fd4d50" + }, + "rawMetadata": "{\"compiler\":{\"version\":\"0.8.27+commit.40a35a09\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"}],\"name\":\"AddressEmptyCode\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"ERC1967InvalidImplementation\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ERC1967NonPayable\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedCall\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidInitialization\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidUsername\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotInitializing\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"OwnableInvalidOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"OwnableUnauthorizedAccount\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"TakenUsername\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UUPSUnauthorizedCallContext\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"slot\",\"type\":\"bytes32\"}],\"name\":\"UUPSUnsupportedProxiableUUID\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UsernameNotRegistered\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"version\",\"type\":\"uint64\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"Upgraded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"username\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"previousUsername\",\"type\":\"string\"}],\"name\":\"UsernameRegistered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"username\",\"type\":\"string\"}],\"name\":\"UsernameResigned\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"UPGRADE_INTERFACE_VERSION\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"username\",\"type\":\"string\"}],\"name\":\"addUsername\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"}],\"name\":\"getUsername\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"addresses\",\"type\":\"address[]\"}],\"name\":\"getUsernames\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"username\",\"type\":\"string\"}],\"internalType\":\"struct UsernamesV1.User[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"username\",\"type\":\"string\"}],\"name\":\"isUsernameRegistered\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"username\",\"type\":\"string\"}],\"name\":\"isUsernameValid\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"proxiableUUID\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"username\",\"type\":\"string\"}],\"name\":\"registerUsername\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"resignUsername\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"upgradeToAndCall\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}],\"devdoc\":{\"errors\":{\"AddressEmptyCode(address)\":[{\"details\":\"There's no code at `target` (it is not a contract).\"}],\"ERC1967InvalidImplementation(address)\":[{\"details\":\"The `implementation` of the proxy is invalid.\"}],\"ERC1967NonPayable()\":[{\"details\":\"An upgrade function sees `msg.value > 0` that may be lost.\"}],\"FailedCall()\":[{\"details\":\"A call to an address target failed. The target may have reverted.\"}],\"InvalidInitialization()\":[{\"details\":\"The contract is already initialized.\"}],\"NotInitializing()\":[{\"details\":\"The contract is not initializing.\"}],\"OwnableInvalidOwner(address)\":[{\"details\":\"The owner is not a valid owner account. (eg. `address(0)`)\"}],\"OwnableUnauthorizedAccount(address)\":[{\"details\":\"The caller account is not authorized to perform an operation.\"}],\"UUPSUnauthorizedCallContext()\":[{\"details\":\"The call is from an unauthorized context.\"}],\"UUPSUnsupportedProxiableUUID(bytes32)\":[{\"details\":\"The storage `slot` is unsupported as a UUID.\"}]},\"events\":{\"Initialized(uint64)\":{\"details\":\"Triggered when the contract has been initialized or reinitialized.\"},\"Upgraded(address)\":{\"details\":\"Emitted when the implementation is upgraded.\"}},\"kind\":\"dev\",\"methods\":{\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"proxiableUUID()\":{\"details\":\"Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the implementation. It is used to validate the implementation's compatibility when performing an upgrade. IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.\"},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner.\"},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"},\"upgradeToAndCall(address,bytes)\":{\"custom:oz-upgrades-unsafe-allow-reachable\":\"delegatecall\",\"details\":\"Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call encoded in `data`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/usernames/UsernamesV1.sol\":\"UsernamesV1\"},\"evmVersion\":\"shanghai\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@contracts/=src/\",\":@forge-std/=lib/forge-std/src/\",\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/\",\":ds-test/=lib/openzeppelin-contracts-upgradeable/lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/\",\":openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/\",\":solidity-stringutils/=lib/openzeppelin-foundry-upgrades/lib/solidity-stringutils/\"]},\"sources\":{\"lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol\":{\"keccak256\":\"0xc163fcf9bb10138631a9ba5564df1fa25db9adff73bd9ee868a8ae1858fe093a\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://9706d43a0124053d9880f6e31a59f31bc0a6a3dc1acd66ce0a16e1111658c5f6\",\"dweb:/ipfs/QmUFmfowzkRwGtDu36cXV9SPTBHJ3n7dG9xQiK5B28jTf2\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol\":{\"keccak256\":\"0x631188737069917d2f909d29ce62c4d48611d326686ba6683e26b72a23bfac0b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://7a61054ae84cd6c4d04c0c4450ba1d6de41e27e0a2c4f1bcdf58f796b401c609\",\"dweb:/ipfs/QmUvtdp7X1mRVyC3CsHrtPbgoqWaXHp3S1ZR24tpAQYJWM\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol\":{\"keccak256\":\"0xf72d3b11f41fccbbdcacd121f994daab8267ccfceb1fb4f247e4ba274c169d27\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://1e46ee40ddc9e2009176ce5d76aa2c046fd68f2ed52d02d77db191365b7c5b2e\",\"dweb:/ipfs/QmZnxgPmCCHosdvbh4J65uTaFYeGtZGzQ1sXRdeh1y68Zr\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol\":{\"keccak256\":\"0xdbef5f0c787055227243a7318ef74c8a5a1108ca3a07f2b3a00ef67769e1e397\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://08e39f23d5b4692f9a40803e53a8156b72b4c1f9902a88cd65ba964db103dab9\",\"dweb:/ipfs/QmPKn6EYDgpga7KtpkA8wV2yJCYGMtc9K4LkJfhKX2RVSV\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol\":{\"keccak256\":\"0xb25a4f11fa80c702bf5cd85adec90e6f6f507f32f4a8e6f5dbc31e8c10029486\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://6917f8a323e7811f041aecd4d9fd6e92455a6fba38a797ac6f6e208c7912b79d\",\"dweb:/ipfs/QmShuYv55wYHGi4EFkDB8QfF7ZCHoKk2efyz3AWY1ExSq7\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol\":{\"keccak256\":\"0xc42facb5094f2f35f066a7155bda23545e39a3156faef3ddc00185544443ba7d\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://d3b36282ab029b46bd082619a308a2ea11c309967b9425b7b7a6eb0b0c1c3196\",\"dweb:/ipfs/QmP2YVfDB2FoREax3vJu7QhDnyYRMw52WPrCD4vdT2kuDA\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol\":{\"keccak256\":\"0x911c3346ee26afe188f3b9dc267ef62a7ccf940aba1afa963e3922f0ca3d8a06\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://04539f4419e44a831807d7203375d2bc6a733da256efd02e51290f5d5015218c\",\"dweb:/ipfs/QmPZ97gsAAgaMRPiE2WJfkzRsudQnW5tPAvMgGj1jcTJtR\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol\":{\"keccak256\":\"0xc59a78b07b44b2cf2e8ab4175fca91e8eca1eee2df7357b8d2a8833e5ea1f64c\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://5aa4f07e65444784c29cd7bfcc2341b34381e4e5b5da9f0c5bd00d7f430e66fa\",\"dweb:/ipfs/QmWRMh4Q9DpaU9GvsiXmDdoNYMyyece9if7hnfLz7uqzWM\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Address.sol\":{\"keccak256\":\"0x9d8da059267bac779a2dbbb9a26c2acf00ca83085e105d62d5d4ef96054a47f5\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c78e2aa4313323cecd1ef12a8d6265b96beee1a199923abf55d9a2a9e291ad23\",\"dweb:/ipfs/QmUTs2KStXucZezzFo3EYeqYu47utu56qrF7jj1Gue65vb\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Errors.sol\":{\"keccak256\":\"0x6afa713bfd42cf0f7656efa91201007ac465e42049d7de1d50753a373648c123\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ba1d02f4847670a1b83dec9f7d37f0b0418d6043447b69f3a29a5f9efc547fcf\",\"dweb:/ipfs/QmQ7iH2keLNUKgq2xSWcRmuBE5eZ3F5whYAkAGzCNNoEWB\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol\":{\"keccak256\":\"0xcf74f855663ce2ae00ed8352666b7935f6cddea2932fdf2c3ecd30a9b1cd0e97\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://9f660b1f351b757dfe01438e59888f31f33ded3afcf5cb5b0d9bf9aa6f320a8b\",\"dweb:/ipfs/QmarDJ5hZEgBtCmmrVzEZWjub9769eD686jmzb2XpSU1cM\"]},\"src/usernames/UsernamesV1.sol\":{\"keccak256\":\"0x3d0012765a2c9a0a96b156052d6e530feb6b140af87c9e99f73526843751d8f7\",\"license\":\"GNU GENERAL PUBLIC LICENSE\",\"urls\":[\"bzz-raw://8156867b0c788dc6ff3eebdd37e949dc9d3a5b5330359ecad6ebc38fc87946f4\",\"dweb:/ipfs/QmemCY8hmvu5dGiW4k9ssJiwNekgWaHC7mzCWutNuJV7Rq\"]}},\"version\":1}", + "metadata": { + "compiler": { "version": "0.8.27+commit.40a35a09" }, + "language": "Solidity", + "output": { + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "type": "error", + "name": "AddressEmptyCode" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "type": "error", + "name": "ERC1967InvalidImplementation" + }, + { "inputs": [], "type": "error", "name": "ERC1967NonPayable" }, + { "inputs": [], "type": "error", "name": "FailedCall" }, + { + "inputs": [], + "type": "error", + "name": "InvalidInitialization" + }, + { "inputs": [], "type": "error", "name": "InvalidUsername" }, + { "inputs": [], "type": "error", "name": "NotInitializing" }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "type": "error", + "name": "OwnableInvalidOwner" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "type": "error", + "name": "OwnableUnauthorizedAccount" + }, + { "inputs": [], "type": "error", "name": "TakenUsername" }, + { + "inputs": [], + "type": "error", + "name": "UUPSUnauthorizedCallContext" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "slot", + "type": "bytes32" + } + ], + "type": "error", + "name": "UUPSUnsupportedProxiableUUID" + }, + { + "inputs": [], + "type": "error", + "name": "UsernameNotRegistered" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "version", + "type": "uint64", + "indexed": false + } + ], + "type": "event", + "name": "Initialized", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "address", + "name": "previousOwner", + "type": "address", + "indexed": true + }, + { + "internalType": "address", + "name": "newOwner", + "type": "address", + "indexed": true + } + ], + "type": "event", + "name": "OwnershipTransferred", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "address", + "name": "implementation", + "type": "address", + "indexed": true + } + ], + "type": "event", + "name": "Upgraded", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address", + "indexed": false + }, + { + "internalType": "string", + "name": "username", + "type": "string", + "indexed": false + }, + { + "internalType": "string", + "name": "previousUsername", + "type": "string", + "indexed": false + } + ], + "type": "event", + "name": "UsernameRegistered", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address", + "indexed": false + }, + { + "internalType": "string", + "name": "username", + "type": "string", + "indexed": false + } + ], + "type": "event", + "name": "UsernameResigned", + "anonymous": false + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "UPGRADE_INTERFACE_VERSION", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ] + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "string", + "name": "username", + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "addUsername" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function", + "name": "getUsername", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ] + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "addresses", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function", + "name": "getUsernames", + "outputs": [ + { + "internalType": "struct UsernamesV1.User[]", + "name": "", + "type": "tuple[]", + "components": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "string", + "name": "username", + "type": "string" + } + ] + } + ] + }, + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "function", + "name": "initialize" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "username", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function", + "name": "isUsernameRegistered", + "outputs": [ + { "internalType": "bool", "name": "", "type": "bool" } + ] + }, + { + "inputs": [ + { + "internalType": "string", + "name": "username", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function", + "name": "isUsernameValid", + "outputs": [ + { "internalType": "bool", "name": "", "type": "bool" } + ] + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ] + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "proxiableUUID", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ] + }, + { + "inputs": [ + { + "internalType": "string", + "name": "username", + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "registerUsername" + }, + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "function", + "name": "renounceOwnership" + }, + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "function", + "name": "resignUsername" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "transferOwnership" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "function", + "name": "upgradeToAndCall" + }, + { + "inputs": [], + "stateMutability": "pure", + "type": "function", + "name": "version", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ] + } + ], + "devdoc": { + "kind": "dev", + "methods": { + "owner()": { + "details": "Returns the address of the current owner." + }, + "proxiableUUID()": { + "details": "Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the implementation. It is used to validate the implementation's compatibility when performing an upgrade. IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier." + }, + "renounceOwnership()": { + "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner." + }, + "transferOwnership(address)": { + "details": "Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner." + }, + "upgradeToAndCall(address,bytes)": { + "custom:oz-upgrades-unsafe-allow-reachable": "delegatecall", + "details": "Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call encoded in `data`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event." + } + }, + "version": 1 + }, + "userdoc": { "kind": "user", "methods": {}, "version": 1 } + }, + "settings": { + "remappings": [ + "@contracts/=src/", + "@forge-std/=lib/forge-std/src/", + "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/", + "@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/", + "ds-test/=lib/openzeppelin-contracts-upgradeable/lib/forge-std/lib/ds-test/src/", + "erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/", + "forge-std/=lib/forge-std/src/", + "halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/", + "openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/", + "openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/", + "openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/", + "solidity-stringutils/=lib/openzeppelin-foundry-upgrades/lib/solidity-stringutils/" + ], + "optimizer": { "enabled": true, "runs": 200 }, + "metadata": { "bytecodeHash": "ipfs" }, + "compilationTarget": { + "src/usernames/UsernamesV1.sol": "UsernamesV1" + }, + "evmVersion": "shanghai", + "libraries": {} + }, + "sources": { + "lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol": { + "keccak256": "0xc163fcf9bb10138631a9ba5564df1fa25db9adff73bd9ee868a8ae1858fe093a", + "urls": [ + "bzz-raw://9706d43a0124053d9880f6e31a59f31bc0a6a3dc1acd66ce0a16e1111658c5f6", + "dweb:/ipfs/QmUFmfowzkRwGtDu36cXV9SPTBHJ3n7dG9xQiK5B28jTf2" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol": { + "keccak256": "0x631188737069917d2f909d29ce62c4d48611d326686ba6683e26b72a23bfac0b", + "urls": [ + "bzz-raw://7a61054ae84cd6c4d04c0c4450ba1d6de41e27e0a2c4f1bcdf58f796b401c609", + "dweb:/ipfs/QmUvtdp7X1mRVyC3CsHrtPbgoqWaXHp3S1ZR24tpAQYJWM" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol": { + "keccak256": "0xf72d3b11f41fccbbdcacd121f994daab8267ccfceb1fb4f247e4ba274c169d27", + "urls": [ + "bzz-raw://1e46ee40ddc9e2009176ce5d76aa2c046fd68f2ed52d02d77db191365b7c5b2e", + "dweb:/ipfs/QmZnxgPmCCHosdvbh4J65uTaFYeGtZGzQ1sXRdeh1y68Zr" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol": { + "keccak256": "0xdbef5f0c787055227243a7318ef74c8a5a1108ca3a07f2b3a00ef67769e1e397", + "urls": [ + "bzz-raw://08e39f23d5b4692f9a40803e53a8156b72b4c1f9902a88cd65ba964db103dab9", + "dweb:/ipfs/QmPKn6EYDgpga7KtpkA8wV2yJCYGMtc9K4LkJfhKX2RVSV" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol": { + "keccak256": "0xb25a4f11fa80c702bf5cd85adec90e6f6f507f32f4a8e6f5dbc31e8c10029486", + "urls": [ + "bzz-raw://6917f8a323e7811f041aecd4d9fd6e92455a6fba38a797ac6f6e208c7912b79d", + "dweb:/ipfs/QmShuYv55wYHGi4EFkDB8QfF7ZCHoKk2efyz3AWY1ExSq7" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol": { + "keccak256": "0xc42facb5094f2f35f066a7155bda23545e39a3156faef3ddc00185544443ba7d", + "urls": [ + "bzz-raw://d3b36282ab029b46bd082619a308a2ea11c309967b9425b7b7a6eb0b0c1c3196", + "dweb:/ipfs/QmP2YVfDB2FoREax3vJu7QhDnyYRMw52WPrCD4vdT2kuDA" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol": { + "keccak256": "0x911c3346ee26afe188f3b9dc267ef62a7ccf940aba1afa963e3922f0ca3d8a06", + "urls": [ + "bzz-raw://04539f4419e44a831807d7203375d2bc6a733da256efd02e51290f5d5015218c", + "dweb:/ipfs/QmPZ97gsAAgaMRPiE2WJfkzRsudQnW5tPAvMgGj1jcTJtR" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol": { + "keccak256": "0xc59a78b07b44b2cf2e8ab4175fca91e8eca1eee2df7357b8d2a8833e5ea1f64c", + "urls": [ + "bzz-raw://5aa4f07e65444784c29cd7bfcc2341b34381e4e5b5da9f0c5bd00d7f430e66fa", + "dweb:/ipfs/QmWRMh4Q9DpaU9GvsiXmDdoNYMyyece9if7hnfLz7uqzWM" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Address.sol": { + "keccak256": "0x9d8da059267bac779a2dbbb9a26c2acf00ca83085e105d62d5d4ef96054a47f5", + "urls": [ + "bzz-raw://c78e2aa4313323cecd1ef12a8d6265b96beee1a199923abf55d9a2a9e291ad23", + "dweb:/ipfs/QmUTs2KStXucZezzFo3EYeqYu47utu56qrF7jj1Gue65vb" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Errors.sol": { + "keccak256": "0x6afa713bfd42cf0f7656efa91201007ac465e42049d7de1d50753a373648c123", + "urls": [ + "bzz-raw://ba1d02f4847670a1b83dec9f7d37f0b0418d6043447b69f3a29a5f9efc547fcf", + "dweb:/ipfs/QmQ7iH2keLNUKgq2xSWcRmuBE5eZ3F5whYAkAGzCNNoEWB" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol": { + "keccak256": "0xcf74f855663ce2ae00ed8352666b7935f6cddea2932fdf2c3ecd30a9b1cd0e97", + "urls": [ + "bzz-raw://9f660b1f351b757dfe01438e59888f31f33ded3afcf5cb5b0d9bf9aa6f320a8b", + "dweb:/ipfs/QmarDJ5hZEgBtCmmrVzEZWjub9769eD686jmzb2XpSU1cM" + ], + "license": "MIT" + }, + "src/usernames/UsernamesV1.sol": { + "keccak256": "0x3d0012765a2c9a0a96b156052d6e530feb6b140af87c9e99f73526843751d8f7", + "urls": [ + "bzz-raw://8156867b0c788dc6ff3eebdd37e949dc9d3a5b5330359ecad6ebc38fc87946f4", + "dweb:/ipfs/QmemCY8hmvu5dGiW4k9ssJiwNekgWaHC7mzCWutNuJV7Rq" + ], + "license": "GNU GENERAL PUBLIC LICENSE" + } + }, + "version": 1 + }, + "id": 37 +} diff --git a/crypto/utils/abi_base.py b/crypto/utils/abi_base.py index 1641b38a..d810c551 100644 --- a/crypto/utils/abi_base.py +++ b/crypto/utils/abi_base.py @@ -4,16 +4,22 @@ import os import re from binascii import unhexlify +from typing import Optional from Cryptodome.Hash import keccak +from crypto.enums.contract_abi_type import ContractAbiType from crypto.identity.address import get_checksum_address class AbiBase: - def __init__(self): - # Cargar el ABI desde un archivo JSON - abi_file_path = os.path.join(os.path.dirname(__file__), 'Abi.Consensus.json') + def __init__(self, abi_type: ContractAbiType = ContractAbiType.CONSENSUS, path: Optional[str] = None): + abi_file_path = self.__contract_abi_path(abi_type, path) + + if abi_file_path is None: + raise Exception('ABI file path is not provided') + with open(abi_file_path, 'r') as f: abi_json = json.load(f) + self.abi = abi_json.get('abi', []) def get_array_components(self, type_str): @@ -51,3 +57,12 @@ def to_function_selector(self, abi_item): hash_ = self.keccak256(signature) selector = '0x' + self.strip_hex_prefix(hash_)[0:8] return selector + + def __contract_abi_path(self, abi_type: ContractAbiType, path: Optional[str] = None) -> Optional[str]: + if abi_type == ContractAbiType.CONSENSUS: + return os.path.join(os.path.dirname(__file__), 'abi/json', 'Abi.Consensus.json') + + if abi_type == ContractAbiType.USERNAMES: + return os.path.join(os.path.dirname(__file__), 'abi/json', 'Abi.Usernames.json') + + return path From 4d5f7408eec8af7a0aadedb6eb46976db10463d9 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Fri, 14 Feb 2025 01:54:20 +0000 Subject: [PATCH 22/25] feat: implement username transaction types --- crypto/enums/abi_function.py | 2 + crypto/enums/contract_abi_type.py | 6 +++ crypto/enums/contract_addresses.py | 6 +++ crypto/exceptions.py | 4 ++ .../builder/username_registration_builder.py | 39 +++++++++++++++++++ .../builder/username_resignation_builder.py | 13 +++++++ .../types/username_registration.py | 21 ++++++++++ .../types/username_resignation.py | 10 +++++ 8 files changed, 101 insertions(+) create mode 100644 crypto/enums/contract_abi_type.py create mode 100644 crypto/enums/contract_addresses.py create mode 100644 crypto/transactions/builder/username_registration_builder.py create mode 100644 crypto/transactions/builder/username_resignation_builder.py create mode 100644 crypto/transactions/types/username_registration.py create mode 100644 crypto/transactions/types/username_resignation.py diff --git a/crypto/enums/abi_function.py b/crypto/enums/abi_function.py index ece44321..c0cad256 100644 --- a/crypto/enums/abi_function.py +++ b/crypto/enums/abi_function.py @@ -3,5 +3,7 @@ class AbiFunction(Enum): VOTE = 'vote' UNVOTE = 'unvote' + USERNAME_REGISTRATION = 'registerUsername' + USERNAME_RESIGNATION = 'resignUsername' VALIDATOR_REGISTRATION = 'registerValidator' VALIDATOR_RESIGNATION = 'resignValidator' diff --git a/crypto/enums/contract_abi_type.py b/crypto/enums/contract_abi_type.py new file mode 100644 index 00000000..a9eefb1d --- /dev/null +++ b/crypto/enums/contract_abi_type.py @@ -0,0 +1,6 @@ +from enum import Enum + +class ContractAbiType(Enum): + CUSTOM = 'custom' + CONSENSUS = 'consensus' + USERNAMES = 'usernames' diff --git a/crypto/enums/contract_addresses.py b/crypto/enums/contract_addresses.py new file mode 100644 index 00000000..ac0b0510 --- /dev/null +++ b/crypto/enums/contract_addresses.py @@ -0,0 +1,6 @@ +from enum import Enum + +class ContractAddresses(Enum): + CONSENSUS = '0x535B3D7A252fa034Ed71F0C53ec0C6F784cB64E1' + MULTIPAYMENT = '0x83769BeEB7e5405ef0B7dc3C66C43E3a51A6d27f' + USERNAMES = '0x2c1DE3b4Dbb4aDebEbB5dcECAe825bE2a9fc6eb6' diff --git a/crypto/exceptions.py b/crypto/exceptions.py index 58b1421d..8c424c13 100644 --- a/crypto/exceptions.py +++ b/crypto/exceptions.py @@ -8,3 +8,7 @@ class ArkSerializerException(ArkCryptoException): class ArkInvalidTransaction(ArkCryptoException): """Raised when transaction is not valid""" + + +class InvalidUsernameException(Exception): + """Raised when username is invalid""" diff --git a/crypto/transactions/builder/username_registration_builder.py b/crypto/transactions/builder/username_registration_builder.py new file mode 100644 index 00000000..8719b699 --- /dev/null +++ b/crypto/transactions/builder/username_registration_builder.py @@ -0,0 +1,39 @@ +import re +from typing import Optional +from crypto.enums.contract_addresses import ContractAddresses +from crypto.exceptions import InvalidUsernameException +from crypto.transactions.builder.base import AbstractTransactionBuilder +from crypto.transactions.types.username_registration import UsernameRegistration + +class UsernameRegistrationBuilder(AbstractTransactionBuilder): + def __init__(self, data: Optional[dict] = None): + super().__init__(data) + + self.recipient_address(ContractAddresses.USERNAMES.value) + + def username(self, username: str): + self.is_valid_username(username) + + self.transaction.data['username'] = username + self.transaction.refresh_payload_data() + + return self + + def get_transaction_instance(self, data: dict): + return UsernameRegistration(data) + + @staticmethod + def is_valid_username(username: str) -> bool: + if len(username) < 1 or len(username) > 20: + raise InvalidUsernameException(f'Username must be between 1 and 20 characters long. Got {len(username)} characters.') + + if re.match('/[^a-z0-9_]/', username): + raise InvalidUsernameException('Username can only contain lowercase letters, numbers and underscores.') + + if re.match('/^_|_$/', username): + raise InvalidUsernameException('Username cannot start or end with an underscore.') + + if re.match('/__/', username): + raise InvalidUsernameException('Username cannot contain consecutive underscores.') + + return True diff --git a/crypto/transactions/builder/username_resignation_builder.py b/crypto/transactions/builder/username_resignation_builder.py new file mode 100644 index 00000000..fd925cb2 --- /dev/null +++ b/crypto/transactions/builder/username_resignation_builder.py @@ -0,0 +1,13 @@ +from typing import Optional +from crypto.enums.contract_addresses import ContractAddresses +from crypto.transactions.builder.base import AbstractTransactionBuilder +from crypto.transactions.types.username_resignation import UsernameResignation + +class UsernameResignationBuilder(AbstractTransactionBuilder): + def __init__(self, data: Optional[dict] = None): + super().__init__(data) + + self.recipient_address(ContractAddresses.USERNAMES.value) + + def get_transaction_instance(self, data: dict): + return UsernameResignation(data) diff --git a/crypto/transactions/types/username_registration.py b/crypto/transactions/types/username_registration.py new file mode 100644 index 00000000..100333a8 --- /dev/null +++ b/crypto/transactions/types/username_registration.py @@ -0,0 +1,21 @@ +from crypto.enums.contract_abi_type import ContractAbiType +from crypto.transactions.types.abstract_transaction import AbstractTransaction +from crypto.utils.abi_encoder import AbiEncoder +from crypto.enums.abi_function import AbiFunction + +class UsernameRegistration(AbstractTransaction): + def __init__(self, data: dict = None): + data = data or {} + payload = self.decode_payload(data) + if payload: + data['username'] = payload.get('args', [None])[0] if payload.get('args') else None + + super().__init__(data) + + def get_payload(self) -> str: + if 'username' not in self.data: + return '' + + encoder = AbiEncoder(ContractAbiType.USERNAMES) + + return encoder.encode_function_call(AbiFunction.USERNAME_REGISTRATION.value, [self.data['username']]) diff --git a/crypto/transactions/types/username_resignation.py b/crypto/transactions/types/username_resignation.py new file mode 100644 index 00000000..8dbf023e --- /dev/null +++ b/crypto/transactions/types/username_resignation.py @@ -0,0 +1,10 @@ +from crypto.enums.contract_abi_type import ContractAbiType +from crypto.transactions.types.abstract_transaction import AbstractTransaction +from crypto.utils.abi_encoder import AbiEncoder +from crypto.enums.abi_function import AbiFunction + +class UsernameResignation(AbstractTransaction): + def get_payload(self) -> str: + encoder = AbiEncoder(ContractAbiType.USERNAMES) + + return encoder.encode_function_call(AbiFunction.USERNAME_RESIGNATION.value) From 6daf73d8ff9672f4a69bbccfbbbe966684a9a18f Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Fri, 14 Feb 2025 01:54:25 +0000 Subject: [PATCH 23/25] test --- .../test_username_registration_builder.py | 77 +++++++++++++++++++ .../test_username_resignation_builder.py | 17 ++++ 2 files changed, 94 insertions(+) create mode 100644 tests/transactions/builder/test_username_registration_builder.py create mode 100644 tests/transactions/builder/test_username_resignation_builder.py diff --git a/tests/transactions/builder/test_username_registration_builder.py b/tests/transactions/builder/test_username_registration_builder.py new file mode 100644 index 00000000..c8536466 --- /dev/null +++ b/tests/transactions/builder/test_username_registration_builder.py @@ -0,0 +1,77 @@ +from crypto.exceptions import InvalidUsernameException +from crypto.transactions.builder.username_registration_builder import UsernameRegistrationBuilder + +def test_username_registration_transaction(passphrase, load_transaction_fixture): + fixture = load_transaction_fixture('username-registration') + + builder = ( + UsernameRegistrationBuilder() + .gas_price(fixture['data']['gasPrice']) + .nonce(fixture['data']['nonce']) + .network(fixture['data']['network']) + .gas_limit(fixture['data']['gasLimit']) + .username('php') + .sign(passphrase) + ) + + assert builder.transaction.serialize().hex() == fixture['serialized'] + assert builder.transaction.data['id'] == fixture['data']['id'] + assert builder.verify() + +def test_it_accepts_valid_usernames(): + try: + UsernameRegistrationBuilder.is_valid_username('john') + UsernameRegistrationBuilder.is_valid_username('john123') + UsernameRegistrationBuilder.is_valid_username('john_doe') + UsernameRegistrationBuilder.is_valid_username('a') + UsernameRegistrationBuilder.is_valid_username('abcdefghijklmnopqrst') + UsernameRegistrationBuilder.is_valid_username('user_123_name') + except InvalidUsernameException as e: + raise Exception(f'Valid username threw an exception: {e}') + +def test_it_rejects_usernames_with_invalid_length(): + for username, character_count in {'': 0, 'abcdefghijklmnopqrstu': 21}.items(): + try: + UsernameRegistrationBuilder.is_valid_username(username) + except InvalidUsernameException as e: + assert str(e) == f'Username must be between 1 and 20 characters long. Got {character_count} characters.' + +def test_it_rejects_usernames_with_invalid_characters(): + usernames = [ + 'John', + 'john@doe', + 'john doe', + 'jöhn', + ] + + for username in usernames: + try: + UsernameRegistrationBuilder.is_valid_username(username) + except InvalidUsernameException as e: + assert str(e) == 'Username can only contain lowercase letters, numbers and underscores' + +def test_it_rejects_usernames_starting_or_ending_with_underscore(): + usernames = [ + '_john', + 'john_', + '_john_', + ] + + for username in usernames: + try: + UsernameRegistrationBuilder.is_valid_username(username) + except InvalidUsernameException as e: + assert str(e) == 'Username cannot start or end with an underscore' + +def test_it_rejects_usernames_with_consecutive_underscores(): + usernames = [ + 'john__doe', + 'john___doe', + 'john__doe__smith', + ] + + for username in usernames: + try: + UsernameRegistrationBuilder.is_valid_username(username) + except InvalidUsernameException as e: + assert str(e) == 'Username cannot contain consecutive underscores' diff --git a/tests/transactions/builder/test_username_resignation_builder.py b/tests/transactions/builder/test_username_resignation_builder.py new file mode 100644 index 00000000..048ec939 --- /dev/null +++ b/tests/transactions/builder/test_username_resignation_builder.py @@ -0,0 +1,17 @@ +from crypto.transactions.builder.username_resignation_builder import UsernameResignationBuilder + +def test_username_resignation_transaction(passphrase, load_transaction_fixture): + fixture = load_transaction_fixture('username-resignation') + + builder = ( + UsernameResignationBuilder() + .gas_price(fixture['data']['gasPrice']) + .nonce(fixture['data']['nonce']) + .network(fixture['data']['network']) + .gas_limit(fixture['data']['gasLimit']) + .sign(passphrase) + ) + + assert builder.transaction.serialize().hex() == fixture['serialized'] + assert builder.transaction.data['id'] == fixture['data']['id'] + assert builder.verify() From bcd7db03b4582152774c0b66449fba1a47906c0c Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Mon, 17 Feb 2025 13:05:28 +0000 Subject: [PATCH 24/25] refactor: set contract address in builder by default --- crypto/transactions/builder/unvote_builder.py | 9 ++++++++- .../builder/validator_registration_builder.py | 9 ++++++++- .../builder/validator_resignation_builder.py | 9 ++++++++- crypto/transactions/builder/vote_builder.py | 9 ++++++++- 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/crypto/transactions/builder/unvote_builder.py b/crypto/transactions/builder/unvote_builder.py index ddb9e248..b89dee9e 100644 --- a/crypto/transactions/builder/unvote_builder.py +++ b/crypto/transactions/builder/unvote_builder.py @@ -1,6 +1,13 @@ +from typing import Optional +from crypto.enums.contract_addresses import ContractAddresses from crypto.transactions.builder.base import AbstractTransactionBuilder from crypto.transactions.types.unvote import Unvote class UnvoteBuilder(AbstractTransactionBuilder): + def __init__(self, data: Optional[dict] = None): + super().__init__(data) + + self.recipient_address(ContractAddresses.CONSENSUS.value) + def get_transaction_instance(self, data: dict): - return Unvote(data) \ No newline at end of file + return Unvote(data) diff --git a/crypto/transactions/builder/validator_registration_builder.py b/crypto/transactions/builder/validator_registration_builder.py index 15df44bc..8d7df3aa 100644 --- a/crypto/transactions/builder/validator_registration_builder.py +++ b/crypto/transactions/builder/validator_registration_builder.py @@ -1,11 +1,18 @@ +from typing import Optional +from crypto.enums.contract_addresses import ContractAddresses from crypto.transactions.builder.base import AbstractTransactionBuilder from crypto.transactions.types.validator_registration import ValidatorRegistration class ValidatorRegistrationBuilder(AbstractTransactionBuilder): + def __init__(self, data: Optional[dict] = None): + super().__init__(data) + + self.recipient_address(ContractAddresses.CONSENSUS.value) + def validator_public_key(self, validator_public_key: str): self.transaction.data['validatorPublicKey'] = validator_public_key self.transaction.refresh_payload_data() return self def get_transaction_instance(self, data: dict): - return ValidatorRegistration(data) \ No newline at end of file + return ValidatorRegistration(data) diff --git a/crypto/transactions/builder/validator_resignation_builder.py b/crypto/transactions/builder/validator_resignation_builder.py index 294cd771..77b3908d 100644 --- a/crypto/transactions/builder/validator_resignation_builder.py +++ b/crypto/transactions/builder/validator_resignation_builder.py @@ -1,6 +1,13 @@ +from typing import Optional +from crypto.enums.contract_addresses import ContractAddresses from crypto.transactions.builder.base import AbstractTransactionBuilder from crypto.transactions.types.validator_resignation import ValidatorResignation class ValidatorResignationBuilder(AbstractTransactionBuilder): + def __init__(self, data: Optional[dict] = None): + super().__init__(data) + + self.recipient_address(ContractAddresses.CONSENSUS.value) + def get_transaction_instance(self, data: dict): - return ValidatorResignation(data) \ No newline at end of file + return ValidatorResignation(data) diff --git a/crypto/transactions/builder/vote_builder.py b/crypto/transactions/builder/vote_builder.py index 7b4dfb03..565dd88b 100644 --- a/crypto/transactions/builder/vote_builder.py +++ b/crypto/transactions/builder/vote_builder.py @@ -1,11 +1,18 @@ +from typing import Optional +from crypto.enums.contract_addresses import ContractAddresses from crypto.transactions.builder.base import AbstractTransactionBuilder from crypto.transactions.types.vote import Vote class VoteBuilder(AbstractTransactionBuilder): + def __init__(self, data: Optional[dict] = None): + super().__init__(data) + + self.recipient_address(ContractAddresses.CONSENSUS.value) + def vote(self, vote: str): self.transaction.data['vote'] = vote self.transaction.refresh_payload_data() return self def get_transaction_instance(self, data: dict) -> Vote: - return Vote(data) \ No newline at end of file + return Vote(data) From db8050a235405ed4da6ea8b751f2ed268642363e Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Mon, 17 Feb 2025 13:05:34 +0000 Subject: [PATCH 25/25] test --- .../builder/test_unvote_builder.py | 26 ++++++++++++++++++ .../test_validator_registration_builder.py | 27 +++++++++++++++++++ .../test_validator_resignation_builder.py | 26 ++++++++++++++++++ .../transactions/builder/test_vote_builder.py | 27 +++++++++++++++++++ 4 files changed, 106 insertions(+) diff --git a/tests/transactions/builder/test_unvote_builder.py b/tests/transactions/builder/test_unvote_builder.py index 48896d73..56bd3929 100644 --- a/tests/transactions/builder/test_unvote_builder.py +++ b/tests/transactions/builder/test_unvote_builder.py @@ -26,3 +26,29 @@ def test_unvote_transaction(passphrase, load_transaction_fixture): assert builder.transaction.serialize().hex() == fixture['serialized'] assert builder.transaction.data['id'] == fixture['data']['id'] assert builder.verify() + +def test_unvote_transaction_with_default_recipient_address(passphrase, load_transaction_fixture): + fixture = load_transaction_fixture('unvote') + + builder = ( + UnvoteBuilder() + .gas_price(fixture['data']['gasPrice']) + .nonce(fixture['data']['nonce']) + .network(fixture['data']['network']) + .gas_limit(fixture['data']['gasLimit']) + .sign(passphrase) + ) + + assert builder.transaction.data['gasPrice'] == fixture['data']['gasPrice'] + assert builder.transaction.data['nonce'] == fixture['data']['nonce'] + assert builder.transaction.data['network'] == fixture['data']['network'] + assert builder.transaction.data['gasLimit'] == fixture['data']['gasLimit'] + assert builder.transaction.data['recipientAddress'].lower() == fixture['data']['recipientAddress'].lower() + assert builder.transaction.data['value'] == fixture['data']['value'] + assert builder.transaction.data['v'] == fixture['data']['v'] + assert builder.transaction.data['r'] == fixture['data']['r'] + assert builder.transaction.data['s'] == fixture['data']['s'] + + assert builder.transaction.serialize().hex() == fixture['serialized'] + assert builder.transaction.data['id'] == fixture['data']['id'] + assert builder.verify() diff --git a/tests/transactions/builder/test_validator_registration_builder.py b/tests/transactions/builder/test_validator_registration_builder.py index 284cd43a..6dec2be2 100644 --- a/tests/transactions/builder/test_validator_registration_builder.py +++ b/tests/transactions/builder/test_validator_registration_builder.py @@ -27,3 +27,30 @@ def test_validator_registration_transaction(passphrase, load_transaction_fixture assert builder.transaction.serialize().hex() == fixture['serialized'] assert builder.transaction.data['id'] == fixture['data']['id'] assert builder.verify() + +def test_validator_registration_transaction_with_default_recipient_address(passphrase, load_transaction_fixture): + fixture = load_transaction_fixture('validator-registration') + + builder = ( + ValidatorRegistrationBuilder() + .gas_price(fixture['data']['gasPrice']) + .nonce(fixture['data']['nonce']) + .network(fixture['data']['network']) + .gas_limit(fixture['data']['gasLimit']) + .validator_public_key('954f46d6097a1d314e900e66e11e0dad0a57cd03e04ec99f0dedd1c765dcb11e6d7fa02e22cf40f9ee23d9cc1c0624bd') + .sign(passphrase) + ) + + assert builder.transaction.data['gasPrice'] == fixture['data']['gasPrice'] + assert builder.transaction.data['nonce'] == fixture['data']['nonce'] + assert builder.transaction.data['network'] == fixture['data']['network'] + assert builder.transaction.data['gasLimit'] == fixture['data']['gasLimit'] + assert builder.transaction.data['recipientAddress'].lower() == fixture['data']['recipientAddress'].lower() + assert builder.transaction.data['value'] == fixture['data']['value'] + assert builder.transaction.data['v'] == fixture['data']['v'] + assert builder.transaction.data['r'] == fixture['data']['r'] + assert builder.transaction.data['s'] == fixture['data']['s'] + + assert builder.transaction.serialize().hex() == fixture['serialized'] + assert builder.transaction.data['id'] == fixture['data']['id'] + assert builder.verify() diff --git a/tests/transactions/builder/test_validator_resignation_builder.py b/tests/transactions/builder/test_validator_resignation_builder.py index d51340a2..9a2c359d 100644 --- a/tests/transactions/builder/test_validator_resignation_builder.py +++ b/tests/transactions/builder/test_validator_resignation_builder.py @@ -26,3 +26,29 @@ def test_validator_resignation_transaction(passphrase, load_transaction_fixture) assert builder.transaction.serialize().hex() == fixture['serialized'] assert builder.transaction.data['id'] == fixture['data']['id'] assert builder.verify() + +def test_validator_resignation_transaction_with_default_recipient_address(passphrase, load_transaction_fixture): + fixture = load_transaction_fixture('validator-resignation') + + builder = ( + ValidatorResignationBuilder() + .gas_price(fixture['data']['gasPrice']) + .nonce(fixture['data']['nonce']) + .network(fixture['data']['network']) + .gas_limit(fixture['data']['gasLimit']) + .sign(passphrase) + ) + + assert builder.transaction.data['gasPrice'] == fixture['data']['gasPrice'] + assert builder.transaction.data['nonce'] == fixture['data']['nonce'] + assert builder.transaction.data['network'] == fixture['data']['network'] + assert builder.transaction.data['gasLimit'] == fixture['data']['gasLimit'] + assert builder.transaction.data['recipientAddress'].lower() == fixture['data']['recipientAddress'].lower() + assert builder.transaction.data['value'] == fixture['data']['value'] + assert builder.transaction.data['v'] == fixture['data']['v'] + assert builder.transaction.data['r'] == fixture['data']['r'] + assert builder.transaction.data['s'] == fixture['data']['s'] + + assert builder.transaction.serialize().hex() == fixture['serialized'] + assert builder.transaction.data['id'] == fixture['data']['id'] + assert builder.verify() diff --git a/tests/transactions/builder/test_vote_builder.py b/tests/transactions/builder/test_vote_builder.py index a94161f3..afa815e8 100644 --- a/tests/transactions/builder/test_vote_builder.py +++ b/tests/transactions/builder/test_vote_builder.py @@ -27,3 +27,30 @@ def test_vote_transaction(passphrase, load_transaction_fixture): assert builder.transaction.serialize().hex() == fixture['serialized'] assert builder.transaction.data['id'] == fixture['data']['id'] assert builder.verify() + +def test_vote_transaction_with_default_recipient_address(passphrase, load_transaction_fixture): + fixture = load_transaction_fixture('vote') + + builder = ( + VoteBuilder() + .gas_price(fixture['data']['gasPrice']) + .nonce(fixture['data']['nonce']) + .network(fixture['data']['network']) + .gas_limit(fixture['data']['gasLimit']) + .vote('0xC3bBE9B1CeE1ff85Ad72b87414B0E9B7F2366763') # Example vote address + .sign(passphrase) + ) + + assert builder.transaction.data['gasPrice'] == fixture['data']['gasPrice'] + assert builder.transaction.data['nonce'] == fixture['data']['nonce'] + assert builder.transaction.data['network'] == fixture['data']['network'] + assert builder.transaction.data['gasLimit'] == fixture['data']['gasLimit'] + assert builder.transaction.data['recipientAddress'].lower() == fixture['data']['recipientAddress'].lower() + assert builder.transaction.data['value'] == fixture['data']['value'] + assert builder.transaction.data['v'] == fixture['data']['v'] + assert builder.transaction.data['r'] == fixture['data']['r'] + assert builder.transaction.data['s'] == fixture['data']['s'] + + assert builder.transaction.serialize().hex() == fixture['serialized'] + assert builder.transaction.data['id'] == fixture['data']['id'] + assert builder.verify()