You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

266 lines
10 KiB

# -*- coding: utf-8 -*-
import asyncio
import pickle
import jmbitcoin as btc
from jmbase.support import jmprint, EXIT_FAILURE, twisted_sys_exit, get_log
jlog = get_log()
class IPCBase:
def encrypt_msg(self, msg_dict):
msg_bytes = pickle.dumps(msg_dict)
return btc.ecies_encrypt(msg_bytes, self.pubkey) + b'\n'
def decrypt_msg(self, enc_bytes):
msg_bytes = btc.ecies_decrypt(self.wallet._hostseckey, enc_bytes)
return pickle.loads(msg_bytes)
class FrostIPCServer(IPCBase):
def __init__(self, wallet):
self.loop = asyncio.get_event_loop()
self.wallet = wallet
self.pubkey = btc.privkey_to_pubkey(wallet._hostseckey)
self.sock_path = f'{wallet._storage.get_location()}.sock'
self.srv = None
self.sr = None
self.sw = None
self.tasks = set()
async def async_init(self):
self.srv = await asyncio.start_unix_server(
self.handle_connection, self.sock_path)
async def serve_forever(self):
return await self.srv.serve_forever()
async def handle_connection(self, sr, sw):
if self.sr or self.sw:
jlog.error('FrostIPCServer.handle_connection: client '
'already connected, ignore other connection attempt')
return
jlog.info('FrostIPCServer.handle_connection: connected new client')
self.sr = sr
self.sw = sw
await self.process_msgs()
async def process_msgs(self):
while True:
try:
line_data = await self.sr.readline()
if not line_data:
if self.sr.at_eof():
jlog.info('FrostIPCServer.process_msg: '
'client disconnected')
self.sr = None
self.sw = None
while self.tasks:
task = self.tasks.pop()
task.cancel()
break
else:
jlog.error('FrostIPCServer.process_msg: '
'empty line ignored')
continue
enc_bytes = line_data.strip()
msg_dict = self.decrypt_msg(enc_bytes)
msg_id = msg_dict['msg_id']
cmd = msg_dict['cmd']
data = msg_dict['data']
task = None
if cmd == 'get_dkg_pubkey':
task = self.loop.create_task(
self.on_get_dkg_pubkey(msg_id, *data))
elif cmd == 'frost_sign':
task = self.loop.create_task(
self.on_frost_sign(msg_id, *data))
if task:
self.tasks.add(task)
except Exception as e:
jlog.error(f'FrostIPCServer.process_msgs: {repr(e)}')
await asyncio.sleep(0.1)
async def on_get_dkg_pubkey(self, msg_id, mixdepth, address_type, index):
try:
wallet = self.wallet
dkg = wallet.dkg
new_pubkey = dkg.find_dkg_pubkey(mixdepth, address_type, index)
if new_pubkey is None:
client = wallet.client_factory.getClient()
frost_client = wallet.client_factory.client
frost_client.dkg_gen_list.append(
(mixdepth, address_type, index))
await client.dkg_gen()
new_pubkey = dkg.find_dkg_pubkey(mixdepth, address_type, index)
if new_pubkey:
await self.send_dkg_pubkey(msg_id, new_pubkey)
else:
raise Exception('No pubkey found or generated')
except Exception as e:
await self.send_dkg_pubkey(msg_id, None)
jlog.error(f'FrostIPCServer.on_get_dkg_pubkey: {repr(e)}')
async def send_dkg_pubkey(self, msg_id, pubkey):
try:
msg_dict = {
'msg_id': msg_id,
'cmd': 'dkg_pubkey',
'data': pubkey,
}
self.sw.write(self.encrypt_msg(msg_dict))
await self.sw.drain()
except Exception as e:
jlog.error(f'FrostIPCServer.send_dkg_pubkey: {repr(e)}')
async def on_frost_sign(self, msg_id, mixdepth, address_type, index,
sighash):
try:
wallet = self.wallet
client = wallet.client_factory.getClient()
frost_client = wallet.client_factory.client
dkg = wallet.dkg
dkg_session_id = dkg.find_session(mixdepth, address_type, index)
session_id, _, _ = client.frost_init(dkg_session_id, sighash)
sig, tweaked_pubkey = await frost_client.wait_on_sig(session_id)
pubkey = dkg.find_dkg_pubkey(mixdepth, address_type, index)
await self.send_frost_sig(msg_id, sig, pubkey, tweaked_pubkey)
except Exception as e:
jlog.error(f'FrostIPCServer.on_frost_sign: {repr(e)}')
await self.send_frost_sig(msg_id, None, None, None)
async def send_frost_sig(self, msg_id, sig, pubkey, tweaked_pubkey):
try:
msg_dict = {
'msg_id': msg_id,
'cmd': 'frost_sig',
'data': (sig, pubkey, tweaked_pubkey),
}
self.sw.write(self.encrypt_msg(msg_dict))
await self.sw.drain()
except Exception as e:
jlog.error(f'FrostIPCServer.send_frost_sig: {repr(e)}')
class FrostIPCClient(IPCBase):
def __init__(self, wallet):
self.loop = asyncio.get_event_loop()
self.msg_id = 0
self.msg_futures = {}
self.wallet = wallet
self.pubkey = btc.privkey_to_pubkey(wallet._hostseckey)
self.sock_path = f'{wallet._storage.get_location()}.sock'
self.sr = None
self.sw = None
async def async_init(self):
try:
self.sr, self.sw = await asyncio.open_unix_connection(
self.sock_path)
self.loop.create_task(self.process_msgs())
except ConnectionRefusedError as e:
jmprint('No servefrost socket found. Run wallet-tool.py '
'wallet.jmdat servefrost in separate console.', "error")
twisted_sys_exit(EXIT_FAILURE)
async def process_msgs(self):
while True:
try:
line_data = await self.sr.readline()
if not line_data:
if self.sr.at_eof():
jlog.info('FrostIPCClient.process_msg: '
'client disconnected')
self.sr = None
self.sw = None
for msg_id, fut in list(self.msg_futures.items()):
fut = self.msg_futures.pop(msg_id)
fut.cancel()
break
else:
jlog.error('FrostIPCClient.process_msg: '
'empty line ignored')
continue
enc_bytes = line_data.strip()
msg_dict = self.decrypt_msg(enc_bytes)
msg_id = msg_dict['msg_id']
cmd = msg_dict['cmd']
data = msg_dict['data']
if cmd in ['dkg_pubkey', 'frost_sig']:
await self.on_response(msg_id, data)
except Exception as e:
jlog.error(f'FrostIPCClient.process_msgs: {repr(e)}')
await asyncio.sleep(0.1)
async def on_response(self, msg_id, data):
fut = self.msg_futures.pop(msg_id, None)
if fut:
fut.set_result(data)
async def get_dkg_pubkey(self, mixdepth, address_type, index):
jlog.debug(f'FrostIPCClient.get_dkg_pubkey for mixdepth={mixdepth}, '
f'address_type={address_type}, index={index}')
try:
self.msg_id += 1
msg_dict = {
'msg_id': self.msg_id,
'cmd': 'get_dkg_pubkey',
'data': (mixdepth, address_type, index),
}
self.sw.write(self.encrypt_msg(msg_dict))
await self.sw.drain()
fut = self.loop.create_future()
self.msg_futures[self.msg_id] = fut
await fut
pubkey = fut.result()
if pubkey is None:
jlog.error(
f'FrostIPCClient.get_dkg_pubkey got None pubkey from '
f'FrostIPCServer for mixdepth={mixdepth}, '
f'address_type={address_type}, index={index}')
return pubkey
jlog.debug(f'FrostIPCClient.get_dkg_pubkey successfully got '
f'pubkey for mixdepth={mixdepth}, '
f'address_type={address_type}, index={index}')
return pubkey
except Exception as e:
jlog.error(f'FrostIPCClient.get_dkg_pubkey: {repr(e)}')
async def frost_sign(self, mixdepth, address_type, index, sighash):
jlog.debug(f'FrostIPCClient.frost_sign for mixdepth={mixdepth}, '
f'address_type={address_type}, index={index}, '
f'sighash={sighash.hex()}')
try:
self.msg_id += 1
msg_dict = {
'msg_id': self.msg_id,
'cmd': 'frost_sign',
'data': (mixdepth, address_type, index, sighash),
}
self.sw.write(self.encrypt_msg(msg_dict))
await self.sw.drain()
fut = self.loop.create_future()
self.msg_futures[self.msg_id] = fut
await fut
sig, pubkey, tweaked_pubkey = fut.result()
if sig is None:
jlog.error(
f'FrostIPCClient.frost_sign got None sig value from '
f'FrostIPCServer for mixdepth={mixdepth}, '
f'address_type={address_type}, index={index}, '
f'sighash={sighash.hex()}')
return sig, pubkey, tweaked_pubkey
jlog.debug(
f'FrostIPCClient.frost_sign successfully got signature '
f'for mixdepth={mixdepth}, address_type={address_type}, '
f'index={index}, sighash={sighash.hex()}')
return sig, pubkey, tweaked_pubkey
except Exception as e:
jlog.error(f'FrostIPCClient.frost_sign: {repr(e)}')
return None, None, None