Skip to content
Snippets Groups Projects
Commit 2a9849f5 authored by Pascal Engélibert's avatar Pascal Engélibert :bicyclist:
Browse files

Change peer system

parent 60c57a6d
No related branches found
No related tags found
No related merge requests found
Pipeline #5707 passed
...@@ -5,6 +5,8 @@ run in an onion network, guaranteeing a strong anonymity. ...@@ -5,6 +5,8 @@ run in an onion network, guaranteeing a strong anonymity.
## How to install it ## How to install it
**Warning**: This version works with a dev version of Silkaj. If the latest release of Silkaj is still `0.7.1`, please use [this branch](https://git.duniter.org/clients/python/silkaj/tree/223_endpoint_click_decoupled) (you can also copy `network_tools.py` from this branch).
### ➤ Debian (Ubuntu, Mint, etc.) ### ➤ Debian (Ubuntu, Mint, etc.)
sh install_gmixer.sh sh install_gmixer.sh
......
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
CopyLeft 2019 Pascal Engélibert <tuxmain@zettascript.org>
ĞMixer-py Test Client (only for testing server) ĞMixer-py Test Client (only for testing server)
This file is part of ĞMixer-py. This file is part of ĞMixer-py.
...@@ -21,16 +22,13 @@ Sources: ...@@ -21,16 +22,13 @@ Sources:
https://www.ietf.org/rfc/rfc3092.txt <- Very important RFC, please read it https://www.ietf.org/rfc/rfc3092.txt <- Very important RFC, please read it
""" """
import sys, os, asyncio, getpass, random, time, secrets import sys, os, asyncio, getpass, random, time, secrets, socket
import ubjson import ubjson
import plyvel import plyvel
import libnacl.sign import libnacl.sign
from duniterpy.key import SigningKey, PublicKey from duniterpy.key import SigningKey, PublicKey
import utils import utils
VERSION = "0.1.0"
AUTHORS = ["Pascal Engélibert <tuxmain@zettascript.org>"]
DIR = "~/.gmixer" DIR = "~/.gmixer"
class Confirmation(): class Confirmation():
...@@ -66,27 +64,27 @@ class Confirmation(): ...@@ -66,27 +64,27 @@ class Confirmation():
} }
def get_peers(host, proxy=None, proxy_onion_only=False): def get_peers(host, proxy=None, proxy_onion_only=False):
header, content = utils.sdata(host, "GET", "/pubkey/list", proxy=proxy, proxy_onion_only=proxy_onion_only) header, content = utils.sdata(host, "GET", "/peers/info", proxy=proxy, proxy_onion_only=proxy_onion_only)
try: try:
data = ubjson.loadb(content) data = ubjson.loadb(content)
assert "pubkey" in data and "peers" in data assert "info" in data and "peers" in data
assert type(data["pubkey"]) == str and type(data["peers"]) == list assert type(data["peers"]) == list
except (ubjson.decoder.DecoderException, AssertionError): except (ubjson.decoder.DecoderException, AssertionError):
print("Error: bad UBJSON") print("Error: bad UBJSON")
return return
peers = [utils.Peer(data["pubkey"], host[0], host[1], True)] peers = [utils.Peer(data["info"]), *[utils.Peer(p) for p in data["peers"]]]
for peer in data["peers"]: #peers = [utils.Peer(p) for p in data["peers"]]
peers.append(utils.Peer(peer["pubkey"], peer["host"], peer["port"], peer["up"])) print([p.pubkey for p in peers])
return peers return peers
def build_path(peers, receiver, layers=3): def build_path(peers, receiver, layers=3):
up_peers = [] up_peers = []
for peer in peers: for peer in peers:
if peer.up: #if peer.up:
up_peers.append(peer) up_peers.append(peer)
if len(up_peers) < layers: if len(up_peers) < layers:
return None, None return None, None
path = [] path = []
...@@ -99,11 +97,11 @@ def build_path(peers, receiver, layers=3): ...@@ -99,11 +97,11 @@ def build_path(peers, receiver, layers=3):
break break
path.append(peer.pubkey) path.append(peer.pubkey)
if host == None: if host == None:
host = (peer.host, peer.port) host = peer.host
path.append(receiver) path.append(receiver)
return host, path return host, path
def mix(db_txs, amount, base, sender, path, host, proxy=None, proxy_onion_only=False, send_tx=True): async def mix(db_txs, amount, base, sender, path, host, proxy=None, proxy_onion_only=False, send_tx=True):
start_time = time.time() start_time = time.time()
onetime_keys = [] onetime_keys = []
...@@ -254,14 +252,14 @@ def mix(db_txs, amount, base, sender, path, host, proxy=None, proxy_onion_only=F ...@@ -254,14 +252,14 @@ def mix(db_txs, amount, base, sender, path, host, proxy=None, proxy_onion_only=F
if send_tx: if send_tx:
try: try:
utils.send_transaction(sender, path[0], amount, utils.gen_comment(comment_seeds[0])) await utils.send_transaction(sender, path[0], amount, utils.gen_comment(comment_seeds[0]))
message["sent"] = True message["sent"] = True
db_txs.put(comment_seeds[0][1], PublicKey(sender.pubkey).encrypt_seal(ubjson.dumpb(message))) db_txs.put(comment_seeds[0][1], PublicKey(sender.pubkey).encrypt_seal(ubjson.dumpb(message)))
except socket.timeout: except socket.timeout:
utils.logprint("Error when sending tx: timeout", LOG_ERROR) print("Error when sending tx: timeout")
except Exception as e: except Exception as e:
utils.logprint("Error when sending tx: " + str(e), LOG_ERROR) print("Error when sending tx: " + str(e))
return return
async def main(db_txs, host, receiver, amount=1000, layers=3, proxy=None, proxy_onion_only=False, send_tx=True): async def main(db_txs, host, receiver, amount=1000, layers=3, proxy=None, proxy_onion_only=False, send_tx=True):
...@@ -292,11 +290,11 @@ async def main(db_txs, host, receiver, amount=1000, layers=3, proxy=None, proxy_ ...@@ -292,11 +290,11 @@ async def main(db_txs, host, receiver, amount=1000, layers=3, proxy=None, proxy_
if input("OK? [yn]: ").lower() == "y": if input("OK? [yn]: ").lower() == "y":
break break
mix(db_txs, amount, 0, keys, path, host1, proxy, proxy_onion_only, send_tx) await mix(db_txs, amount, 0, keys, path, host1, proxy, proxy_onion_only, send_tx)
if __name__ == "__main__": if __name__ == "__main__":
if "--help" in sys.argv: if "--help" in sys.argv:
print("ĞMixer-py client "+VERSION+""" print("ĞMixer-py client "+utils.VERSION+"""
Options: Options:
-r <pubkey> receiver pubkey -r <pubkey> receiver pubkey
......
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
CopyLeft 2019 Pascal Engélibert <tuxmain@zettascript.org>
This file is part of ĞMixer-py. This file is part of ĞMixer-py.
ĞMixer-py is free software: you can redistribute it and/or modify ĞMixer-py is free software: you can redistribute it and/or modify
...@@ -17,7 +18,7 @@ ...@@ -17,7 +18,7 @@
along with ĞMixer-py. If not, see <https://www.gnu.org/licenses/>. along with ĞMixer-py. If not, see <https://www.gnu.org/licenses/>.
""" """
import sys, os, json, asyncio, getpass, random, time, socket, secrets import sys, os, json, asyncio, getpass, random, time, socket, secrets, base64
from threading import Thread from threading import Thread
import ubjson import ubjson
import plyvel import plyvel
...@@ -28,9 +29,6 @@ from duniterpy.api.client import Client ...@@ -28,9 +29,6 @@ from duniterpy.api.client import Client
from duniterpy.key import SigningKey, PublicKey from duniterpy.key import SigningKey, PublicKey
import utils import utils
VERSION = "0.1.0"
AUTHORS = ["Pascal Engélibert <tuxmain@zettascript.org>"]
""" """
Used terms: Used terms:
sender_ = node which has sent something to me sender_ = node which has sent something to me
...@@ -54,6 +52,9 @@ BMA_HOSTS = ["g1.duniter.fr 443", "g1.duniter.org 443", "g1.presles.fr 443", "g1 ...@@ -54,6 +52,9 @@ BMA_HOSTS = ["g1.duniter.fr 443", "g1.duniter.org 443", "g1.presles.fr 443", "g1
MIX_INTERVAL = 60 MIX_INTERVAL = 60
MIX_MIN_TXS = 5 # minimum amount of txs to mix MIX_MIN_TXS = 5 # minimum amount of txs to mix
MIX_REQ_AGE_MAX = 604800 # maximum mix request age before return to sender MIX_REQ_AGE_MAX = 604800 # maximum mix request age before return to sender
PEER_INFO_INTERVAL = 60 # interval for renewing my peer document
PEER_SIG_AGE_MAX = 600 # max age of a peer document signature
PEER_DETECT_INTERVAL = 120 # interval for fetching peer list
def send_response(client, code, resp, dataformat="ubjson"): def send_response(client, code, resp, dataformat="ubjson"):
if dataformat == "ubjson": if dataformat == "ubjson":
...@@ -88,6 +89,8 @@ class TX: ...@@ -88,6 +89,8 @@ class TX:
self.can_confirm = False self.can_confirm = False
self.need_confirm = True self.need_confirm = True
self.confirms = b"" self.confirms = b""
self.sender_hash = None
self.receiver_hash = None
self.tx_sent = False self.tx_sent = False
def gen_mix_confirm(self, keys): def gen_mix_confirm(self, keys):
...@@ -128,11 +131,13 @@ class TX: ...@@ -128,11 +131,13 @@ class TX:
"send_confirm": self.send_confirm, "send_confirm": self.send_confirm,
"date": self.date, "date": self.date,
"expire": self.expire, "expire": self.expire,
"need_send" : self.need_send, "need_send": self.need_send,
"can_confirm" : self.can_confirm, "can_confirm": self.can_confirm,
"need_confirm" : self.need_confirm, "need_confirm": self.need_confirm,
"confirms" : self.confirms, "confirms": self.confirms,
"tx_sent" : self.tx_sent, "tx_sent": self.tx_sent,
"sender_hash": self.sender_hash,
"receiver_hash": self.receiver_hash
})) }))
def import_ubjson(d): def import_ubjson(d):
...@@ -143,6 +148,8 @@ class TX: ...@@ -143,6 +148,8 @@ class TX:
tx.need_confirm = d["need_confirm"] tx.need_confirm = d["need_confirm"]
tx.confirms = d["confirms"] tx.confirms = d["confirms"]
tx.tx_sent = d["tx_sent"] tx.tx_sent = d["tx_sent"]
tx.sender_hash = d["sender_hash"]
tx.receiver_hash = d["receiver_hash"]
return tx return tx
def load_txs(db_txs, pool, tx_in_index, tx_out_index): def load_txs(db_txs, pool, tx_in_index, tx_out_index):
...@@ -158,7 +165,7 @@ def save_txs(db_txs, pool): ...@@ -158,7 +165,7 @@ def save_txs(db_txs, pool):
for tx in pool: for tx in pool:
tx.export_ubjson(db_txs) tx.export_ubjson(db_txs)
# Read ini config file # Read json config file
def read_config(cdir, conf_overwrite={}): def read_config(cdir, conf_overwrite={}):
if not os.path.isfile(cdir+"/config.json"): if not os.path.isfile(cdir+"/config.json"):
configfile = open(cdir+"/config.json", "w") configfile = open(cdir+"/config.json", "w")
...@@ -177,6 +184,9 @@ def read_config(cdir, conf_overwrite={}): ...@@ -177,6 +184,9 @@ def read_config(cdir, conf_overwrite={}):
conf["server"].setdefault("bind_port", BIND_PORT) conf["server"].setdefault("bind_port", BIND_PORT)
conf["server"].setdefault("public_host", PUBLIC_HOST) conf["server"].setdefault("public_host", PUBLIC_HOST)
conf["server"].setdefault("public_port", PUBLIC_PORT) conf["server"].setdefault("public_port", PUBLIC_PORT)
conf["server"].setdefault("peer_info_interval", PEER_INFO_INTERVAL)
conf["server"].setdefault("peer_sig_age_max", PEER_SIG_AGE_MAX)
conf["server"].setdefault("peer_detect_interval", PEER_DETECT_INTERVAL)
conf.setdefault("client", {}) conf.setdefault("client", {})
conf["client"].setdefault("bma_hosts", BMA_HOSTS) conf["client"].setdefault("bma_hosts", BMA_HOSTS)
conf["client"].setdefault("proxy", None) conf["client"].setdefault("proxy", None)
...@@ -188,6 +198,9 @@ def read_config(cdir, conf_overwrite={}): ...@@ -188,6 +198,9 @@ def read_config(cdir, conf_overwrite={}):
conf["mix"].setdefault("mix_interval", MIX_INTERVAL) conf["mix"].setdefault("mix_interval", MIX_INTERVAL)
conf["mix"].setdefault("mix_min_txs", MIX_MIN_TXS) conf["mix"].setdefault("mix_min_txs", MIX_MIN_TXS)
conf["mix"].setdefault("mix_req_age_max", MIX_REQ_AGE_MAX) conf["mix"].setdefault("mix_req_age_max", MIX_REQ_AGE_MAX)
conf.setdefault("idty", {})
conf["idty"].setdefault("sig", "")
conf["idty"].setdefault("pubkey", "")
for key in conf_overwrite: for key in conf_overwrite:
c = conf c = conf
...@@ -202,13 +215,13 @@ def read_config(cdir, conf_overwrite={}): ...@@ -202,13 +215,13 @@ def read_config(cdir, conf_overwrite={}):
return conf return conf
class ServerThread(Thread): class ServerThread(Thread):
def __init__(self, conf, peers, peers_index, keys, pool, tx_in_index, tx_out_index, db_txs): def __init__(self, conf, peers, keys, local_peer, pool, tx_in_index, tx_out_index, db_txs):
Thread.__init__(self) Thread.__init__(self)
self.conf = conf self.conf = conf
self.peers = peers self.peers = peers
self.peers_index = peers_index
self.keys = keys self.keys = keys
self.local_peer = local_peer
self.pool = pool self.pool = pool
self.tx_in_index = tx_in_index self.tx_in_index = tx_in_index
self.tx_out_index = tx_out_index self.tx_out_index = tx_out_index
...@@ -289,23 +302,40 @@ class ServerThread(Thread): ...@@ -289,23 +302,40 @@ class ServerThread(Thread):
resp["pubkey"] = self.keys.pubkey resp["pubkey"] = self.keys.pubkey
if "version" in url: if "version" in url:
resp["version"] = VERSION resp["version"] = utils.VERSION
if "mix" in url: if "mix" in url:
sender_pubkey = utils.getargv("mix", "", 1, url) entry_node = "client" in url
if entry_node:
sender_pubkey = utils.getargv("mix", "", 1, url)
try:
sender_keys = PublicKey(sender_pubkey)
except ValueError:
send_response(client, "401 Unauthorized", {"error": "bad_sender_pubkey"}, resp_format)
continue
else:
try:
sender_hash = base64.urlsafe_b64decode(utils.getargv("mix", "", 1, url))
except base64.binascii.Error:
send_response(client, "401 Unauthorized", {"error": "bad_url"}, resp_format)
continue
if not sender_hash in self.peers:
send_response(client, "401 Unauthorized", {"error": "unknown_peer"}, resp_format)
continue
peer = self.peers[sender_hash]
sender_pubkey = peer.pubkey
sender_keys = peer.keys
try: try:
in_amount = int(utils.getargv("mix", "", 2, url)) in_amount = int(utils.getargv("mix", "", 2, url))
in_base = int(utils.getargv("mix", "", 3, url)) in_base = int(utils.getargv("mix", "", 3, url))
except ValueError: except ValueError:
send_response(client, "401 Unauthorized", {"error": "bad_amount_base"}, resp_format) send_response(client, "401 Unauthorized", {"error": "bad_amount_base"}, resp_format)
continue continue
send_confirm = not "client" in url
try:
sender_keys = PublicKey(sender_pubkey)
except ValueError:
send_response(client, "401 Unauthorized", {"error": "bad_sender_pubkey"}, resp_format)
continue
try: try:
raw = libnacl.sign.Verifier(sender_keys.hex_pk()).verify(content) # Verify raw = libnacl.sign.Verifier(sender_keys.hex_pk()).verify(content) # Verify
...@@ -355,10 +385,12 @@ class ServerThread(Thread): ...@@ -355,10 +385,12 @@ class ServerThread(Thread):
# Save tx in pool # Save tx in pool
t = time.time() t = time.time()
tx = TX(sender_pubkey, receiver_pubkey, onetime_pubkey, in_amount, in_base, in_amount, in_base, message, in_seeds, out_seeds, send_confirm, t, t+self.conf["mix"]["mix_req_age_max"]) tx = TX(sender_pubkey, receiver_pubkey, onetime_pubkey, in_amount, in_base, in_amount, in_base, message, in_seeds, out_seeds, not entry_node, t, t+self.conf["mix"]["mix_req_age_max"])
last_node = len(message) == 0 last_node = len(message) == 0
tx.need_send = not last_node tx.need_send = not last_node
tx.can_confirm = last_node tx.can_confirm = last_node
if not entry_node:
tx.sender_hash = peer.hash
utils.logprint("TX "+tx.sender_pubkey[:8]+" -> "+tx.receiver_pubkey[:8]+" = "+str(tx.in_amount)+":"+str(tx.in_base)+" -> "+str(tx.out_amount)+":"+str(tx.out_base), utils.LOG_TRACE) utils.logprint("TX "+tx.sender_pubkey[:8]+" -> "+tx.receiver_pubkey[:8]+" = "+str(tx.in_amount)+":"+str(tx.in_base)+" -> "+str(tx.out_amount)+":"+str(tx.out_base), utils.LOG_TRACE)
self.tx_out_index[out_seeds[1]] = tx self.tx_out_index[out_seeds[1]] = tx
self.tx_in_index[in_seeds[1]] = tx self.tx_in_index[in_seeds[1]] = tx
...@@ -368,24 +400,39 @@ class ServerThread(Thread): ...@@ -368,24 +400,39 @@ class ServerThread(Thread):
resp["mix_ok"] = in_seeds[1] resp["mix_ok"] = in_seeds[1]
elif "confirm" in url: elif "confirm" in url:
receiver_pubkey = utils.getargv("confirm", "", 1, url) try:
receiver_hash = base64.urlsafe_b64decode(utils.getargv("confirm", "", 1, url))
except base64.binascii.Error:
send_response(client, "401 Unauthorized", {"error": "bad_url"}, resp_format)
continue
try: try:
out_seed1 = bytes.fromhex(utils.getargv("confirm", "", 2, url)) out_seed1 = bytes.fromhex(utils.getargv("confirm", "", 2, url))
except ValueError: except ValueError:
send_response(client, "401 Unauthorized", {"error": "bad_url"}, resp_format) send_response(client, "401 Unauthorized", {"error": "bad_url"}, resp_format)
continue continue
if not receiver_hash in self.peers:
send_response(client, "401 Unauthorized", {"error": "unknown_peer"}, resp_format)
continue
peer = self.peers[receiver_hash]
if not out_seed1 in self.tx_out_index: if not out_seed1 in self.tx_out_index:
send_response(client, "404 Not Found", {"error": "unknown_tx"}, resp_format) send_response(client, "404 Not Found", {"error": "unknown_tx"}, resp_format)
continue continue
tx = self.tx_out_index[out_seed1] tx = self.tx_out_index[out_seed1]
if peer.hash != tx.receiver_hash:
send_response(client, "404 Not Found", {"error": "unknown_tx"}, resp_format)
continue
if len(tx.confirms) > 0 or tx.can_confirm or not tx.need_confirm or tx.need_send: if len(tx.confirms) > 0 or tx.can_confirm or not tx.need_confirm or tx.need_send:
send_response(client, "403 Forbidden", {"error": "cannot_confirm"}, resp_format) send_response(client, "403 Forbidden", {"error": "cannot_confirm"}, resp_format)
continue continue
if receiver_pubkey != tx.receiver_pubkey: if peer.pubkey != tx.receiver_pubkey:
send_response(client, "401 Unauthorized", {"error": "bad_rec_pubkey"}, resp_format) send_response(client, "401 Unauthorized", {"error": "bad_rec_pubkey"}, resp_format)
continue continue
...@@ -443,44 +490,32 @@ class ServerThread(Thread): ...@@ -443,44 +490,32 @@ class ServerThread(Thread):
tx.export_ubjson(self.db_txs) tx.export_ubjson(self.db_txs)
utils.logprint("Confirmed "+tx.sender_pubkey[:8]+" -> "+tx.receiver_pubkey[:8]+" = "+str(tx.in_amount)+":"+str(tx.in_base)+" -> "+str(tx.out_amount)+":"+str(tx.out_base), utils.LOG_TRACE) utils.logprint("Confirmed "+tx.sender_pubkey[:8]+" -> "+tx.receiver_pubkey[:8]+" = "+str(tx.in_amount)+":"+str(tx.in_base)+" -> "+str(tx.out_amount)+":"+str(tx.out_base), utils.LOG_TRACE)
if "list" in url: if "peers" in url:
peers_list = [] resp["peers"] = [self.peers[peer].raw for peer in self.peers]
for peer in self.peers:
peers_list.append({"pubkey":peer.pubkey, "host":peer.host, "port":peer.port, "up":peer.up})
resp["peers"] = peers_list
if "new" in url: if "new" in url:
new_pubkey = utils.getargv("new", "", 1, url)
try: try:
new_keys = PublicKey(new_pubkey) new_peer = utils.Peer(content)
except ValueError: except Exception as e: # TODO more specific exceptions
send_response(client, "401 Unauthorized", {"error": "bad_pubkey"}, resp_format) send_response(client, "400 Bad Request", {"error": "bad_peer"}, resp_format)
continue utils.logprint("Peer: bad peer: "+str(e), utils.LOG_ERROR)
try:
message = libnacl.sign.Verifier(new_keys.hex_pk()).verify(content) # Verify
except ValueError:
send_response(client, "401 Unauthorized", {"error": "bad_signature"}, resp_format)
continue continue
try:
message = self.keys.decrypt_seal(message) # Decrypt if new_peer.sigtime + self.conf["server"]["peer_sig_age_max"] <= new_peer.rectime:
except libnacl.CryptError: send_response(client, "400 Bad Request", {"error": "too_old_sig"}, resp_format)
send_response(client, "403 Forbidden", {"error": "bad_encryption"}, resp_format) utils.logprint("Peer: too old sig: "+new_peer.to_human_str(), utils.LOG_WARN)
continue continue
try:
message = ubjson.loadb(message) if new_peer.hash in self.peers and new_peer.sigtime <= self.peers[new_peer.hash].sigtime:
except ubjson.decoder.DecoderException: send_response(client, "400 Bad Request", {"error": "more_recent_sig_exists"}, resp_format)
send_response(client, "400 Bad Request", {"error": "bad_ubjson"}, resp_format) utils.logprint("Peer: have more recent sig: "+new_peer.to_human_str(), utils.LOG_WARN)
continue continue
if new_pubkey == message["pubkey"]: utils.logprint("Peer: "+new_peer.to_human_str(), utils.LOG_TRACE)
if not new_pubkey in self.peers_index: self.peers[new_peer.hash] = new_peer
peer = utils.Peer(new_pubkey, message["host"], message["port"], True)
self.peers.append(peer) if "info" in url:
self.peers_index[new_pubkey] = peer resp["info"] = self.local_peer.raw
utils.logprint("Add "+str(peer), utils.LOG_TRACE)
else:
self.peers_index[new_pubkey].up = True
utils.logprint("Up "+str(peer), utils.LOG_TRACE)
# Send response # Send response
send_response(client, "200 OK", resp, resp_format) send_response(client, "200 OK", resp, resp_format)
...@@ -491,13 +526,13 @@ class ServerThread(Thread): ...@@ -491,13 +526,13 @@ class ServerThread(Thread):
self.sock.shutdown(socket.SHUT_WR) self.sock.shutdown(socket.SHUT_WR)
class ClientThread(Thread): class ClientThread(Thread):
def __init__(self, conf, peers, peers_index, keys, pool, tx_in_index, tx_out_index, db_txs): def __init__(self, conf, peers, keys, local_peer, pool, tx_in_index, tx_out_index, db_txs):
Thread.__init__(self) Thread.__init__(self)
self.conf = conf self.conf = conf
self.peers = peers self.peers = peers
self.peers_index = peers_index
self.keys = keys self.keys = keys
self.local_peer = local_peer
self.pool = pool self.pool = pool
self.tx_in_index = tx_in_index self.tx_in_index = tx_in_index
self.tx_out_index = tx_out_index self.tx_out_index = tx_out_index
...@@ -508,62 +543,86 @@ class ClientThread(Thread): ...@@ -508,62 +543,86 @@ class ClientThread(Thread):
def detect_peers(self):# Check known peers and ask them for their known peer list def detect_peers(self):# Check known peers and ask them for their known peer list
utils.logprint("Start peers detection", utils.LOG_TRACE) utils.logprint("Start peers detection", utils.LOG_TRACE)
modified = True
asked = []
while modified: """
modified = False Try to interrogate 3 different random peers. When possible, do not interrogate 2 peers pertaining to the same identity.
"""
new_peers = {}
left_peers = [self.peers[p] for p in self.peers] # peers we have not tested and pertaining to not tested identities
random.shuffle(left_peers)
left2_peers = left_peers.copy() # peers we have not tested
answered = 0 # number of up tested peers
while answered < 3 and len(left2_peers) > 0:
for peer in self.peers: # Choose a peer
if peer.pubkey in asked: if len(left_peers) > 0:
continue peer = left_peers.pop()
asked.append(peer.pubkey) left2_peers.remove(peer)
to_remove = []
message = ubjson.dumpb({ for i in left_peers:
"pubkey": self.keys.pubkey, if i.idty == peer.idty:
"host": self.conf["server"]["public_host"], to_remove.append(i)
"port": self.conf["server"]["public_port"], for i in to_remove:
"time": time.time() left_peers.remove(i)
}) else:
message = peer.keys.encrypt_seal(message) # Encrypt peer = left2_peers.pop()
message = self.keys.sign(message) # Sign
utils.logprint("Ask "+str(peer), utils.LOG_TRACE) # Ask the chosen peer
try:
header, content = utils.sdata(peer.host, "GET", "/peers/info", proxy=self.conf["client"]["proxy"], proxy_onion_only=self.conf["client"]["proxy_onion_only"])
try: try:
header, content = utils.sdata((peer.host, peer.port), "POST", "/list/new/"+self.keys.pubkey, message, proxy=self.conf["client"]["proxy"], proxy_onion_only=self.conf["client"]["proxy_onion_only"]) # Send data = ubjson.loadb(content)
except (ConnectionRefusedError, socks.GeneralProxyError, socket.gaierror, socket.timeout): assert "peers" in data and type(data["peers"]) == list , "no peer list"
peer.up = False assert "info" in data , "no peer info"
utils.logprint("Down "+str(peer), utils.LOG_TRACE) except (ubjson.decoder.DecoderException, AssertionError) as e:
utils.logprint("Peer detection: Encoding error: "+peer.to_human_str()+"\n\t"+str(e), utils.LOG_WARN)
continue continue
peer.up = True
utils.logprint("Up "+str(peer), utils.LOG_TRACE)
try: try:
message = ubjson.loadb(content) new_peer = utils.Peer(data["info"])
assert "peers" in message except Exception as e:
assert type(message["peers"]) == list print("Peer detection: info: "+str(e))
except (ubjson.decoder.DecoderException, AssertionError): new_peers.setdefault(new_peer.hash, [])
utils.logprint("Bad json from "+str(peer), utils.LOG_ERROR) new_peers[new_peer.hash].append(new_peer)
continue
for i_peer in message["peers"]: for raw in data["peers"]:
try:
assert "pubkey" in i_peer and "host" in i_peer and "port" in i_peer
int(i_peer["port"])
except (AssertionError, ValueError):
utils.logprint("Bad json from "+str(peer), utils.LOG_ERROR)
continue
if i_peer["pubkey"] in self.peers_index or i_peer["pubkey"] == self.keys.pubkey:
continue
try: try:
new_peer = utils.Peer(i_peer["pubkey"], i_peer["host"], i_peer["port"], None) new_peer = utils.Peer(raw)
except ValueError: except Exception as e:
utils.logprint("Bad pubkey from "+str(peer), utils.LOG_ERROR) utils.logprint("Peer detection: "+str(e), utils.LOG_WARN)
if new_peer.hash == self.local_peer.hash:
continue continue
self.peers.append(new_peer) new_peers.setdefault(new_peer.hash, [])
self.peers_index[new_peer.pubkey] = new_peer new_peers[new_peer.hash].append(new_peer)
modified = True
utils.logprint("Add "+str(new_peer), utils.LOG_TRACE) answered += 1
except (ConnectionRefusedError, socks.GeneralProxyError, socket.gaierror, socket.timeout):
utils.logprint("Peer detection: Network error: "+peer.to_human_str(), utils.LOG_WARN)
# Choose the more recent peer infos
for peer in new_peers:
new_peer = max(new_peers[peer], key=lambda p: p.sigtime) # select the more recent
if new_peer.sigtime + self.conf["server"]["peer_sig_age_max"] <= new_peer.rectime or \
(peer in self.peers and new_peer.sigtime <= self.peers[peer].sigtime):
utils.logprint("Peer detection: too old sig", utils.LOG_TRACE)
continue
self.peers[peer] = new_peer
utils.logprint("Peer: "+new_peer.to_human_str(), utils.LOG_TRACE)
utils.logprint("Finished peers detection", utils.LOG_TRACE) utils.logprint("Finished peers detection", utils.LOG_TRACE)
def spread_peer_info(self):
utils.logprint("Start spreading peer info", utils.LOG_TRACE)
for peer in self.peers:
try:
utils.sdata(self.peers[peer].host, "POST", "/new", self.local_peer.raw, proxy=self.conf["client"]["proxy"], proxy_onion_only=self.conf["client"]["proxy_onion_only"])
except (ConnectionRefusedError, socks.GeneralProxyError, socket.gaierror, socket.timeout):
utils.logprint("Network error: "+self.peers[peer].to_human_str(), utils.LOG_WARN)
utils.logprint("Finished spreading peer info", utils.LOG_TRACE)
async def mix(self): async def mix(self):
can_mix = False can_mix = False
for tx in self.pool: for tx in self.pool:
...@@ -624,7 +683,7 @@ class ClientThread(Thread): ...@@ -624,7 +683,7 @@ class ClientThread(Thread):
random.shuffle(txs) random.shuffle(txs)
for tx in txs: for tx in txs:
try: try:
utils.send_transaction(self.keys, tx.receiver_pubkey, tx.out_amount, utils.gen_comment(tx.out_seeds)) await utils.send_transaction(self.keys, tx.receiver_pubkey, tx.out_amount, utils.gen_comment(tx.out_seeds))
tx.tx_sent = True tx.tx_sent = True
tx.export_ubjson(self.db_txs) tx.export_ubjson(self.db_txs)
except socket.timeout: except socket.timeout:
...@@ -642,8 +701,13 @@ class ClientThread(Thread): ...@@ -642,8 +701,13 @@ class ClientThread(Thread):
asyncio.new_event_loop().run_until_complete(self.start_client()) asyncio.new_event_loop().run_until_complete(self.start_client())
async def start_client(self): async def start_client(self):
next_peers_detection = 0 t = time.time()
next_mix = time.time() + self.conf["mix"]["mix_interval"] next_mix = t + self.conf["mix"]["mix_interval"]
next_peers_detection = t + self.conf["server"]["peer_detect_interval"]
next_peer_info = t + self.conf["server"]["peer_info_interval"]
self.detect_peers()
self.spread_peer_info()
while self.work: while self.work:
t = time.time() t = time.time()
...@@ -651,7 +715,7 @@ class ClientThread(Thread): ...@@ -651,7 +715,7 @@ class ClientThread(Thread):
# Detect peers # Detect peers
if t > next_peers_detection: if t > next_peers_detection:
self.detect_peers() self.detect_peers()
next_peers_detection = time.time()+120 next_peers_detection = time.time() + self.conf["server"]["peer_detect_interval"]
# Mix # Mix
if t > next_mix: if t > next_mix:
...@@ -662,51 +726,66 @@ class ClientThread(Thread): ...@@ -662,51 +726,66 @@ class ClientThread(Thread):
if tx.need_send: if tx.need_send:
utils.logprint("Send "+tx.sender_pubkey[:8]+" -> "+tx.receiver_pubkey[:8]+" = "+str(tx.in_amount)+":"+str(tx.in_base)+" -> "+str(tx.out_amount)+":"+str(tx.out_base), utils.LOG_TRACE) utils.logprint("Send "+tx.sender_pubkey[:8]+" -> "+tx.receiver_pubkey[:8]+" = "+str(tx.in_amount)+":"+str(tx.in_base)+" -> "+str(tx.out_amount)+":"+str(tx.out_base), utils.LOG_TRACE)
if tx.receiver_pubkey in self.peers_index:
peer = self.peers_index[tx.receiver_pubkey] # Find all the peers with that pubkey
peers = []
for peer in self.peers:
if self.peers[peer].pubkey == tx.receiver_pubkey:
peers.append(self.peers[peer])
random.shuffle(peers)
for peer in peers:
message = self.keys.sign(tx.out_seeds[1] + tx.message) # Sign message = self.keys.sign(tx.out_seeds[1] + tx.message) # Sign
try: try:
header, content = utils.sdata((peer.host, peer.port), "POST", "/mix/"+self.keys.pubkey+"/"+str(tx.out_amount)+"/"+str(tx.out_base), message, proxy=self.conf["client"]["proxy"], proxy_onion_only=self.conf["client"]["proxy_onion_only"]) header, content = utils.sdata(peer.host, "POST", "/mix/"+base64.urlsafe_b64encode(self.local_peer.hash).decode()+"/"+str(tx.out_amount)+"/"+str(tx.out_base), message, proxy=self.conf["client"]["proxy"], proxy_onion_only=self.conf["client"]["proxy_onion_only"])
data = ubjson.loadb(content) data = ubjson.loadb(content)
assert data["mix_ok"] == tx.out_seeds[1] assert data["mix_ok"] == tx.out_seeds[1]
tx.need_send = False
tx.export_ubjson(self.db_txs)
peer.up = True
utils.logprint("Sent "+tx.sender_pubkey[:8]+" -> "+tx.receiver_pubkey[:8]+" = "+str(tx.in_amount)+":"+str(tx.in_base)+" -> "+str(tx.out_amount)+":"+str(tx.out_base), utils.LOG_TRACE)
utils.logprint("Up "+str(peer), utils.LOG_TRACE)
except (ConnectionRefusedError, socks.GeneralProxyError, socket.gaierror, socket.timeout): except (ConnectionRefusedError, socks.GeneralProxyError, socket.gaierror, socket.timeout):
peer.up = False peer.up = False
utils.logprint("Down "+str(peer), utils.LOG_TRACE) utils.logprint("Network error: "+peer.to_human_str(), utils.LOG_WARN)
continue
except (ubjson.decoder.DecoderException, KeyError, AssertionError): except (ubjson.decoder.DecoderException, KeyError, AssertionError):
utils.logprint("Error: bad response from "+str(peer), utils.LOG_WARN) utils.logprint("Bad response: "+peer.to_human_str(), utils.LOG_WARN)
continue
tx.need_send = False
tx.receiver_hash = peer.hash
tx.export_ubjson(self.db_txs)
utils.logprint("Sent "+tx.sender_pubkey[:8]+" -> "+tx.receiver_pubkey[:8]+" = "+str(tx.in_amount)+":"+str(tx.in_base)+" -> "+str(tx.out_amount)+":"+str(tx.out_base), utils.LOG_TRACE)
break
else: if len(peers) == 0:
utils.logprint("Unknown peer: "+tx.receiver_pubkey, utils.LOG_WARN) utils.logprint("No peer for: "+tx.receiver_pubkey, utils.LOG_WARN)
elif tx.can_confirm and tx.need_confirm and tx.send_confirm: elif tx.can_confirm and tx.need_confirm and tx.send_confirm:
utils.logprint("Confirm "+tx.sender_pubkey[:8]+" -> "+tx.receiver_pubkey[:8]+" = "+str(tx.in_amount)+":"+str(tx.in_base)+" -> "+str(tx.out_amount)+":"+str(tx.out_base), utils.LOG_TRACE) utils.logprint("Confirm "+tx.sender_pubkey[:8]+" -> "+tx.receiver_pubkey[:8]+" = "+str(tx.in_amount)+":"+str(tx.in_base)+" -> "+str(tx.out_amount)+":"+str(tx.out_base), utils.LOG_TRACE)
if tx.sender_pubkey in self.peers_index:
peer = self.peers_index[tx.sender_pubkey] if tx.sender_hash in self.peers:
peer = self.peers[tx.sender_hash]
message = tx.gen_mix_confirm(self.keys) message = tx.gen_mix_confirm(self.keys)
try: try:
header, content = utils.sdata((peer.host, peer.port), "POST", "/confirm/"+self.keys.pubkey+"/"+tx.in_seeds[1].hex(), message, proxy=self.conf["client"]["proxy"], proxy_onion_only=self.conf["client"]["proxy_onion_only"]) header, content = utils.sdata(peer.host, "POST", "/confirm/"+base64.urlsafe_b64encode(self.local_peer.hash).decode()+"/"+tx.in_seeds[1].hex(), message, proxy=self.conf["client"]["proxy"], proxy_onion_only=self.conf["client"]["proxy_onion_only"])
data = ubjson.loadb(content) data = ubjson.loadb(content)
assert data["confirm_ok"] == tx.in_seeds[2] assert data["confirm_ok"] == tx.in_seeds[2]
tx.need_confirm = False tx.need_confirm = False
tx.export_ubjson(self.db_txs) tx.export_ubjson(self.db_txs)
peer.up = True peer.up = True
utils.logprint("Confirmed "+tx.sender_pubkey[:8]+" -> "+tx.receiver_pubkey[:8]+" = "+str(tx.in_amount)+":"+str(tx.in_base)+" -> "+str(tx.out_amount)+":"+str(tx.out_base), utils.LOG_TRACE) utils.logprint("Confirmed "+tx.sender_pubkey[:8]+" -> "+tx.receiver_pubkey[:8]+" = "+str(tx.in_amount)+":"+str(tx.in_base)+" -> "+str(tx.out_amount)+":"+str(tx.out_base), utils.LOG_TRACE)
utils.logprint("Up "+str(peer), utils.LOG_TRACE)
except (ConnectionRefusedError, socks.GeneralProxyError, socket.gaierror, socket.timeout): except (ConnectionRefusedError, socks.GeneralProxyError, socket.gaierror, socket.timeout):
peer.up = False peer.up = False
utils.logprint("Down "+str(peer), utils.LOG_TRACE)
except (ubjson.decoder.DecoderException, KeyError, AssertionError): except (ubjson.decoder.DecoderException, KeyError, AssertionError):
utils.logprint("Error: bad response from "+str(peer), utils.LOG_WARN) utils.logprint("Bad response: "+peer.to_human_str(), utils.LOG_WARN)
else: else:
utils.logprint("Unknown peer: "+tx.sender_pubkey, utils.LOG_WARN) utils.logprint("No peer for: "+tx.receiver_pubkey, utils.LOG_WARN)
# Generate peer info
if t > next_peer_info:
self.local_peer = utils.Peer.generate(self.conf, self.keys)
utils.logprint("Generated new peer info", utils.LOG_TRACE)
self.spread_peer_info()
next_peer_info = time.time() + self.conf["server"]["peer_info_interval"]
# Remove expired requests # Remove expired requests
expire_txs = [] expire_txs = []
...@@ -722,7 +801,7 @@ class ClientThread(Thread): ...@@ -722,7 +801,7 @@ class ClientThread(Thread):
if len(expire_txs) > 0: if len(expire_txs) > 0:
utils.logprint("Removed "+str(len(expire_txs))+" expired txs", utils.LOG_TRACE) utils.logprint("Removed "+str(len(expire_txs))+" expired txs", utils.LOG_TRACE)
time.sleep(5) time.sleep(4)
def get_credentials(conf): def get_credentials(conf):
salt = conf["crypto"]["id_salt"] salt = conf["crypto"]["id_salt"]
...@@ -733,11 +812,24 @@ def get_credentials(conf): ...@@ -733,11 +812,24 @@ def get_credentials(conf):
password = getpass.getpass("Enter your password: ") password = getpass.getpass("Enter your password: ")
return salt, password return salt, password
def gen_idty_sig(keys, idty_keys):
doc = {
"doctype": "gmixer/idtysig",
"docver": "1",
"pubkey": keys.pubkey,
"sigtime": time.time()
}
return idty_keys.sign(ubjson.dumpb(doc))
# Main function # Main function
def main(): def main():
# Load conf & peers # Load conf
conf = read_config(DIR) conf = read_config(DIR)
peers, peers_index = utils.read_peers(DIR)
# Load peers
peers = {}
db_peers = plyvel.DB(DIR+"/db_peers", create_if_missing=True)
utils.load_peers(db_peers, peers)
# Load txs # Load txs
pool = [] pool = []
...@@ -751,9 +843,12 @@ def main(): ...@@ -751,9 +843,12 @@ def main():
keys = SigningKey.from_credentials(salt, password) keys = SigningKey.from_credentials(salt, password)
utils.logprint("Pubkey: "+keys.pubkey, utils.LOG_INFO) utils.logprint("Pubkey: "+keys.pubkey, utils.LOG_INFO)
# Generate peer info
local_peer = utils.Peer.generate(conf, keys)
# Start threads # Start threads
clientThread = ClientThread(conf, peers, peers_index, keys, pool, tx_in_index, tx_out_index, db_txs) clientThread = ClientThread(conf, peers, keys, local_peer, pool, tx_in_index, tx_out_index, db_txs)
serverThread = ServerThread(conf, peers, peers_index, keys, pool, tx_in_index, tx_out_index, db_txs) serverThread = ServerThread(conf, peers, keys, local_peer, pool, tx_in_index, tx_out_index, db_txs)
clientThread.start() clientThread.start()
serverThread.start() serverThread.start()
...@@ -772,7 +867,8 @@ def main(): ...@@ -772,7 +867,8 @@ def main():
clientThread.join() clientThread.join()
# Save # Save
utils.write_peers(DIR, peers) utils.save_peers(db_peers, peers)
db_peers.close()
save_txs(db_txs, pool) save_txs(db_txs, pool)
db_txs.close() db_txs.close()
...@@ -789,23 +885,88 @@ if __name__ == "__main__": ...@@ -789,23 +885,88 @@ if __name__ == "__main__":
conf_overwrite = {} conf_overwrite = {}
if "-P" in sys.argv: if "-P" in sys.argv:
import subprocess import subprocess
print("Fetching public address...") utils.logprint("Fetching public address...", LOG_INFO)
PUBLIC_HOST = subprocess.run(['curl', '-4', 'https://zettascript.org/tux/ip/'], stdout=subprocess.PIPE).stdout.decode("utf-8") PUBLIC_HOST = subprocess.run(['curl', '-4', 'https://zettascript.org/tux/ip/'], stdout=subprocess.PIPE).stdout.decode("utf-8")
print("Public host: " + PUBLIC_HOST) utils.logprint("Public host: " + PUBLIC_HOST, LOG_INFO)
conf_overwrite["server.public_host"] = PUBLIC_HOST conf_overwrite["server.public_host"] = PUBLIC_HOST
read_config(DIR, conf_overwrite) read_config(DIR, conf_overwrite)
if "-s" in sys.argv: if "-s" in sys.argv:
main() main()
elif "-i" in sys.argv: elif "-i" in sys.argv or "-I" in sys.argv:
conf_overwrite = {}
if "-I" in sys.argv:
conf_overwrite["crypto.id_salt"], conf_overwrite["crypto.id_password"] = utils.gen_keys()
conf = read_config(DIR, conf_overwrite)
salt, password = get_credentials(conf)
keys = SigningKey.from_credentials(salt, password)
if conf["crypto"]["id_salt"] == "":
conf_overwrite["crypto.id_salt"] = salt
if conf["crypto"]["id_password"] == "":
conf_overwrite["crypto.id_password"] = password
if "-g" in sys.argv:
loop = True
while loop:
idty_keys = SigningKey.from_credentials(
getpass.getpass("Identity passphrase (salt):"),
getpass.getpass("Identity password:")
)
print(idty_keys.pubkey)
loop = input("Is that the right pubkey? [yn]: ").lower() != "y"
conf_overwrite["idty.sig"] = gen_idty_sig(keys, idty_keys).hex()
conf_overwrite["idty.pubkey"] = idty_keys.pubkey
conf = read_config(DIR, conf_overwrite)
elif "-p" in sys.argv:
peer_file = open(os.path.expanduser(utils.getargv("-p", "peers.ubjson")), "rb")
new_peers = ubjson.loadb(peer_file.read())
peer_file.close()
# Load conf
conf = read_config(DIR) conf = read_config(DIR)
utils.read_peers(DIR)
# Load peers
peers = {}
db_peers = plyvel.DB(DIR+"/db_peers", create_if_missing=True)
utils.load_peers(db_peers, peers)
# Import peers
for raw in new_peers:
try:
new_peer = utils.Peer(raw)
except Exception as e: # TODO more specific exceptions
print("Error: invalid peer data: "+str(e))
continue
if new_peer.hash in peers and peers[new_peer].sigtime > new_peer.sigtime:
print("Too old: "+new_peer.to_human_str())
continue
peers[new_peer.hash] = new_peer
print("Peer: "+new_peer.to_human_str())
# Save
utils.save_peers(db_peers, peers)
db_peers.close()
elif "-I" in sys.argv: elif "-e" in sys.argv:
ID_SALT, ID_PASSWORD = gen_keys() # Load conf
conf = read_config(DIR) conf = read_config(DIR)
utils.read_peers(DIR)
# Get private key
salt, password = get_credentials(conf)
keys = SigningKey.from_credentials(salt, password)
print("Pubkey: "+keys.pubkey)
# Generate peer info
local_peer = utils.Peer.generate(conf, keys)
peer_file = open(os.path.expanduser(utils.getargv("-e", "peer_info.ubjson")), "wb")
peer_file.write(ubjson.dumpb([local_peer.raw]))
peer_file.close()
elif "-k" in sys.argv: elif "-k" in sys.argv:
conf = read_config(DIR) conf = read_config(DIR)
...@@ -814,22 +975,26 @@ if __name__ == "__main__": ...@@ -814,22 +975,26 @@ if __name__ == "__main__":
print(keys.pubkey) print(keys.pubkey)
elif "-V" in sys.argv or "--version" in sys.argv: elif "-V" in sys.argv or "--version" in sys.argv:
print(VERSION) print(utils.VERSION)
elif "--help" in sys.argv: elif "--help" in sys.argv:
print("\ print("\
ĞMixer-py Server "+VERSION+"\n\ ĞMixer-py Server "+utils.VERSION+"\n\
\n\ \n\
Options:\n\ Options:\n\
-c <path> Change config & data dir\n\
default: ~/.gmixer\n\
-s Start server\n\ -s Start server\n\
-i Init config\n\ -i Init config\n\
-I Init config (generate random keys)\n\ -I Init config (generate random keys)\n\
-P Auto set public address (overwrites config)\n\ -p <path> Import peers from file\n\
-e <path> Export peer info to file (output is compatible with -p)\n\
-k Display public key\n\ -k Display public key\n\
-v Verbose\n\
-V Display version\n\ -V Display version\n\
--version\n\ --version\n\
--help Display help\n\ --help Display help\n\
\n\
-d <path> Change config & data dir\n\
default: ~/.gmixer\n\
-v Verbose\n\
-P Auto set public address (overwrites config)\n\
-g (with -i or -I only) Generate identity signature\n\
") ")
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
CopyLeft 2019 Pascal Engélibert <tuxmain@zettascript.org>
This file is part of ĞMixer-py. This file is part of ĞMixer-py.
ĞMixer-py is free software: you can redistribute it and/or modify ĞMixer-py is free software: you can redistribute it and/or modify
...@@ -16,11 +17,15 @@ ...@@ -16,11 +17,15 @@
along with ĞMixer-py. If not, see <https://www.gnu.org/licenses/>. along with ĞMixer-py. If not, see <https://www.gnu.org/licenses/>.
""" """
import sys, os, re, socket, time, secrets, hashlib import sys, os, re, socket, time, secrets, hashlib, base64
import socks import socks
import ubjson
import libnacl.sign
import plyvel
from duniterpy.key import SigningKey, PublicKey from duniterpy.key import SigningKey, PublicKey
import silkaj.money, silkaj.tx, silkaj.auth import silkaj.money, silkaj.tx
VERSION = "0.2.0"
#-------- DATA #-------- DATA
...@@ -37,7 +42,7 @@ def gen_keys() -> (str, str): ...@@ -37,7 +42,7 @@ def gen_keys() -> (str, str):
return secrets.token_urlsafe(), secrets.token_urlsafe() return secrets.token_urlsafe(), secrets.token_urlsafe()
def gen_comment(seeds:[bytes, bytes, bytes]) -> str: def gen_comment(seeds:[bytes, bytes, bytes]) -> str:
return socks.b64encode(hashlib.sha512(b"".join(seeds)).digest()).decode() return base64.urlsafe_b64encode(hashlib.sha512(b"".join(seeds)).digest()).decode()
#-------- NETWORK #-------- NETWORK
...@@ -120,49 +125,62 @@ def getargv(arg:str, default:str="", n:int=1, args:list=sys.argv) -> str: ...@@ -120,49 +125,62 @@ def getargv(arg:str, default:str="", n:int=1, args:list=sys.argv) -> str:
#-------- ĞMixer #-------- ĞMixer
class Peer: class Peer:
def __init__(self, pubkey:str, host:str, port:int, up:bool): VERSION = "1"
self.pubkey = pubkey def __init__(self, raw:bytes):
self.host = host self.rectime = time.time()
self.port = int(port) self.raw = raw
self.up = up data = ubjson.loadb(raw)
pubkey = data["pubkey"]
raw = libnacl.sign.Verifier(PublicKey(pubkey).hex_pk()).verify(data["raw"])
data = ubjson.loadb(raw)
# TODO try except
self.keys = PublicKey(pubkey) # TODO tests
def __str__(self) -> str: self.doctype = data["doctype"]
return self.pubkey[:8] + "@" + self.host + ":" + str(self.port) self.docver = data["docver"]
self.pubkey = data["pubkey"]
self.sigtime = data["sigtime"]
self.host = tuple(data["host"]) # socket cannot manage lists
self.idty = data["idty"]
self.idtysig = data["idtysig"]
self.hash = hashlib.sha512((self.pubkey+"@"+self.host[0]+":"+str(self.host[1])).encode()).digest()
self.keys = PublicKey(self.pubkey)
self.up = None
def export_str(self) -> str: def to_human_str(self, short=True):
return self.pubkey + " " + self.host + " " + str(self.port) return (self.pubkey[:8] if short else self.pubkey)+"@"+self.host[0]+":"+str(self.host[1])
# Read peers list
def read_peers(cdir:str) -> (list, dict):
if not os.path.isfile(cdir+"/peers"):
open(cdir+"/peers", "w").close()
peers = [] def generate(conf:dict, keys:SigningKey) -> bytes:
peers_index = {} data = {
peersfile = open(cdir+"/peers", "r") "doctype": "peer",
while True: "docver": Peer.VERSION,
line = peersfile.readline() "pubkey": keys.pubkey,
if len(line) <= 1: "sigtime": time.time(),
break "host": [conf["server"]["public_host"], conf["server"]["public_port"]],
else: "idty": conf["idty"]["pubkey"],
cols = line.replace("\n", "").split(" ") "idtysig": bytes.fromhex(conf["idty"]["sig"])
peer = Peer(cols[0], cols[1], cols[2], None) }
peers.append(peer) raw = keys.sign(ubjson.dumpb(data))
peers_index[peer.pubkey] = peer data = {
peersfile.close() "pubkey": keys.pubkey,
return peers, peers_index "raw": raw
}
# Save peers list raw = ubjson.dumpb(data)
def write_peers(cdir:str, peers:list): return Peer(raw)
peersfile = open(cdir+"/peers", "w")
def load_peers(db_peers:plyvel.DB, peers:dict):
for _, data in db_peers:
peer = Peer(data)
peers[peer.hash] = peer
def save_peers(db_peers:plyvel.DB, peers:dict):
for peer in peers: for peer in peers:
peersfile.write(peer.export_str()+"\n") db_peers.put(peers[peer].hash, peers[peer].raw)
peersfile.close()
def send_transaction(sender_keys:SigningKey, receiver_pubkey:str, amount:int, comment:str): async def send_transaction(sender_keys:SigningKey, receiver_pubkey:str, amount:int, comment:str):
sender_amount = silkaj.money.get_amount_from_pubkey(sender_keys.pubkey)[0] #sender_amount = silkaj.money.get_amount_from_pubkey(sender_keys.pubkey)[0]
assert sender_amount >= amount, "not enough money" #assert sender_amount >= amount, "not enough money"
silkaj.tx.generate_and_send_transaction(sender_keys.hex_seed().decode(), sender_keys.pubkey, amount, [receiver_pubkey], comment) await silkaj.tx.handle_intermediaries_transactions(sender_keys, sender_keys.pubkey, amount, [receiver_pubkey], comment)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment