diff --git a/koreid.py b/koreid.py new file mode 100644 index 0000000..49e25b6 --- /dev/null +++ b/koreid.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python3 +""" +Vector Zulu — KoreID Generator +Generates KoreID display numbers per the KoreID Display Generation Specification. + +Format: /---- +Example: 061925a1/b231f4e5-3487-0a1b-03e8-001540009abc + +S1 (8) = DDMMYY (6-digit UTC date) + CT (2-hex ID-type) +S2 (8) = PT (2-hex parent ID-type) + T (1-hex parent treasury) + PNNNNN (5-hex parent network) +S3 (4) = CCC (3-hex ISO currency) + CK (1-hex XOR checksum) +S4 (4) = First 4 hex of current 8-hex network code +S5 (4) = Last 4 hex of current 8-hex network code +S6 (12) = PPPP (4-hex parent monthly seq) + T (1-hex current treasury) + CCCC (4-hex current monthly seq) + RRR (3-hex nonce) +""" + +import os +import uuid +import hashlib +import logging +from datetime import datetime, timezone +from functools import reduce + +log = logging.getLogger(__name__) + +# ── Lookup tables (placeholders — populate from Bryan's full spec) ──────────── + +# kore_id_type: maps event type names to 2-hex codes +# TODO: replace with values from Bryan's kore_id_type SQL table +ID_TYPES = { + 'MINT': 'a1', # SV token mint event + 'BURN': 'a2', # SV token burn/redemption event + 'TRANSFER': 'a3', # SV token transfer event + 'RESERVE': 'a4', # Reserve oracle update + 'SWAP': 'a5', # Uniswap swap event + 'ONRAMP': 'a6', # Fiat on-ramp + 'OFFRAMP': 'a7', # Fiat off-ramp + 'EXTERNAL': 'b1', # Generic external response +} + +# kore_treasury: maps treasury names to 1-hex codes +# TODO: replace with values from Bryan's kore_treasury SQL table +TREASURIES = { + 'VECTOR_ZULU': '1', + 'SOVEREIGN_GLOBAL': '2', + 'KORE_COLLECTIVE': '3', + 'MERIDIAN': '4', +} + +# kore_network: maps network names to 5-hex parent network codes +# TODO: replace with values from Bryan's kore_network SQL table +PARENT_NETWORKS = { + 'KORECHAIN': 'f4e50', + 'ETHEREUM': '0a1b0', + 'AMITIS': '03e80', + 'TRON': '00154', +} + +# kore_network_current: maps network names to 8-hex current network codes +# TODO: replace with values from Bryan's kore_network_current SQL table +CURRENT_NETWORKS = { + 'ETHEREUM': '0a1b03e8', + 'AMITIS': '03e80015', + 'KORECHAIN': 'f4e5b231', + 'TRON': '001540ab', +} + +# kore_currency: maps ISO currency codes to 3-hex values +# TODO: replace with values from Bryan's kore_currency SQL table +CURRENCIES = { + 'USD': '001', + 'GBP': '002', + 'EUR': '003', + 'CHF': '004', + 'JPY': '005', + 'SGD': '006', + 'BTC': '010', + 'ETH': '011', + 'USDT': '012', + 'USDC': '013', + 'GPSC': '020', + 'AMTS': '030', +} + +# Parent ID type for VZ as a child of KoreChain +PARENT_ID_TYPE = 'b2' # TODO: confirm with Bryan + + +class KoreIDGenerator: + """ + Generates KoreID display numbers for events originating from Vector Zulu. + Requires a sequence store (Postgres) for monthly sequence tracking. + """ + + def __init__(self, pg_conn=None): + self.pg = pg_conn + + def _xor_checksum(self, hex_string: str) -> str: + """XOR all bytes of a hex string, return 1-hex result.""" + bytes_list = [int(hex_string[i:i+2], 16) for i in range(0, len(hex_string), 2)] + result = reduce(lambda a, b: a ^ b, bytes_list, 0) + return format(result & 0xF, 'x') + + def _get_sequence(self, mmyy: str, id_type: str, treasury: str, + network: str, currency: str) -> tuple[int, int]: + """ + Get or create monthly sequence entry. + Returns (parent_seq, current_seq) — increments current_seq. + Falls back to random if no DB connection. + """ + if not self.pg: + import random + return (random.randint(0, 0xFFFF), random.randint(0, 0xFFFF)) + + try: + with self.pg.cursor() as cur: + cur.execute(""" + INSERT INTO kore_sequence (mmyy, ct, t, net, ccc, parent_seq, current_seq) + VALUES (%s, %s, %s, %s, %s, 0, 1) + ON CONFLICT (mmyy, ct, t, net, ccc) + DO UPDATE SET current_seq = kore_sequence.current_seq + 1 + RETURNING parent_seq, current_seq + """, (mmyy, id_type, treasury, network, currency)) + row = cur.fetchone() + self.pg.commit() + return (row[0], row[1]) + except Exception as e: + log.error(f"Sequence DB error: {e}") + import random + return (random.randint(0, 0xFFFF), random.randint(0, 0xFFFF)) + + def generate( + self, + event_type: str, + currency: str, + current_network: str = 'ETHEREUM', + parent_network: str = 'KORECHAIN', + treasury: str = 'VECTOR_ZULU', + parent_treasury: str = 'KORE_COLLECTIVE', + nonce: str = None, + ) -> dict: + """ + Generate a KoreID for a Vector Zulu event. + + Returns dict with: + - koreid: the display ID string + - guid: UUIDv4 for internal storage + - metadata: all components for traceability + """ + now = datetime.now(timezone.utc) + mmyy = now.strftime('%m%y') + ddmmyy = now.strftime('%d%m%y') + + # Resolve codes + ct = ID_TYPES.get(event_type.upper(), 'ff') + pt = PARENT_ID_TYPE + t = TREASURIES.get(treasury.upper(), '0') + pt_val = TREASURIES.get(parent_treasury.upper(), '0') + pnnnnn = PARENT_NETWORKS.get(parent_network.upper(), '00000') + ccc = CURRENCIES.get(currency.upper(), '000') + net8 = CURRENT_NETWORKS.get(current_network.upper(), '00000000') + + # S1: DDMMYY + CT + s1 = ddmmyy + ct # 8 chars + + # S2: PT + T(parent) + PNNNNN + s2 = pt + pt_val + pnnnnn # 8 chars + + # S3: CCC + checksum + ck = self._xor_checksum(ccc + '0') # XOR of currency bytes + s3 = ccc + ck # 4 chars + + # S4 / S5: split current network code + s4 = net8[:4] # 4 chars + s5 = net8[4:] # 4 chars + + # S6: PPPP + T(current) + CCCC + RRR + parent_seq, current_seq = self._get_sequence(mmyy, ct, t, pnnnnn, ccc) + pppp = format(parent_seq & 0xFFFF, '04x') + cccc = format(current_seq & 0xFFFF, '04x') + + if nonce is None: + import random + nonce = format(random.randint(0, 0xFFF), '03x') + + s6 = pppp + t + cccc + nonce # 12 chars + + koreid = f"{s1}/{s2}-{s3}-{s4}-{s5}-{s6}" + guid = str(uuid.uuid4()) + + return { + 'koreid': koreid, + 'guid': guid, + 'components': { + 's1': s1, 's2': s2, 's3': s3, + 's4': s4, 's5': s5, 's6': s6, + }, + 'metadata': { + 'event_type': event_type, + 'currency': currency, + 'current_network': current_network, + 'parent_network': parent_network, + 'treasury': treasury, + 'generated_at': now.isoformat(), + 'id_type_code': ct, + 'parent_seq': parent_seq, + 'current_seq': current_seq, + } + } + + +def wrap_ethereum_event( + event_type: str, + amount: float, + currency: str, + tx_hash: str, + block_number: int, + from_address: str, + to_address: str, + token_symbol: str, + koreid_generator: KoreIDGenerator, + linked_koreid: str = None, +) -> dict: + """ + Convert an Ethereum event into a KoreID-formatted payload + for posting back to KoreChain via KoreOracle. + + Preserves full traceability per Bryan's spec: + - KoreID reference + - hash (TX hash) + - signature (block + tx proof) + - chain linkage + """ + kid = koreid_generator.generate( + event_type=event_type, + currency=currency, + current_network='ETHEREUM', + parent_network='KORECHAIN', + ) + + payload = { + 'koreid': kid['koreid'], + 'guid': kid['guid'], + 'value': amount, + 'proof': { + 'hash': tx_hash, + 'block_number': block_number, + 'chain_reference': kid['koreid'], + 'network': 'ETHEREUM', + 'network_code': CURRENT_NETWORKS.get('ETHEREUM'), + }, + 'source': { + 'type': 'ETHEREUM_EVENT', + 'event_type': event_type, + 'token': token_symbol, + 'from_address': from_address, + 'to_address': to_address, + 'tx_hash': tx_hash, + }, + 'linked_to': linked_koreid, + 'generated_at': datetime.now(timezone.utc).isoformat(), + } + + return payload + + +if __name__ == '__main__': + # Test generation + gen = KoreIDGenerator() + result = gen.generate('MINT', 'GBP', 'ETHEREUM', 'KORECHAIN', 'VECTOR_ZULU') + print("Generated KoreID:", result['koreid']) + print("GUID:", result['guid']) + print("Components:", result['components']) + + # Test event wrapping + payload = wrap_ethereum_event( + event_type='MINT', + amount=1000000.00, + currency='GBP', + tx_hash='0xabc123def456', + block_number=25375588, + from_address='0x0000000000000000000000000000000000000000', + to_address='0x8B59352944134D6c2e175d92Bb07b25443163A53', + token_symbol='SVGBP', + koreid_generator=gen, + ) + import json + print("\nWrapped payload:") + print(json.dumps(payload, indent=2))